summaryrefslogtreecommitdiffstats
path: root/servers/slapd/overlays
diff options
context:
space:
mode:
Diffstat (limited to 'servers/slapd/overlays')
-rw-r--r--servers/slapd/overlays/Makefile.in172
-rw-r--r--servers/slapd/overlays/README5
-rw-r--r--servers/slapd/overlays/accesslog.c2874
-rw-r--r--servers/slapd/overlays/auditlog.c242
-rw-r--r--servers/slapd/overlays/autoca.c1121
-rw-r--r--servers/slapd/overlays/collect.c440
-rw-r--r--servers/slapd/overlays/constraint.c1236
-rw-r--r--servers/slapd/overlays/dds.c2056
-rw-r--r--servers/slapd/overlays/deref.c586
-rw-r--r--servers/slapd/overlays/dyngroup.c234
-rw-r--r--servers/slapd/overlays/dynlist.c2968
-rw-r--r--servers/slapd/overlays/homedir.c2074
-rw-r--r--servers/slapd/overlays/memberof.c2185
-rw-r--r--servers/slapd/overlays/otp.c1004
-rw-r--r--servers/slapd/overlays/overlays.c44
-rw-r--r--servers/slapd/overlays/pcache.c5815
-rw-r--r--servers/slapd/overlays/ppolicy.c3490
-rw-r--r--servers/slapd/overlays/refint.c1086
-rw-r--r--servers/slapd/overlays/remoteauth.c1002
-rw-r--r--servers/slapd/overlays/retcode.c1577
-rw-r--r--servers/slapd/overlays/rwm.c2768
-rw-r--r--servers/slapd/overlays/rwm.h183
-rw-r--r--servers/slapd/overlays/rwmconf.c413
-rw-r--r--servers/slapd/overlays/rwmdn.c215
-rw-r--r--servers/slapd/overlays/rwmmap.c1347
-rw-r--r--servers/slapd/overlays/seqmod.c207
-rw-r--r--servers/slapd/overlays/slapover.txt158
-rw-r--r--servers/slapd/overlays/sssvlv.c1439
-rw-r--r--servers/slapd/overlays/syncprov.c4412
-rw-r--r--servers/slapd/overlays/translucent.c1497
-rw-r--r--servers/slapd/overlays/unique.c1549
-rw-r--r--servers/slapd/overlays/valsort.c585
32 files changed, 44984 insertions, 0 deletions
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
new file mode 100644
index 0000000..e6711fe
--- /dev/null
+++ b/servers/slapd/overlays/Makefile.in
@@ -0,0 +1,172 @@
+# Makefile.in for overlays
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2003-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = overlays.c \
+ accesslog.c \
+ auditlog.c \
+ autoca.c \
+ constraint.c \
+ dds.c \
+ deref.c \
+ dyngroup.c \
+ dynlist.c \
+ homedir.c \
+ memberof.c \
+ otp.c \
+ pcache.c \
+ collect.c \
+ ppolicy.c \
+ refint.c \
+ remoteauth.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_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)
+
+autoca.la : autoca.lo
+ $(LTLINK_MOD) -module -o $@ autoca.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)
+
+homedir.la : homedir.lo
+ $(LTLINK_MOD) -module -o $@ homedir.lo version.lo $(LINK_LIBS)
+
+memberof.la : memberof.lo
+ $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
+
+otp.la : otp.lo
+ $(LTLINK_MOD) -module -o $@ otp.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)
+
+remoteauth.la : remoteauth.lo
+ $(LTLINK_MOD) -module -o $@ remoteauth.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..182be57
--- /dev/null
+++ b/servers/slapd/overlays/accesslog.c
@@ -0,0 +1,2874 @@
+/* accesslog.c - log operations for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 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 <assert.h>
+
+#include "slap.h"
+#include "slap-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;
+ int li_open;
+
+ 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;
+ BerVarray li_mincsn;
+ int *li_sids, li_numcsns;
+
+ /*
+ * Allow partial concurrency, main operation processing serialised with
+ * li_op_rmutex (there might be multiple such in progress by the same
+ * thread at a time, think overlays), the actual logging and mincsn
+ * management are serialised with li_log_mutex.
+ *
+ * ITS#9538:
+ * Any CSN assignment should happen while li_op_rmutex is held and
+ * li_log_mutex should be acquired before the former has been released.
+ */
+ ldap_pvt_thread_mutex_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_QUOTE|ARG_MAGIC|LOG_DB,
+ log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' "
+ "DESC 'Suffix of database for log content' "
+ "EQUALITY distinguishedNameMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY caseExactMatch "
+ "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,
+ *ad_minCSN, *ad_reqNewDN;
+
+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 },
+
+ /*
+ * ITS#8486
+ */
+ { "( " LOG_SCHEMA_AT ".32 NAME 'minCSN' "
+ "DESC 'CSN set that the logs are recorded from' "
+ "EQUALITY CSNMatch "
+ "ORDERING CSNOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", &ad_minCSN },
+
+ /*
+ * ITS#9552
+ */
+ { "( " LOG_SCHEMA_AT ".33 NAME 'reqNewDN' "
+ "DESC 'New DN after rename' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewDN },
+ { 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 $ 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 $ reqNewDN ) )",
+ &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 {
+ struct log_info *li;
+ int slots;
+ int used;
+ int mincsn_updated;
+ BerVarray dn;
+ BerVarray ndn;
+} purge_data;
+
+static int
+log_old_lookup( Operation *op, SlapReply *rs )
+{
+ purge_data *pd = op->o_callback->sc_private;
+ struct log_info *li = pd->li;
+ Attribute *a;
+
+ if ( rs->sr_type != REP_SEARCH) return 0;
+
+ if ( slapd_shutdown ) return 0;
+
+ /* Update minCSN */
+ 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;
+ int i, sid;
+
+ /* Find the correct sid */
+ sid = slap_parse_csn_sid( &a->a_nvals[0] );
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ for ( i=0; i < li->li_numcsns; i++ ) {
+ if ( sid <= li->li_sids[i] ) break;
+ }
+ if ( i >= li->li_numcsns || sid != li->li_sids[i] ) {
+ Debug( LDAP_DEBUG_ANY, "log_old_lookup: "
+ "csn=%s with sid not in minCSN set!\n",
+ a->a_nvals[0].bv_val );
+ slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i,
+ sid, &a->a_nvals[0] );
+ } else {
+ /* Paranoid len check, normalized CSNs are always the same length */
+ if ( len > li->li_mincsn[i].bv_len )
+ len = li->li_mincsn[i].bv_len;
+ if ( ber_bvcmp( &li->li_mincsn[i], &a->a_nvals[0] ) < 0 ) {
+ pd->mincsn_updated = 1;
+ AC_MEMCPY( li->li_mincsn[i].bv_val, a->a_nvals[0].bv_val, len );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ }
+ 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 = { .li = li };
+ 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;
+
+ 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;
+
+ op->o_callback = &nullsc;
+ op->o_dont_replicate = 1;
+ op->o_csn = slap_empty_bv;
+
+ if ( pd.mincsn_updated ) {
+ Modifications mod;
+ /* update context's minCSN to reflect oldest CSN */
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ mod.sml_numvals = li->li_numcsns;
+ mod.sml_values = li->li_mincsn;
+ mod.sml_nvalues = li->li_mincsn;
+ mod.sml_desc = ad_minCSN;
+ 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;
+ if ( !slapd_shutdown ) {
+ Debug( LDAP_DEBUG_SYNC, "accesslog_purge: "
+ "updating minCSN with %d values\n",
+ li->li_numcsns );
+ op->o_bd->be_modify( op, &rs );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ }
+
+ /* delete the expired entries */
+ op->o_tag = LDAP_REQ_DELETE;
+ 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_pausewait( &connection_pool );
+ }
+ ch_free( pd.ndn );
+ ch_free( pd.dn );
+ }
+
+ 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 = li->li_oldattrs, **lp = &li->li_oldattrs;
+ int i;
+
+ for ( 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 = li->li_bases, **lp = &li->li_bases;
+ int i;
+
+ for ( 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;
+ }
+ if ( !rc && ( li->li_db->bd_self == c->be->bd_self )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid suffix, points to itself",
+ 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 if ( li->li_open ) {
+ 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;
+ log_attr **lp = &li->li_oldattrs;
+
+ for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ )
+ lp = &(*lp)->next;
+
+ 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;
+ if ( *lp ) {
+ la->next = (*lp)->next;
+ } else {
+ la->next = NULL;
+ }
+ *lp = la;
+ lp = &la->next;
+ } 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 );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ }
+ }
+ break;
+ case LOG_BASE: {
+ int i;
+ slap_mask_t m = 0;
+ log_base **lp = &li->li_bases;
+
+ for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ )
+ lp = &(*lp)->lb_next;
+
+ 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;
+ if ( *lp ) {
+ lb->lb_next = (*lp)->lb_next;
+ } else {
+ lb->lb_next = NULL;
+ }
+ *lp = 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 );
+ 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 );
+ 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, &timestamp );
+ 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, &timestamp, &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, &timestamp, &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, &timestamp );
+ 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, &timestamp, 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_callback *sc = op->o_callback;
+ slap_overinst *on = (slap_overinst *)sc->sc_private;
+ log_info *li = on->on_bi.bi_private;
+ Attribute *a, *last_attr;
+ Modifications *m;
+ struct berval *b, uuid = BER_BVNULL;
+ int i, success;
+ 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};
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+
+ /* ITS#9051 Make sure we only remove the callback on a final response */
+ if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED &&
+ rs->sr_type != REP_SASL )
+ return SLAP_CB_CONTINUE;
+
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ /* can't do anything if logDB isn't open */
+ if ( !SLAP_DBOPEN( li->li_db ) ) {
+ goto skip;
+ }
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ goto skip;
+
+ /*
+ * ITS#9051 Technically LDAP_REFERRAL and LDAP_SASL_BIND_IN_PROGRESS
+ * are not errors, but they aren't really success either
+ */
+ success = rs->sr_err == LDAP_SUCCESS ||
+ rs->sr_err == LDAP_COMPARE_TRUE ||
+ rs->sr_err == LDAP_COMPARE_FALSE;
+ if ( li->li_success && !success )
+ goto skip;
+
+ 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 )
+ goto skip;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ if ( !( lo->mask & LOG_OP_WRITES ) ) {
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+ }
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( !success || BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ if ( !( lo->mask & LOG_OP_WRITES ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_response: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ }
+ slap_queue_csn( &op2, &op->o_csn );
+ }
+ }
+
+ 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 );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ 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 );
+ }
+ attr_merge_one( e, ad_reqNewDN, &op->orr_newDN, &op->orr_nnewDN );
+ 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_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;
+ /* contextCSN updates may still reach here */
+ op2.o_dont_replicate = op->o_dont_replicate;
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_response: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ if ( e == op2.ora_e ) entry_free( e );
+ e = NULL;
+
+ if ( ( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn ) ) {
+ Modifications mod;
+ int i, sid = slap_parse_csn_sid( &op->o_csn );
+
+ for ( i=0; i < li->li_numcsns; i++ ) {
+ if ( sid <= li->li_sids[i] ) break;
+ }
+ if ( i >= li->li_numcsns || sid != li->li_sids[i] ) {
+ /* SID not in minCSN set, add */
+ struct berval bv[2];
+
+ Debug( LDAP_DEBUG_TRACE, "accesslog_response: "
+ "adding minCSN %s\n",
+ op->o_csn.bv_val );
+ slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i,
+ sid, &op->o_csn );
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_req_dn = li->li_db->be_suffix[0];
+ op2.o_req_ndn = li->li_db->be_nsuffix[0];
+
+ bv[0] = op->o_csn;
+ BER_BVZERO( &bv[1] );
+
+ mod.sml_numvals = 1;
+ mod.sml_values = bv;
+ mod.sml_nvalues = bv;
+ mod.sml_desc = ad_minCSN;
+ mod.sml_op = LDAP_MOD_ADD;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op2.orm_modlist = &mod;
+ op2.orm_no_opattrs = 1;
+
+ Debug( LDAP_DEBUG_SYNC, "accesslog_response: "
+ "adding a new csn=%s into minCSN\n",
+ bv[0].bv_val );
+ rs_reinit( &rs2, REP_RESULT );
+ op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "accesslog_response: "
+ "got result 0x%x adding minCSN %s\n",
+ rs2.sr_err, op->o_csn.bv_val );
+ }
+ } else if ( ber_bvcmp( &op->o_csn, &li->li_mincsn[i] ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "accesslog_response: "
+ "csn=%s older than existing minCSN csn=%s for this sid\n",
+ op->o_csn.bv_val, li->li_mincsn[i].bv_val );
+ }
+ }
+
+done:
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ if ( old ) entry_free( old );
+ return SLAP_CB_CONTINUE;
+
+skip:
+ if ( lo->mask & LOG_OP_WRITES ) {
+ /* We haven't transitioned to li_log_mutex yet */
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_op_misc( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc;
+ slap_verbmasks *lo;
+ int logop;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ /* ignore these internal reads */
+ if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ 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;
+ slap_callback *cb;
+ int logop;
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ /* can't do anything if logDB isn't open */
+ if ( !SLAP_DBOPEN( li->li_db ))
+ return SLAP_CB_CONTINUE;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ if ( !( li->li_ops & lo->mask )) {
+ log_base *lb;
+ int 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;
+ }
+
+ 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;
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+
+ 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;
+ log_info *li = on->on_bi.bi_private;
+ Operation op2 = {};
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ void *cids[SLAP_MAX_CIDS];
+ SlapReply rs2 = {REP_RESULT};
+ Entry *e;
+
+ if ( op->o_conn->c_authz_backend != on->on_info->oi_origdb )
+ return SLAP_CB_CONTINUE;
+
+ 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;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_unbind: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ op2.o_csn = op->o_csn;
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ e = accesslog_entry( op, rs, li, LOG_EN_UNBIND, &op2 );
+ 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 ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_unbind: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+
+ 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 csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ 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;
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_csn.bv_val = csnbuf;
+ op2.o_csn.bv_len = sizeof(csnbuf);
+
+ ldap_pvt_thread_mutex_lock( &li->li_op_rmutex );
+ if ( SLAP_LASTMOD( li->li_db ) ) {
+ /*
+ * Make sure we have a CSN before we release li_op_rmutex to preserve
+ * ordering
+ */
+ if ( BER_BVISEMPTY( &op->o_csn ) ) {
+ slap_get_csn( &op2, &op2.o_csn, 1 );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s accesslog_abandon: "
+ "the op had a CSN assigned, if you're replicating the "
+ "accesslog at %s, you might lose changes\n",
+ op->o_log_prefix, li->li_db_suffix.bv_val );
+ assert(0);
+ op2.o_csn = op->o_csn;
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex );
+
+ 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_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 ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_abandon: "
+ "got result 0x%x adding log entry %s\n",
+ op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val );
+ }
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ 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_mutex_recursive_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 );
+ }
+ if ( li->li_sids )
+ ch_free( li->li_sids );
+ if ( li->li_mincsn )
+ ber_bvarray_free( li->li_mincsn );
+ if ( li->li_db_suffix.bv_val )
+ ch_free( li->li_db_suffix.bv_val );
+ ldap_pvt_thread_mutex_destroy( &li->li_log_mutex );
+ ldap_pvt_thread_mutex_destroy( &li->li_op_rmutex );
+ free( li );
+ return LDAP_SUCCESS;
+}
+
+/* Create the logdb's root entry if it's missing, load mincsn */
+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;
+
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ 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 ) {
+ Attribute *a = attr_find( e->e_attrs, ad_minCSN );
+ if ( !a ) {
+ /* TODO: find the lowest CSN we are safe to put in */
+ a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ SlapReply rs = {REP_RESULT};
+ Modifications mod;
+ BackendDB db = *li->li_db;
+
+ op->o_bd = &db;
+
+ mod.sml_numvals = a->a_numvals;
+ mod.sml_values = a->a_vals;
+ mod.sml_nvalues = a->a_nvals;
+ mod.sml_desc = ad_minCSN;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ 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;
+
+ Debug( LDAP_DEBUG_SYNC, "accesslog_db_root: "
+ "setting up minCSN with %d values\n",
+ a->a_numvals );
+
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ rc = op->o_bd->be_modify( op, &rs );
+ }
+ }
+ if ( a ) {
+ ber_bvarray_dup_x( &li->li_mincsn, a->a_vals, NULL );
+ li->li_numcsns = a->a_numvals;
+ li->li_sids = slap_parse_csn_sids( li->li_mincsn, li->li_numcsns, NULL );
+ slap_sort_csn_sids( li->li_mincsn, li->li_sids, li->li_numcsns, NULL );
+ }
+ 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 );
+ attr_merge( e, ad_minCSN, 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 ( rs.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC, "%s accesslog_db_root: "
+ "got result 0x%x adding log root entry %s\n",
+ op->o_log_prefix, rs.sr_err, op->o_req_dn.bv_val );
+ }
+ if ( e == op->ora_e )
+ entry_free( e );
+ }
+ li->li_open = 1;
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_runqueue_remove( &slapd_rq, rtask );
+
+ if ( li->li_age && li->li_cycle ) {
+ assert( li->li_task == NULL );
+ li->li_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ li->li_cycle, accesslog_purge, li,
+ "accesslog_purge", li->li_db->be_suffix[0].bv_val );
+ }
+ 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" );
+ return 1;
+ }
+ if ( li->li_db->bd_self == be->bd_self ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog: \"logdb <suffix>\" is this database, cannot log to itself.\n" );
+ 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;
+}
+
+static int
+accesslog_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ struct re_s *re = li->li_task;
+
+ li->li_open = 0;
+
+ if ( re ) {
+ 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 );
+ }
+
+ 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, *rdnTimestampOrdering;
+
+ 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_db_close = accesslog_db_close;
+
+ 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" );
+ 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" );
+ 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" );
+ return -1;
+ }
+ }
+
+ /* Inject custom normalizer for reqStart/reqEnd */
+ rdnTimestampMatch = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampSyntax = ch_malloc( sizeof( Syntax ));
+ *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality;
+ rdnTimestampMatch->smr_normalize = rdnTimestampNormalize;
+ *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering;
+ rdnTimestampOrdering->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_ordering = rdnTimestampOrdering;
+ ad_reqStart->ad_type->sat_syntax = rdnTimestampSyntax;
+
+ rdnTimestampMatch = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule ));
+ rdnTimestampSyntax = ch_malloc( sizeof( Syntax ));
+ *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality;
+ *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering;
+ *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax;
+ ad_reqEnd->ad_type->sat_equality = rdnTimestampMatch;
+ ad_reqEnd->ad_type->sat_ordering = rdnTimestampOrdering;
+ 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" );
+ return -1;
+ }
+ }
+
+ 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..9292d4a
--- /dev/null
+++ b/servers/slapd/overlays/auditlog.c
@@ -0,0 +1,242 @@
+/* auditlog.c - log modifications for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 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 "slap-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' "
+ "EQUALITY caseExactMatch "
+ "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_flags = SLAPO_BFLAG_SINGLE;
+ 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/autoca.c b/servers/slapd/overlays/autoca.c
new file mode 100644
index 0000000..5fcd204
--- /dev/null
+++ b/servers/slapd/overlays/autoca.c
@@ -0,0 +1,1121 @@
+/* autoca.c - Automatic Certificate Authority */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 The OpenLDAP Foundation.
+ * Copyright 2009-2018 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_AUTOCA
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/evp.h>
+#include <openssl/bn.h>
+
+/* Starting with OpenSSL 1.1.0, rsa.h is no longer included in
+ * x509.h, so we need to explicitly include it for the
+ * call to EVP_PKEY_CTX_set_rsa_keygen_bits
+ */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+#include <openssl/rsa.h>
+#define X509_get_notBefore(x) X509_getm_notBefore(x)
+#define X509_get_notAfter(x) X509_getm_notAfter(x)
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+#define BN_pseudo_rand(bn, bits, top, bottom) BN_rand(bn, bits, top, bottom)
+#endif
+
+/* This overlay implements a certificate authority that can generate
+ * certificates automatically for any entry in the directory.
+ * On startup it generates a self-signed CA cert for the directory's
+ * suffix entry and uses this to sign all other certs that it generates.
+ * User and server certs are generated on demand, using a Search request.
+ */
+
+#define LBER_TAG_OID ((ber_tag_t) 0x06UL)
+#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL)
+
+#define KEYBITS 2048
+#define MIN_KEYBITS 512
+
+#define ACA_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.11"
+
+#define ACA_SCHEMA_AT ACA_SCHEMA_ROOT ".1"
+#define ACA_SCHEMA_OC ACA_SCHEMA_ROOT ".2"
+
+static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey;
+static AttributeDescription *ad_mail, *ad_ipaddr;
+static ObjectClass *oc_caObj, *oc_usrObj;
+
+static char *aca_attrs[] = {
+ "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' "
+ "DESC 'X.509 CA private key, use ;binary' "
+ "SUP pKCS8PrivateKey )",
+ "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' "
+ "DESC 'X.509 user private key, use ;binary' "
+ "SUP pKCS8PrivateKey )",
+ NULL
+};
+
+static struct {
+ char *at;
+ AttributeDescription **ad;
+} aca_attr2[] = {
+ { "cACertificate;binary", &ad_caCert },
+ { "cAPrivateKey;binary", &ad_caPkey },
+ { "userCertificate;binary", &ad_usrCert },
+ { "userPrivateKey;binary", &ad_usrPkey },
+ { "mail", &ad_mail },
+ { NULL }
+};
+
+static struct {
+ char *ot;
+ ObjectClass **oc;
+} aca_ocs[] = {
+ { "( " ACA_SCHEMA_OC ".1 NAME 'autoCA' "
+ "DESC 'Automated PKI certificate authority' "
+ "SUP pkiCA AUXILIARY "
+ "MAY cAPrivateKey )", &oc_caObj },
+ { "( " ACA_SCHEMA_OC ".2 NAME 'autoCAuser' "
+ "DESC 'Automated PKI CA user' "
+ "SUP pkiUser AUXILIARY "
+ "MAY userPrivateKey )", &oc_usrObj },
+ { NULL }
+};
+
+typedef struct autoca_info {
+ X509 *ai_cert;
+ EVP_PKEY *ai_pkey;
+ ObjectClass *ai_usrclass;
+ ObjectClass *ai_srvclass;
+ struct berval ai_localdn;
+ struct berval ai_localndn;
+ int ai_usrkeybits;
+ int ai_srvkeybits;
+ int ai_cakeybits;
+ int ai_usrdays;
+ int ai_srvdays;
+ int ai_cadays;
+} autoca_info;
+
+/* Rewrite an LDAP DN in DER form
+ * Input must be valid DN, therefore no error checking is done here.
+ */
+static int autoca_dnbv2der( Operation *op, struct berval *bv, struct berval *der )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPDN dn;
+ LDAPRDN rdn;
+ LDAPAVA *ava;
+ AttributeDescription *ad;
+ int irdn, iava;
+
+ ldap_bv2dn_x( bv, &dn, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* count RDNs, we need them in reverse order */
+ for (irdn = 0; dn[irdn]; irdn++);
+ irdn--;
+
+ /* DN is a SEQuence of RDNs */
+ ber_start_seq( ber, LBER_SEQUENCE );
+ for (; irdn >=0; irdn--)
+ {
+ /* RDN is a SET of AVAs */
+ ber_start_set( ber, LBER_SET );
+ rdn = dn[irdn];
+ for (iava = 0; rdn[iava]; iava++)
+ {
+ const char *text;
+ char oid[1024];
+ struct berval bvo = { sizeof(oid), oid };
+ struct berval bva;
+
+ /* AVA is a SEQuence of attr and value */
+ ber_start_seq( ber, LBER_SEQUENCE );
+ ava = rdn[iava];
+ ad = NULL;
+ slap_bv2ad( &ava->la_attr, &ad, &text );
+ ber_str2bv( ad->ad_type->sat_oid, 0, 0, &bva );
+ ber_encode_oid( &bva, &bvo );
+ ber_put_berval( ber, &bvo, LBER_TAG_OID );
+ ber_put_berval( ber, &ava->la_value, LBER_TAG_UTF8 );
+ ber_put_seq( ber );
+ }
+ ber_put_set( ber );
+ }
+ ber_put_seq( ber );
+ ber_flatten2( ber, der, 0 );
+ ldap_dnfree_x( dn, op->o_tmpmemctx );
+ return 0;
+}
+
+static int autoca_genpkey(int bits, EVP_PKEY **pkey)
+{
+ EVP_PKEY_CTX *kctx;
+ int rc;
+
+ kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
+ if (kctx == NULL)
+ return -1;
+ if (EVP_PKEY_keygen_init(kctx) <= 0)
+ {
+ EVP_PKEY_CTX_free(kctx);
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, bits) <= 0)
+ {
+ EVP_PKEY_CTX_free(kctx);
+ return -1;
+ }
+ rc = EVP_PKEY_keygen(kctx, pkey);
+ EVP_PKEY_CTX_free(kctx);
+ return rc;
+}
+
+static int autoca_signcert(X509 *cert, EVP_PKEY *pkey)
+{
+ EVP_MD_CTX *ctx = EVP_MD_CTX_create();
+ EVP_PKEY_CTX *pkctx = NULL;
+ int rc = -1;
+
+ if ( ctx == NULL )
+ return -1;
+ if (EVP_DigestSignInit(ctx, &pkctx, NULL, NULL, pkey))
+ {
+ rc = X509_sign_ctx(cert, ctx);
+ }
+ EVP_MD_CTX_destroy(ctx);
+ return rc;
+}
+
+#define SERIAL_BITS 64 /* should be less than 160 */
+
+typedef struct myext {
+ char *name;
+ char *value;
+} myext;
+
+static myext CAexts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "critical,CA:true" },
+ { "keyUsage", "digitalSignature,cRLSign,keyCertSign" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+static myext usrExts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "CA:false" },
+ { "keyUsage", "digitalSignature,nonRepudiation,keyEncipherment" },
+ { "extendedKeyUsage", "clientAuth,emailProtection,codeSigning" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+static myext srvExts[] = {
+ { "subjectKeyIdentifier", "hash" },
+ { "authorityKeyIdentifier", "keyid:always,issuer" },
+ { "basicConstraints", "CA:false" },
+ { "keyUsage", "digitalSignature,keyEncipherment" },
+ { "extendedKeyUsage", "serverAuth,clientAuth" },
+ { "nsComment", "OpenLDAP automatic certificate" },
+ { NULL }
+};
+
+typedef struct genargs {
+ X509 *issuer_cert;
+ EVP_PKEY *issuer_pkey;
+ struct berval *subjectDN;
+ myext *cert_exts;
+ myext *more_exts;
+ X509 *newcert;
+ EVP_PKEY *newpkey;
+ struct berval dercert;
+ struct berval derpkey;
+ int keybits;
+ int days;
+} genargs;
+
+static int autoca_gencert( Operation *op, genargs *args )
+{
+ X509_NAME *subj_name, *issuer_name;
+ X509 *subj_cert;
+ struct berval derdn;
+ unsigned char *pp;
+ EVP_PKEY *evpk = NULL;
+ int rc;
+
+ if ((subj_cert = X509_new()) == NULL)
+ return -1;
+
+ autoca_dnbv2der( op, args->subjectDN, &derdn );
+ pp = (unsigned char *)derdn.bv_val;
+ subj_name = d2i_X509_NAME( NULL, (const unsigned char **)&pp, derdn.bv_len );
+ op->o_tmpfree( derdn.bv_val, op->o_tmpmemctx );
+ if ( subj_name == NULL )
+ {
+fail1:
+ X509_free( subj_cert );
+ return -1;
+ }
+
+ rc = autoca_genpkey( args->keybits, &evpk );
+ if ( rc <= 0 )
+ {
+fail2:
+ if ( subj_name ) X509_NAME_free( subj_name );
+ goto fail1;
+ }
+ /* encode DER in PKCS#8 */
+ {
+ PKCS8_PRIV_KEY_INFO *p8inf;
+ if (( p8inf = EVP_PKEY2PKCS8( evpk )) == NULL )
+ goto fail2;
+ args->derpkey.bv_len = i2d_PKCS8_PRIV_KEY_INFO( p8inf, NULL );
+ args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx );
+ pp = (unsigned char *)args->derpkey.bv_val;
+ i2d_PKCS8_PRIV_KEY_INFO( p8inf, &pp );
+ PKCS8_PRIV_KEY_INFO_free( p8inf );
+ }
+ args->newpkey = evpk;
+
+ /* set random serial */
+ {
+ BIGNUM *bn = BN_new();
+ if ( bn == NULL )
+ {
+fail3:
+ EVP_PKEY_free( evpk );
+ goto fail2;
+ }
+ if (!BN_pseudo_rand(bn, SERIAL_BITS, 0, 0))
+ {
+ BN_free( bn );
+ goto fail3;
+ }
+ if (!BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(subj_cert)))
+ {
+ BN_free( bn );
+ goto fail3;
+ }
+ BN_free(bn);
+ }
+ if (args->issuer_cert) {
+ issuer_name = X509_get_subject_name(args->issuer_cert);
+ } else {
+ issuer_name = subj_name;
+ args->issuer_cert = subj_cert;
+ args->issuer_pkey = evpk;
+ }
+ if (!X509_set_version(subj_cert, 2) || /* set version to V3 */
+ !X509_set_issuer_name(subj_cert, issuer_name) ||
+ !X509_set_subject_name(subj_cert, subj_name) ||
+ !X509_gmtime_adj(X509_get_notBefore(subj_cert), 0) ||
+ !X509_time_adj_ex(X509_get_notAfter(subj_cert), args->days, 0, NULL) ||
+ !X509_set_pubkey(subj_cert, evpk))
+ {
+ goto fail3;
+ }
+ X509_NAME_free(subj_name);
+ subj_name = NULL;
+
+ /* set cert extensions */
+ {
+ X509V3_CTX ctx;
+ X509_EXTENSION *ext;
+ int i;
+
+ X509V3_set_ctx(&ctx, args->issuer_cert, subj_cert, NULL, NULL, 0);
+ for (i=0; args->cert_exts[i].name; i++) {
+ ext = X509V3_EXT_nconf(NULL, &ctx, args->cert_exts[i].name, args->cert_exts[i].value);
+ if ( ext == NULL )
+ goto fail3;
+ rc = X509_add_ext(subj_cert, ext, -1);
+ X509_EXTENSION_free(ext);
+ if ( !rc )
+ goto fail3;
+ }
+ if (args->more_exts) {
+ for (i=0; args->more_exts[i].name; i++) {
+ ext = X509V3_EXT_nconf(NULL, &ctx, args->more_exts[i].name, args->more_exts[i].value);
+ if ( ext == NULL )
+ goto fail3;
+ rc = X509_add_ext(subj_cert, ext, -1);
+ X509_EXTENSION_free(ext);
+ if ( !rc )
+ goto fail3;
+ }
+ }
+ }
+ rc = autoca_signcert( subj_cert, args->issuer_pkey );
+ if ( rc < 0 )
+ goto fail3;
+ args->dercert.bv_len = i2d_X509( subj_cert, NULL );
+ args->dercert.bv_val = op->o_tmpalloc( args->dercert.bv_len, op->o_tmpmemctx );
+ pp = (unsigned char *)args->dercert.bv_val;
+ i2d_X509( subj_cert, &pp );
+ args->newcert = subj_cert;
+ return 0;
+}
+
+typedef struct saveargs {
+ ObjectClass *oc;
+ struct berval *dercert;
+ struct berval *derpkey;
+ slap_overinst *on;
+ struct berval *dn;
+ struct berval *ndn;
+ int isca;
+} saveargs;
+
+static int autoca_savecert( Operation *op, saveargs *args )
+{
+ Modifications mod[3], *mp = mod;
+ struct berval bvs[6], *bp = bvs;
+ BackendInfo *bi;
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+
+ if ( args->oc ) {
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = slap_schema.si_ad_objectClass;
+ mp->sml_op = LDAP_MOD_ADD;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = args->oc->soc_cname;
+ BER_BVZERO( bp );
+ bp++;
+ mp->sml_next = mp+1;
+ mp++;
+ }
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = args->isca ? ad_caCert : ad_usrCert;
+ mp->sml_op = LDAP_MOD_REPLACE;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = *args->dercert;
+ BER_BVZERO( bp );
+ bp++;
+ mp->sml_next = mp+1;
+ mp++;
+
+ mp->sml_numvals = 1;
+ mp->sml_values = bp;
+ mp->sml_nvalues = NULL;
+ mp->sml_desc = args->isca ? ad_caPkey : ad_usrPkey;
+ mp->sml_op = LDAP_MOD_ADD;
+ mp->sml_flags = SLAP_MOD_INTERNAL;
+ *bp++ = *args->derpkey;
+ BER_BVZERO( bp );
+ mp->sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ bi = op->o_bd->bd_info;
+ op->o_bd->bd_info = args->on->on_info->oi_orig;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = *args->dn;
+ op->o_req_ndn = *args->ndn;
+ op->o_bd->be_modify( op, &rs );
+ op->o_bd->bd_info = bi;
+ return rs.sr_err;
+}
+
+static const struct berval configDN = BER_BVC("cn=config");
+
+/* must run as a pool thread to avoid cn=config deadlock */
+static void *
+autoca_setca_task( void *ctx, void *arg )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ struct berval *cacert = arg;
+ Modifications mod;
+ struct berval bvs[2];
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+ const char *text;
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ mod.sml_numvals = 1;
+ mod.sml_values = bvs;
+ mod.sml_nvalues = NULL;
+ mod.sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCACertificate;binary", &mod.sml_desc, &text ))
+ goto leave;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ bvs[0] = *cacert;
+ BER_BVZERO( &bvs[1] );
+ mod.sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ op->o_bd = select_backend( (struct berval *)&configDN, 0 );
+ if ( !op->o_bd )
+ goto leave;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = configDN;
+ op->o_req_ndn = configDN;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->be_modify( op, &rs );
+leave:
+ ch_free( arg );
+ return NULL;
+}
+
+static int
+autoca_setca( struct berval *cacert )
+{
+ struct berval *bv = ch_malloc( sizeof(struct berval) + cacert->bv_len );
+ bv->bv_len = cacert->bv_len;
+ bv->bv_val = (char *)(bv+1);
+ AC_MEMCPY( bv->bv_val, cacert->bv_val, bv->bv_len );
+ return ldap_pvt_thread_pool_submit( &connection_pool, autoca_setca_task, bv );
+}
+
+static int
+autoca_setlocal( Operation *op, struct berval *cert, struct berval *pkey )
+{
+ Modifications mod[2];
+ struct berval bvs[4];
+ slap_callback cb = {0};
+ SlapReply rs = {REP_RESULT};
+ const char *text;
+
+ mod[0].sml_numvals = 1;
+ mod[0].sml_values = bvs;
+ mod[0].sml_nvalues = NULL;
+ mod[0].sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCertificate;binary", &mod[0].sml_desc, &text ))
+ return -1;
+ mod[0].sml_op = LDAP_MOD_REPLACE;
+ mod[0].sml_flags = SLAP_MOD_INTERNAL;
+ bvs[0] = *cert;
+ BER_BVZERO( &bvs[1] );
+ mod[0].sml_next = &mod[1];
+
+ mod[1].sml_numvals = 1;
+ mod[1].sml_values = &bvs[2];
+ mod[1].sml_nvalues = NULL;
+ mod[1].sml_desc = NULL;
+ if ( slap_str2ad( "olcTLSCertificateKey;binary", &mod[1].sml_desc, &text ))
+ return -1;
+ mod[1].sml_op = LDAP_MOD_REPLACE;
+ mod[1].sml_flags = SLAP_MOD_INTERNAL;
+ bvs[2] = *pkey;
+ BER_BVZERO( &bvs[3] );
+ mod[1].sml_next = NULL;
+
+ cb.sc_response = slap_null_cb;
+ op->o_bd = select_backend( (struct berval *)&configDN, 0 );
+ if ( !op->o_bd )
+ return -1;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_callback = &cb;
+ op->orm_modlist = mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = configDN;
+ op->o_req_ndn = configDN;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->be_modify( op, &rs );
+ return rs.sr_err;
+}
+
+enum {
+ ACA_USRCLASS = 1,
+ ACA_SRVCLASS,
+ ACA_USRKEYBITS,
+ ACA_SRVKEYBITS,
+ ACA_CAKEYBITS,
+ ACA_USRDAYS,
+ ACA_SRVDAYS,
+ ACA_CADAYS,
+ ACA_LOCALDN
+};
+
+static int autoca_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ autoca_info *ai = on->on_bi.bi_private;
+ int rc = 0;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ if ( ai->ai_usrclass ) {
+ c->value_string = ch_strdup( ai->ai_usrclass->soc_cname.bv_val );
+ } else {
+ rc = 1;
+ }
+ break;
+ case ACA_SRVCLASS:
+ if ( ai->ai_srvclass ) {
+ c->value_string = ch_strdup( ai->ai_srvclass->soc_cname.bv_val );
+ } else {
+ rc = 1;
+ }
+ break;
+ case ACA_USRKEYBITS:
+ c->value_int = ai->ai_usrkeybits;
+ break;
+ case ACA_SRVKEYBITS:
+ c->value_int = ai->ai_srvkeybits;
+ break;
+ case ACA_CAKEYBITS:
+ c->value_int = ai->ai_cakeybits;
+ break;
+ case ACA_USRDAYS:
+ c->value_int = ai->ai_usrdays;
+ break;
+ case ACA_SRVDAYS:
+ c->value_int = ai->ai_srvdays;
+ break;
+ case ACA_CADAYS:
+ c->value_int = ai->ai_cadays;
+ break;
+ case ACA_LOCALDN:
+ if ( !BER_BVISNULL( &ai->ai_localdn )) {
+ rc = value_add_one( &c->rvalue_vals, &ai->ai_localdn );
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ ai->ai_usrclass = NULL;
+ break;
+ case ACA_SRVCLASS:
+ ai->ai_srvclass = NULL;
+ break;
+ case ACA_LOCALDN:
+ if ( ai->ai_localdn.bv_val ) {
+ ch_free( ai->ai_localdn.bv_val );
+ ch_free( ai->ai_localndn.bv_val );
+ BER_BVZERO( &ai->ai_localdn );
+ BER_BVZERO( &ai->ai_localndn );
+ }
+ break;
+ /* single-valued attrs, all no-ops */
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch( c->type ) {
+ case ACA_USRCLASS:
+ {
+ ObjectClass *oc = oc_find( c->value_string );
+ if ( oc )
+ ai->ai_usrclass = oc;
+ else
+ rc = 1;
+ }
+ break;
+ case ACA_SRVCLASS:
+ {
+ ObjectClass *oc = oc_find( c->value_string );
+ if ( oc )
+ ai->ai_srvclass = oc;
+ else
+ rc = 1;
+ }
+ case ACA_USRKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_usrkeybits = c->value_int;
+ break;
+ case ACA_SRVKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_srvkeybits = c->value_int;
+ break;
+ case ACA_CAKEYBITS:
+ if ( c->value_int < MIN_KEYBITS )
+ rc = 1;
+ else
+ ai->ai_cakeybits = c->value_int;
+ break;
+ case ACA_USRDAYS:
+ ai->ai_usrdays = c->value_int;
+ break;
+ case ACA_SRVDAYS:
+ ai->ai_srvdays = c->value_int;
+ break;
+ case ACA_CADAYS:
+ ai->ai_cadays = c->value_int;
+ break;
+ case ACA_LOCALDN:
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !dnIsSuffix( &c->value_ndn, c->be->be_nsuffix )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DN is not a subordinate of backend" );
+ Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n",
+ c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( ai->ai_localdn.bv_val ) {
+ ch_free( ai->ai_localdn.bv_val );
+ ch_free( ai->ai_localndn.bv_val );
+ }
+ ai->ai_localdn = c->value_dn;
+ ai->ai_localndn = c->value_ndn;
+ }
+ }
+ return rc;
+}
+
+static ConfigTable autoca_cfg[] = {
+ { "userClass", "objectclass", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|ACA_USRCLASS, autoca_cf,
+ "( OLcfgOvAt:22.1 NAME 'olcAutoCAuserClass' "
+ "DESC 'ObjectClass of user entries' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "serverClass", "objectclass", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf,
+ "( OLcfgOvAt:22.2 NAME 'olcAutoCAserverClass' "
+ "DESC 'ObjectClass of server entries' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "userKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_USRKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.3 NAME 'olcAutoCAuserKeybits' "
+ "DESC 'Size of PrivateKey for user entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "serverKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_SRVKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.4 NAME 'olcAutoCAserverKeybits' "
+ "DESC 'Size of PrivateKey for server entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "caKeybits", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_CAKEYBITS, autoca_cf,
+ "( OLcfgOvAt:22.5 NAME 'olcAutoCAKeybits' "
+ "DESC 'Size of PrivateKey for CA certificate' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "userDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_USRDAYS, autoca_cf,
+ "( OLcfgOvAt:22.6 NAME 'olcAutoCAuserDays' "
+ "DESC 'Lifetime of user certificates in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "serverDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_SRVDAYS, autoca_cf,
+ "( OLcfgOvAt:22.7 NAME 'olcAutoCAserverDays' "
+ "DESC 'Lifetime of server certificates in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "caDays", "integer", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|ACA_CADAYS, autoca_cf,
+ "( OLcfgOvAt:22.8 NAME 'olcAutoCADays' "
+ "DESC 'Lifetime of CA certificate in days' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "localdn", "dn", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|ACA_LOCALDN, autoca_cf,
+ "( OLcfgOvAt:22.9 NAME 'olcAutoCAlocalDN' "
+ "DESC 'DN of local server cert' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs autoca_ocs[] = {
+ { "( OLcfgOvOc:22.1 "
+ "NAME 'olcAutoCAConfig' "
+ "DESC 'AutoCA configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcAutoCAuserClass $ olcAutoCAserverClass $ "
+ "olcAutoCAuserKeybits $ olcAutoCAserverKeybits $ olcAutoCAKeyBits $ "
+ "olcAutoCAuserDays $ olcAutoCAserverDays $ olcAutoCADays $ "
+ "olcAutoCAlocalDN ) )",
+ Cft_Overlay, autoca_cfg },
+ { NULL, 0, NULL }
+};
+
+static int
+autoca_op_response(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = op->o_callback->sc_private;
+ autoca_info *ai = on->on_bi.bi_private;
+ Attribute *a;
+ int isusr = 0;
+
+ if (rs->sr_type != REP_SEARCH)
+ return SLAP_CB_CONTINUE;
+
+ /* If root or self */
+ if ( !be_isroot( op ) &&
+ !dn_match( &rs->sr_entry->e_nname, &op->o_ndn ))
+ return SLAP_CB_CONTINUE;
+
+ isusr = is_entry_objectclass( rs->sr_entry, ai->ai_usrclass, SLAP_OCF_CHECK_SUP );
+ if ( !isusr )
+ {
+ if (!is_entry_objectclass( rs->sr_entry, ai->ai_srvclass, SLAP_OCF_CHECK_SUP ))
+ return SLAP_CB_CONTINUE;
+ }
+ a = attr_find( rs->sr_entry->e_attrs, ad_usrPkey );
+ if ( !a )
+ {
+ Operation op2;
+ genargs args;
+ saveargs arg2;
+ myext extras[2];
+ int rc;
+
+ args.issuer_cert = ai->ai_cert;
+ args.issuer_pkey = ai->ai_pkey;
+ args.subjectDN = &rs->sr_entry->e_name;
+ args.more_exts = NULL;
+ if ( isusr )
+ {
+ args.cert_exts = usrExts;
+ args.keybits = ai->ai_usrkeybits;
+ args.days = ai->ai_usrdays;
+ a = attr_find( rs->sr_entry->e_attrs, ad_mail );
+ if ( a )
+ {
+ extras[0].name = "subjectAltName";
+ extras[1].name = NULL;
+ extras[0].value = op->o_tmpalloc( sizeof("email:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+ sprintf(extras[0].value, "email:%s", a->a_vals[0].bv_val);
+ args.more_exts = extras;
+ }
+ } else
+ {
+ args.cert_exts = srvExts;
+ args.keybits = ai->ai_srvkeybits;
+ args.days = ai->ai_srvdays;
+ if ( ad_ipaddr && (a = attr_find( rs->sr_entry->e_attrs, ad_ipaddr )))
+ {
+ extras[0].name = "subjectAltName";
+ extras[1].name = NULL;
+ extras[0].value = op->o_tmpalloc( sizeof("IP:") + a->a_vals[0].bv_len, op->o_tmpmemctx );
+ sprintf(extras[0].value, "IP:%s", a->a_vals[0].bv_val);
+ args.more_exts = extras;
+ }
+ }
+ rc = autoca_gencert( op, &args );
+ if ( rc )
+ return SLAP_CB_CONTINUE;
+ X509_free( args.newcert );
+ EVP_PKEY_free( args.newpkey );
+
+ if ( is_entry_objectclass( rs->sr_entry, oc_usrObj, 0 ))
+ arg2.oc = NULL;
+ else
+ arg2.oc = oc_usrObj;
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ))
+ {
+ Entry *e = entry_dup( rs->sr_entry );
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+ arg2.dercert = &args.dercert;
+ arg2.derpkey = &args.derpkey;
+ arg2.on = on;
+ arg2.dn = &rs->sr_entry->e_name;
+ arg2.ndn = &rs->sr_entry->e_nname;
+ arg2.isca = 0;
+ op2 = *op;
+ rc = autoca_savecert( &op2, &arg2 );
+ if ( !rc )
+ {
+ /* If this is our cert DN, configure it */
+ if ( dn_match( &rs->sr_entry->e_nname, &ai->ai_localndn ))
+ autoca_setlocal( &op2, &args.dercert, &args.derpkey );
+ attr_merge_one( rs->sr_entry, ad_usrCert, &args.dercert, NULL );
+ attr_merge_one( rs->sr_entry, ad_usrPkey, &args.derpkey, NULL );
+ }
+ op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_op_search(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ /* we only act on a search that returns just our cert/key attrs */
+ if ( op->ors_attrs && op->ors_attrs[0].an_desc == ad_usrCert &&
+ op->ors_attrs[1].an_desc == ad_usrPkey &&
+ op->ors_attrs[2].an_name.bv_val == NULL )
+ {
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ sc->sc_response = autoca_op_response;
+ sc->sc_private = on;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+autoca_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ autoca_info *ai;
+
+ ai = ch_calloc(1, sizeof(autoca_info));
+ on->on_bi.bi_private = ai;
+
+ /* set defaults */
+ ai->ai_usrclass = oc_find( "person" );
+ ai->ai_srvclass = oc_find( "ipHost" );
+ ai->ai_usrkeybits = KEYBITS;
+ ai->ai_srvkeybits = KEYBITS;
+ ai->ai_cakeybits = KEYBITS;
+ ai->ai_usrdays = 365; /* 1 year */
+ ai->ai_srvdays = 1826; /* 5 years */
+ ai->ai_cadays = 3652; /* 10 years */
+ return 0;
+}
+
+static int
+autoca_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ autoca_info *ai = on->on_bi.bi_private;
+
+ if ( ai->ai_cert )
+ X509_free( ai->ai_cert );
+ if ( ai->ai_pkey )
+ EVP_PKEY_free( ai->ai_pkey );
+ ch_free( ai );
+
+ return 0;
+}
+
+static int
+autoca_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ autoca_info *ai = on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc;
+
+ if (slapMode & SLAP_TOOL_MODE)
+ return 0;
+
+ if ( ! *aca_attr2[0].ad ) {
+ int i, code;
+ const char *text;
+
+ for ( i=0; aca_attr2[i].at; i++ ) {
+ code = slap_str2ad( aca_attr2[i].at, aca_attr2[i].ad, &text );
+ if ( code ) return code;
+ }
+
+ /* Schema may not be loaded, ignore if missing */
+ slap_str2ad( "ipHostNumber", &ad_ipaddr, &text );
+
+ for ( i=0; aca_ocs[i].ot; i++ ) {
+ code = register_oc( aca_ocs[i].ot, aca_ocs[i].oc, 0 );
+ if ( code ) return code;
+ }
+ }
+
+ 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;
+ rc = overlay_entry_get_ov( op, be->be_nsuffix, NULL,
+ NULL, 0, &e, on );
+
+ if ( e ) {
+ int gotoc = 0, gotat = 0;
+ if ( is_entry_objectclass( e, oc_caObj, 0 )) {
+ gotoc = 1;
+ a = attr_find( e->e_attrs, ad_caPkey );
+ if ( a ) {
+ const unsigned char *pp;
+ pp = (unsigned char *)a->a_vals[0].bv_val;
+ ai->ai_pkey = d2i_AutoPrivateKey( NULL, &pp, a->a_vals[0].bv_len );
+ if ( ai->ai_pkey )
+ {
+ a = attr_find( e->e_attrs, ad_caCert );
+ if ( a )
+ {
+ pp = (unsigned char *)a->a_vals[0].bv_val;
+ ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len );
+ /* If TLS wasn't configured yet, set this as our CA */
+ if ( !slap_tls_ctx )
+ autoca_setca( a->a_vals );
+ }
+ }
+ gotat = 1;
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ /* generate attrs, store... */
+ if ( !gotat ) {
+ genargs args;
+ saveargs arg2;
+
+ args.issuer_cert = NULL;
+ args.issuer_pkey = NULL;
+ args.subjectDN = &be->be_suffix[0];
+ args.cert_exts = CAexts;
+ args.more_exts = NULL;
+ args.keybits = ai->ai_cakeybits;
+ args.days = ai->ai_cadays;
+
+ rc = autoca_gencert( op, &args );
+ if ( rc )
+ return -1;
+
+ ai->ai_cert = args.newcert;
+ ai->ai_pkey = args.newpkey;
+
+ arg2.dn = be->be_suffix;
+ arg2.ndn = be->be_nsuffix;
+ arg2.isca = 1;
+ if ( !gotoc )
+ arg2.oc = oc_caObj;
+ else
+ arg2.oc = NULL;
+ arg2.on = on;
+ arg2.dercert = &args.dercert;
+ arg2.derpkey = &args.derpkey;
+
+ autoca_savecert( op, &arg2 );
+
+ /* If TLS wasn't configured yet, set this as our CA */
+ if ( !slap_tls_ctx )
+ autoca_setca( &args.dercert );
+
+ op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ return 0;
+}
+
+static slap_overinst autoca;
+
+/* 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 autoca_initialize() {
+ int i, code;
+
+ autoca.on_bi.bi_type = "autoca";
+ autoca.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ autoca.on_bi.bi_db_init = autoca_db_init;
+ autoca.on_bi.bi_db_destroy = autoca_db_destroy;
+ autoca.on_bi.bi_db_open = autoca_db_open;
+ autoca.on_bi.bi_op_search = autoca_op_search;
+
+ autoca.on_bi.bi_cf_ocs = autoca_ocs;
+ code = config_register_schema( autoca_cfg, autoca_ocs );
+ if ( code ) return code;
+
+ for ( i=0; aca_attrs[i]; i++ ) {
+ code = register_at( aca_attrs[i], NULL, 0 );
+ if ( code ) return code;
+ }
+
+ return overlay_register( &autoca );
+}
+
+#if SLAPD_OVER_AUTOCA == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return autoca_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_AUTOCA) */
diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c
new file mode 100644
index 0000000..bbc6219
--- /dev/null
+++ b/servers/slapd/overlays/collect.c
@@ -0,0 +1,440 @@
+/* collect.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 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 "slap-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 );
+ 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 );
+ 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 );
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ 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..f939b37
--- /dev/null
+++ b/servers/slapd/overlays/constraint.c
@@ -0,0 +1,1236 @@
+/* $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>
+ * Emmanuel 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 "slap-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 NEG_REGEX_STR "negregex"
+#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 constrained 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_NEG_REGEX,
+ CONSTRAINT_SET,
+ CONSTRAINT_URI,
+};
+
+static ConfigDriver constraint_cf_gen;
+
+static ConfigTable constraintcfg[] = {
+ { "constraint_attribute", "attribute[list]> (regex|negregex|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_NEG_REGEX:
+ tstr = NEG_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;
+ }
+ }
+
+ int is_regex = strcasecmp( c->argv[2], REGEX_STR ) == 0;
+ int is_neg_regex = strcasecmp( c->argv[2], NEG_REGEX_STR ) == 0;
+ if ( is_regex || is_neg_regex ) {
+ int err;
+
+ ap.type = is_regex ? CONSTRAINT_REGEX : CONSTRAINT_NEG_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 );
+ 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 **app, *a2 = ch_calloc( sizeof(constraint), 1 );
+
+ 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;
+
+ for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next )
+ /* Get to the end */ ;
+
+ a2->ap_next = *app;
+ *app = a2;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg );
+ 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" );
+ }
+ 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_NEG_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 );
+ rc = LDAP_OTHER;
+
+ } else {
+ SlapReply nrs = { REP_RESULT };
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri filter = %s\n",
+ filterstr.bv_val );
+
+ rc = nop.o_bd->be_search( &nop, &nrs );
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri rc = %d, found = %d\n",
+ rc, found );
+ }
+ 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) || be_shadow_update( op ) ) {
+ 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 );
+
+ 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) || be_shadow_update( op ) ) {
+ 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" );
+ 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 );
+
+ 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 */
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ ber_bvreplace( &target_entry_copy->e_name, &op->orr_newDN );
+ ber_bvreplace( &target_entry_copy->e_nname, &op->orr_nnewDN );
+ }
+
+ /* 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_flags = SLAPO_BFLAG_SINGLE;
+ 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..c19f042
--- /dev/null
+++ b/servers/slapd/overlays/dds.c
@@ -0,0 +1,2056 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 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 "slap-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 advertise 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;
+ op->o_do_not_cache = 1;
+
+ 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.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:
+ Log( 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.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:
+ Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS dn=\"%s\" expired.\n",
+ de->de_ndn.bv_val );
+ ndeletes++;
+ break;
+
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ Log( 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:
+ Log( 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;
+
+ Log( 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 ) {
+ Log( 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 ) {
+ Log( 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 ) {
+ Log( 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 ) {
+ Log( 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 ) {
+ Log( 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:;
+ Log( 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.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 );
+
+ Log( 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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY integerMatch "
+ "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 ] );
+ Log( 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 );
+ Log( 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 ] );
+ Log( 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 );
+ Log( 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 ] );
+ Log( 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 );
+ Log( 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 ] );
+ Log( 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 );
+ Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t < 60 ) {
+ Log( 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 ] );
+ Log( 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 );
+ Log( 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 );
+ Log( 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 ) ) {
+ Log( 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 */
+ {
+ Log( 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->o_do_not_cache = 1;
+
+ 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.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:
+ Log( 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:
+ Log( 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 ) ) {
+ Log( 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;
+ }
+
+ Log( 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" );
+ 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 ) {
+ Log( 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_flags = SLAPO_BFLAG_SINGLE;
+ 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 {
+ Log( 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..93b7f69
--- /dev/null
+++ b/servers/slapd/overlays/deref.c
@@ -0,0 +1,586 @@
+/* deref.c - dereference overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2022 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 "slap-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 ] );
+ ber_bvarray_free_x( 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 );
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ 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..5d890d6
--- /dev/null
+++ b/servers/slapd/overlays/dyngroup.c
@@ -0,0 +1,234 @@
+/* dyngroup.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 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 "slap-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 }, **app, *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 );
+ 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 );
+ 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) );
+
+ for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next )
+ /* Get to the end */ ;
+
+ a2->ap_mem = ap.ap_mem;
+ a2->ap_uri = ap.ap_uri;
+ a2->ap_next = *app;
+ *app = a2;
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static ConfigTable dgroupcfg[] = {
+ { "attrpair", "member-attribute> <URL-attribute", 3, 3, 0,
+ ARG_MAGIC, dgroup_cf,
+ "( OLcfgOvAt:17.1 NAME ( 'olcDynGroupAttrPair' 'olcDGAttrPair' ) "
+ "EQUALITY caseIgnoreMatch "
+ "DESC 'Member and MemberURL attribute pair' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dgroupocs[] = {
+ { "( OLcfgOvOc:17.1 "
+ "NAME ( 'olcDynGroupConfig' 'olcDGConfig' ) "
+ "DESC 'Dynamic Group configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcDynGroupAttrPair)",
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ 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..5c38b64
--- /dev/null
+++ b/servers/slapd/overlays/dynlist.c
@@ -0,0 +1,2968 @@
+/* dynlist.c - dynamic list overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 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 "slap-config.h"
+#include "lutil.h"
+
+static AttributeDescription *ad_dgIdentity, *ad_dgAuthz;
+static AttributeDescription *ad_memberOf;
+
+typedef struct dynlist_map_t {
+ AttributeDescription *dlm_member_ad;
+ AttributeDescription *dlm_mapped_ad;
+ AttributeDescription *dlm_memberOf_ad;
+ ObjectClass *dlm_static_oc;
+ int dlm_memberOf_nested;
+ int dlm_member_oper;
+ int dlm_memberOf_oper;
+ 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;
+
+typedef struct dynlist_gen_t {
+ dynlist_info_t *dlg_dli;
+ int dlg_memberOf;
+ int dlg_simple;
+} dynlist_gen_t;
+
+#define DYNLIST_USAGE \
+ "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad>[+<memberOf-ad>[@<static-oc>[*]] ...]\": "
+
+static int
+ad_infilter( AttributeDescription *ad, Filter *f )
+{
+ if ( !f )
+ return 0;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case SLAPD_FILTER_COMPUTED:
+ return 0;
+ case LDAP_FILTER_PRESENT:
+ return f->f_desc == ad;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ return f->f_av_desc == ad;
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ for ( f = f->f_list; f; f = f->f_next )
+ if ( ad_infilter( ad, f ))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static Filter *
+transform_filter( Operation *op, dynlist_info_t *dli, int not, Filter *orig )
+{
+ Filter *f;
+ dynlist_map_t *dlm;
+
+ /* Tilt the filter towards TRUE if it could match through this dli */
+ int result = not ? LDAP_COMPARE_FALSE : LDAP_COMPARE_TRUE;
+
+ if ( orig ) {
+ f = orig;
+ } else {
+ f = orig = filter_dup( op->ors_filter, op->o_tmpmemctx );
+ }
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ 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 ( f->f_av_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_PRESENT:
+ 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 ( f->f_sub_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ 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 ( f->f_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_EXT:
+ 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 ( f->f_mr_desc == ad ) {
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = result;
+ break;
+ }
+ }
+ break;
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ for ( f = f->f_list; f; f = f->f_next )
+ transform_filter( op, dli, not, f );
+ break;
+ case LDAP_FILTER_NOT:
+ transform_filter( op, dli, !not, f->f_list );
+ case SLAPD_FILTER_COMPUTED:
+ break;
+ }
+
+ return orig;
+}
+
+typedef struct dynlist_filterinst_t {
+ AttributeAssertion *df_a;
+ Entry *df_e;
+} dynlist_filterinst_t;
+
+/* Record occurrences of ad in filter. Ignore in negated filters. */
+static void
+dynlist_filter_instances( Operation *op, AttributeDescription *ad, Filter *f, int not, int *dfn, dynlist_filterinst_t **dfp )
+{
+ if ( !f )
+ return;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ if ( !not && f->f_av_desc == ad ) {
+ dynlist_filterinst_t *df = *dfp;
+ int n = *dfn;
+ df = op->o_tmprealloc( df, (n + 1) * sizeof(dynlist_filterinst_t), op->o_tmpmemctx );
+ df[n].df_a = f->f_ava;
+ df[n++].df_e = NULL;
+ *dfp = df;
+ *dfn = n;
+ }
+ break;
+ case SLAPD_FILTER_COMPUTED:
+ case LDAP_FILTER_PRESENT:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ break;
+ case LDAP_FILTER_NOT: not ^= 1;
+ /* FALLTHRU */
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ for ( f = f->f_list; f; f = f->f_next )
+ dynlist_filter_instances( op, ad, f, not, dfn, dfp );
+ }
+}
+
+static int
+dynlist_make_filter( Operation *op, Entry *e, dynlist_info_t *dli, const char *url, struct berval *oldf, struct berval *newf )
+{
+ 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 parentheses 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;
+ char **dlc_attrs;
+} 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 && dlc->dlc_attrs == 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;
+}
+
+typedef struct dynlist_name_t {
+ struct berval dy_nname;
+ struct berval dy_name;
+ dynlist_info_t *dy_dli;
+ dynlist_map_t *dy_dlm;
+ AttributeDescription *dy_staticmember;
+ int dy_seen;
+ int dy_numuris;
+ TAvlnode *dy_subs;
+ TAvlnode *dy_sups;
+ LDAPURLDesc *dy_uris[];
+} dynlist_name_t;
+
+static void
+dynlist_urlmembers( Operation *op, dynlist_name_t *dyn, slap_callback *sc )
+{
+ Operation o = *op;
+ LDAPURLDesc *ludp;
+ int i;
+
+ o.ors_deref = LDAP_DEREF_NEVER;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+ o.ors_attrs = NULL;
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+ o.o_callback = sc;
+ o.o_do_not_cache = 1;
+
+ for (i=0; i<dyn->dy_numuris; i++) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_attrs )
+ continue;
+ o.o_req_dn.bv_val = ludp->lud_dn;
+ o.o_req_dn.bv_len = ludp->lud_port;
+ o.o_req_ndn = o.o_req_dn;
+ o.ors_scope = ludp->lud_scope;
+ o.ors_filter = (Filter *)ludp->lud_filter;
+ filter2bv_x( op, o.ors_filter, &o.ors_filterstr );
+ 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 );
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ (void)o.o_bd->be_search( &o, &r );
+ }
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+}
+
+static void
+dynlist_nested_memberOf( Entry *e, AttributeDescription *ad, TAvlnode *sups )
+{
+ TAvlnode *ptr;
+ dynlist_name_t *dyn;
+ Attribute *a;
+
+ a = attr_find( e->e_attrs, ad );
+ for ( ptr = ldap_tavl_end( sups, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ if ( a ) {
+ unsigned slot;
+ if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &dyn->dy_nname, &slot, NULL ) == LDAP_SUCCESS )
+ continue;
+ }
+ attr_merge_one( e, ad, &dyn->dy_name, &dyn->dy_nname );
+ if ( !a )
+ a = attr_find( e->e_attrs, ad );
+ if ( dyn->dy_sups )
+ dynlist_nested_memberOf( e, ad, dyn->dy_sups );
+ }
+}
+
+typedef struct dynlist_member_t {
+ Entry *dm_e;
+ AttributeDescription *dm_ad;
+ Modification dm_mod;
+ TAvlnode *dm_groups;
+ struct berval dm_bv[2];
+ struct berval dm_nbv[2];
+ const char *dm_text;
+ char dm_textbuf[1024];
+} dynlist_member_t;
+
+static int
+dynlist_ptr_cmp( const void *c1, const void *c2 )
+{
+ return ( c1 < c2 ) ? -1 : c1 > c2;
+}
+
+static int
+dynlist_nested_member_dg( Operation *op, SlapReply *rs )
+{
+ dynlist_member_t *dm = op->o_callback->sc_private;
+
+ if ( rs->sr_type != REP_SEARCH )
+ return LDAP_SUCCESS;
+
+ dm->dm_bv[0] = rs->sr_entry->e_name;
+ dm->dm_nbv[0] = rs->sr_entry->e_nname;
+ modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1,
+ &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf ));
+
+ return LDAP_SUCCESS;
+}
+
+static void
+dynlist_nested_member( Operation *op, slap_overinst *on, dynlist_member_t *dm, TAvlnode *subs )
+{
+ TAvlnode *ptr;
+ dynlist_name_t *dyn;
+ Entry *ne;
+ Attribute *a, *b;
+
+ a = attr_find( dm->dm_e->e_attrs, dm->dm_ad );
+ if ( !a )
+ return;
+
+ for ( ptr = ldap_tavl_end( subs, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ if ( ldap_tavl_insert( &dm->dm_groups, dyn, dynlist_ptr_cmp, ldap_avl_dup_error ))
+ continue;
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &ne, on ) != LDAP_SUCCESS || ne == NULL )
+ continue;
+ b = attr_find( ne->e_attrs, dm->dm_ad );
+ if ( b ) {
+ dm->dm_mod.sm_values = b->a_vals;
+ dm->dm_mod.sm_nvalues = b->a_nvals;
+ dm->dm_mod.sm_numvals = b->a_numvals;
+ modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1,
+ &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf ));
+ }
+ overlay_entry_release_ov( op, ne, 0, on );
+ if ( dyn->dy_numuris ) {
+ slap_callback cb = { 0 };
+ cb.sc_private = dm;
+ BER_BVZERO( &dm->dm_bv[1] );
+ BER_BVZERO( &dm->dm_nbv[1] );
+ dm->dm_mod.sm_values = dm->dm_bv;
+ dm->dm_mod.sm_nvalues = dm->dm_nbv;
+ dm->dm_mod.sm_numvals = 1;
+ cb.sc_response = dynlist_nested_member_dg;
+ dynlist_urlmembers( op, dyn, &cb );
+ }
+ if ( dyn->dy_subs )
+ dynlist_nested_member( op, on, dm, dyn->dy_subs );
+ }
+}
+
+static int
+dynlist_prepare_entry( Operation *op, SlapReply *rs, slap_overinst *on, dynlist_info_t *dli, dynlist_name_t *dyn )
+{
+ 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;
+
+ e = rs->sr_entry;
+ a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad );
+ if ( a == NULL ) {
+ /* FIXME: error? */
+ goto checkdyn;
+ }
+
+ 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 )
+ || ad_infilter( ad, op->ors_filter ))
+ break;
+ }
+
+ /* If nothing matched and this was a search, skip over to nesting check.
+ * If this was a compare, keep on going.
+ */
+ if ( dli->dli_dlm && !dlm && o.o_acl_priv != ACL_COMPARE )
+ goto checkdyn;
+
+ 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 )
+ {
+ goto checkdyn;
+ }
+ }
+
+ o.o_dn = id->a_vals[0];
+ o.o_ndn = id->a_nvals[0];
+ o.o_groups = NULL;
+ }
+
+ /* 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;
+ o.o_do_not_cache = 1;
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+
+ 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 );
+ 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 ( ad_infilter( o.ors_attrs[j].an_desc, op->ors_filter )) {
+ /* if referenced in filter, must retrieve */
+ } else 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 );
+ }
+ dlc.dlc_attrs = lud->lud_attrs;
+
+ if ( lud->lud_filter == NULL ) {
+ ber_dupbv_x( &o.ors_filterstr,
+ &dli->dli_default_filter, op->o_tmpmemctx );
+
+ } else {
+ /* don't allow recursion in lists */
+ if ( lud->lud_attrs ) {
+ struct berval flt;
+ ber_str2bv( lud->lud_filter, 0, 0, &flt );
+ if ( dynlist_make_filter( op, rs->sr_entry, dli, url->bv_val, &flt, &o.ors_filterstr ) ) {
+ /* error */
+ goto cleanup;
+ }
+ } else {
+ ber_str2bv( lud->lud_filter, 0, 0, &o.ors_filterstr );
+ }
+ }
+ 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 );
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ (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 );
+ }
+ if ( lud->lud_attrs ) {
+ 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 );
+ } else {
+ if ( o.ors_filterstr.bv_val != lud->lud_filter )
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ ldap_free_urldesc( lud );
+ }
+
+checkdyn:
+ /* handle nested groups */
+ if ( dyn && ( dyn->dy_sups || dyn->dy_subs )) {
+ /* ensure e is modifiable */
+ if ( e == rs->sr_entry && !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ e = entry_dup( rs->sr_entry );
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+ if ( dyn->dy_subs ) {
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad ) {
+ dynlist_member_t dm;
+ dm.dm_groups = NULL;
+ dm.dm_mod.sm_op = LDAP_MOD_ADD;
+ dm.dm_mod.sm_desc = dlm->dlm_member_ad;
+ dm.dm_mod.sm_type = dlm->dlm_member_ad->ad_cname;
+ dm.dm_e = e;
+ dm.dm_ad = dlm->dlm_member_ad;
+ dynlist_nested_member( op, on, &dm, dyn->dy_subs );
+ if ( dm.dm_groups )
+ ldap_tavl_free( dm.dm_groups, NULL );
+ }
+ }
+ }
+ if ( dyn->dy_sups ) {
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups );
+ }
+ }
+ }
+ }
+
+ if ( e != rs->sr_entry ) {
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dynlist_check_scope( Operation *op, Entry *e, dynlist_info_t *dli )
+{
+ if ( dli->dli_lud ) {
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) &&
+ !dnIsSuffixScope( &e->e_nname,
+ &dli->dli_uri_nbase,
+ dli->dli_lud->lud_scope ))
+ return 0;
+ if ( dli->dli_uri_filter && test_filter( op, e,
+ dli->dli_uri_filter ) != LDAP_COMPARE_TRUE )
+ return 0;
+ }
+ return 1;
+}
+
+static int
+dynlist_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+ Operation o = *op;
+ Entry *e = NULL;
+ dynlist_map_t *dlm;
+ BackendDB *be;
+ int ret = SLAP_CB_CONTINUE;
+
+ if ( get_manageDSAit( op ) )
+ return SLAP_CB_CONTINUE;
+
+ for ( ; dli != NULL; dli = dli->dli_next ) {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ /* builtin dyngroup evaluator only works for DNs */
+ if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName )
+ continue;
+ if ( op->oq_compare.rs_ava->aa_desc == 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;
+
+ if ( e == NULL && ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL ))
+ {
+ return SLAP_CB_CONTINUE;
+ }
+ if ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) ||
+ !dynlist_check_scope( op, e, dli )) {
+ continue;
+ }
+
+ o.o_do_not_cache = 1;
+
+ if ( ad_dgIdentity && backend_attribute( &o, e, &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, e, &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, e, &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;
+
+ /* If also using static groups, fallback to
+ * vanilla compare
+ */
+ if ( dlm->dlm_static_oc )
+ return SLAP_CB_CONTINUE;
+
+ break;
+ }
+
+done:;
+ if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx );
+ overlay_entry_release_ov( &o, e, 0, on );
+
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+
+ be = select_backend( &o.o_req_ndn, 1 );
+ if ( !be || !be->be_search ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( e == NULL && ( 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 *)dlg->dlg_dli;
+ while ( dli != NULL && ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) ||
+ !dynlist_check_scope( op, e, dli ))) {
+ 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 };
+ Attribute *a;
+ AttributeName an[2];
+
+ 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;
+ r.sr_entry = e;
+ r.sr_attrs = an;
+
+ o.o_acl_priv = ACL_COMPARE;
+ dynlist_prepare_entry( &o, &r, on, dli, NULL );
+ a = attrs_find( r.sr_entry->e_attrs, op->orc_ava->aa_desc );
+
+ ret = LDAP_NO_SUCH_ATTRIBUTE;
+ for ( ; a ; a = attrs_find( a->a_next, op->orc_ava->aa_desc )) {
+ ret = LDAP_COMPARE_FALSE;
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->orc_ava->aa_value, NULL, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+ ret = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+ rs->sr_err = ret;
+
+ if ( r.sr_entry != e )
+ entry_free( r.sr_entry );
+ send_ldap_result( op, rs );
+ }
+
+release:;
+ if ( e != NULL ) {
+ overlay_entry_release_ov( &o, e, 0, on );
+ }
+
+ return ret;
+}
+
+#define WANT_MEMBEROF 1
+#define WANT_MEMBER 2
+
+typedef struct dynlist_search_t {
+ slap_overinst *ds_on;
+ TAvlnode *ds_names;
+ TAvlnode *ds_fnodes;
+ dynlist_info_t *ds_dli;
+ dynlist_map_t *ds_dlm;
+ Filter *ds_origfilter;
+ struct berval ds_origfilterbv;
+ int ds_want;
+ int ds_found;
+} dynlist_search_t;
+
+static int
+dynlist_avl_cmp( const void *c1, const void *c2 )
+{
+ const dynlist_name_t *n1, *n2;
+ int rc;
+ n1 = c1; n2 = c2;
+
+ rc = n1->dy_nname.bv_len - n2->dy_nname.bv_len;
+ if ( rc ) return rc;
+ return ber_bvcmp( &n1->dy_nname, &n2->dy_nname );
+}
+
+/* build a list of dynamic entries */
+static int
+dynlist_search1resp( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ Attribute *a, *b = NULL;
+
+ if ( ds->ds_dlm && ds->ds_dlm->dlm_static_oc && is_entry_objectclass( rs->sr_entry, ds->ds_dlm->dlm_static_oc, 0 ))
+ b = attr_find( rs->sr_entry->e_attrs, ds->ds_dlm->dlm_member_ad );
+ a = attr_find( rs->sr_entry->e_attrs, ds->ds_dli->dli_ad );
+
+ /* enforce scope of dynamic entries */
+ if ( a && !dynlist_check_scope( op, rs->sr_entry, ds->ds_dli ))
+ a = NULL;
+
+ if ( a || b ) {
+ unsigned len;
+ dynlist_name_t *dyn;
+ struct berval bv, nbase;
+ LDAPURLDesc *ludp;
+ int i, j = 0;
+
+ if ( a )
+ len = a->a_numvals * sizeof(LDAPURLDesc *);
+ else
+ len = 0;
+
+ dyn = ch_calloc(1, sizeof(dynlist_name_t)+rs->sr_entry->e_nname.bv_len + 1 +
+ rs->sr_entry->e_name.bv_len + 1 + len);
+ dyn->dy_name.bv_val = ((char *)(dyn+1)) + len;
+ dyn->dy_name.bv_len = rs->sr_entry->e_name.bv_len;
+ dyn->dy_nname.bv_val = dyn->dy_name.bv_val + dyn->dy_name.bv_len + 1;
+ dyn->dy_nname.bv_len = rs->sr_entry->e_nname.bv_len;
+ dyn->dy_dli = ds->ds_dli;
+ dyn->dy_dlm = ds->ds_dlm;
+ if ( a ) {
+ Filter *f;
+ /* parse and validate the URIs */
+ for (i=0; i<a->a_numvals; i++) {
+ if (ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS )
+ continue;
+ if (( ludp->lud_host && *ludp->lud_host)
+ || ludp->lud_exts ) {
+ skipit:
+ ldap_free_urldesc( ludp );
+ continue;
+ }
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ goto skipit;
+ ldap_memfree( ludp->lud_dn );
+ ludp->lud_dn = ldap_strdup( nbase.bv_val );
+ op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx );
+ /* cheat here, reuse fields */
+ ludp->lud_port = nbase.bv_len;
+ if ( ludp->lud_filter && *ludp->lud_filter ) {
+ f = str2filter( ludp->lud_filter );
+ if ( f == NULL )
+ goto skipit;
+ ldap_memfree( ludp->lud_filter );
+ } else {
+ f = ch_malloc( sizeof( Filter ));
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = LDAP_COMPARE_TRUE;
+ f->f_next = NULL;
+ }
+ ludp->lud_filter = (char *)f;
+ dyn->dy_uris[j] = ludp;
+ j++;
+ }
+ }
+ dyn->dy_numuris = j;
+ memcpy(dyn->dy_name.bv_val, rs->sr_entry->e_name.bv_val, rs->sr_entry->e_name.bv_len );
+ memcpy(dyn->dy_nname.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len );
+ if ( b )
+ dyn->dy_staticmember = ds->ds_dlm->dlm_member_ad;
+
+ if ( ldap_tavl_insert( &ds->ds_names, dyn, dynlist_avl_cmp, ldap_avl_dup_error )) {
+ for (i=dyn->dy_numuris-1; i>=0; i--) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_filter ) {
+ filter_free( (Filter *)ludp->lud_filter );
+ ludp->lud_filter = NULL;
+ }
+ ldap_free_urldesc( ludp );
+ }
+ ch_free( dyn );
+ } else {
+ ds->ds_found++;
+ }
+ }
+ }
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion
+ * of its dynamic members
+ * using (&(entryDN=<groupURIbase>)<groupURIfilter>)
+ */
+static int
+dynlist_filter_dyngroup( Operation *op, Filter *n, Attribute *a )
+{
+ Filter *andf = NULL, *dnf, *urif, *orf = NULL;
+ LDAPURLDesc *ludp;
+ struct berval bv, nbase;
+ int i;
+
+ for (i=0; i<a->a_numvals; i++) {
+ if ( ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS )
+ continue;
+ if (( ludp->lud_host && *ludp->lud_host )
+ || ludp->lud_attrs
+ || ludp->lud_exts ) {
+ skip:
+ ldap_free_urldesc( ludp );
+ continue;
+ }
+ ber_str2bv( ludp->lud_dn, 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS )
+ goto skip;
+ if ( ludp->lud_filter && *ludp->lud_filter ) {
+ urif = str2filter_x( op, ludp->lud_filter );
+ if ( urif == NULL ) {
+ op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx );
+ goto skip;
+ }
+ } else {
+ urif = NULL;
+ }
+ if ( !andf && n->f_choice == SLAPD_FILTER_COMPUTED ) {
+ andf = n;
+ andf->f_next = NULL;
+ } else {
+ orf = n;
+ if ( n->f_choice != LDAP_FILTER_OR ) {
+ andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ *andf = *n;
+ orf->f_choice = LDAP_FILTER_OR;
+ orf->f_next = NULL;
+ orf->f_list = andf;
+ }
+ andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ andf->f_next = orf->f_list;
+ orf->f_list = andf;
+ }
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ andf->f_choice = LDAP_FILTER_AND;
+ andf->f_list = dnf;
+ dnf->f_next = urif;
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ dnf->f_choice = LDAP_FILTER_EQUALITY;
+ dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ dnf->f_av_desc = slap_schema.si_ad_entryDN;
+ dnf->f_av_value = nbase;
+ } else {
+ dnf->f_choice = LDAP_FILTER_EXT;
+ dnf->f_mra = op->o_tmpcalloc( 1, sizeof(MatchingRuleAssertion), op->o_tmpmemctx );
+ dnf->f_mr_desc = slap_schema.si_ad_entryDN;
+ dnf->f_mr_value = nbase;
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_ONELEVEL:
+ dnf->f_mr_rule = slap_schema.si_mr_dnOneLevelMatch;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ dnf->f_mr_rule = slap_schema.si_mr_dnSubtreeMatch;
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ dnf->f_mr_rule = slap_schema.si_mr_dnSubordinateMatch;
+ break;
+ }
+ ber_str2bv( dnf->f_mr_rule->smr_names[0], 0, 0, &dnf->f_mr_rule_text );
+ }
+ ldap_free_urldesc( ludp );
+ }
+ if ( !andf )
+ return -1;
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion
+ * of its static members
+ * using (|(entryDN=<memberN>)[...])
+ */
+static int
+dynlist_filter_stgroup( Operation *op, Filter *n, Attribute *a )
+{
+ Filter *dnf, *orf = NULL;
+ int i;
+
+ if ( a->a_numvals == 1 && n->f_choice == SLAPD_FILTER_COMPUTED ) {
+ dnf = n;
+ } else {
+ orf = n;
+ if ( n->f_choice != LDAP_FILTER_OR ) {
+ orf->f_choice = LDAP_FILTER_OR;
+ orf->f_list = NULL;
+ }
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ dnf->f_next = orf->f_list;
+ orf->f_list = dnf;
+ }
+
+ for (i=0; i<a->a_numvals; i++) {
+ if ( i ) {
+ dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ dnf->f_next = orf->f_list;
+ orf->f_list = dnf;
+ }
+ dnf->f_choice = LDAP_FILTER_EQUALITY;
+ dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ dnf->f_av_desc = slap_schema.si_ad_entryDN;
+ ber_dupbv_x( &dnf->f_av_value, &a->a_nvals[i], op->o_tmpmemctx );
+ }
+ return 0;
+}
+
+/* replace a filter clause (memberOf=<groupDN>) with an expansion of
+ * its members.
+ */
+static int
+dynlist_filter_group( Operation *op, dynlist_name_t *dyn, Filter *n, dynlist_search_t *ds )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ Entry *e;
+ Attribute *a;
+ int rc = -1;
+
+ if ( ldap_tavl_insert( &ds->ds_fnodes, dyn, dynlist_ptr_cmp, ldap_avl_dup_error ))
+ return 0;
+
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL ) {
+ return -1;
+ }
+ if ( ds->ds_dlm->dlm_static_oc && is_entry_objectclass( e, ds->ds_dlm->dlm_static_oc, 0 )) {
+ a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad );
+ if ( a ) {
+ rc = dynlist_filter_stgroup( op, n, a );
+ }
+ } else {
+ a = attr_find( e->e_attrs, ds->ds_dli->dli_ad );
+ if ( a ) {
+ rc = dynlist_filter_dyngroup( op, n, a );
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ if ( dyn->dy_subs && !rc ) {
+ TAvlnode *ptr;
+ for ( ptr = ldap_tavl_end( dyn->dy_subs, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dyn = ptr->avl_data;
+ rc = dynlist_filter_group( op, dyn, n, ds );
+ if ( rc )
+ break;
+ }
+ }
+ return rc;
+}
+
+/* Dup the filter, replacing any references to given ad with group evaluation */
+static Filter *
+dynlist_filter_dup( Operation *op, Filter *f, AttributeDescription *ad, dynlist_search_t *ds )
+{
+ Filter *n;
+
+ if ( !f )
+ return NULL;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_next = NULL;
+ if ( f->f_av_desc == ad ) {
+ dynlist_name_t *dyn = ldap_tavl_find( ds->ds_names, &f->f_av_value, dynlist_avl_cmp );
+ n->f_choice = SLAPD_FILTER_COMPUTED;
+ if ( dyn && !dynlist_filter_group( op, dyn, n, ds ))
+ break;
+ }
+ n->f_choice = LDAP_FILTER_EQUALITY;
+ n->f_ava = ava_dup( f->f_ava, op->o_tmpmemctx );
+ break;
+ case SLAPD_FILTER_COMPUTED:
+ case LDAP_FILTER_PRESENT:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ n = filter_dup( f, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_NOT:
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR: {
+ Filter **p;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_next = NULL;
+ n->f_choice = f->f_choice;
+
+ for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) {
+ *p = dynlist_filter_dup( op, f, ad, ds );
+ if ( !*p )
+ continue;
+ p = &(*p)->f_next;
+ }
+ }
+ break;
+ }
+ return n;
+}
+
+static void
+dynlist_search_free( void *ptr )
+{
+ dynlist_name_t *dyn = (dynlist_name_t *)ptr;
+ LDAPURLDesc *ludp;
+ int i;
+
+ for (i=dyn->dy_numuris-1; i>=0; i--) {
+ ludp = dyn->dy_uris[i];
+ if ( ludp->lud_filter ) {
+ filter_free( (Filter *)ludp->lud_filter );
+ ludp->lud_filter = NULL;
+ }
+ ldap_free_urldesc( ludp );
+ }
+ if ( dyn->dy_subs )
+ ldap_tavl_free( dyn->dy_subs, NULL );
+ if ( dyn->dy_sups )
+ ldap_tavl_free( dyn->dy_sups, NULL );
+ ch_free( ptr );
+}
+
+static int
+dynlist_search_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || op->o_abandon ||
+ rs->sr_err == SLAPD_ABANDON ) {
+ slap_callback *sc = op->o_callback;
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ ldap_tavl_free( ds->ds_names, dynlist_search_free );
+ if ( ds->ds_fnodes )
+ ldap_tavl_free( ds->ds_fnodes, NULL );
+ if ( ds->ds_origfilter ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+ op->ors_filter = ds->ds_origfilter;
+ op->ors_filterstr = ds->ds_origfilterbv;
+ }
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ }
+ return 0;
+}
+
+static int
+dynlist_test_dynmember(Operation *op, dynlist_name_t *dyn, Entry *e)
+{
+ LDAPURLDesc *ludp;
+ struct berval nbase, bv;
+ int i, rc = LDAP_COMPARE_FALSE;
+
+ for (i=0; i<dyn->dy_numuris; i++) {
+ ludp = dyn->dy_uris[i];
+ nbase.bv_val = ludp->lud_dn;
+ nbase.bv_len = ludp->lud_port;
+ if ( ludp->lud_attrs )
+ continue;
+ switch( ludp->lud_scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( !dn_match( &nbase, &e->e_nname ))
+ continue;
+ break;
+ case LDAP_SCOPE_ONELEVEL:
+ dnParent( &e->e_nname, &bv );
+ if ( !dn_match( &nbase, &bv ))
+ continue;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if ( !dnIsSuffix( &e->e_nname, &nbase ))
+ continue;
+ break;
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( dn_match( &nbase, &e->e_nname ) ||
+ !dnIsSuffix( &e->e_nname, &nbase ))
+ continue;
+ break;
+ }
+ if ( !ludp->lud_filter ) /* there really should always be a filter */
+ rc = LDAP_COMPARE_TRUE;
+ else
+ rc = test_filter( op, e, (Filter *)ludp->lud_filter );
+ if ( rc == LDAP_COMPARE_TRUE )
+ break;
+ }
+ return rc;
+}
+
+static int
+dynlist_test_membership(Operation *op, slap_overinst *on, dynlist_name_t *dyn, Entry *e)
+{
+ if ( dyn->dy_staticmember ) {
+ Entry *grp;
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &grp, on ) == LDAP_SUCCESS && grp ) {
+ Attribute *a = attr_find( grp->e_attrs, dyn->dy_staticmember );
+ int rc;
+ if ( a ) {
+ rc = value_find_ex( dyn->dy_staticmember, SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, a->a_nvals, &e->e_nname, op->o_tmpmemctx );
+ rc = ( rc == LDAP_SUCCESS ) ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ } else {
+ rc = LDAP_COMPARE_FALSE;
+ }
+ overlay_entry_release_ov( op, grp, 0, on );
+ return rc;
+ }
+ }
+ return dynlist_test_dynmember( op, dyn, e );
+}
+
+static void
+dynlist_add_memberOf(Operation *op, SlapReply *rs, dynlist_search_t *ds)
+{
+ TAvlnode *ptr;
+ Entry *e = rs->sr_entry;
+ dynlist_name_t *dyn;
+ Attribute *a;
+
+ /* See if there are any memberOf values to attach to this entry */
+ for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ dynlist_map_t *dlm;
+ dyn = ptr->avl_data;
+ for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ if ( dynlist_test_membership( op, ds->ds_on, dyn, e ) == LDAP_COMPARE_TRUE ) {
+ /* 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 == rs->sr_entry ) {
+ e = entry_dup( rs->sr_entry );
+ }
+ a = attr_find( e->e_attrs, dlm->dlm_memberOf_ad );
+ if ( a ) {
+ unsigned slot;
+ if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH,
+ &dyn->dy_nname, &slot, NULL ) != LDAP_SUCCESS )
+ a = NULL;
+ }
+ if ( !a )
+ attr_merge_one( e, dlm->dlm_memberOf_ad, &dyn->dy_name, &dyn->dy_nname );
+ if ( dyn->dy_sups ) {
+ dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups );
+ }
+ break;
+ }
+ }
+ }
+ }
+ 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;
+ }
+}
+
+/* See if a DN-valued filter attribute belongs to a dyngroup */
+static int
+dynmember( dynlist_name_t *dyn, Filter *f, int ndf, dynlist_filterinst_t *df )
+{
+ int i;
+ int ret = 1; /* default to accepting everything */
+
+ for ( i = 0; i < ndf; i++ ) {
+ if ( df[i].df_e ) {
+ ret = dynlist_test_dynmember( NULL, dyn, df[i].df_e ) == LDAP_COMPARE_TRUE;
+ if ( ret )
+ break;
+ }
+ }
+ return ret;
+}
+
+/* process the search responses */
+static int
+dynlist_search2resp( Operation *op, SlapReply *rs )
+{
+ dynlist_search_t *ds = op->o_callback->sc_private;
+ dynlist_name_t *dyn;
+ int rc;
+
+ if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+ rc = SLAP_CB_CONTINUE;
+ /* See if this is one of our dynamic groups */
+ dyn = NULL;
+ if ( ds->ds_names ) {
+ dyn = ldap_tavl_find( ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp );
+ if ( dyn ) {
+ dyn->dy_seen = 1;
+ rc = dynlist_prepare_entry( op, rs, ds->ds_on, dyn->dy_dli, dyn );
+ } else if ( ds->ds_want )
+ dynlist_add_memberOf( op, rs, ds );
+ }
+ /* Then check for dynamic lists */
+ if ( dyn == NULL ) {
+ dynlist_info_t *dli;
+ Attribute *a = attr_find ( rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass );
+ if ( a ) {
+ for ( dli = ds->ds_dli; dli; dli = dli->dli_next ) {
+ if ( is_entry_objectclass_or_sub( rs->sr_entry, dli->dli_oc ) &&
+ dynlist_check_scope( op, rs->sr_entry, dli ))
+ rc = dynlist_prepare_entry( op, rs, ds->ds_on, dli, NULL );
+ }
+ }
+ }
+ if ( ds->ds_origfilter && test_filter( op, rs->sr_entry, ds->ds_origfilter ) != LDAP_COMPARE_TRUE ) {
+ rs_flush_entry( op, rs, NULL );
+ return LDAP_SUCCESS;
+ }
+ return rc;
+ } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) {
+ slap_overinst *on = ds->ds_on;
+ TAvlnode *ptr, *skip = NULL;
+ SlapReply r = *rs;
+ dynlist_map_t *dlm = NULL;
+ Filter *f = ds->ds_origfilter ? ds->ds_origfilter : op->ors_filter;
+ dynlist_filterinst_t *df = NULL;
+ int ndf = 0;
+
+ if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED )
+ return SLAP_CB_CONTINUE;
+
+ /* Check for any unexpanded dynamic group entries that weren't picked up
+ * by the original search filter.
+ */
+ ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT );
+ while ( ptr ) {
+ dyn = ptr->avl_data;
+ if ( dyn->dy_seen )
+ goto next;
+ dyn->dy_seen = 1;
+ if ( !dnIsSuffixScope( &dyn->dy_nname, &op->o_req_ndn, op->ors_scope ))
+ goto next;
+ /* can only pre-check if this is a dyngroup, otherwise just build the entry */
+ if ( dyn->dy_dli->dli_dlm && !dyn->dy_dli->dli_dlm->dlm_next &&
+ dyn->dy_dlm && !dyn->dy_dlm->dlm_mapped_ad ) {
+ if ( !dlm ) {
+ AttributeDescription *ad;
+ int i;
+ dlm = dyn->dy_dlm;
+ ad = dlm->dlm_member_ad;
+ /* can only pre-check DN-valued attrs */
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ /* find any instances of this ad in the filter */
+ dynlist_filter_instances( op, ad, f, 0, &ndf, &df );
+ for ( i = 0; i < ndf; i++ ) {
+ overlay_entry_get_ov( op, &df[i].df_a->aa_value, NULL, NULL, 0, &df[i].df_e, on );
+ }
+ }
+ } else if ( dlm != dyn->dy_dlm ) { /* if a different map, do it later */
+ if ( !skip )
+ skip = ptr;
+ dyn->dy_seen = 0; /* we'll want to process it next time thru */
+ goto next;
+ }
+ /* only pre-check for non-nested */
+ if ( !dyn->dy_sups && !dyn->dy_subs && ndf && !dynmember( dyn, f, ndf, df ))
+ goto next;
+ }
+ if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &r.sr_entry, on ) != LDAP_SUCCESS ||
+ r.sr_entry == NULL )
+ goto next;
+ r.sr_flags = REP_ENTRY_MUSTRELEASE;
+ dynlist_prepare_entry( op, &r, on, dyn->dy_dli, dyn );
+ if ( test_filter( op, r.sr_entry, f ) == LDAP_COMPARE_TRUE ) {
+ r.sr_attrs = op->ors_attrs;
+ rs->sr_err = send_search_entry( op, &r );
+ if ( rs->sr_err != LDAP_SUCCESS )
+ break;
+ r.sr_entry = NULL;
+ }
+ if ( r.sr_entry )
+ rs_flush_entry( op, &r, NULL );
+next:
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT );
+ if ( !ptr ) {
+ int i;
+ for ( i = 0; i<ndf; i++ ) {
+ if ( df[i].df_e )
+ overlay_entry_release_ov( op, df[i].df_e, 0, on );
+ }
+ op->o_tmpfree( df, op->o_tmpmemctx );
+ ndf = 0;
+ if ( skip ) { /* go back for dyns we skipped */
+ ptr = skip;
+ skip = NULL;
+ dlm = NULL;
+ df = NULL;
+ }
+ }
+ }
+ if ( ndf ) {
+ int i;
+ for ( i = 0; i<ndf; i++ ) {
+ if ( df[i].df_e )
+ overlay_entry_release_ov( op, df[i].df_e, 0, on );
+ }
+ op->o_tmpfree( df, op->o_tmpmemctx );
+ }
+ rs->sr_nentries = r.sr_nentries;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+dynlist_fix_filter( Operation *op, AttributeDescription *ad, dynlist_search_t *ds )
+{
+ Filter *f;
+ f = dynlist_filter_dup( op, op->ors_filter, ad, ds );
+ if ( ds->ds_origfilter ) {
+ filter_free_x( op, op->ors_filter, 1 );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ } else {
+ ds->ds_origfilter = op->ors_filter;
+ ds->ds_origfilterbv = op->ors_filterstr;
+ }
+ op->ors_filter = f;
+ filter2bv_x( op, f, &op->ors_filterstr );
+}
+
+typedef struct dynlist_link_t {
+ dynlist_search_t *dl_ds;
+ dynlist_name_t *dl_sup;
+} dynlist_link_t;
+
+static int
+dynlist_nestlink_dg( Operation *op, SlapReply *rs )
+{
+ dynlist_link_t *dll = op->o_callback->sc_private;
+ dynlist_search_t *ds = dll->dl_ds;
+ dynlist_name_t *di = dll->dl_sup, *dj;
+
+ if ( rs->sr_type != REP_SEARCH )
+ return LDAP_SUCCESS;
+
+ dj = ldap_tavl_find( dll->dl_ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp );
+ if ( dj ) {
+ if ( ds->ds_want & WANT_MEMBEROF ) {
+ ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ if ( ds->ds_want & WANT_MEMBER ) {
+ ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ }
+ return LDAP_SUCCESS;
+}
+
+/* Connect all nested groups to their parents/children */
+static void
+dynlist_nestlink( Operation *op, dynlist_search_t *ds )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_name_t *di, *dj;
+ TAvlnode *ptr;
+ Entry *e;
+ Attribute *a;
+ int i;
+
+ for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+ ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) {
+ di = ptr->avl_data;
+ if ( ds->ds_dlm ) {
+ if ( overlay_entry_get_ov( op, &di->dy_nname, NULL, NULL, 0, &e, on ) != LDAP_SUCCESS || e == NULL )
+ continue;
+ a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad );
+ if ( a ) {
+ for ( i=0; i < a->a_numvals; i++ ) {
+ dj = ldap_tavl_find( ds->ds_names, &a->a_nvals[i], dynlist_avl_cmp );
+ if ( dj ) {
+ if ( ds->ds_want & WANT_MEMBEROF ) {
+ ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ if ( ds->ds_want & WANT_MEMBER ) {
+ ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error );
+ }
+ }
+ }
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ if ( di->dy_numuris ) {
+ slap_callback cb = { 0 };
+ dynlist_link_t dll;
+ dll.dl_ds = ds;
+ dll.dl_sup = di;
+ cb.sc_private = &dll;
+ cb.sc_response = dynlist_nestlink_dg;
+ dynlist_urlmembers( op, di, &cb );
+ }
+ }
+}
+
+static int
+dynlist_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli;
+ Operation o = *op;
+ dynlist_map_t *dlm;
+ Filter f[4];
+ AttributeAssertion ava[2];
+ AttributeName an[2] = {0};
+
+ slap_callback *sc;
+ dynlist_search_t *ds;
+ ObjectClass *static_oc;
+ int nested, found, tmpwant;
+ int opattrs, userattrs;
+
+ if ( get_manageDSAit( op ) )
+ return SLAP_CB_CONTINUE;
+
+ sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(dynlist_search_t), op->o_tmpmemctx );
+ sc->sc_private = (void *)(sc+1);
+ ds = sc->sc_private;
+
+ memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag ));
+ o.o_managedsait = SLAP_CONTROL_CRITICAL;
+ o.o_do_not_cache = 1;
+
+ /* Are we using memberOf, and does it affect this request? */
+ if ( dlg->dlg_memberOf ) {
+ int attrflags = slap_attr_flags( op->ors_attrs );
+ opattrs = SLAP_OPATTRS( attrflags );
+ userattrs = SLAP_USERATTRS( attrflags );
+ }
+
+ if (dlg->dlg_simple)
+ goto simple;
+ /* Find all groups in scope. For group expansion
+ * we only need the groups within the search scope, but
+ * for memberOf populating, we need all dyngroups.
+ */
+ for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) {
+ static_oc = NULL;
+ nested = 0;
+ tmpwant = 0;
+
+ if ( !dli->dli_dlm ) {
+ /* A dynamic list returning arbitrary attrs:
+ * we don't know what attrs it might return,
+ * so we can't check if any of its attrs are
+ * in the filter. So assume none of them are.
+ *
+ * If filtering is desired, the filterable attrs
+ * must be explicitly mapped (even to
+ * themselves if nothing else).
+ */
+ continue;
+ } else {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+ int want = 0;
+
+ /* is attribute in filter? */
+ if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) {
+ want |= WANT_MEMBEROF;
+ /* with nesting, filter attributes also require nestlink */
+ if ( dlm->dlm_memberOf_nested ) {
+ /* WANT_ flags have inverted meaning here:
+ * to satisfy (memberOf=) filter, we need to also
+ * find all subordinate groups. No special
+ * treatment is needed for (member=) since we
+ * already search all group entries.
+ */
+ want |= WANT_MEMBER;
+ }
+ }
+
+ /* if attribute is not requested, skip it */
+ if ( op->ors_attrs == NULL ) {
+ if ( !dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ } else {
+ if ( ad_inlist( dlm->dlm_memberOf_ad, op->ors_attrs )) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && ad_inlist( dlm->dlm_member_ad, op->ors_attrs ))
+ want |= WANT_MEMBER;
+ } else {
+ if ( opattrs ) {
+ if ( dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ }
+ if ( userattrs ) {
+ if ( !dlm->dlm_memberOf_oper ) {
+ want |= WANT_MEMBEROF;
+ if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper )
+ want |= WANT_MEMBER;
+ }
+ }
+ }
+ }
+ if ( want ) {
+ nested = dlm->dlm_memberOf_nested;
+ ds->ds_want = tmpwant = want;
+ if ( dlm->dlm_static_oc ) {
+ static_oc = dlm->dlm_static_oc;
+ ds->ds_dlm = dlm;
+ }
+ }
+ }
+ {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( ad_infilter( ad, op->ors_filter )) {
+ tmpwant |= WANT_MEMBER;
+ ds->ds_want = tmpwant;
+ ds->ds_dlm = dlm;
+ }
+ }
+ }
+ }
+
+ if ( tmpwant ) {
+ Filter *f_new = NULL;
+
+ if ( tmpwant == WANT_MEMBER ) {
+ /*
+ * If we only need to list groups, not their members, keep the
+ * filter, assuming any references to mapped attributes make it
+ * succeed.
+ *
+ * A nested groups search will indicate that it needs both.
+ */
+ f_new = transform_filter( op, dli, 0, NULL );
+ }
+
+ if ( static_oc ) {
+ f[0].f_choice = LDAP_FILTER_AND;
+ f[0].f_list = &f[1];
+ f[0].f_next = NULL;
+ f[1].f_choice = LDAP_FILTER_OR;
+ f[1].f_list = &f[2];
+ f[1].f_next = f_new;
+ f[2].f_choice = LDAP_FILTER_EQUALITY;
+ f[2].f_next = &f[3];
+ f[2].f_ava = &ava[0];
+ f[2].f_av_desc = slap_schema.si_ad_objectClass;
+ f[2].f_av_value = dli->dli_oc->soc_cname;
+ f[3].f_choice = LDAP_FILTER_EQUALITY;
+ f[3].f_ava = &ava[1];
+ f[3].f_av_desc = slap_schema.si_ad_objectClass;
+ f[3].f_av_value = static_oc->soc_cname;
+ f[3].f_next = NULL;
+ } else {
+ f[0].f_choice = LDAP_FILTER_AND;
+ f[0].f_list = &f[1];
+ f[0].f_next = NULL;
+ f[1].f_choice = LDAP_FILTER_EQUALITY;
+ f[1].f_ava = ava;
+ f[1].f_av_desc = slap_schema.si_ad_objectClass;
+ f[1].f_av_value = dli->dli_oc->soc_cname;
+ f[1].f_next = f_new;
+ }
+
+ if ( o.o_callback != sc ) {
+ o.o_callback = sc;
+ o.ors_filter = f;
+ if ( tmpwant ) {
+ o.o_req_dn = op->o_bd->be_suffix[0];
+ o.o_req_ndn = op->o_bd->be_nsuffix[0];
+ o.ors_scope = LDAP_SCOPE_SUBTREE;
+ } else {
+ o.o_req_dn = op->o_req_dn;
+ o.o_req_ndn = op->o_req_ndn;
+ o.ors_scope = op->ors_scope;
+ }
+ o.ors_attrsonly = 0;
+ o.ors_attrs = an;
+ o.o_bd = select_backend( op->o_bd->be_nsuffix, 1 );
+ BER_BVZERO( &o.ors_filterstr );
+ sc->sc_response = dynlist_search1resp;
+ }
+
+ ds->ds_dli = dli;
+ if ( o.ors_filterstr.bv_val )
+ o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx );
+ filter2bv_x( &o, f, &o.ors_filterstr );
+ an[0].an_desc = dli->dli_ad;
+ an[0].an_name = dli->dli_ad->ad_cname;
+ found = ds->ds_found;
+ {
+ SlapReply r = { REP_SEARCH };
+ (void)o.o_bd->be_search( &o, &r );
+ }
+ o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx );
+ o.ors_filterstr.bv_val = NULL;
+ filter_free_x( &o, f_new, 1 );
+ if ( found != ds->ds_found && nested )
+ dynlist_nestlink( op, ds );
+ }
+ }
+simple:
+
+ if ( dlg->dlg_dli || ds->ds_names != NULL ) {
+ sc->sc_response = dynlist_search2resp;
+ sc->sc_cleanup = dynlist_search_cleanup;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ ds->ds_on = on;
+
+ /* dynamic lists need this */
+ ds->ds_dli = dlg->dlg_dli;
+
+ /* see if filter needs fixing */
+ if ( dlg->dlg_memberOf ) {
+ for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_memberOf_ad ) {
+
+ /* if attribute is in filter, fix it */
+ if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) {
+ ds->ds_dli = dli;
+ ds->ds_dlm = dlm;
+ dynlist_fix_filter( op, dlm->dlm_memberOf_ad, ds );
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ }
+ 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" );
+ 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 ( 'olcDynListAttrSet' '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
+ { "dynlist-simple", NULL, 0, 0, 0, ARG_OFFSET|ARG_ON_OFF,
+ (void *)offsetof(dynlist_gen_t, dlg_simple),
+ "( OLcfgOvAt:8.2 NAME 'olcDynListSimple' "
+ "DESC 'Simple mode - disable features added since 2.4.' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dlocs[] = {
+ { "( OLcfgOvOc:8.1 "
+ "NAME ( 'olcDynListConfig' 'olcDynamicList' ) "
+ "DESC 'Dynamic list configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcDynListAttrSet $ olcDynListSimple ) )",
+ Cft_Overlay, dlcfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+dl_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+
+ 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 );
+
+ if ( dlm->dlm_memberOf_ad ) {
+ *ptr++ = '+';
+ ptr = lutil_strcopy( ptr, dlm->dlm_memberOf_ad->ad_cname.bv_val );
+ if ( dlm->dlm_static_oc ) {
+ *ptr++ = '@';
+ ptr = lutil_strcopy( ptr, dlm->dlm_static_oc->soc_cname.bv_val );
+ }
+ if ( dlm->dlm_memberOf_nested ) {
+ *ptr++ = '*';
+ }
+ }
+ }
+
+ 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 );
+ }
+
+ dlg->dlg_dli = NULL;
+ dlg->dlg_memberOf = 0;
+
+ } else {
+ dynlist_info_t **dlip;
+ dynlist_map_t *dlm;
+ dynlist_map_t *dlm_next;
+
+ for ( i = 0, dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ 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;
+ if ( dlm->dlm_memberOf_ad )
+ dlg->dlg_memberOf--;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+
+ dli = (dynlist_info_t *)dlg->dlg_dli;
+ }
+ 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 );
+ 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 );
+
+ 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 );
+ 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 );
+ 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;
+ AttributeDescription *memberOf_ad = NULL;
+ ObjectClass *static_oc = NULL;
+ int nested = 0;
+ 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 );
+ rc = 1;
+ goto done_uri;
+ }
+ arg = cp + 1;
+ }
+ if ( ( cp = strchr( arg, '+' ) ) != NULL ) {
+ struct berval bv;
+ char *ocp, *np;
+ np = strrchr( cp+1, '*' );
+ if ( np ) {
+ nested = 1;
+ *np = '\0';
+ }
+ ocp = strchr( cp+1, '@' );
+ if ( ocp ) {
+ static_oc = oc_find( ocp+1 );
+ if ( !static_oc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find static-oc ObjectClass #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ *ocp = '\0';
+ }
+ ber_str2bv( cp+1, 0, 0, &bv );
+ rc = slap_bv2ad( &bv, &memberOf_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find memberOf AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg );
+ rc = 1;
+ goto done_uri;
+ }
+ dlg->dlg_memberOf++;
+ *cp = '\0';
+ }
+
+ 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 );
+ 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_memberOf_ad = memberOf_ad;
+ dlmp->dlm_static_oc = static_oc;
+ dlmp->dlm_memberOf_nested = nested;
+ dlmp->dlm_member_oper = is_at_operational( member_ad->ad_type );
+ if ( memberOf_ad ) {
+ dlmp->dlm_memberOf_oper = is_at_operational( memberOf_ad->ad_type );
+ } else {
+ dlmp->dlm_memberOf_oper = 0;
+ }
+ 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 **)&dlg->dlg_dli;
+ 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 );
+ rc = 1;
+ goto done_uri;
+ }
+ dlip = &(*dlip)->dli_next;
+ }
+ dli_next = *dlip;
+
+ } else {
+ for ( dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ *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 );
+ /* 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 );
+ 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 );
+ 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 );
+ 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 );
+ return 1;
+ }
+
+ for ( dlip = (dynlist_info_t **)&dlg->dlg_dli;
+ *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 );
+#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_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dynlist_gen_t *dlg;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Debug( LDAP_DEBUG_ANY, "dynlist cannot be used as global overlay.\n" );
+ return 1;
+ }
+
+ dlg = (dynlist_gen_t *)ch_calloc( 1, sizeof( *dlg ));
+ on->on_bi.bi_private = dlg;
+
+ return 0;
+}
+
+static int
+dynlist_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ if ( dli == NULL ) {
+ dli = ch_calloc( 1, sizeof( dynlist_info_t ) );
+ dlg->dlg_dli = 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 );
+ 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 );
+ 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 );
+ /* 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 );
+ /* 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_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private;
+ dynlist_info_t *dli = dlg->dlg_dli,
+ *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 );
+ }
+ ch_free( dlg );
+ }
+
+ 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)
+{
+ const char *text;
+ int rc = 0;
+
+ /* See if we need to define memberOf opattr */
+ rc = slap_str2ad( "memberOf", &ad_memberOf, &text );
+ if ( rc ) {
+ rc = register_at(
+ "( 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 " /* added */
+ "X-ORIGIN 'iPlanet Delegated Administrator' )",
+ &ad_memberOf, 0 );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dynlist_initialize: register_at (memberOf) failed\n" );
+ return rc;
+ }
+ }
+
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ dynlist.on_bi.bi_db_init = dynlist_db_init;
+ 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_bi.bi_op_search = dynlist_search;
+ dynlist.on_bi.bi_op_compare = dynlist_compare;
+
+ 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/homedir.c b/servers/slapd/overlays/homedir.c
new file mode 100644
index 0000000..159090e
--- /dev/null
+++ b/servers/slapd/overlays/homedir.c
@@ -0,0 +1,2074 @@
+/* homedir.c - create/remove user home directories */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 The OpenLDAP Foundation.
+ * Portions copyright 2009-2010 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
+ * Corp. for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_HOMEDIR
+
+#define _FILE_OFFSET_BITS 64
+
+#include <stdio.h>
+#include <fcntl.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/errno.h>
+#include <sys/stat.h>
+#include <ac/unistd.h>
+#include <ac/dirent.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "slap-config.h"
+
+#define DEFAULT_MIN_UID ( 100 )
+#define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" )
+
+typedef struct homedir_regexp {
+ char *match;
+ char *replace;
+ regex_t compiled;
+ struct homedir_regexp *next;
+} homedir_regexp;
+
+typedef enum {
+ DEL_IGNORE,
+ DEL_DELETE,
+ DEL_ARCHIVE
+} delete_style;
+
+typedef struct homedir_data {
+ char *skeleton_path;
+ unsigned min_uid;
+ AttributeDescription *home_ad;
+ AttributeDescription *uidn_ad;
+ AttributeDescription *gidn_ad;
+ homedir_regexp *regexps;
+ delete_style style;
+ char *archive_path;
+} homedir_data;
+
+typedef struct homedir_cb_data {
+ slap_overinst *on;
+ Entry *entry;
+} homedir_cb_data;
+
+typedef struct name_list {
+ char *name;
+ struct stat st;
+ struct name_list *next;
+} name_list;
+
+typedef struct name_list_list {
+ name_list *list;
+ struct name_list_list *next;
+} name_list_list;
+
+typedef enum {
+ TRAVERSE_CB_CONTINUE,
+ TRAVERSE_CB_DONE,
+ TRAVERSE_CB_FAIL
+} traverse_cb_ret;
+
+/* private, file info, context */
+typedef traverse_cb_ret (*traverse_cb_func)(
+ void *,
+ const char *,
+ const struct stat *,
+ void * );
+typedef struct traverse_cb {
+ traverse_cb_func pre_func;
+ traverse_cb_func post_func;
+ void *pre_private;
+ void *post_private;
+} traverse_cb;
+
+typedef struct copy_private {
+ int source_prefix_len;
+ const char *dest_prefix;
+ int dest_prefix_len;
+ uid_t uidn;
+ gid_t gidn;
+} copy_private;
+
+typedef struct chown_private {
+ uid_t old_uidn;
+ uid_t new_uidn;
+ gid_t old_gidn;
+ gid_t new_gidn;
+} chown_private;
+
+typedef struct ustar_header {
+ char name[100];
+ char mode[8];
+ char uid[8];
+ char gid[8];
+ char size[12];
+ char mtime[12];
+ char checksum[8];
+ char typeflag[1];
+ char linkname[100];
+ char magic[6];
+ char version[2];
+ char uname[32];
+ char gname[32];
+ char devmajor[8];
+ char devminor[8];
+ char prefix[155];
+ char pad[12];
+} ustar_header;
+
+typedef struct tar_private {
+ FILE *file;
+ const char *name;
+} tar_private;
+
+/* FIXME: This mutex really needs to be executable-global, but this
+ * will have to do for now.
+ */
+static ldap_pvt_thread_mutex_t readdir_mutex;
+static ConfigDriver homedir_regexp_cfg;
+static ConfigDriver homedir_style_cfg;
+static slap_overinst homedir;
+
+static ConfigTable homedircfg[] = {
+ { "homedir-skeleton-path", "pathname", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(homedir_data, skeleton_path),
+ "( OLcfgCtAt:8.1 "
+ "NAME 'olcSkeletonPath' "
+ "DESC 'Pathname for home directory skeleton template' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, { .v_string = DEFAULT_SKEL }
+ },
+
+ { "homedir-min-uidnumber", "uid number", 2, 2, 0,
+ ARG_UINT|ARG_OFFSET,
+ (void *)offsetof(homedir_data, min_uid),
+ "( OLcfgCtAt:8.2 "
+ "NAME 'olcMinimumUidNumber' "
+ "DESC 'Minimum uidNumber attribute to consider' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, { .v_uint = DEFAULT_MIN_UID }
+ },
+
+ { "homedir-regexp", "regexp> <path", 3, 3, 0,
+ ARG_MAGIC,
+ homedir_regexp_cfg,
+ "( OLcfgCtAt:8.3 "
+ "NAME 'olcHomedirRegexp' "
+ "DESC 'Regular expression for matching and transforming paths' "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL
+ },
+
+ { "homedir-delete-style", "style", 2, 2, 0,
+ ARG_MAGIC,
+ homedir_style_cfg,
+ "( OLcfgCtAt:8.4 "
+ "NAME 'olcHomedirDeleteStyle' "
+ "DESC 'Action to perform when removing a home directory' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ { "homedir-archive-path", "pathname", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(homedir_data, archive_path),
+ "( OLcfgCtAt:8.5 "
+ "NAME 'olcHomedirArchivePath' "
+ "DESC 'Pathname for home directory archival' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs homedirocs[] = {
+ { "( OLcfgCtOc:8.1 "
+ "NAME 'olcHomedirConfig' "
+ "DESC 'Homedir configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSkeletonPath $ olcMinimumUidNumber "
+ "$ olcHomedirRegexp $ olcHomedirDeleteStyle "
+ "$ olcHomedirArchivePath ) )",
+ Cft_Overlay, homedircfg },
+
+ { NULL, 0, NULL }
+};
+
+static int
+homedir_regexp_cfg( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ homedir_data *data = (homedir_data *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert( data != NULL );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT: {
+ int i;
+ homedir_regexp *r;
+ struct berval bv;
+ char buf[4096];
+
+ bv.bv_val = buf;
+ for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) {
+ bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i,
+ r->match, r->replace );
+ if ( bv.bv_len >= sizeof(buf) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: "
+ "emit serialization failed: size %lu\n",
+ (unsigned long)bv.bv_len );
+ return ARG_BAD_CONF;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ } break;
+
+ case LDAP_MOD_DELETE:
+ if ( c->valx < 0 ) { /* delete all values */
+ homedir_regexp *r, *rnext;
+
+ for ( r = data->regexps; r != NULL; r = rnext ) {
+ rnext = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+ }
+ data->regexps = NULL;
+ rc = 0;
+
+ } else { /* delete value by index*/
+ homedir_regexp **rp, *r;
+ int i;
+
+ for ( i = 0, rp = &data->regexps; i < c->valx;
+ ++i, rp = &(*rp)->next )
+ ;
+
+ r = *rp;
+ *rp = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+
+ rc = 0;
+ }
+ break;
+
+ case LDAP_MOD_ADD: /* fallthrough */
+ case SLAP_CONFIG_ADD: { /* add values */
+ char *match = c->argv[1];
+ char *replace = c->argv[2];
+ regex_t compiled;
+ homedir_regexp **rp, *r;
+
+ memset( &compiled, 0, sizeof(compiled) );
+ rc = regcomp( &compiled, match, REG_EXTENDED );
+ if ( rc ) {
+ regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) );
+ regfree( &compiled );
+ return ARG_BAD_CONF;
+ }
+
+ r = ch_calloc( 1, sizeof(homedir_regexp) );
+ r->match = strdup( match );
+ r->replace = strdup( replace );
+ r->compiled = compiled;
+
+ if ( c->valx == -1 ) { /* append */
+ for ( rp = &data->regexps; ( *rp ) != NULL;
+ rp = &(*rp)->next )
+ ;
+ *rp = r;
+
+ } else { /* insert at valx */
+ int i;
+ for ( i = 0, rp = &data->regexps; i < c->valx;
+ rp = &(*rp)->next, ++i )
+ ;
+ r->next = *rp;
+ *rp = r;
+ }
+ rc = 0;
+ break;
+ }
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+homedir_style_cfg( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ homedir_data *data = (homedir_data *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+ struct berval bv;
+
+ assert( data != NULL );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" :
+ data->style == DEL_DELETE ? "DELETE" :
+ "ARCHIVE";
+ bv.bv_len = strlen( bv.bv_val );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( rc != 0 ) return ARG_BAD_CONF;
+ break;
+
+ case LDAP_MOD_DELETE:
+ data->style = DEL_IGNORE;
+ rc = 0;
+ break;
+
+ case LDAP_MOD_ADD: /* fallthrough */
+ case SLAP_CONFIG_ADD: /* add values */
+ if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 )
+ data->style = DEL_IGNORE;
+ else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 )
+ data->style = DEL_DELETE;
+ else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 )
+ data->style = DEL_ARCHIVE;
+ else {
+ Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: "
+ "unrecognized style keyword\n" );
+ return ARG_BAD_CONF;
+ }
+ rc = 0;
+ break;
+
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+#define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) )
+static void
+report_errno( const char *parent_func, const char *func, const char *filename )
+{
+ int save_errno = errno;
+ char ebuf[1024];
+
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "%s: %s: \"%s\": %d (%s)\n",
+ HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func),
+ HOMEDIR_NULLWRAP(filename), save_errno,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+}
+
+static int
+copy_link(
+ const char *dest_file,
+ const char *source_file,
+ const struct stat *st,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ char *buf = NULL;
+ int rc;
+
+ assert( dest_file != NULL );
+ assert( source_file != NULL );
+ assert( st != NULL );
+ assert( (st->st_mode & S_IFMT) == S_IFLNK );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_link: %s to %s\n",
+ source_file, dest_file );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_link: %s uid %ld gid %ld\n",
+ dest_file, (long)uidn, (long)gidn );
+
+ /* calloc +1 for terminator */
+ buf = ber_memcalloc_x( 1, st->st_size + 1, ctx );
+ if ( buf == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "copy_link: alloc failed\n" );
+ return 1;
+ }
+ rc = readlink( source_file, buf, st->st_size );
+ if ( rc == -1 ) {
+ report_errno( "copy_link", "readlink", source_file );
+ goto fail;
+ }
+ rc = symlink( buf, dest_file );
+ if ( rc ) {
+ report_errno( "copy_link", "symlink", dest_file );
+ goto fail;
+ }
+ rc = lchown( dest_file, uidn, gidn );
+ if ( rc ) {
+ report_errno( "copy_link", "lchown", dest_file );
+ goto fail;
+ }
+ goto out;
+
+fail:
+ rc = 1;
+
+out:
+ if ( buf != NULL ) ber_memfree_x( buf, ctx );
+ return rc;
+}
+
+static int
+copy_blocks(
+ FILE *source,
+ FILE *dest,
+ const char *source_file,
+ const char *dest_file )
+{
+ char buf[4096];
+ size_t nread = 0;
+ int done = 0;
+
+ while ( !done ) {
+ nread = fread( buf, 1, sizeof(buf), source );
+ if ( nread == 0 ) {
+ if ( feof( source ) ) {
+ done = 1;
+ } else if ( ferror( source ) ) {
+ if ( source_file != NULL )
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "read error on %s\n",
+ source_file );
+ goto fail;
+ }
+ } else {
+ size_t nwritten = 0;
+ nwritten = fwrite( buf, 1, nread, dest );
+ if ( nwritten < nread ) {
+ if ( dest_file != NULL )
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "write error on %s\n",
+ dest_file );
+ goto fail;
+ }
+ }
+ }
+ return 0;
+fail:
+ return 1;
+}
+
+static int
+copy_file(
+ const char *dest_file,
+ const char *source_file,
+ uid_t uid,
+ gid_t gid,
+ int mode )
+{
+ FILE *source = NULL;
+ FILE *dest = NULL;
+ int rc;
+
+ assert( dest_file != NULL );
+ assert( source_file != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s to %s mode 0%o\n",
+ source_file, dest_file, mode );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s uid %ld gid %ld\n",
+ dest_file, (long)uid, (long)gid );
+
+ source = fopen( source_file, "rb" );
+ if ( source == NULL ) {
+ report_errno( "copy_file", "fopen", source_file );
+ goto fail;
+ }
+ dest = fopen( dest_file, "wb" );
+ if ( dest == NULL ) {
+ report_errno( "copy_file", "fopen", dest_file );
+ goto fail;
+ }
+
+ rc = copy_blocks( source, dest, source_file, dest_file );
+ if ( rc != 0 ) goto fail;
+
+ fclose( source );
+ source = NULL;
+ rc = fclose( dest );
+ dest = NULL;
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "fclose", dest_file );
+ goto fail;
+ }
+
+ /* set owner/permission */
+ rc = lchown( dest_file, uid, gid );
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "lchown", dest_file );
+ goto fail;
+ }
+ rc = chmod( dest_file, mode );
+ if ( rc != 0 ) {
+ report_errno( "copy_file", "chmod", dest_file );
+ goto fail;
+ }
+
+ rc = 0;
+ goto out;
+fail:
+ rc = 1;
+out:
+ if ( source != NULL ) fclose( source );
+ if ( dest != NULL ) fclose( dest );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_file: %s to %s exit %d\n",
+ source_file, dest_file, rc );
+ return rc;
+}
+
+static void
+free_name_list( name_list *names, void *ctx )
+{
+ name_list *next;
+
+ while ( names != NULL ) {
+ next = names->next;
+ if ( names->name != NULL ) ber_memfree_x( names->name, ctx );
+ ber_memfree_x( names, ctx );
+ names = next;
+ }
+}
+
+static int
+grab_names( const char *dir_path, name_list **names, void *ctx )
+{
+ int locked = 0;
+ DIR *dir = NULL;
+ struct dirent *entry = NULL;
+ name_list **tail = NULL;
+ int dir_path_len = 0;
+ int rc = 0;
+
+ assert( dir_path != NULL );
+ assert( names != NULL );
+ assert( *names == NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: %s\n", dir_path );
+
+ tail = names;
+ dir_path_len = strlen( dir_path );
+ ldap_pvt_thread_mutex_lock( &readdir_mutex );
+ locked = 1;
+
+ dir = opendir( dir_path );
+ if ( dir == NULL ) {
+ report_errno( "grab_names", "opendir", dir_path );
+ goto fail;
+ }
+
+ while ( ( entry = readdir( dir ) ) != NULL ) {
+ /* no d_namelen in ac/dirent.h */
+ int d_namelen = strlen( entry->d_name );
+ int full_len;
+
+ /* Skip . and .. */
+ if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) ||
+ ( d_namelen == 2 && entry->d_name[0] == '.' &&
+ entry->d_name[1] == '.' ) ) {
+ continue;
+ }
+
+ *tail = ber_memcalloc_x( 1, sizeof(**tail), ctx );
+ if ( *tail == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "grab_names: list alloc failed\n" );
+ goto fail;
+ }
+ (*tail)->next = NULL;
+
+ /* +1 for dirsep, +1 for term */
+ full_len = dir_path_len + 1 + d_namelen + 1;
+ (*tail)->name = ber_memalloc_x( full_len, ctx );
+ if ( (*tail)->name == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "grab_names: name alloc failed\n" );
+ goto fail;
+ }
+ snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s",
+ dir_path, entry->d_name );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: found \"%s\"\n",
+ (*tail)->name );
+
+ rc = lstat( (*tail)->name, &(*tail)->st );
+ if ( rc ) {
+ report_errno( "grab_names", "lstat", (*tail)->name );
+ goto fail;
+ }
+
+ tail = &(*tail)->next;
+ }
+ closedir( dir );
+ ldap_pvt_thread_mutex_unlock( &readdir_mutex );
+ locked = 0;
+
+ dir = NULL;
+ goto success;
+
+success:
+ rc = 0;
+ goto out;
+fail:
+ rc = 1;
+ goto out;
+out:
+ if ( dir != NULL ) closedir( dir );
+ if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex );
+ if ( rc != 0 && *names != NULL ) {
+ free_name_list( *names, ctx );
+ *names = NULL;
+ }
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "grab_names: %s exit %d\n",
+ dir_path, rc );
+ return rc;
+}
+
+static int
+traverse( const char *path, const traverse_cb *cb, void *ctx )
+{
+ name_list *next_name = NULL;
+ name_list_list *dir_stack = NULL;
+ name_list_list *next_dir;
+ int rc = 0;
+
+ assert( path != NULL );
+ assert( cb != NULL );
+ assert( cb->pre_func || cb->post_func );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: %s\n", path );
+
+ dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx );
+ if ( dir_stack == NULL ) goto alloc_fail;
+ dir_stack->next = NULL;
+ dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx );
+ if ( dir_stack->list == NULL ) goto alloc_fail;
+ rc = lstat( path, &dir_stack->list->st );
+ if ( rc != 0 ) {
+ report_errno( "traverse", "lstat", path );
+ goto fail;
+ }
+ dir_stack->list->next = NULL;
+ dir_stack->list->name = ber_strdup_x( path, ctx );
+ if ( dir_stack->list->name == NULL ) goto alloc_fail;
+
+ while ( dir_stack != NULL ) {
+ while ( dir_stack->list != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: top of loop with \"%s\"\n",
+ dir_stack->list->name );
+
+ if ( cb->pre_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name,
+ &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) {
+ /* push dir onto stack */
+ next_dir = dir_stack;
+ dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx );
+ if ( dir_stack == NULL ) {
+ dir_stack = next_dir;
+ goto alloc_fail;
+ }
+ dir_stack->list = NULL;
+ dir_stack->next = next_dir;
+ rc = grab_names(
+ dir_stack->next->list->name, &dir_stack->list, ctx );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: grab_names %s failed\n",
+ dir_stack->next->list->name );
+ goto fail;
+ }
+ } else {
+ /* just a file */
+ if ( cb->post_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->post_func( cb->post_private,
+ dir_stack->list->name, &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ next_name = dir_stack->list->next;
+ ber_memfree_x( dir_stack->list->name, ctx );
+ ber_memfree_x( dir_stack->list, ctx );
+ dir_stack->list = next_name;
+ }
+ }
+ /* Time to pop a directory off the stack */
+ next_dir = dir_stack->next;
+ ber_memfree_x( dir_stack, ctx );
+ dir_stack = next_dir;
+ if ( dir_stack != NULL ) {
+ if ( cb->post_func != NULL ) {
+ traverse_cb_ret cb_rc;
+ cb_rc = cb->post_func( cb->post_private, dir_stack->list->name,
+ &dir_stack->list->st, ctx );
+
+ if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done;
+ if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail;
+ }
+ next_name = dir_stack->list->next;
+ ber_memfree_x( dir_stack->list->name, ctx );
+ ber_memfree_x( dir_stack->list, ctx );
+ dir_stack->list = next_name;
+ }
+ }
+
+ goto success;
+
+cb_done:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse: cb signaled completion\n" );
+success:
+ rc = 0;
+ goto out;
+
+cb_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: cb signaled failure\n" );
+ goto fail;
+alloc_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse: allocation failed\n" );
+fail:
+ rc = 1;
+ goto out;
+
+out:
+ while ( dir_stack != NULL ) {
+ free_name_list( dir_stack->list, ctx );
+ next_dir = dir_stack->next;
+ ber_memfree_x( dir_stack, ctx );
+ dir_stack = next_dir;
+ }
+ return rc;
+}
+
+static traverse_cb_ret
+traverse_copy_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ copy_private *cp = private;
+ char *dest_name = NULL;
+ int source_name_len;
+ int dest_name_len;
+ int rc;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: %s entering\n",
+ name );
+
+ assert( cp->source_prefix_len >= 0 );
+ assert( cp->dest_prefix != NULL );
+ assert( cp->dest_prefix_len > 1 );
+
+ source_name_len = strlen( name );
+ assert( source_name_len >= cp->source_prefix_len );
+ /* +1 for terminator */
+ dest_name_len =
+ source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1;
+ dest_name = ber_memalloc_x( dest_name_len, ctx );
+ if ( dest_name == NULL ) goto alloc_fail;
+
+ snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix,
+ name + cp->source_prefix_len );
+
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFDIR:
+ rc = mkdir( dest_name, st->st_mode & 06775 );
+ if ( rc ) {
+ int save_errno = errno;
+ switch ( save_errno ) {
+ case EEXIST:
+ /* directory already present; nothing to do */
+ goto exists;
+ break;
+ case ENOENT:
+ /* FIXME: should mkdir -p here */
+ /* fallthrough for now */
+ default:
+ report_errno( "traverse_copy_pre", "mkdir", dest_name );
+ goto fail;
+ }
+ }
+ rc = lchown( dest_name, cp->uidn, cp->gidn );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "lchown", dest_name );
+ goto fail;
+ }
+ rc = chmod( dest_name, st->st_mode & 07777 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "chmod", dest_name );
+ goto fail;
+ }
+ break;
+ case S_IFREG:
+ rc = copy_file(
+ dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 );
+ if ( rc ) goto fail;
+ break;
+ case S_IFIFO:
+ rc = mkfifo( dest_name, 0700 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "mkfifo", dest_name );
+ goto fail;
+ }
+ rc = lchown( dest_name, cp->uidn, cp->gidn );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "lchown", dest_name );
+ goto fail;
+ }
+ rc = chmod( dest_name, st->st_mode & 07777 );
+ if ( rc ) {
+ report_errno( "traverse_copy_pre", "chmod", dest_name );
+ goto fail;
+ }
+ break;
+ case S_IFLNK:
+ rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx );
+ if ( rc ) goto fail;
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: skipping special: %s\n",
+ name );
+ }
+
+ goto success;
+
+alloc_fail:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "traverse_copy_pre: allocation failed\n" );
+fail:
+ rc = TRAVERSE_CB_FAIL;
+ goto out;
+
+exists:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: \"%s\" already exists,"
+ " skipping the rest\n",
+ dest_name );
+ rc = TRAVERSE_CB_DONE;
+ goto out;
+
+success:
+ rc = TRAVERSE_CB_CONTINUE;
+out:
+ if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_copy_pre: exit %d\n", rc );
+ return rc;
+}
+
+static int
+copy_tree(
+ const char *dest_path,
+ const char *source_path,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ traverse_cb cb;
+ copy_private cp;
+ int rc;
+
+ assert( dest_path != NULL );
+ assert( source_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_tree: %s to %s entering\n",
+ source_path, dest_path );
+
+ cb.pre_func = traverse_copy_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &cp;
+ cb.post_private = NULL;
+
+ cp.source_prefix_len = strlen( source_path );
+ cp.dest_prefix = dest_path;
+ cp.dest_prefix_len = strlen( dest_path );
+ cp.uidn = uidn;
+ cp.gidn = gidn;
+
+ if ( cp.source_prefix_len <= cp.dest_prefix_len &&
+ strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 &&
+ ( cp.source_prefix_len == cp.dest_prefix_len ||
+ dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "copy_tree: aborting: %s contains %s\n",
+ source_path, dest_path );
+ return 1;
+ }
+
+ rc = traverse( source_path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "copy_tree: %s exit %d\n", source_path,
+ rc );
+
+ return rc;
+}
+
+static int
+homedir_provision(
+ const char *dest_path,
+ const char *skel_path,
+ uid_t uidn,
+ gid_t gidn,
+ void *ctx )
+{
+ int rc;
+
+ assert( dest_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s from skeleton %s\n",
+ dest_path, skel_path == NULL ? "(none)" : skel_path );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s uidn %ld gidn %ld\n",
+ dest_path, (long)uidn, (long)gidn );
+
+ if ( skel_path == NULL ) {
+ rc = mkdir( dest_path, 0700 );
+ if ( rc ) {
+ int save_errno = errno;
+ switch ( save_errno ) {
+ case EEXIST:
+ /* directory already present; nothing to do */
+ /* but down chown either */
+ rc = 0;
+ goto out;
+ break;
+ default:
+ report_errno( "provision_homedir", "mkdir", dest_path );
+ goto fail;
+ }
+ }
+ rc = lchown( dest_path, uidn, gidn );
+ if ( rc ) {
+ report_errno( "provision_homedir", "lchown", dest_path );
+ goto fail;
+ }
+
+ } else {
+ rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx );
+ }
+
+ goto out;
+
+fail:
+ rc = 1;
+ goto out;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_provision: %s to %s exit %d\n",
+ skel_path, dest_path, rc );
+ return rc;
+}
+
+/* traverse func for rm -rf */
+static traverse_cb_ret
+traverse_remove_post(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s entering\n",
+ name );
+
+ if ( (st->st_mode & S_IFMT) == S_IFDIR ) {
+ rc = rmdir( name );
+ if ( rc != 0 ) {
+ report_errno( "traverse_remove_post", "rmdir", name );
+ goto fail;
+ }
+ } else {
+ rc = unlink( name );
+ if ( rc != 0 ) {
+ report_errno( "traverse_remove_post", "unlink", name );
+ goto fail;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s exit continue\n",
+ name );
+ return TRAVERSE_CB_CONTINUE;
+
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_remove_post: %s exit failure\n",
+ name );
+ return TRAVERSE_CB_FAIL;
+}
+
+static int
+delete_tree( const char *path, void *ctx )
+{
+ const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL };
+ int rc;
+
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "delete_tree: %s entering\n", path );
+
+ rc = traverse( path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "delete_tree: %s exit %d\n", path, rc );
+
+ return rc;
+}
+
+static int
+get_tar_name(
+ const char *path,
+ const char *tar_path,
+ char *tar_name,
+ int name_size )
+{
+ int rc = 0;
+ const char *ch;
+ int fd = -1;
+ int counter = 0;
+ time_t now;
+
+ assert( path != NULL );
+ assert( tar_path != NULL );
+ assert( tar_name != NULL );
+
+ for ( ch = path + strlen( path );
+ *ch != LDAP_DIRSEP[0] && ch > path;
+ --ch )
+ ;
+ if ( ch <= path || strlen( ch ) < 2 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "get_tar_name: unable to construct a tar name from input "
+ "path \"%s\"\n",
+ path );
+ goto fail;
+ }
+ ++ch; /* skip past sep */
+ time( &now );
+
+ while ( fd < 0 ) {
+ snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar",
+ tar_path, ch, (long)now, counter );
+ fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 );
+ if ( fd < 0 ) {
+ int save_errno = errno;
+ if ( save_errno != EEXIST ) {
+ report_errno( "get_tar_name", "open", tar_name );
+ goto fail;
+ }
+ ++counter;
+ }
+ }
+
+ rc = 0;
+ goto out;
+
+fail:
+ rc = 1;
+ *tar_name = '\0';
+out:
+ if ( fd >= 0 ) close( fd );
+ return rc;
+}
+
+/* traverse func for rechown */
+static traverse_cb_ret
+traverse_chown_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+ chown_private *cp = private;
+ uid_t set_uidn = -1;
+ gid_t set_gidn = -1;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s entering\n",
+ name );
+
+ if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn;
+ if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn;
+
+ if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) {
+ rc = lchown( name, set_uidn, set_gidn );
+ if ( rc ) {
+ report_errno( "traverse_chown_pre", "lchown", name );
+ goto fail;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s exit continue\n",
+ name );
+ return TRAVERSE_CB_CONTINUE;
+
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_chown_pre: %s exit failure\n",
+ name );
+ return TRAVERSE_CB_FAIL;
+}
+
+static int
+chown_tree(
+ const char *path,
+ uid_t old_uidn,
+ uid_t new_uidn,
+ gid_t old_gidn,
+ gid_t new_gidn,
+ void *ctx )
+{
+ traverse_cb cb;
+ chown_private cp;
+ int rc;
+
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "chown_tree: %s entering\n", path );
+
+ cb.pre_func = traverse_chown_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &cp;
+ cb.post_private = NULL;
+
+ cp.old_uidn = old_uidn;
+ cp.new_uidn = new_uidn;
+ cp.old_gidn = old_gidn;
+ cp.new_gidn = new_gidn;
+
+ rc = traverse( path, &cb, ctx );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "chown_tree: %s exit %d\n", path, rc );
+
+ return rc;
+}
+
+static int
+homedir_rename( const char *source_path, const char *dest_path )
+{
+ int rc = 0;
+
+ assert( source_path != NULL );
+ assert( dest_path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_rename: %s to %s\n",
+ source_path, dest_path );
+ rc = rename( source_path, dest_path );
+ if ( rc != 0 ) {
+ char ebuf[1024];
+ int save_errno = errno;
+
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_rename: rename(\"%s\", \"%s\"): (%s)\n",
+ source_path, dest_path,
+ AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_rename: %s to %s exit %d\n",
+ source_path, dest_path, rc );
+ return rc;
+}
+
+/* FIXME: This assumes ASCII; needs fixing for z/OS */
+static int
+tar_set_header( ustar_header *tar, const struct stat *st, const char *name )
+{
+ int name_len;
+ int rc;
+ const char *ch, *end;
+
+ assert( tar != NULL );
+ assert( st != NULL );
+ assert( name != NULL );
+ assert( sizeof(*tar) == 512 );
+ assert( sizeof(tar->name) == 100 );
+ assert( sizeof(tar->prefix) == 155 );
+ assert( sizeof(tar->checksum) == 8 );
+
+ memset( tar, 0, sizeof(*tar) );
+
+ assert( name[0] == LDAP_DIRSEP[0] );
+ name += 1; /* skip leading / */
+
+ name_len = strlen( name );
+
+ /* fits in tar->name? */
+ /* Yes, name and prefix do not need a trailing nul. */
+ if ( name_len <= 100 ) {
+ strncpy( tar->name, name, 100 );
+
+ /* try fit in tar->name + tar->prefix */
+ } else {
+ /* try to find something to stick into tar->name */
+ for ( ch = name + name_len - 100, end = name + name_len;
+ ch < end && *ch != LDAP_DIRSEP[0];
+ ++ch )
+ ;
+ if ( end - ch > 0 ) /* +1 skip past sep */
+ ch++;
+ else {
+ /* reset; name too long for UStar */
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "tar_set_header: name too long: \"%s\"\n",
+ name );
+ ch = name + name_len - 100;
+ }
+ strncpy( tar->name, ch + 1, 100 );
+ {
+ int prefix_len = ( ch - 1 ) - name;
+ if ( prefix_len > 155 ) prefix_len = 155;
+ strncpy( tar->prefix, name, prefix_len );
+ }
+ }
+
+ snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 );
+ snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid );
+ snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid );
+ snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime );
+ snprintf( tar->size, 12, "%010lo ", (long)0 );
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFREG:
+ tar->typeflag[0] = '0';
+ snprintf( tar->size, 12, "%010lo ", (long)st->st_size );
+ break;
+ case S_IFLNK:
+ tar->typeflag[0] = '2';
+ rc = readlink( name - 1, tar->linkname, 99 );
+ if ( rc == -1 ) {
+ report_errno( "tar_set_header", "readlink", name );
+ goto fail;
+ }
+ break;
+ case S_IFCHR:
+ tar->typeflag[0] = '3';
+ /* FIXME: this is probably wrong but shouldn't likely be an issue */
+ snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
+ snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
+ break;
+ case S_IFBLK:
+ tar->typeflag[0] = '4';
+ /* FIXME: this is probably wrong but shouldn't likely be an issue */
+ snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 );
+ snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff );
+ break;
+ case S_IFDIR:
+ tar->typeflag[0] = '5';
+ break;
+ case S_IFIFO:
+ tar->typeflag[0] = '6';
+ break;
+ default:
+ goto fail;
+ }
+ snprintf( tar->magic, 6, "ustar" );
+ tar->version[0] = '0';
+ tar->version[1] = '0';
+
+ {
+ unsigned char *uch = (unsigned char *)tar;
+ unsigned char *uend = uch + 512;
+ unsigned long sum = 0;
+
+ memset( &tar->checksum, ' ', sizeof(tar->checksum) );
+
+ for ( ; uch < uend; ++uch )
+ sum += *uch;
+
+ /* zero-padded, six octal digits, followed by NUL then space (!) */
+ /* Yes, that's terminated exactly reverse of the others. */
+ snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum );
+ }
+
+ return 0;
+fail:
+ return 1;
+}
+
+static traverse_cb_ret
+traverse_tar_pre(
+ void *private,
+ const char *name,
+ const struct stat *st,
+ void *ctx )
+{
+ int rc;
+ traverse_cb_ret cbrc;
+ tar_private *tp = private;
+ ustar_header tar;
+ FILE *source = NULL;
+
+ assert( private != NULL );
+ assert( name != NULL );
+ assert( st != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s entering\n", name );
+
+ switch ( st->st_mode & S_IFMT ) {
+ case S_IFREG:
+ if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s is larger than 8GiB POSIX UStar "
+ "file size limit\n",
+ name );
+ goto fail;
+ }
+ /* fallthrough */
+ case S_IFDIR:
+ case S_IFLNK:
+ case S_IFIFO:
+ case S_IFCHR:
+ case S_IFBLK:
+ rc = tar_set_header( &tar, st, name );
+ if ( rc ) goto fail;
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: skipping \"%s\" mode %o\n",
+ name, st->st_mode );
+ goto done;
+ }
+
+ rc = fwrite( &tar, 1, 512, tp->file );
+ if ( rc != 512 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: write error in tar header\n" );
+ goto fail;
+ }
+
+ if ( (st->st_mode & S_IFMT) == S_IFREG ) {
+ source = fopen( name, "rb" );
+ if ( source == NULL ) {
+ report_errno( "traverse_tar_pre", "fopen", name );
+ goto fail;
+ }
+ rc = copy_blocks( source, tp->file, name, tp->name );
+ if ( rc != 0 ) goto fail;
+ fclose( source );
+ source = NULL;
+ }
+
+ { /* advance to end of record */
+ off_t pos = ftello( tp->file );
+ if ( pos == -1 ) {
+ report_errno( "traverse_tar_pre", "ftello", tp->name );
+ goto fail;
+ }
+ pos += ( 512 - ( pos % 512 ) ) % 512;
+ rc = fseeko( tp->file, pos, SEEK_SET );
+ if ( rc != 0 ) {
+ report_errno( "traverse_tar_pre", "fseeko", tp->name );
+ goto fail;
+ }
+ }
+
+done:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s exit continue\n",
+ name );
+ cbrc = TRAVERSE_CB_CONTINUE;
+ goto out;
+fail:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "traverse_tar_pre: %s exit failure\n",
+ name );
+ cbrc = TRAVERSE_CB_FAIL;
+
+out:
+ if ( source != NULL ) fclose( source );
+ return cbrc;
+}
+
+static int
+tar_tree( const char *path, const char *tar_name, void *ctx )
+{
+ traverse_cb cb;
+ tar_private tp;
+ int rc;
+
+ assert( path != NULL );
+ assert( tar_name != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "tar_tree: %s into %s entering\n", path,
+ tar_name );
+
+ cb.pre_func = traverse_tar_pre;
+ cb.post_func = NULL;
+ cb.pre_private = &tp;
+ cb.post_private = NULL;
+
+ tp.name = tar_name;
+ tp.file = fopen( tar_name, "wb" );
+ if ( tp.file == NULL ) {
+ report_errno( "tar_tree", "fopen", tar_name );
+ goto fail;
+ }
+
+ rc = traverse( path, &cb, ctx );
+ if ( rc != 0 ) goto fail;
+
+ {
+ off_t pos = ftello( tp.file );
+ if ( pos == -1 ) {
+ report_errno( "tar_tree", "ftello", tp.name );
+ goto fail;
+ }
+ pos += 1024; /* two zero records */
+ pos += ( 10240 - ( pos % 10240 ) ) % 10240;
+ rc = ftruncate( fileno( tp.file ), pos );
+ if ( rc != 0 ) {
+ report_errno( "tar_tree", "ftrunctate", tp.name );
+ goto fail;
+ }
+ }
+
+ rc = fclose( tp.file );
+ tp.file = NULL;
+ if ( rc != 0 ) {
+ report_errno( "tar_tree", "fclose", tp.name );
+ goto fail;
+ }
+ goto out;
+
+fail:
+ rc = 1;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "tar_tree: %s exit %d\n", path, rc );
+ if ( tp.file != NULL ) fclose( tp.file );
+ return rc;
+}
+
+static int
+homedir_deprovision( const homedir_data *data, const char *path, void *ctx )
+{
+ int rc = 0;
+ char tar_name[1024];
+
+ assert( data != NULL );
+ assert( path != NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: %s entering\n",
+ path );
+
+ switch ( data->style ) {
+ case DEL_IGNORE:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: style is ignore\n" );
+ break;
+ case DEL_ARCHIVE:
+ if ( data->archive_path == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_deprovision: archive path not set\n" );
+ goto fail;
+ }
+ rc = get_tar_name( path, data->archive_path, tar_name, 1024 );
+ if ( rc != 0 ) goto fail;
+ rc = tar_tree( path, tar_name, ctx );
+ if ( rc != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_deprovision: archive failed, not deleting\n" );
+ goto fail;
+ }
+ /* fall-through */
+ case DEL_DELETE:
+ rc = delete_tree( path, ctx );
+ break;
+ default:
+ abort();
+ }
+
+ rc = 0;
+ goto out;
+
+fail:
+ rc = 1;
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_deprovision: %s leaving\n",
+ path );
+
+ return rc;
+}
+
+/* FIXME: This assumes ASCII; needs fixing for z/OS */
+/* FIXME: This should also be in a slapd library function somewhere */
+#define MAX_MATCHES ( 10 )
+static int
+homedir_match(
+ const homedir_regexp *r,
+ const char *homedir,
+ char *result,
+ size_t result_size )
+{
+ int rc;
+ int n;
+ regmatch_t matches[MAX_MATCHES];
+ char *resc, *repc;
+
+ assert( r != NULL );
+ assert( homedir != NULL );
+ assert( result_size > 1 );
+
+ memset( matches, 0, sizeof(matches) );
+ rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 );
+ if ( rc ) {
+ if ( rc != REG_NOMATCH ) {
+ char msg[256];
+ regerror( rc, &r->compiled, msg, sizeof(msg) );
+ Debug( LDAP_DEBUG_ANY, "homedir_match: "
+ "%s\n", msg );
+ }
+ return rc;
+ }
+
+ for ( resc = result, repc = r->replace;
+ result_size > 1 && *repc != '\0';
+ ++repc, ++resc, --result_size ) {
+ switch ( *repc ) {
+ case '$':
+ ++repc;
+ n = ( *repc ) - '0';
+ if ( n < 0 || n > ( MAX_MATCHES - 1 ) ||
+ matches[n].rm_so < 0 ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "invalid regex term expansion in \"%s\" "
+ "at char %ld, n is %d\n",
+ r->replace, (long)( repc - r->replace ), n );
+ return 1;
+ }
+ {
+ size_t match_len = matches[n].rm_eo - matches[n].rm_so;
+ const char *match_start = homedir + matches[n].rm_so;
+ if ( match_len >= result_size ) goto too_long;
+
+ memcpy( resc, match_start, match_len );
+ result_size -= match_len;
+ resc += match_len - 1;
+ }
+ break;
+
+ case '\\':
+ ++repc;
+ /* fallthrough */
+
+ default:
+ *resc = *repc;
+ }
+ }
+ *resc = '\0';
+ if ( *repc != '\0' ) goto too_long;
+
+ return 0;
+
+too_long:
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "regex expansion of %s too long\n",
+ r->replace );
+ *result = '\0';
+ return 1;
+}
+
+/* Sift through an entry for interesting values
+ * return 0 on success and set vars
+ * return 1 if homedir is not present or not valid
+ * sets presence if any homedir attributes are noticed
+ */
+static int
+harvest_values(
+ const homedir_data *data,
+ const Entry *e,
+ char *home_buf,
+ int home_buf_size,
+ uid_t *uidn,
+ gid_t *gidn,
+ int *presence )
+{
+ Attribute *a;
+ char *homedir = NULL;
+
+ assert( data != NULL );
+ assert( e != NULL );
+ assert( home_buf != NULL );
+ assert( home_buf_size > 1 );
+ assert( uidn != NULL );
+ assert( gidn != NULL );
+ assert( presence != NULL );
+
+ *presence = 0;
+ if ( e == NULL ) return 1;
+ *uidn = 0;
+ *gidn = 0;
+
+ for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) {
+ if ( a->a_desc == data->home_ad ) {
+ homedir = a->a_vals[0].bv_val;
+ *presence = 1;
+ } else if ( a->a_desc == data->uidn_ad ) {
+ *uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
+ *presence = 1;
+ } else if ( a->a_desc == data->gidn_ad ) {
+ *gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 );
+ *presence = 1;
+ }
+ }
+ if ( homedir != NULL ) {
+ homedir_regexp *r;
+
+ for ( r = data->regexps; r != NULL; r = r->next ) {
+ int rc = homedir_match( r, homedir, home_buf, home_buf_size );
+ if ( rc == 0 ) return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+homedir_mod_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = NULL;
+ slap_callback **cbp = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: entering\n" );
+
+ for ( cbp = &op->o_callback;
+ *cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup;
+ cbp = &(*cbp)->sc_next )
+ ;
+
+ if ( *cbp == NULL ) goto out;
+ cb = *cbp;
+
+ cb_data = (homedir_cb_data *)cb->sc_private;
+ e = cb_data->entry;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: found <%s>\n",
+ e->e_nname.bv_val );
+ entry_free( e );
+ op->o_tmpfree( cb_data, op->o_tmpmemctx );
+ *cbp = cb->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+
+out:
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_cleanup: leaving\n" );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_mod_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = NULL;
+ homedir_data *data = NULL;
+ slap_callback *cb = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+ int rc = SLAP_CB_CONTINUE;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: entering\n" );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: op was not successful\n" );
+ goto out;
+ }
+
+ /* Retrieve stashed entry */
+ for ( cb = op->o_callback;
+ cb != NULL && cb->sc_cleanup != homedir_mod_cleanup;
+ cb = cb->sc_next )
+ ;
+ if ( cb == NULL ) goto out;
+ cb_data = (homedir_cb_data *)cb->sc_private;
+ e = cb_data->entry;
+ on = cb_data->on;
+ data = on->on_bi.bi_private;
+ assert( e != NULL );
+ assert( data != NULL );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: found <%s>\n",
+ e->e_nname.bv_val );
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_DELETE: {
+ char home_buf[1024];
+ uid_t uidn = 0;
+ gid_t gidn = 0;
+ int presence;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: successful delete found\n" );
+ rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn,
+ &gidn, &presence );
+ if ( rc == 0 && uidn >= data->min_uid ) {
+ homedir_deprovision( data, home_buf, op->o_tmpmemctx );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: skipping\n" );
+ }
+ rc = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN: {
+ Operation nop = *op;
+ Entry *old_entry = e;
+ Entry *new_entry = NULL;
+ Entry *etmp;
+ char old_home[1024];
+ char new_home[1024];
+ uid_t old_uidn, new_uidn;
+ uid_t old_gidn, new_gidn;
+ int old_valid = 0;
+ int new_valid = 0;
+ int old_presence, new_presence;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: successful modify/modrdn found\n" );
+
+ /* retrieve the revised entry */
+ nop.o_bd = on->on_info->oi_origdb;
+ rc = overlay_entry_get_ov(
+ &nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on );
+ if ( etmp != NULL ) {
+ new_entry = entry_dup( etmp );
+ overlay_entry_release_ov( &nop, etmp, 0, on );
+ }
+ if ( rc || new_entry == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: unable to get revised <%s>\n",
+ op->o_req_ndn.bv_val );
+ if ( new_entry != NULL ) {
+ entry_free( new_entry );
+ new_entry = NULL;
+ }
+ }
+
+ /* analyze old and new */
+ rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn,
+ &old_gidn, &old_presence );
+ if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1;
+ if ( new_entry != NULL ) {
+ rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn,
+ &new_gidn, &new_presence );
+ if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1;
+ entry_free( new_entry );
+ new_entry = NULL;
+ }
+
+ if ( new_valid && !old_valid ) { /* like an add */
+ if ( old_presence )
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: old entry is now valid\n" );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like an add\n" );
+ homedir_provision( new_home, data->skeleton_path, new_uidn,
+ new_gidn, op->o_tmpmemctx );
+
+ } else if ( old_valid && !new_valid &&
+ !new_presence ) { /* like a del */
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like a del\n" );
+ homedir_deprovision( data, old_home, op->o_tmpmemctx );
+
+ } else if ( new_valid && old_valid ) { /* change */
+ int did_something = 0;
+
+ if ( strcmp( old_home, new_home ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: treating like a rename\n" );
+ homedir_rename( old_home, new_home );
+ did_something = 1;
+ }
+ if ( old_uidn != new_uidn || old_gidn != new_gidn ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: rechowning\n" );
+ chown_tree( new_home, old_uidn, new_uidn, old_gidn,
+ new_gidn, op->o_tmpmemctx );
+ did_something = 1;
+ }
+ if ( !did_something ) {
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: nothing to do\n" );
+ }
+ } else if ( old_presence || new_presence ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_mod_response: <%s> values present "
+ "but invalid; ignoring\n",
+ op->o_req_ndn.bv_val );
+ }
+ rc = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ default:
+ rc = SLAP_CB_CONTINUE;
+ }
+
+out:
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_mod_response: leaving\n" );
+ return rc;
+}
+
+static int
+homedir_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *cb = NULL;
+ homedir_cb_data *cb_data = NULL;
+ Entry *e = NULL;
+ Entry *se = NULL;
+ Operation nop = *op;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_op_mod: entering\n" );
+
+ /* retrieve the entry */
+ nop.o_bd = on->on_info->oi_origdb;
+ rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on );
+ if ( e != NULL ) {
+ se = entry_dup( e );
+ overlay_entry_release_ov( &nop, e, 0, on );
+ e = se;
+ }
+ if ( rc || e == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "homedir_op_mod: unable to get <%s>\n",
+ op->o_req_ndn.bv_val );
+ goto out;
+ }
+
+ /* Allocate the callback to hold the entry */
+ cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx );
+ cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx );
+ cb->sc_cleanup = homedir_mod_cleanup;
+ cb->sc_response = homedir_mod_response;
+ cb->sc_private = cb_data;
+ cb_data->entry = e;
+ e = NULL;
+ cb_data->on = on;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+out:
+ if ( e != NULL ) entry_free( e );
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_op_mod: leaving\n" );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ homedir_data *data = on->on_bi.bi_private;
+
+ Debug( LDAP_DEBUG_TRACE, "homedir: "
+ "homedir_response: entering\n" );
+ if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE;
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD: { /* Check for new homedir */
+ char home_buf[1024];
+ uid_t uidn = 0;
+ gid_t gidn = 0;
+ int rc, presence;
+
+ rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf),
+ &uidn, &gidn, &presence );
+ if ( rc == 0 && uidn >= data->min_uid ) {
+ homedir_provision( home_buf, data->skeleton_path, uidn, gidn,
+ op->o_tmpmemctx );
+ }
+ return SLAP_CB_CONTINUE;
+ }
+
+ default:
+ return SLAP_CB_CONTINUE;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+homedir_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ homedir_data *data = ch_calloc( 1, sizeof(homedir_data) );
+ const char *text;
+
+ if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) ||
+ slap_str2ad( "uidNumber", &data->uidn_ad, &text ) ||
+ slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "homedir: "
+ "nis schema not available\n" );
+ return 1;
+ }
+
+ data->skeleton_path = strdup( DEFAULT_SKEL );
+ data->min_uid = DEFAULT_MIN_UID;
+ data->archive_path = NULL;
+
+ on->on_bi.bi_private = data;
+ return 0;
+}
+
+static int
+homedir_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ homedir_data *data = on->on_bi.bi_private;
+ homedir_regexp *r, *rnext;
+
+ if ( data != NULL ) {
+ for ( r = data->regexps; r != NULL; r = rnext ) {
+ rnext = r->next;
+ ch_free( r->match );
+ ch_free( r->replace );
+ regfree( &r->compiled );
+ ch_free( r );
+ }
+ data->regexps = NULL;
+ if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path );
+ if ( data->archive_path != NULL ) ch_free( data->archive_path );
+ ch_free( data );
+ }
+
+ return 0;
+}
+
+int
+homedir_initialize()
+{
+ int rc;
+
+ assert( ' ' == 32 ); /* Lots of ASCII requirements for now */
+
+ memset( &homedir, 0, sizeof(homedir) );
+
+ homedir.on_bi.bi_type = "homedir";
+ homedir.on_bi.bi_db_init = homedir_db_init;
+ homedir.on_bi.bi_db_destroy = homedir_db_destroy;
+ homedir.on_bi.bi_op_delete = homedir_op_mod;
+ homedir.on_bi.bi_op_modify = homedir_op_mod;
+ homedir.on_response = homedir_response;
+
+ homedir.on_bi.bi_cf_ocs = homedirocs;
+ rc = config_register_schema( homedircfg, homedirocs );
+ if ( rc ) return rc;
+
+ ldap_pvt_thread_mutex_init( &readdir_mutex );
+
+ return overlay_register( &homedir );
+}
+
+int
+homedir_terminate()
+{
+ ldap_pvt_thread_mutex_destroy( &readdir_mutex );
+ return 0;
+}
+
+#if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC)
+int
+init_module( int argc, char *argv[] )
+{
+ return homedir_initialize();
+}
+
+int
+term_module()
+{
+ return homedir_terminate();
+}
+#endif
+
+#endif /* SLAPD_OVER_HOMEDIR */
diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c
new file mode 100644
index 0000000..5affbbf
--- /dev/null
+++ b/servers/slapd/overlays/memberof.c
@@ -0,0 +1,2185 @@
+/* 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 "slap-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 ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d\n",
+ op->o_log_prefix, op2.o_req_dn.bv_val,
+ ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err );
+ }
+
+ 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 ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d\n",
+ op->o_log_prefix, op2.o_req_dn.bv_val,
+ ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err );
+ }
+
+ 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 );
+ 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;
+
+ 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;
+ }
+
+ save_dn = op->o_req_dn;
+ save_ndn = op->o_req_ndn;
+
+ op->o_req_dn = op->orr_newDN;
+ op->o_req_ndn = op->orr_nnewDN;
+ 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 ( mci->what & MEMBEROF_IS_GROUP ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->orr_nnewDN,
+ 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,
+ &op->orr_newDN, &op->orr_nnewDN );
+ }
+ 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, &op->orr_nnewDN,
+ 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,
+ &op->orr_newDN, &op->orr_nnewDN );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+done:;
+ 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 );
+ 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_QUOTE|ARG_DN|MO_DN, mo_cf_gen,
+ "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' "
+ "DESC 'DN to be used as modifiersName' "
+ "EQUALITY distinguishedNameMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs mo_ocs[] = {
+ { "( OLcfgOvOc:18.1 "
+ "NAME ( 'olcMemberOfConfig' '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 );
+ 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 );
+ 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 );
+ 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 " /* added */
+ "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, 1 );
+ if ( code && code != SLAP_SCHERR_ATTR_DUP ) {
+ Debug( LDAP_DEBUG_ANY,
+ "memberof_initialize: register_at #%d failed\n",
+ i );
+ 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/otp.c b/servers/slapd/overlays/otp.c
new file mode 100644
index 0000000..590ee50
--- /dev/null
+++ b/servers/slapd/overlays/otp.c
@@ -0,0 +1,1004 @@
+/* otp.c - OATH 2-factor authentication module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2015-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2015 by Howard Chu, Symas Corp.
+ * Portions Copyright 2016-2017 by Michael Ströder <michael@stroeder.com>
+ * Portions Copyright 2018 by Ondřej Kuzník, Symas Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work includes code from the lastbind overlay.
+ */
+
+#include <portable.h>
+
+#ifdef SLAPD_OVER_OTP
+
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+
+#include <lber.h>
+#include <lber_pvt.h>
+#include "lutil.h"
+#include <ac/stdlib.h>
+#include <ac/ctype.h>
+#include <ac/string.h>
+/* include socket.h to get sys/types.h and/or winsock2.h */
+#include <ac/socket.h>
+
+#if HAVE_OPENSSL
+#include <openssl/sha.h>
+#include <openssl/hmac.h>
+
+#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_LENGTH
+
+#if OPENSSL_VERSION_MAJOR >= 3
+#define TOTP_SHA1 SN_sha1
+#define TOTP_SHA224 SN_sha224
+#define TOTP_SHA256 SN_sha256
+#define TOTP_SHA384 SN_sha384
+#define TOTP_SHA512 SN_sha512
+#define TOTP_HMAC_CTX EVP_MAC_CTX *
+#else
+#define TOTP_SHA1 EVP_sha1()
+#define TOTP_SHA224 EVP_sha224()
+#define TOTP_SHA256 EVP_sha256()
+#define TOTP_SHA384 EVP_sha384()
+#define TOTP_SHA512 EVP_sha512()
+#define TOTP_HMAC_CTX HMAC_CTX *
+#endif
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static HMAC_CTX *
+HMAC_CTX_new( void )
+{
+ HMAC_CTX *ctx = OPENSSL_malloc( sizeof(*ctx) );
+ if ( ctx != NULL ) {
+ HMAC_CTX_init( ctx );
+ }
+ return ctx;
+}
+
+static void
+HMAC_CTX_free( HMAC_CTX *ctx )
+{
+ if ( ctx != NULL ) {
+ HMAC_CTX_cleanup( ctx );
+ OPENSSL_free( ctx );
+ }
+}
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+
+#if OPENSSL_VERSION_MAJOR >= 3
+static EVP_MAC *evp_mac;
+#define HMAC_setup( ctx, key, len, hash ) \
+ { OSSL_PARAM params[2]; \
+ ctx = EVP_MAC_CTX_new( evp_mac ); \
+ params[0] = OSSL_PARAM_construct_utf8_string( "digest", (char *)hash, 0 ); \
+ params[1] = OSSL_PARAM_construct_end(); \
+ EVP_MAC_init( ctx, key, len, params ); }
+#define HMAC_crunch( ctx, buf, len ) EVP_MAC_update( ctx, buf, len )
+#define HMAC_finish( ctx, dig, dlen ) \
+ { size_t outlen; \
+ EVP_MAC_final( ctx, dig, &outlen, TOTP_SHA512_DIGEST_LENGTH ); \
+ dlen = outlen; } \
+ EVP_MAC_CTX_free( ctx )
+
+#else
+#define HMAC_setup( ctx, key, len, hash ) \
+ ctx = HMAC_CTX_new(); \
+ HMAC_Init_ex( ctx, key, len, hash, 0 )
+#define HMAC_crunch( ctx, buf, len ) HMAC_Update( ctx, buf, len )
+#define HMAC_finish( ctx, dig, dlen ) \
+ HMAC_Final( ctx, dig, &dlen ); \
+ HMAC_CTX_free( ctx )
+#endif
+
+#elif HAVE_GNUTLS
+#include <nettle/hmac.h>
+
+#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_SIZE
+#define TOTP_SHA1 &nettle_sha1
+#define TOTP_SHA224 &nettle_sha224
+#define TOTP_SHA256 &nettle_sha256
+#define TOTP_SHA384 &nettle_sha384
+#define TOTP_SHA512 &nettle_sha512
+#define TOTP_HMAC_CTX struct hmac_sha512_ctx
+
+#define HMAC_setup( ctx, key, len, hash ) \
+ const struct nettle_hash *h = hash; \
+ hmac_set_key( &ctx.outer, &ctx.inner, &ctx.state, h, len, key )
+#define HMAC_crunch( ctx, buf, len ) hmac_update( &ctx.state, h, len, buf )
+#define HMAC_finish( ctx, dig, dlen ) \
+ hmac_digest( &ctx.outer, &ctx.inner, &ctx.state, h, h->digest_size, dig ); \
+ dlen = h->digest_size
+
+#else
+#error Unsupported crypto backend.
+#endif
+
+#include "slap.h"
+#include "slap-config.h"
+
+/* Schema from OATH-LDAP project by Michael Ströder */
+
+static struct {
+ char *name, *oid;
+} otp_oid[] = {
+ { "oath-ldap", "1.3.6.1.4.1.5427.1.389.4226" },
+ { "oath-ldap-at", "oath-ldap:4" },
+ { "oath-ldap-oc", "oath-ldap:6" },
+ { NULL }
+};
+
+AttributeDescription *ad_oathOTPToken;
+AttributeDescription *ad_oathSecret;
+AttributeDescription *ad_oathOTPLength;
+AttributeDescription *ad_oathHMACAlgorithm;
+
+AttributeDescription *ad_oathHOTPParams;
+AttributeDescription *ad_oathHOTPToken;
+AttributeDescription *ad_oathHOTPCounter;
+AttributeDescription *ad_oathHOTPLookahead;
+
+AttributeDescription *ad_oathTOTPTimeStepPeriod;
+AttributeDescription *ad_oathTOTPParams;
+AttributeDescription *ad_oathTOTPToken;
+AttributeDescription *ad_oathTOTPLastTimeStep;
+AttributeDescription *ad_oathTOTPTimeStepWindow;
+AttributeDescription *ad_oathTOTPTimeStepDrift;
+
+static struct otp_at {
+ char *schema;
+ AttributeDescription **adp;
+} otp_at[] = {
+ { "( oath-ldap-at:1 "
+ "NAME 'oathSecret' "
+ "DESC 'OATH-LDAP: Shared Secret (possibly encrypted with public key in oathEncKey)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
+ &ad_oathSecret },
+
+ { "( oath-ldap-at:2 "
+ "NAME 'oathTokenSerialNumber' "
+ "DESC 'OATH-LDAP: Proprietary hardware token serial number assigned by vendor' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64})" },
+
+ { "( oath-ldap-at:3 "
+ "NAME 'oathTokenIdentifier' "
+ "DESC 'OATH-LDAP: Globally unique OATH token identifier' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )" },
+
+ { "( oath-ldap-at:4 "
+ "NAME 'oathParamsEntry' "
+ "DESC 'OATH-LDAP: DN pointing to OATH parameter/policy object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP distinguishedName )" },
+ { "( oath-ldap-at:4.1 "
+ "NAME 'oathTOTPTimeStepPeriod' "
+ "DESC 'OATH-LDAP: Time window for TOTP (seconds)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ &ad_oathTOTPTimeStepPeriod },
+
+ { "( oath-ldap-at:5 "
+ "NAME 'oathOTPLength' "
+ "DESC 'OATH-LDAP: Length of OTP (number of digits)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
+ &ad_oathOTPLength },
+ { "( oath-ldap-at:5.1 "
+ "NAME 'oathHOTPParams' "
+ "DESC 'OATH-LDAP: DN pointing to HOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathParamsEntry )",
+ &ad_oathHOTPParams },
+ { "( oath-ldap-at:5.2 "
+ "NAME 'oathTOTPParams' "
+ "DESC 'OATH-LDAP: DN pointing to TOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathParamsEntry )",
+ &ad_oathTOTPParams },
+
+ { "( oath-ldap-at:6 "
+ "NAME 'oathHMACAlgorithm' "
+ "DESC 'OATH-LDAP: HMAC algorithm used for generating OTP values' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
+ &ad_oathHMACAlgorithm },
+
+ { "( oath-ldap-at:7 "
+ "NAME 'oathTimestamp' "
+ "DESC 'OATH-LDAP: Timestamp (not directly used).' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )" },
+ { "( oath-ldap-at:7.1 "
+ "NAME 'oathLastFailure' "
+ "DESC 'OATH-LDAP: Timestamp of last failed OATH validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+ { "( oath-ldap-at:7.2 "
+ "NAME 'oathLastLogin' "
+ "DESC 'OATH-LDAP: Timestamp of last successful OATH validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+ { "( oath-ldap-at:7.3 "
+ "NAME 'oathSecretTime' "
+ "DESC 'OATH-LDAP: Timestamp of generation of oathSecret attribute.' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathTimestamp )" },
+
+ { "( oath-ldap-at:8 "
+ "NAME 'oathSecretMaxAge' "
+ "DESC 'OATH-LDAP: Time in seconds for which the shared secret (oathSecret) will be valid from oathSecretTime value.' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+
+ { "( oath-ldap-at:9 "
+ "NAME 'oathToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP distinguishedName )" },
+ { "( oath-ldap-at:9.1 "
+ "NAME 'oathHOTPToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH/HOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathToken )",
+ &ad_oathHOTPToken },
+ { "( oath-ldap-at:9.2 "
+ "NAME 'oathTOTPToken' "
+ "DESC 'OATH-LDAP: DN pointing to OATH/TOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathToken )",
+ &ad_oathTOTPToken },
+
+ { "( oath-ldap-at:10 "
+ "NAME 'oathCounter' "
+ "DESC 'OATH-LDAP: Counter for OATH data (not directly used)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+ { "( oath-ldap-at:10.1 "
+ "NAME 'oathFailureCount' "
+ "DESC 'OATH-LDAP: OATH failure counter' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.2 "
+ "NAME 'oathHOTPCounter' "
+ "DESC 'OATH-LDAP: Counter for HOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathHOTPCounter },
+ { "( oath-ldap-at:10.3 "
+ "NAME 'oathHOTPLookAhead' "
+ "DESC 'OATH-LDAP: Look-ahead window for HOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathHOTPLookahead },
+ { "( oath-ldap-at:10.5 "
+ "NAME 'oathThrottleLimit' "
+ "DESC 'OATH-LDAP: Failure throttle limit' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.6 "
+ "NAME 'oathTOTPLastTimeStep' "
+ "DESC 'OATH-LDAP: Last time step seen for TOTP (time/period)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPLastTimeStep },
+ { "( oath-ldap-at:10.7 "
+ "NAME 'oathMaxUsageCount' "
+ "DESC 'OATH-LDAP: Maximum number of times a token can be used' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )" },
+ { "( oath-ldap-at:10.8 "
+ "NAME 'oathTOTPTimeStepWindow' "
+ "DESC 'OATH-LDAP: Size of time step +/- tolerance window used for TOTP validation' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPTimeStepWindow },
+ { "( oath-ldap-at:10.9 "
+ "NAME 'oathTOTPTimeStepDrift' "
+ "DESC 'OATH-LDAP: Last observed time step shift seen for TOTP' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "SUP oathCounter )",
+ &ad_oathTOTPTimeStepDrift },
+
+ { "( oath-ldap-at:11 "
+ "NAME 'oathSecretLength' "
+ "DESC 'OATH-LDAP: Length of plain-text shared secret (number of bytes)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+
+ { "( oath-ldap-at:12 "
+ "NAME 'oathEncKey' "
+ "DESC 'OATH-LDAP: public key to be used for encrypting new shared secrets' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
+
+ { "( oath-ldap-at:13 "
+ "NAME 'oathResultCode' "
+ "DESC 'OATH-LDAP: LDAP resultCode to use in response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
+ { "( oath-ldap-at:13.1 "
+ "NAME 'oathSuccessResultCode' "
+ "DESC 'OATH-LDAP: success resultCode to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathResultCode )" },
+ { "( oath-ldap-at:13.2 "
+ "NAME 'oathFailureResultCode' "
+ "DESC 'OATH-LDAP: failure resultCode to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathResultCode )" },
+
+ { "( oath-ldap-at:14 "
+ "NAME 'oathTokenPIN' "
+ "DESC 'OATH-LDAP: Configuration PIN (possibly encrypted with oathEncKey)' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
+
+ { "( oath-ldap-at:15 "
+ "NAME 'oathMessage' "
+ "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SINGLE-VALUE "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )" },
+ { "( oath-ldap-at:15.1 "
+ "NAME 'oathSuccessMessage' "
+ "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathMessage )" },
+ { "( oath-ldap-at:15.2 "
+ "NAME 'oathFailureMessage' "
+ "DESC 'OATH-LDAP: failure diagnosticMessage to use in bind/compare response' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "SUP oathMessage )" },
+
+ { NULL }
+};
+
+ObjectClass *oc_oathOTPUser;
+ObjectClass *oc_oathHOTPToken;
+ObjectClass *oc_oathTOTPToken;
+ObjectClass *oc_oathHOTPParams;
+ObjectClass *oc_oathTOTPParams;
+
+static struct otp_oc {
+ char *schema;
+ ObjectClass **ocp;
+} otp_oc[] = {
+ { "( oath-ldap-oc:1 "
+ "NAME 'oathUser' "
+ "DESC 'OATH-LDAP: User Object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT )",
+ &oc_oathOTPUser },
+ { "( oath-ldap-oc:1.1 "
+ "NAME 'oathHOTPUser' "
+ "DESC 'OATH-LDAP: HOTP user object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathUser "
+ "MAY ( oathHOTPToken ) )" },
+ { "( oath-ldap-oc:1.2 "
+ "NAME 'oathTOTPUser' "
+ "DESC 'OATH-LDAP: TOTP user object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathUser "
+ "MUST ( oathTOTPToken ) )" },
+ { "( oath-ldap-oc:2 "
+ "NAME 'oathParams' "
+ "DESC 'OATH-LDAP: Parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT "
+ "MUST ( oathOTPLength $ oathHMACAlgorithm ) "
+ "MAY ( oathSecretMaxAge $ oathSecretLength $ "
+ "oathMaxUsageCount $ oathThrottleLimit $ oathEncKey $ "
+ "oathSuccessResultCode $ oathSuccessMessage $ "
+ "oathFailureResultCode $ oathFailureMessage ) )" },
+ { "( oath-ldap-oc:2.1 "
+ "NAME 'oathHOTPParams' "
+ "DESC 'OATH-LDAP: HOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathParams "
+ "MUST ( oathHOTPLookAhead ) )",
+ &oc_oathHOTPParams },
+ { "( oath-ldap-oc:2.2 "
+ "NAME 'oathTOTPParams' "
+ "DESC 'OATH-LDAP: TOTP parameter object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathParams "
+ "MUST ( oathTOTPTimeStepPeriod ) "
+ "MAY ( oathTOTPTimeStepWindow ) )",
+ &oc_oathTOTPParams },
+ { "( oath-ldap-oc:3 "
+ "NAME 'oathToken' "
+ "DESC 'OATH-LDAP: User Object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "ABSTRACT "
+ "MAY ( oathSecret $ oathSecretTime $ "
+ "oathLastLogin $ oathFailureCount $ oathLastFailure $ "
+ "oathTokenSerialNumber $ oathTokenIdentifier $ oathTokenPIN ) )" },
+ { "( oath-ldap-oc:3.1 "
+ "NAME 'oathHOTPToken' "
+ "DESC 'OATH-LDAP: HOTP token object' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathToken "
+ "MAY ( oathHOTPParams $ oathHOTPCounter ) )",
+ &oc_oathHOTPToken },
+ { "( oath-ldap-oc:3.2 "
+ "NAME 'oathTOTPToken' "
+ "DESC 'OATH-LDAP: TOTP token' "
+ "X-ORIGIN 'OATH-LDAP' "
+ "AUXILIARY "
+ "SUP oathToken "
+ "MAY ( oathTOTPParams $ oathTOTPLastTimeStep $ oathTOTPTimeStepDrift ) )",
+ &oc_oathTOTPToken },
+ { NULL }
+};
+
+typedef struct myval {
+ ber_len_t mv_len;
+ void *mv_val;
+} myval;
+
+static void
+do_hmac( const void *hash, myval *key, myval *data, myval *out )
+{
+ TOTP_HMAC_CTX ctx;
+ unsigned int digestLen;
+
+ HMAC_setup( ctx, key->mv_val, key->mv_len, hash );
+ HMAC_crunch( ctx, data->mv_val, data->mv_len );
+ HMAC_finish( ctx, out->mv_val, digestLen );
+ out->mv_len = digestLen;
+}
+
+#define MAX_DIGITS 8
+static const int DIGITS_POWER[] = {
+ 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
+};
+
+static const void *
+otp_choose_mech( struct berval *oid )
+{
+ /* RFC 8018 OIDs */
+ const struct berval oid_hmacwithSHA1 = BER_BVC("1.2.840.113549.2.7");
+ const struct berval oid_hmacwithSHA224 = BER_BVC("1.2.840.113549.2.8");
+ const struct berval oid_hmacwithSHA256 = BER_BVC("1.2.840.113549.2.9");
+ const struct berval oid_hmacwithSHA384 = BER_BVC("1.2.840.113549.2.10");
+ const struct berval oid_hmacwithSHA512 = BER_BVC("1.2.840.113549.2.11");
+
+ if ( !ber_bvcmp( &oid_hmacwithSHA1, oid ) ) {
+ return TOTP_SHA1;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA224, oid ) ) {
+ return TOTP_SHA224;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA256, oid ) ) {
+ return TOTP_SHA256;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA384, oid ) ) {
+ return TOTP_SHA384;
+ } else if ( !ber_bvcmp( &oid_hmacwithSHA512, oid ) ) {
+ return TOTP_SHA512;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "otp_choose_mech: "
+ "hmac OID %s unsupported\n",
+ oid->bv_val );
+ return NULL;
+}
+
+static void
+generate(
+ struct berval *bv,
+ uint64_t tval,
+ int digits,
+ struct berval *out,
+ const void *mech )
+{
+ unsigned char digest[TOTP_SHA512_DIGEST_LENGTH];
+ myval digval;
+ myval key, data;
+ unsigned char msg[8];
+ int i, offset, res, otp;
+
+#if WORDS_BIGENDIAN
+ *(uint64_t *)msg = tval;
+#else
+ for ( i = 7; i >= 0; i-- ) {
+ msg[i] = tval & 0xff;
+ tval >>= 8;
+ }
+#endif
+
+ key.mv_len = bv->bv_len;
+ key.mv_val = bv->bv_val;
+
+ data.mv_val = msg;
+ data.mv_len = sizeof(msg);
+
+ digval.mv_val = digest;
+ digval.mv_len = sizeof(digest);
+ do_hmac( mech, &key, &data, &digval );
+
+ offset = digest[digval.mv_len - 1] & 0xf;
+ res = ( (digest[offset] & 0x7f) << 24 ) |
+ ( ( digest[offset + 1] & 0xff ) << 16 ) |
+ ( ( digest[offset + 2] & 0xff ) << 8 ) |
+ ( digest[offset + 3] & 0xff );
+
+ otp = res % DIGITS_POWER[digits];
+ out->bv_len = snprintf( out->bv_val, out->bv_len, "%0*d", digits, otp );
+}
+
+static int
+otp_bind_response( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ /* If the bind succeeded, return our result */
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static long
+otp_hotp( Operation *op, Entry *token )
+{
+ char outbuf[MAX_DIGITS + 1];
+ Entry *params = NULL;
+ Attribute *a;
+ BerValue *secret, client_otp;
+ const void *mech;
+ long last_step = -1, found = -1;
+ int i, otp_len, window;
+
+ a = attr_find( token->e_attrs, ad_oathSecret );
+ secret = &a->a_vals[0];
+
+ a = attr_find( token->e_attrs, ad_oathHOTPCounter );
+ if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathHOTPCounter value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( token->e_attrs, ad_oathHOTPParams );
+ if ( !a ||
+ be_entry_get_rw( op, &a->a_nvals[0], oc_oathHOTPParams, NULL, 0,
+ &params ) ) {
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathOTPLength );
+ if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathOTPLength value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
+ /* Client didn't even send the token, fail immediately */
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathHOTPLookahead );
+ if ( lutil_atoi( &window, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_hotp: "
+ "could not parse oathHOTPLookAhead value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ window++;
+
+ a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
+ if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
+ goto done;
+ }
+ be_entry_release_r( op, params );
+ params = NULL;
+
+ /* We are provided "password" + "OTP", split accordingly */
+ client_otp.bv_len = otp_len;
+ client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
+
+ /* If check succeeds, advance the step counter accordingly */
+ for ( i = 1; i <= window; i++ ) {
+ BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
+
+ generate( secret, last_step + i, otp_len, &out, mech );
+ if ( !ber_bvcmp( &out, &client_otp ) ) {
+ found = last_step + i;
+ /* Would we leak information if we stopped right now? */
+ }
+ }
+
+ if ( found >= 0 ) {
+ /* OTP check passed, trim the password */
+ op->orb_cred.bv_len -= otp_len;
+ Debug( LDAP_DEBUG_STATS, "%s HOTP token %s no. %ld redeemed\n",
+ op->o_log_prefix, token->e_name.bv_val, found );
+ }
+
+done:
+ memset( outbuf, 0, sizeof(outbuf) );
+ if ( params ) {
+ be_entry_release_r( op, params );
+ }
+ return found;
+}
+
+static long
+otp_totp( Operation *op, Entry *token, long *drift )
+{
+ char outbuf[MAX_DIGITS + 1];
+ Entry *params = NULL;
+ Attribute *a;
+ BerValue *secret, client_otp;
+ const void *mech;
+ long t, last_step = -1, found = -1, window = 0, old_drift;
+ int i, otp_len, time_step;
+
+ a = attr_find( token->e_attrs, ad_oathSecret );
+ secret = &a->a_vals[0];
+
+ a = attr_find( token->e_attrs, ad_oathTOTPLastTimeStep );
+ if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPLastTimeStep value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( token->e_attrs, ad_oathTOTPParams );
+ if ( !a ||
+ be_entry_get_rw( op, &a->a_nvals[0], oc_oathTOTPParams, NULL, 0,
+ &params ) ) {
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepPeriod );
+ if ( lutil_atoi( &time_step, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepPeriod value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepWindow );
+ if ( a && lutil_atol( &window, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepWindow value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathTOTPTimeStepDrift );
+ if ( a && lutil_atol( drift, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathTOTPTimeStepDrift value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ old_drift = *drift;
+ t = op->o_time / time_step + *drift;
+
+ a = attr_find( params->e_attrs, ad_oathOTPLength );
+ if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "otp_totp: "
+ "could not parse oathOTPLength value %s\n",
+ a->a_vals[0].bv_val );
+ goto done;
+ }
+ if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
+ /* Client didn't even send the token, fail immediately */
+ goto done;
+ }
+
+ a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
+ if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
+ goto done;
+ }
+ be_entry_release_r( op, params );
+ params = NULL;
+
+ /* We are provided "password" + "OTP", split accordingly */
+ client_otp.bv_len = otp_len;
+ client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
+
+ /* If check succeeds, advance the step counter accordingly */
+ /* Negation of A001057 series that enumerates all integers:
+ * (0, -1, 1, -2, 2, ...) */
+ for ( i = 0; i >= -window; i = ( i < 0 ) ? -i : ~i ) {
+ BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
+
+ if ( t + i <= last_step ) continue;
+
+ generate( secret, t + i, otp_len, &out, mech );
+ if ( !ber_bvcmp( &out, &client_otp ) ) {
+ found = t + i;
+ *drift = old_drift + i;
+ /* Would we leak information if we stopped right now? */
+ }
+ }
+
+ /* OTP check passed, trim the password */
+ if ( found >= 0 ) {
+ assert( found > last_step );
+
+ op->orb_cred.bv_len -= otp_len;
+ Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with new drift of %ld\n",
+ op->o_log_prefix, token->e_name.bv_val, *drift );
+ }
+
+done:
+ memset( outbuf, 0, sizeof(outbuf) );
+ if ( params ) {
+ be_entry_release_r( op, params );
+ }
+ return found;
+}
+
+static int
+otp_op_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ BerValue totpdn = BER_BVNULL, hotpdn = BER_BVNULL, ndn;
+ Entry *user = NULL, *token = NULL;
+ AttributeDescription *ad = NULL, *drift_ad = NULL;
+ Attribute *a;
+ long t = -1, drift = 0;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( op->oq_bind.rb_method != LDAP_AUTH_SIMPLE ) {
+ return rc;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ if ( be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &user ) ) {
+ goto done;
+ }
+
+ if ( !is_entry_objectclass_or_sub( user, oc_oathOTPUser ) ) {
+ be_entry_release_r( op, user );
+ goto done;
+ }
+
+ if ( (a = attr_find( user->e_attrs, ad_oathTOTPToken )) ) {
+ ber_dupbv_x( &totpdn, &a->a_nvals[0], op->o_tmpmemctx );
+ }
+
+ if ( (a = attr_find( user->e_attrs, ad_oathHOTPToken )) ) {
+ ber_dupbv_x( &hotpdn, &a->a_nvals[0], op->o_tmpmemctx );
+ }
+ be_entry_release_r( op, user );
+
+ if ( !BER_BVISNULL( &totpdn ) &&
+ be_entry_get_rw( op, &totpdn, oc_oathTOTPToken, ad_oathSecret, 0,
+ &token ) == LDAP_SUCCESS ) {
+ ndn = totpdn;
+ ad = ad_oathTOTPLastTimeStep;
+ drift_ad = ad_oathTOTPTimeStepDrift;
+ t = otp_totp( op, token, &drift );
+ be_entry_release_r( op, token );
+ token = NULL;
+ }
+ if ( t < 0 && !BER_BVISNULL( &hotpdn ) &&
+ be_entry_get_rw( op, &hotpdn, oc_oathHOTPToken, ad_oathSecret, 0,
+ &token ) == LDAP_SUCCESS ) {
+ ndn = hotpdn;
+ ad = ad_oathHOTPCounter;
+ t = otp_hotp( op, token );
+ be_entry_release_r( op, token );
+ token = NULL;
+ }
+
+ /* If check succeeds, advance the step counter and drift accordingly */
+ if ( t >= 0 ) {
+ char outbuf[32], drift_buf[32];
+ Operation op2;
+ Opheader oh;
+ Modifications mod[2], *m = mod;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb = { .sc_response = &slap_null_cb };
+ BerValue bv[2], bv_drift[2];
+
+ bv[0].bv_val = outbuf;
+ bv[0].bv_len = snprintf( bv[0].bv_val, sizeof(outbuf), "%ld", t );
+ BER_BVZERO( &bv[1] );
+
+ m->sml_numvals = 1;
+ m->sml_values = bv;
+ m->sml_nvalues = NULL;
+ m->sml_desc = ad;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+
+ if ( drift_ad ) {
+ m->sml_next = &mod[1];
+
+ bv_drift[0].bv_val = drift_buf;
+ bv_drift[0].bv_len = snprintf(
+ bv_drift[0].bv_val, sizeof(drift_buf), "%ld", drift );
+ BER_BVZERO( &bv_drift[1] );
+
+ m++;
+ m->sml_numvals = 1;
+ m->sml_values = bv_drift;
+ m->sml_nvalues = NULL;
+ m->sml_desc = drift_ad;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+ }
+ m->sml_next = NULL;
+
+ op2 = *op;
+ oh = *op->o_hdr;
+ op2.o_hdr = &oh;
+
+ op2.o_callback = &cb;
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.orm_modlist = mod;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ op2.o_req_dn = ndn;
+ op2.o_req_ndn = ndn;
+ op2.o_opid = -1;
+
+ op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ rc = LDAP_OTHER;
+ goto done;
+ }
+ } else {
+ /* Client failed the bind, but we still have to pass it over to the
+ * backend and fail the Bind later */
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_response = otp_bind_response;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+
+done:
+ if ( !BER_BVISNULL( &hotpdn ) ) {
+ ber_memfree_x( hotpdn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &totpdn ) ) {
+ ber_memfree_x( totpdn.bv_val, op->o_tmpmemctx );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+ return rc;
+}
+
+static slap_overinst otp;
+
+int
+otp_initialize( void )
+{
+ ConfigArgs ca;
+ char *argv[4];
+ int i;
+
+ otp.on_bi.bi_type = "otp";
+ otp.on_bi.bi_op_bind = otp_op_bind;
+
+ ca.argv = argv;
+ argv[0] = "otp";
+ ca.argv = argv;
+ ca.argc = 3;
+ ca.fname = argv[0];
+
+ argv[3] = NULL;
+ for ( i = 0; otp_oid[i].name; i++ ) {
+ argv[1] = otp_oid[i].name;
+ argv[2] = otp_oid[i].oid;
+ parse_oidm( &ca, 0, NULL );
+ }
+
+ /* schema integration */
+ for ( i = 0; otp_at[i].schema; i++ ) {
+ if ( register_at( otp_at[i].schema, otp_at[i].adp, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "otp_initialize: "
+ "register_at failed\n" );
+ return -1;
+ }
+ }
+
+ for ( i = 0; otp_oc[i].schema; i++ ) {
+ if ( register_oc( otp_oc[i].schema, otp_oc[i].ocp, 0 ) ) {
+ Debug( LDAP_DEBUG_ANY, "otp_initialize: "
+ "register_oc failed\n" );
+ return -1;
+ }
+ }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ evp_mac = EVP_MAC_fetch( NULL, "HMAC", "provider=default" );
+#endif
+ return overlay_register( &otp );
+}
+
+#if SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return otp_initialize();
+}
+#endif /* SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_OTP) */
diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c
new file mode 100644
index 0000000..8290200
--- /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-2022 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 );
+ break;
+ }
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c
new file mode 100644
index 0000000..2b947e4
--- /dev/null
+++ b/servers/slapd/overlays/pcache.c
@@ -0,0 +1,5815 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 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 "ldap_avl.h"
+
+#include "../back-monitor/back-monitor.h"
+
+#include "slap-config.h"
+
+/*
+ * 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
+
+/* 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 {
+ TAvlnode *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 */
+ int in_lru; /* query is in LRU list */
+ 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;
+
+ qc->in_lru = 1;
+ 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 );
+}
+
+/* remove_query from LRU list */
+
+static void
+remove_query (query_manager* qm, CachedQuery* qc)
+{
+ CachedQuery* up;
+ CachedQuery* down;
+
+ if (!qc || !qc->in_lru)
+ return;
+
+ qc->in_lru = 0;
+ 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, TAvlnode *root, Filter *inputf, Filter *first )
+{
+ Filter* fs;
+ Filter* fi;
+ MatchingRule* mrule = NULL;
+ int res=0, eqpass= 0;
+ int ret, rc, dir;
+ TAvlnode *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 = ldap_tavl_end( root, 1 );
+ dir = TAVL_DIR_LEFT;
+ } else {
+ ptr = ldap_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 = ldap_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 = ldap_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 );
+ qbase.base = query->base;
+
+ first = filter_first( query->filter );
+
+ ldap_pvt_thread_rdwr_rlock(&templa->t_rwlock);
+ for( ;; ) {
+ /* Find the base */
+ qbptr = ldap_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 );
+ 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 ] );
+
+ 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 );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ qbase = ldap_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';
+ ldap_avl_insert( &templ->qbase, qbase, pcache_dn_cmp, ldap_avl_dup_error );
+ }
+ new_cached_query->next = templ->query;
+ new_cached_query->prev = NULL;
+ new_cached_query->qbase = qbase;
+ rc = ldap_tavl_insert( &qbase->scopes[query->scope], new_cached_query,
+ pcache_query_cmp, ldap_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 );
+
+ /* 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 );
+ 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;
+ }
+ ldap_tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp );
+ qc->qbase->queries--;
+ if ( qc->qbase->queries == 0 ) {
+ ldap_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" );
+ 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 );
+ 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 );
+ 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 );
+ Debug( pcache_debug, "Unlock CR index = %p\n", (void *) temp );
+ 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 );
+
+ 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 );
+
+ 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 );
+ return_val = remove_query_data( op, uuid );
+ Debug( pcache_debug,
+ "QUERY REMOVED, SIZE=%d\n",
+ return_val );
+ 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 );
+ ldap_pvt_thread_mutex_unlock( &cm->cache_mutex );
+ Debug( pcache_debug,
+ "QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries );
+ }
+}
+
+/*
+ * 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 = &sc;
+
+ 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 = &sc;
+
+ 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 );
+
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ }
+ } 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 ) {
+ BackendInfo *bi = cm->db.bd_info;
+ int rc;
+
+ /* execute, if possible */
+ if ( (&bi->bi_op_bind)[ type ] ) {
+ Operation op2 = *op;
+
+ op2.o_bd = &cm->db;
+
+ rc = (&bi->bi_op_bind)[ 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 );
+
+ 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 );
+
+ /* 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 );
+ 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 );
+ 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" );
+
+ 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" );
+ 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" );
+ }
+
+ 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;
+ CachedQuery *expires;
+ 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;
+ expires = NULL;
+ 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;
+ }
+
+ Debug( pcache_debug, "Lock CR index = %p\n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ 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 ) {
+ /* perform actual refresh below */
+ continue;
+ }
+ }
+
+ if (query->expiry_time < op->o_time) {
+ int rem = 0;
+ if ( query != templ->query_last )
+ continue;
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if (query->in_lru) {
+ remove_query(qm, query);
+ rem = 1;
+ }
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ if (!rem)
+ continue;
+ remove_from_template(query, templ);
+ Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n",
+ (void *) templ, templ->no_of_queries );
+ query->prev = expires;
+ expires = query;
+ query->qtemp = NULL;
+ } 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;
+ }
+ }
+ Debug( pcache_debug, "Unlock CR index = %p\n",
+ (void *) templ );
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+ for ( query=expires; query; query=qprev ) {
+ int rem;
+ qprev = query->prev;
+ 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 );
+ 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 );
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+ Debug( pcache_debug,
+ "STALE QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries );
+ 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);
+ }
+
+ /* handle refreshes that we skipped earlier */
+ if ( templ->ttr ) {
+ ldap_pvt_thread_rdwr_rlock(&templ->t_rwlock);
+ 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->expiry_time > op->o_time ) {
+ refresh_query( op, query, on );
+ query->refresh_time = op->o_time + templ->ttr;
+ }
+ }
+ }
+ ldap_pvt_thread_rdwr_runlock(&templ->t_rwlock);
+ }
+ }
+
+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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY integerMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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 ))
+ config_push_cleanup( ca, 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.be_cf_ocs )
+ config_build_entry( op, rs, pe, ca, &bv, cm->db.be_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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ return( 1 );
+ }
+
+ cm->cc_period = (time_t)t;
+ Debug( pcache_debug,
+ "Total # of attribute sets to be cached = %d.\n",
+ cm->numattrsets );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ }
+ }
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+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 );
+ 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 );
+ 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 );
+ 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" );
+ Debug( pcache_debug, " query template: %s\n",
+ temp->querystr.bv_val );
+ 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" );
+ 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 );
+ }
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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->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 );
+ 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 );
+ rf++;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i );
+ }
+ ncf++;
+
+ } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i );
+ nrf++;
+ }
+ }
+
+ if ( ncf || rf || nrf ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf );
+
+ 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++)
+ ldap_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" : "" );
+
+ 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 );
+ }
+ ldap_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" );
+ 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" );
+ 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" );
+ 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 );
+ 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 );
+ 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" );
+ 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 );
+ }
+ 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_CONFIG, "pcache_monitor_db_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n" );
+ }
+
+ 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
+ * mdb_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 ( !BER_BVISNULL( &cm->monitor_ndn )) {
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe;
+
+ if ( mi && mi->bi_extra ) {
+ struct berval dummy = BER_BVNULL;
+ mbe = mi->bi_extra;
+ mbe->unregister_entry_callback( &cm->monitor_ndn,
+ (monitor_callback_t *)cm->monitor_cb,
+ &dummy, 0, &dummy );
+ }
+ }
+
+ 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 );
+ 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 );
+ return code;
+ }
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+ 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, "pcache_initialize: "
+ "unable to add objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid );
+ 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 );
+ 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 );
+ 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..29a77c8
--- /dev/null
+++ b/servers/slapd/overlays/ppolicy.c
@@ -0,0 +1,3490 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 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 "slap-config.h"
+
+#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
+#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5
+#endif
+
+ /* External password quality checking function.
+ * The error message must have a preallocated buffer and size
+ * passed in. Module can still allocate a buffer for
+ * it if the provided one is too small.
+ */
+typedef int (check_func)( char *passwd, struct berval *errmsg, Entry *ent, struct berval *arg );
+#define ERRBUFSIZ 256
+
+/* 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 disable_write;
+ int send_netscape_controls; /* send netscape password controls */
+ char *pwdCheckModule; /* name of module to dynamically
+ load to check password */
+#ifdef SLAPD_MODULES
+ lt_dlhandle pwdCheckHandle; /* handle from lt_dlopen */
+ check_func *pwdCheckFunc;
+#endif /* SLAPD_MODULES */
+ 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 account_usability_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 pwdMaxIdle; /* number of seconds since last successful bind before
+ passwd gets locked out */
+ 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 pwdMaxLength; /* maximum number of chars in password */
+ int pwdExpireWarning; /* number of seconds that warning controls are
+ sent before a password expires */
+ int pwdGraceExpiry; /* number of seconds after expiry grace logins are
+ valid */
+ 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 pwdMinDelay; /* base bind delay in seconds on failure */
+ int pwdMaxDelay; /* maximum bind delay in seconds */
+ 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 */
+ int pwdUseCheckModule; /* 0 = do not use password check module, 1 = use */
+ struct berval pwdCheckModuleArg; /* Optional argument to the password check
+ module */
+} 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, *ad_pwdStartTime, *ad_pwdEndTime,
+ *ad_pwdLastSuccess, *ad_pwdAccountTmpLockoutEnd;
+
+/* Policy attributes */
+static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdMaxIdle,
+ *ad_pwdInHistory, *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxLength,
+ *ad_pwdMaxFailure, *ad_pwdGraceExpiry, *ad_pwdGraceAuthNLimit,
+ *ad_pwdExpireWarning, *ad_pwdMinDelay, *ad_pwdMaxDelay,
+ *ad_pwdLockoutDuration, *ad_pwdFailureCountInterval,
+ *ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdUseCheckModule, *ad_pwdLockout,
+ *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
+ *ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
+
+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 /* FIXME: ITS#9671 until we introduce a separate lockout flag? */
+ "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 /* ITS#9671: until we implement ITS#9343 or similar */
+ "NO-USER-MODIFICATION "
+#endif
+ "USAGE directoryOperation )",
+ &ad_pwdPolicySubentry },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.27 "
+ "NAME ( 'pwdStartTime' ) "
+ "DESC 'The time the password becomes enabled' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation )",
+ &ad_pwdStartTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.28 "
+ "NAME ( 'pwdEndTime' ) "
+ "DESC 'The time the password becomes disabled' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "USAGE directoryOperation )",
+ &ad_pwdEndTime },
+ /* Defined in schema_prep.c now
+ { "( 1.3.6.1.4.1.42.2.27.8.1.29 "
+ "NAME ( 'pwdLastSuccess' ) "
+ "DESC 'The timestamp of the last successful authentication' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdLastSuccess },
+ */
+ { "( 1.3.6.1.4.1.42.2.27.8.1.33 "
+ "NAME ( 'pwdAccountTmpLockoutEnd' ) "
+ "DESC 'Temporary lockout end' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_pwdAccountTmpLockoutEnd },
+
+ { "( 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 )",
+ &ad_pwdAttribute },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.2 "
+ "NAME ( 'pwdMinAge' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMinAge },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.3 "
+ "NAME ( 'pwdMaxAge' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxAge },
+ { "( 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 )",
+ &ad_pwdInHistory },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.5 "
+ "NAME ( 'pwdCheckQuality' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckQuality },
+ { "( 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 )",
+ &ad_pwdMinLength },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.31 "
+ "NAME ( 'pwdMaxLength' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxLength },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.7 "
+ "NAME ( 'pwdExpireWarning' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdExpireWarning },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.8 "
+ "NAME ( 'pwdGraceAuthNLimit' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdGraceAuthNLimit },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.30 "
+ "NAME ( 'pwdGraceExpiry' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdGraceExpiry },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.9 "
+ "NAME ( 'pwdLockout' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdLockout },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.10 "
+ "NAME ( 'pwdLockoutDuration' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdLockoutDuration },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.11 "
+ "NAME ( 'pwdMaxFailure' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxFailure },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.12 "
+ "NAME ( 'pwdFailureCountInterval' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdFailureCountInterval },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.13 "
+ "NAME ( 'pwdMustChange' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdMustChange },
+ { "( 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 )",
+ &ad_pwdAllowUserChange },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.15 "
+ "NAME ( 'pwdSafeModify' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_pwdSafeModify },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.24 "
+ "NAME ( 'pwdMinDelay' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMinDelay },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.25 "
+ "NAME ( 'pwdMaxDelay' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxDelay },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.26 "
+ "NAME ( 'pwdMaxIdle' ) "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxIdle },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.32 "
+ "NAME ( 'pwdMaxRecordedFailure' ) "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_pwdMaxRecordedFailure },
+ { "( 1.3.6.1.4.1.4754.1.99.1 "
+ "NAME ( 'pwdCheckModule' ) "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 "
+ "DESC 'Obsolete, no longer used' "
+ "OBSOLETE "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckModule },
+ { "( 1.3.6.1.4.1.4754.1.99.2 "
+ "NAME ( 'pwdCheckModuleArg' ) "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "DESC 'Argument to pass to check_password() function' "
+ "SINGLE-VALUE )",
+ &ad_pwdCheckModuleArg },
+ { "( 1.3.6.1.4.1.4754.1.99.3 "
+ "NAME ( 'pwdUseCheckModule' ) "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "DESC 'Toggle use of the loaded pwdCheckModule' "
+ "SINGLE-VALUE )",
+ &ad_pwdUseCheckModule },
+
+ { NULL, NULL }
+};
+
+static char *pwd_ocs[] = {
+ "( 1.3.6.1.4.1.4754.2.99.1 "
+ "NAME 'pwdPolicyChecker' "
+ "SUP top "
+ "AUXILIARY "
+ "MAY ( pwdCheckModule $ pwdCheckModuleArg $ pwdUseCheckModule ) )" ,
+ "( 1.3.6.1.4.1.42.2.27.8.2.1 "
+ "NAME 'pwdPolicy' "
+ "SUP top "
+ "AUXILIARY "
+ "MUST ( pwdAttribute ) "
+ "MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $ "
+ "pwdMinLength $ pwdMaxLength $ pwdExpireWarning $ "
+ "pwdGraceAuthNLimit $ pwdGraceExpiry $ pwdLockout $ "
+ "pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ "
+ "pwdMustChange $ pwdAllowUserChange $ pwdSafeModify $ "
+ "pwdMinDelay $ pwdMaxDelay $ pwdMaxIdle $ "
+ "pwdMaxRecordedFailure ) )",
+ NULL
+};
+
+static ldap_pvt_thread_mutex_t chk_syntax_mutex;
+
+enum {
+ PPOLICY_DEFAULT = 1,
+ PPOLICY_HASH_CLEARTEXT,
+ PPOLICY_USE_LOCKOUT,
+ PPOLICY_DISABLE_WRITE,
+ PPOLICY_CHECK_MODULE,
+};
+
+static ConfigDriver ppolicy_cf_default, ppolicy_cf_checkmod;
+
+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' "
+ "EQUALITY distinguishedNameMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_disable_write", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_DISABLE_WRITE,
+ (void *)offsetof(pp_info,disable_write),
+ "( OLcfgOvAt:12.5 NAME 'olcPPolicyDisableWrite' "
+ "DESC 'Prevent all policy overlay writes' "
+ "EQUALITY booleanMatch "
+ "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 },
+ { "ppolicy_check_module", "path", 2, 2, 0,
+#ifdef SLAPD_MODULES
+ ARG_STRING|ARG_MAGIC|PPOLICY_CHECK_MODULE, ppolicy_cf_checkmod,
+#else
+ ARG_IGNORED, NULL,
+#endif /* SLAPD_MODULES */
+ "( OLcfgOvAt:12.7 NAME 'olcPPolicyCheckModule' "
+ "DESC 'Loadable module that instantiates check_password() function' "
+ "EQUALITY caseExactIA5Match "
+ "SYNTAX OMsIA5String "
+ "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 $ "
+ "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls $ "
+ "olcPPolicyCheckModule ) )",
+ 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" );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n" );
+ 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" );
+ 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:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n" );
+ 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;
+}
+
+#ifdef SLAPD_MODULES
+static int
+ppolicy_cf_checkmod( 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_CHECK_MODULE );
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_checkmod\n" );
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( pi->pwdCheckModule ) {
+ c->value_string = ch_strdup( pi->pwdCheckModule );
+ rc = 0;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( pi->pwdCheckHandle ) {
+ lt_dlclose( pi->pwdCheckHandle );
+ pi->pwdCheckHandle = NULL;
+ pi->pwdCheckFunc = NULL;
+ }
+ ch_free( pi->pwdCheckModule );
+ pi->pwdCheckModule = NULL;
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ pi->pwdCheckHandle = lt_dlopen( c->value_string );
+ if ( pi->pwdCheckHandle == NULL ) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlopen(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ if (( pi->pwdCheckFunc = lt_dlsym( pi->pwdCheckHandle, "check_password" )) == NULL) {
+ const char *dlerr = lt_dlerror();
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> lt_dlsym(%s) failed: %s",
+ c->argv[0], c->value_string, dlerr );
+ Debug(LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+ } else {
+ pi->pwdCheckModule = c->value_string;
+ rc = 0;
+ }
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+#endif /* SLAPD_MODULES */
+
+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;
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdStartTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * Password has a defined start of validity
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( now < then ) {
+ return 1;
+ }
+ }
+ }
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdEndTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * Password has a defined end of validity
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( then <= now ) {
+ return 1;
+ }
+ }
+ }
+
+ if ( !pp->pwdLockout )
+ return 0;
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+ time_t then, now = op->o_time;
+
+ /*
+ * We have temporarily locked the account after a failure
+ */
+ if ( vals[0].bv_val != NULL ) {
+ if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) {
+ return 1;
+ }
+ if ( now < then ) {
+ return 1;
+ }
+ }
+ }
+
+ /* Only check if database maintains lastbind */
+ if ( pp->pwdMaxIdle && SLAP_LASTBIND( op->o_bd ) ) {
+ time_t lastbindtime = (time_t)-1;
+
+ la = attr_find( e->e_attrs, ad_pwdLastSuccess );
+ if ( la == NULL ) {
+ la = attr_find( e->e_attrs, ad_pwdChangedTime );
+ }
+ if ( la != NULL ) {
+ lastbindtime = parse_time( la->a_nvals[0].bv_val );
+ }
+
+ if ( lastbindtime != (time_t)-1 &&
+ op->o_time > lastbindtime + pp->pwdMaxIdle ) {
+ return 1;
+ }
+ }
+
+ 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 ((then = parse_time( vals[0].bv_val )) == (time_t)0)
+ return 1;
+
+ now = slap_get_time();
+
+ /* Still in the future? not yet in effect */
+ if (now < then)
+ return 0;
+
+ if (!pp->pwdLockoutDuration)
+ return 1;
+
+ if (now < then + pp->pwdLockoutDuration)
+ return 1;
+
+ if ( mod != NULL ) {
+ 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_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY;
+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
+add_account_control(
+ Operation *op,
+ SlapReply *rs,
+ int available,
+ int remaining,
+ LDAPAccountUsabilityMoreInfo *more_info )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ LDAPControl c = { 0 }, *cp = NULL, **ctrls;
+ int i = 0;
+
+ BER_BVZERO( &c.ldctl_value );
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( available ) {
+ ber_put_int( ber, remaining, LDAP_TAG_X_ACCOUNT_USABILITY_AVAILABLE );
+ } else {
+ assert( more_info != NULL );
+
+ ber_start_seq( ber, LDAP_TAG_X_ACCOUNT_USABILITY_NOT_AVAILABLE );
+ ber_put_boolean( ber, more_info->inactive, LDAP_TAG_X_ACCOUNT_USABILITY_INACTIVE );
+ ber_put_boolean( ber, more_info->reset, LDAP_TAG_X_ACCOUNT_USABILITY_RESET );
+ ber_put_boolean( ber, more_info->expired, LDAP_TAG_X_ACCOUNT_USABILITY_EXPIRED );
+ ber_put_int( ber, more_info->remaining_grace, LDAP_TAG_X_ACCOUNT_USABILITY_REMAINING_GRACE );
+ ber_put_int( ber, more_info->seconds_before_unlock, LDAP_TAG_X_ACCOUNT_USABILITY_UNTIL_UNLOCK );
+ ber_put_seq( ber );
+ }
+
+ if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) {
+ goto fail;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) /* Count */;
+ }
+
+ ctrls = op->o_tmprealloc( rs->sr_ctrls, sizeof(LDAPControl *)*( i + 2 ), op->o_tmpmemctx );
+ if ( ctrls == NULL ) {
+ goto fail;
+ }
+
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = (char *)ppolicy_account_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 );
+
+ ctrls[ i ] = cp;
+ ctrls[ i + 1 ] = NULL;
+ rs->sr_ctrls = ctrls;
+
+fail:
+ (void)ber_free_buf(ber);
+}
+
+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;
+}
+
+
+static int
+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;
+ BackendDB *bd, *bd_orig = op->o_bd;
+ AttributeDescription *ad = NULL;
+ Attribute *a;
+ BerVarray vals;
+ int rc = LDAP_SUCCESS;
+ Entry *pe = NULL;
+#if 0
+ const char *text;
+#endif
+
+ ppolicy_get_default( pp );
+
+ ad = ad_pwdPolicySubentry;
+ if ( (a = attr_find( e->e_attrs, ad )) == 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" );
+ goto defaultpol;
+ }
+ }
+
+ op->o_bd = bd = select_backend( vals, 0 );
+ if ( op->o_bd == NULL ) {
+ op->o_bd = bd_orig;
+ goto defaultpol;
+ }
+
+ rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe );
+ op->o_bd = bd_orig;
+
+ 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
+
+ ad = ad_pwdMinAge;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxAge;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxIdle;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxIdle, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdInHistory;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdCheckQuality;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMinLength;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxLength;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxLength, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxFailure;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxRecordedFailure;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdGraceExpiry;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdGraceExpiry, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdGraceAuthNLimit;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdExpireWarning;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdFailureCountInterval;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdLockoutDuration;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMinDelay;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMinDelay, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdMaxDelay;
+ if ( (a = attr_find( pe->e_attrs, ad ))
+ && lutil_atoi( &pp->pwdMaxDelay, a->a_vals[0].bv_val ) != 0 ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto defaultpol;
+ }
+
+ ad = ad_pwdCheckModule;
+ if ( attr_find( pe->e_attrs, ad )) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "WARNING: Ignoring OBSOLETE attribute %s in policy %s.\n",
+ ad->ad_cname.bv_val, pe->e_name.bv_val );
+ }
+
+ ad = ad_pwdUseCheckModule;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdUseCheckModule = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdCheckModuleArg;
+ if ( (a = attr_find( pe->e_attrs, ad )) ) {
+ ber_dupbv_x( &pp->pwdCheckModuleArg, &a->a_vals[0], op->o_tmpmemctx );
+ }
+
+ ad = ad_pwdLockout;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdMustChange;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdAllowUserChange;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ ad = ad_pwdSafeModify;
+ if ( (a = attr_find( pe->e_attrs, ad )) )
+ pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
+ pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
+
+ if ( !pp->pwdMaxRecordedFailure && pp->pwdMinDelay )
+ pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
+
+ if ( pp->pwdMinDelay && !pp->pwdMaxDelay ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "pwdMinDelay was set but pwdMaxDelay wasn't, assuming they "
+ "are equal\n" );
+ pp->pwdMaxDelay = pp->pwdMinDelay;
+ }
+
+ op->o_bd = bd;
+ be_entry_release_r( op, pe );
+ op->o_bd = bd_orig;
+
+ return LDAP_SUCCESS;
+
+defaultpol:
+ if ( pe ) {
+ op->o_bd = bd;
+ be_entry_release_r( op, pe );
+ op->o_bd = bd_orig;
+ }
+
+ if ( rc && !BER_BVISNULL( vals ) ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_get: "
+ "policy subentry %s missing or invalid at '%s', "
+ "no policy will be applied!\n",
+ vals->bv_val, ad ? ad->ad_cname.bv_val : "" );
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_get: using default policy\n" );
+ }
+
+ ppolicy_get_default( pp );
+
+ return -1;
+}
+
+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, pp_info *pi, PassPolicy *pp, LDAPPasswordPolicyError *err,
+ Entry *e, struct berval *errmsg )
+{
+ int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
+ char *ptr;
+ struct berval sch;
+
+ assert( cred != NULL );
+ assert( pp != NULL );
+ assert( errmsg != NULL );
+
+ ptr = errmsg->bv_val;
+ *ptr = '\0';
+
+ ptr = cred->bv_val;
+
+ if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( err ) *err = PP_passwordTooShort;
+ return rc;
+ }
+
+ if ( pp->pwdMaxLength && cred->bv_len > pp->pwdMaxLength ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( err ) *err = PP_passwordTooLong;
+ 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->pwdUseCheckModule) {
+#ifdef SLAPD_MODULES
+ check_func *prog;
+
+ if ( !pi->pwdCheckFunc ) {
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: no CheckModule loaded\n" );
+ ok = LDAP_OTHER;
+ } else {
+ struct berval *arg = NULL;
+ if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) {
+ arg = &pp->pwdCheckModuleArg;
+ }
+
+ ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
+ ok = pi->pwdCheckFunc( ptr, errmsg, e, arg );
+ 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",
+ pi->pwdCheckModule, errmsg->bv_val ? errmsg->bv_val : "", ok );
+ }
+ }
+#else
+ Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
+ "supported. pwdCheckModule ignored.\n" );
+#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 {
+ pp_info *pi;
+ BackendDB *be;
+ 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;
+ pp_info *pi = ppb->pi;
+ Modifications *mod = ppb->mod, *m;
+ int pwExpired = 0;
+ int ngut = -1, warn = -1, fc = 0, 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;
+ BackendDB *be = op->o_bd;
+ 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 = ppb->be;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ op->o_bd = be;
+
+ if ( rc != LDAP_SUCCESS ) {
+ ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex );
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* ITS#7089 Skip lockout checks/modifications if password attribute missing */
+ if ( attr_find( e->e_attrs, ppb->pp.ad ) == NULL ) {
+ goto done;
+ }
+
+ 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, &timestamp );
+
+ /* 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_nsec / 1000 );
+ timestamp_usec.bv_len += STRLENOF(".123456");
+
+ if ( rs->sr_err == LDAP_INVALID_CREDENTIALS && ppb->pp.pwdMaxRecordedFailure ) {
+ int i = 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], &timestamp_usec );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp_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], &timestamp );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp );
+ m->sml_next = mod;
+ mod = m;
+ } else if ( ppb->pp.pwdMinDelay ) {
+ int waittime = ppb->pp.pwdMinDelay << fc;
+ time_t wait_end;
+ char lockout_stamp_buf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval lockout_stamp = BER_BVC(lockout_stamp_buf);
+
+ if ( waittime > ppb->pp.pwdMaxDelay ) {
+ waittime = ppb->pp.pwdMaxDelay;
+ }
+ wait_end = now + waittime;
+
+ slap_timestamp( &wait_end, &lockout_stamp );
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdAccountTmpLockoutEnd->ad_cname;
+ m->sml_desc = ad_pwdAccountTmpLockoutEnd;
+ 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], &lockout_stamp );
+ ber_dupbv( &m->sml_nvalues[0], &lockout_stamp );
+ 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 ( ppb->pp.pwdGraceExpiry &&
+ now - pwtime > ppb->pp.pwdMaxAge + ppb->pp.pwdGraceExpiry ) {
+ /* Grace logins have expired now */
+ ngut = 0;
+ } else 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 );
+
+ ngut--;
+
+ if (ngut < 0) {
+ 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], &timestamp_usec );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp_usec );
+ 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 pwdtime is -1 there is no password Change Time attribute on the
+ * entry so we skip the expiry check.
+ *
+ */
+ if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1) ||
+ (pwtime == -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_TRACE,
+ "ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n",
+ op->o_req_dn.bv_val, warn );
+ }
+ }
+
+done:
+ op->o_bd = ppb->be;
+ be_entry_release_r( op, e );
+ op->o_bd = be;
+
+locked:
+ if ( mod && !pi->disable_write ) {
+ 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 = ppb->be;
+ }
+ rc = op2.o_bd->be_modify( &op2, &r2 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s ppolicy_bind_response: "
+ "ppolicy state change failed with rc=%d text=%s\n",
+ op->o_log_prefix, rc, r2.sr_text );
+ }
+ }
+ if ( mod ) {
+ 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;
+ }
+ 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->pi = on->on_bi.bi_private;
+ ppb->be = op->o_bd->bd_self;
+ 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;
+
+ if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) {
+ 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" );
+ 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_account_usability_entry_cb( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = op->o_callback->sc_private;
+ BackendInfo *bi = op->o_bd->bd_info;
+ LDAPControl *ctrl = NULL;
+ PassPolicy pp;
+ Attribute *a;
+ Entry *e = NULL;
+ time_t pwtime = 0, seconds_until_expiry = -1, now = op->o_time;
+ int isExpired = 0, grace = -1;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ) != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( ppolicy_get( op, e, &pp ) != LDAP_SUCCESS ) {
+ /* TODO: If there is no policy, should we check if */
+ goto done;
+ }
+
+ if ( !access_allowed( op, e, pp.ad, NULL, ACL_COMPARE, NULL ) ) {
+ goto done;
+ }
+
+ if ( attr_find( e->e_attrs, pp.ad ) == NULL ) {
+ goto done;
+ }
+
+ if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) {
+ pwtime = parse_time( a->a_nvals[0].bv_val );
+ }
+
+ if ( pp.pwdMaxAge && pwtime ) {
+ seconds_until_expiry = pwtime + pp.pwdMaxAge - now;
+ if ( seconds_until_expiry <= 0 ) isExpired = 1;
+ if ( pp.pwdGraceAuthNLimit ) {
+ if ( !pp.pwdGraceExpiry || seconds_until_expiry + pp.pwdGraceExpiry > 0 ) {
+ grace = pp.pwdGraceAuthNLimit;
+ if ( attr_find( e->e_attrs, ad_pwdGraceUseTime ) ) {
+ grace -= a->a_numvals;
+ }
+ }
+ }
+ }
+ if ( !isExpired && pp.pwdMaxIdle && (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) ) {
+ time_t lastbindtime = pwtime;
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) != NULL ) {
+ lastbindtime = parse_time( a->a_nvals[0].bv_val );
+ }
+
+ if ( lastbindtime ) {
+ int remaining_idle = lastbindtime + pp.pwdMaxIdle - now;
+ if ( remaining_idle <= 0 ) {
+ isExpired = 1;
+ } else if ( seconds_until_expiry == -1 || remaining_idle < seconds_until_expiry ) {
+ seconds_until_expiry = remaining_idle;
+ }
+ }
+ }
+
+ if ( isExpired || account_locked( op, e, &pp, NULL ) ) {
+ LDAPAccountUsabilityMoreInfo more_info = { 0, 0, 0, -1, -1 };
+ time_t then, lockoutEnd = 0;
+
+ if ( isExpired ) more_info.remaining_grace = grace;
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
+ then = parse_time( a->a_vals[0].bv_val );
+ if ( then == 0 )
+ lockoutEnd = -1;
+
+ /* Still in the future? not yet in effect */
+ if ( now < then )
+ then = 0;
+
+ if ( !pp.pwdLockoutDuration )
+ lockoutEnd = -1;
+
+ if ( now < then + pp.pwdLockoutDuration )
+ lockoutEnd = then + pp.pwdLockoutDuration;
+ }
+
+ if ( (a = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) {
+ then = parse_time( a->a_vals[0].bv_val );
+ if ( lockoutEnd != -1 && then > lockoutEnd )
+ lockoutEnd = then;
+ }
+
+ if ( lockoutEnd > now ) {
+ more_info.inactive = 1;
+ more_info.seconds_before_unlock = lockoutEnd - now;
+ }
+
+ if ( pp.pwdMustChange &&
+ (a = attr_find( e->e_attrs, ad_pwdReset )) &&
+ bvmatch( &a->a_nvals[0], &slap_true_bv ) )
+ {
+ more_info.reset = 1;
+ }
+
+ add_account_control( op, rs, 0, -1, &more_info );
+ } else {
+ add_account_control( op, rs, 1, seconds_until_expiry, NULL );
+ }
+
+done:
+ op->o_bd->bd_info = bi;
+ if ( e ) {
+ be_entry_release_r( op, e );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ int rc = ppolicy_restrict( op, rs );
+
+ if ( rc != SLAP_CB_CONTINUE ) {
+ return rc;
+ }
+
+ if ( op->o_ctrlflag[account_usability_cid] ) {
+ slap_callback *cb;
+
+ cb = op->o_tmpcalloc( sizeof(slap_callback), 1, op->o_tmpmemctx );
+
+ cb->sc_response = ppolicy_account_usability_entry_cb;
+ cb->sc_private = on;
+ overlay_callback_after_backover( op, cb, 1 );
+ }
+
+ 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->pi = on->on_bi.bi_private;
+ ppb->be = op->o_bd->bd_self;
+ 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;
+
+ if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) {
+ 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;
+ int is_pwdadmin = 0;
+
+ if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+ return rs->sr_err;
+
+ /* If this is a replica, assume the provider checked everything */
+ if ( be_shadow_update( op ) )
+ return SLAP_CB_CONTINUE;
+
+ ppolicy_get( op, op->ora_e, &pp );
+
+ if ( access_allowed( op, op->ora_e, pp.ad, NULL, ACL_MANAGE, NULL ) ) {
+ is_pwdadmin = 1;
+ }
+
+ /* Check for password in entry */
+ if ( (pa = attr_find( op->oq_add.rs_e->e_attrs, pp.ad )) ) {
+ 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 password admin
+ * then we need to check that the password fits in with the
+ * security policy for the new entry.
+ */
+
+ if ( pp.pwdCheckQuality > 0 && !is_pwdadmin ) {
+ struct berval *bv = &(pa->a_vals[0]);
+ int rc, send_ctrl = 0;
+ LDAPPasswordPolicyError pErr = PP_noError;
+ char errbuf[ERRBUFSIZ];
+ struct berval errmsg = BER_BVC( errbuf );
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+ rc = check_password_quality( bv, pi, &pp, &pErr, op->ora_e, &errmsg );
+ if (rc != LDAP_SUCCESS) {
+ char *txt = errmsg.bv_val;
+ 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[0] ? txt : "Password fails quality checking policy" );
+ if ( txt != errbuf ) {
+ 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 ) &&
+ !attr_find( op->ora_e->e_attrs, ad_pwdChangedTime ) ) {
+ 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, &timestamp );
+
+ attr_merge_one( op->ora_e, ad_pwdChangedTime, &timestamp, &timestamp );
+ }
+ }
+ 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_text_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+
+ if ( rs->sr_text == sc->sc_private ) {
+ rs->sr_text = NULL;
+ }
+ free( sc->sc_private );
+
+ op->o_callback = sc->sc_next;
+ 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 = 0, pwmop = -1, deladd,
+ hsize = 0, hskip;
+ PassPolicy pp;
+ Modifications *mods = NULL, *modtail = NULL,
+ *ml, *delmod, *addmod;
+ Attribute *pa, *ha, at;
+ const char *txt;
+ char errbuf[ERRBUFSIZ];
+ 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, is_pwdadmin = 0;
+ int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0,
+ got_del_success = 0;
+ int got_changed = 0, got_history = 0;
+ int have_policy = 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 ( pi->disable_write ) 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 ( be_shadow_update( op ) ) {
+ Modifications **prev;
+ Attribute *a_grace, *a_lock, *a_fail, *a_success;
+
+ 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 );
+ a_success = attr_find( e->e_attrs, ad_pwdLastSuccess );
+
+ 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 ( ml->sml_desc == ad_pwdLastSuccess ) {
+ if ( !a_success || got_del_success ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_success = 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,
+ * success, 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;
+ }
+ if ( a_success && !got_del_success ) {
+ 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_pwdLastSuccess;
+ 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_hash_cleartext depends on pwmod being determined first */
+ if ( ppolicy_get( op, e, &pp ) == LDAP_SUCCESS ) {
+ have_policy = 1;
+ }
+
+ if ( access_allowed( op, e, pp.ad, NULL, ACL_MANAGE, NULL ) ) {
+ is_pwdadmin = 1;
+ }
+
+ 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;
+ } else if ( ml->sml_desc == ad_pwdLastSuccess ) {
+ got_del_success = 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" );
+ 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 (!have_policy || !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 (is_pwdadmin) 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" );
+ 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 );
+
+ 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) {
+ struct berval errmsg = BER_BVC( errbuf );
+
+ rc = check_password_quality( bv, pi, &pp, &pErr, e, &errmsg );
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = rc;
+ txt = errmsg.bv_val;
+ if ( txt && txt[0] ) {
+ rs->sr_text = txt;
+ if ( txt != errbuf )
+ 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, &timestamp );
+
+ 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], &timestamp );
+ ber_dupbv( &mods->sml_nvalues[0], &timestamp );
+ } 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;
+ }
+
+ if ( zapReset ) {
+ /*
+ * ITS#7084 Is this a modification by the password
+ * administrator? Then force a reset if configured.
+ * Otherwise clear it.
+ */
+ if ( pp.pwdMustChange && is_pwdadmin ) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_REPLACE;
+ mods->sml_desc = ad_pwdReset;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ 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], (struct berval *)&slap_true_bv );
+ ber_dupbv( &mods->sml_nvalues[0], (struct berval *)&slap_true_bv );
+
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ } else if ( 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;
+ }
+ }
+
+ /* TODO: do we remove pwdLastSuccess or set it to 'now'? */
+ if (!got_del_success && attr_find(e->e_attrs, ad_pwdLastSuccess )){
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_desc = ad_pwdLastSuccess;
+ 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" );
+ }
+ }
+
+ /*
+ * 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;
+ }
+ } else {
+ /* ITS#8762 Make sure we drop pwdFailureTime if unlocking */
+ if (got_del_lock && !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;
+ }
+ }
+ 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 ) {
+ if ( is_pwdexop ) {
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+
+ /* Setup a callback so we can free the text when sent */
+ cb->sc_cleanup = ppolicy_text_cleanup;
+ cb->sc_private = (void *)txt;
+ overlay_callback_after_backover( op, cb, 1 );
+ } else {
+ if ( rs->sr_text == txt ) {
+ rs->sr_text = NULL;
+ }
+ free( (char *)txt );
+ }
+ }
+ 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
+ppolicy_au_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "account usability control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ op->o_ctrlflag[account_usability_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 );
+ }
+ return 1;
+ }
+
+ 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
+)
+{
+ int rc;
+
+ if ( (rc = overlay_register_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY )) != LDAP_SUCCESS ) {
+ return rc;
+ }
+ 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 );
+ overlay_unregister_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY );
+#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" );
+ 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;
+ }
+ }
+ ad_pwdLastSuccess = slap_schema.si_ad_pwdLastSuccess;
+ {
+ 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;
+ }
+
+ for (i=0; pwd_ocs[i]; i++) {
+ code = register_oc( pwd_ocs[i], NULL, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY, "ppolicy_initialize: "
+ "register_oc failed\n" );
+ return code;
+ }
+ }
+
+ 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 );
+ return code;
+ }
+
+ code = register_supported_control( LDAP_CONTROL_X_ACCOUNT_USABILITY,
+ SLAP_CTRL_SEARCH, NULL,
+ ppolicy_au_parseCtrl, &account_usability_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code );
+ 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 );
+ 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 );
+ 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_search;
+ 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..201803b
--- /dev/null
+++ b/servers/slapd/overlays/refint.c
@@ -0,0 +1,1086 @@
+/* refint.c - referential integrity module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 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 "slap-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_QUOTE|ARG_MAGIC|REFINT_NOTHING, refint_cf_gen,
+ "( OLcfgOvAt:11.2 NAME 'olcRefintNothing' "
+ "DESC 'Replacement DN to supply when needed' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "refint_modifiersName", "DN", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|REFINT_MODIFIERSNAME, refint_cf_gen,
+ "( OLcfgOvAt:11.3 NAME 'olcRefintModifiersName' "
+ "DESC 'The DN to use as modifiersName' "
+ "EQUALITY distinguishedNameMatch "
+ "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:
+ /* fallthru to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ rc = 0;
+ if ( c->op != SLAP_CONFIG_ADD && c->argc > 2 ) {
+ /* We wouldn't know how to delete these values later */
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "Supplying multiple names in a single %s value is "
+ "unsupported and will be disallowed in a future version\n",
+ c->argv[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;
+
+ for ( pipp = &dd->attrs; *pipp; pipp = &(*pipp)->next )
+ /* Get to the end */ ;
+ ip->next = *pipp;
+ *pipp = 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 );
+ 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" );
+ return -1;
+ }
+ id->db = db;
+ } else {
+ Debug( LDAP_DEBUG_CONFIG,
+ "refint_response: no backend for our baseDN %s??\n",
+ id->dn.bv_val );
+ 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" );
+
+ 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 );
+ 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" );
+ 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 );
+ 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 );
+ }
+
+ 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;
+ 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 ) {
+ ber_dupbv( &rq->newdn, &op->orr_newDN );
+ ber_dupbv( &rq->newndn, &op->orr_nnewDN );
+ }
+
+ 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" );
+ 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/remoteauth.c b/servers/slapd/overlays/remoteauth.c
new file mode 100644
index 0000000..1d60af0
--- /dev/null
+++ b/servers/slapd/overlays/remoteauth.c
@@ -0,0 +1,1002 @@
+/* $OpenLDAP$ */
+/* remoteauth.c - Overlay to delegate bind processing to a remote server */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2017-2021 Ondřej Kuzník, Symas Corporation.
+ * Portions Copyright 2004-2017 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>.
+ */
+
+#include "portable.h"
+
+#include <ldap.h>
+#if 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 "lutil.h"
+#include "slap.h"
+#include "slap-config.h"
+
+#ifndef UP_STR
+#define UP_STR "userPassword"
+#endif /* UP_STR */
+
+#ifndef LDAP_PREFIX
+#define LDAP_PREFIX "ldap://"
+#endif /* LDAP_PREFIX */
+
+#ifndef FILE_PREFIX
+#define FILE_PREFIX "file://"
+#endif /* LDAP_PREFIX */
+
+typedef struct _ad_info {
+ struct _ad_info *next;
+ char *domain;
+ char *realm;
+} ad_info;
+
+typedef struct _ad_pin {
+ struct _ad_pin *next;
+ char *hostname;
+ char *pin;
+} ad_pin;
+
+typedef struct _ad_private {
+ char *dn;
+ AttributeDescription *dn_ad;
+ char *domain_attr;
+ AttributeDescription *domain_ad;
+
+ AttributeDescription *up_ad;
+ ad_info *mappings;
+
+ char *default_realm;
+ char *default_domain;
+
+ int up_set;
+ int retry_count;
+ int store_on_success;
+
+ ad_pin *pins;
+ slap_bindconf ad_tls;
+} ad_private;
+
+enum {
+ REMOTE_AUTH_MAPPING = 1,
+ REMOTE_AUTH_DN_ATTRIBUTE,
+ REMOTE_AUTH_DOMAIN_ATTRIBUTE,
+ REMOTE_AUTH_DEFAULT_DOMAIN,
+ REMOTE_AUTH_DEFAULT_REALM,
+ REMOTE_AUTH_CACERT_DIR,
+ REMOTE_AUTH_CACERT_FILE,
+ REMOTE_AUTH_VALIDATE_CERTS,
+ REMOTE_AUTH_RETRY_COUNT,
+ REMOTE_AUTH_TLS,
+ REMOTE_AUTH_TLS_PIN,
+ REMOTE_AUTH_STORE_ON_SUCCESS,
+};
+
+static ConfigDriver remoteauth_cf_gen;
+
+static ConfigTable remoteauthcfg[] = {
+ { "remoteauth_mapping", "mapping between domain and realm", 2, 3, 0,
+ ARG_MAGIC|REMOTE_AUTH_MAPPING,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.1 NAME 'olcRemoteAuthMapping' "
+ "DESC 'Mapping from domain name to server' "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+ { "remoteauth_dn_attribute", "Attribute to use as AD bind DN", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DN_ATTRIBUTE,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.2 NAME 'olcRemoteAuthDNAttribute' "
+ "DESC 'Attribute in entry to use as bind DN for AD' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_domain_attribute", "Attribute to use as domain determinant", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DOMAIN_ATTRIBUTE,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.3 NAME 'olcRemoteAuthDomainAttribute' "
+ "DESC 'Attribute in entry to determine windows domain' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_default_domain", "Default Windows domain", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DEFAULT_DOMAIN,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.4 NAME 'olcRemoteAuthDefaultDomain' "
+ "DESC 'Default Windows domain to use' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_default_realm", "Default AD realm", 2, 2, 0,
+ ARG_MAGIC|REMOTE_AUTH_DEFAULT_REALM,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.5 NAME 'olcRemoteAuthDefaultRealm' "
+ "DESC 'Default AD realm to use' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_store", "on|off", 1, 2, 0,
+ ARG_OFFSET|ARG_ON_OFF|REMOTE_AUTH_STORE_ON_SUCCESS,
+ (void *)offsetof(ad_private, store_on_success),
+ "( OLcfgOvAt:24.6 NAME 'olcRemoteAuthStore' "
+ "DESC 'Store password locally on success' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_retry_count", "integer", 2, 2, 0,
+ ARG_OFFSET|ARG_UINT|REMOTE_AUTH_RETRY_COUNT,
+ (void *)offsetof(ad_private, retry_count),
+ "( OLcfgOvAt:24.7 NAME 'olcRemoteAuthRetryCount' "
+ "DESC 'Number of retries attempted' "
+ "SYNTAX OMsInteger SINGLE-VALUE )",
+ NULL, { .v_uint = 3 }
+ },
+ { "remoteauth_tls", "tls settings", 2, 0, 0,
+ ARG_MAGIC|REMOTE_AUTH_TLS,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.8 NAME 'olcRemoteAuthTLS' "
+ "DESC 'StartTLS settings' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "remoteauth_tls_peerkey_hash", "mapping between hostnames and their public key hash", 3, 3, 0,
+ ARG_MAGIC|REMOTE_AUTH_TLS_PIN,
+ remoteauth_cf_gen,
+ "( OLcfgOvAt:24.9 NAME 'olcRemoteAuthTLSPeerkeyHash' "
+ "DESC 'StartTLS hostname to public key pin mapping file' "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL }
+};
+
+static ConfigOCs remoteauthocs[] = {
+ { "( OLcfgOvOc:24.1 "
+ "NAME 'olcRemoteAuthCfg' "
+ "DESC 'Remote Directory passthough authentication configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcRemoteAuthTLS "
+ "MAY ( olcRemoteAuthMapping $ olcRemoteAuthDNAttribute $ "
+ " olcRemoteAuthDomainAttribute $ olcRemoteAuthDefaultDomain $ "
+ " olcRemoteAuthDefaultRealm $ olcRemoteAuthStore $ "
+ " olcRemoteAuthRetryCount $ olcRemoteAuthTLSPeerkeyHash ) )",
+ Cft_Overlay, remoteauthcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+remoteauth_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ ad_private *ad = (ad_private *)on->on_bi.bi_private;
+ struct berval bv;
+ int i, rc = 0;
+ ad_info *map;
+ const char *text = NULL;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ for ( map = ad->mappings; map; map = map->next ) {
+ char *str;
+
+ str = ch_malloc( strlen( map->domain ) +
+ strlen( map->realm ) + 2 );
+ sprintf( str, "%s %s", map->domain, map->realm );
+ ber_str2bv( str, 0, 0, &bv );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( !rc )
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ ch_free( str );
+ if ( rc ) break;
+ }
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( ad->dn )
+ value_add_one( &c->rvalue_vals, &ad->dn_ad->ad_cname );
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( ad->domain_attr )
+ value_add_one(
+ &c->rvalue_vals, &ad->domain_ad->ad_cname );
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ber_str2bv( ad->default_domain, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ber_str2bv( ad->default_realm, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ case REMOTE_AUTH_TLS:
+ bindconf_tls_unparse( &ad->ad_tls, &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 );
+ }
+
+ value_add_one( &c->rvalue_vals, &bv );
+ ch_free( bv.bv_val );
+ break;
+ case REMOTE_AUTH_TLS_PIN: {
+ ad_pin *pin = ad->pins;
+ for ( pin = ad->pins; pin; pin = pin->next ) {
+ bv.bv_val = ch_malloc( strlen( pin->hostname ) +
+ strlen( pin->pin ) + 2 );
+ bv.bv_len = sprintf(
+ bv.bv_val, "%s %s", pin->hostname, pin->pin );
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ if ( rc ) return rc;
+ }
+ } break;
+
+ default:
+ abort();
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ if ( c->valx < 0 ) {
+ /* delete all mappings */
+ while ( ad->mappings ) {
+ map = ad->mappings;
+ ad->mappings = ad->mappings->next;
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ }
+ } else {
+ /* delete a specific mapping indicated by 'valx'*/
+ ad_info *pmap = NULL;
+
+ for ( map = ad->mappings, i = 0;
+ ( map ) && ( i < c->valx );
+ pmap = map, map = map->next, i++ )
+ ;
+
+ if ( pmap ) {
+ pmap->next = map->next;
+ map->next = NULL;
+
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ } else if ( ad->mappings ) {
+ /* delete the first item in the list */
+ map = ad->mappings;
+ ad->mappings = map->next;
+ ch_free( map->domain );
+ ch_free( map->realm );
+ ch_free( map );
+ }
+ }
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( ad->dn ) {
+ ch_free( ad->dn );
+ ad->dn = NULL; /* Don't free AttributeDescription */
+ }
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( ad->domain_attr ) {
+ ch_free( ad->domain_attr );
+ /* Don't free AttributeDescription */
+ ad->domain_attr = NULL;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ch_free( ad->default_domain );
+ ad->default_domain = NULL;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ch_free( ad->default_realm );
+ ad->default_realm = NULL;
+ }
+ break;
+ case REMOTE_AUTH_TLS:
+ /* MUST + SINGLE-VALUE -> this is a replace */
+ bindconf_free( &ad->ad_tls );
+ break;
+ case REMOTE_AUTH_TLS_PIN:
+ while ( ad->pins ) {
+ ad_pin *pin = ad->pins;
+ ad->pins = ad->pins->next;
+ ch_free( pin->hostname );
+ ch_free( pin->pin );
+ ch_free( pin );
+ }
+ break;
+ /* ARG_OFFSET */
+ case REMOTE_AUTH_STORE_ON_SUCCESS:
+ case REMOTE_AUTH_RETRY_COUNT:
+ abort();
+ break;
+ default:
+ abort();
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch ( c->type ) {
+ case REMOTE_AUTH_MAPPING:
+ /* add mapping to head of list */
+ map = ch_malloc( sizeof(ad_info) );
+ map->domain = ber_strdup( c->argv[1] );
+ map->realm = ber_strdup( c->argv[2] );
+ map->next = ad->mappings;
+ ad->mappings = map;
+
+ break;
+ case REMOTE_AUTH_DN_ATTRIBUTE:
+ if ( slap_str2ad( c->argv[1], &ad->dn_ad, &text ) ==
+ LDAP_SUCCESS ) {
+ ad->dn = ber_strdup( ad->dn_ad->ad_cname.bv_val );
+ } else {
+ strncpy( c->cr_msg, text, sizeof(c->cr_msg) );
+ c->cr_msg[sizeof(c->cr_msg) - 1] = '\0';
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REMOTE_AUTH_DOMAIN_ATTRIBUTE:
+ if ( slap_str2ad( c->argv[1], &ad->domain_ad, &text ) ==
+ LDAP_SUCCESS ) {
+ ad->domain_attr =
+ ber_strdup( ad->domain_ad->ad_cname.bv_val );
+ } else {
+ strncpy( c->cr_msg, text, sizeof(c->cr_msg) );
+ c->cr_msg[sizeof(c->cr_msg) - 1] = '\0';
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REMOTE_AUTH_DEFAULT_DOMAIN:
+ if ( ad->default_domain ) {
+ ch_free( ad->default_domain );
+ ad->default_domain = NULL;
+ }
+ ad->default_domain = ber_strdup( c->argv[1] );
+ break;
+ case REMOTE_AUTH_DEFAULT_REALM:
+ if ( ad->default_realm ) {
+ ch_free( ad->default_realm );
+ ad->default_realm = NULL;
+ }
+ ad->default_realm = ber_strdup( c->argv[1] );
+ break;
+ case REMOTE_AUTH_TLS:
+ for ( i=1; i < c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &ad->ad_tls ) ) {
+ rc = 1;
+ break;
+ }
+ }
+ bindconf_tls_defaults( &ad->ad_tls );
+ break;
+ case REMOTE_AUTH_TLS_PIN: {
+ ad_pin *pin = ch_calloc( 1, sizeof(ad_pin) );
+
+ pin->hostname = ber_strdup( c->argv[1] );
+ pin->pin = ber_strdup( c->argv[2] );
+ pin->next = ad->pins;
+ ad->pins = pin;
+ } break;
+ /* ARG_OFFSET */
+ case REMOTE_AUTH_STORE_ON_SUCCESS:
+ case REMOTE_AUTH_RETRY_COUNT:
+ abort();
+ break;
+ default:
+ abort();
+ }
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static char *
+get_realm(
+ const char *domain,
+ ad_info *mappings,
+ const char *default_realm,
+ int *isfile )
+{
+ ad_info *ai;
+ char *dom = NULL, *ch, *ret = NULL;
+
+ if ( isfile ) *isfile = 0;
+
+ if ( !domain ) {
+ ret = default_realm ? ch_strdup( default_realm ) : NULL;
+ goto exit;
+ }
+
+ /* munge any DOMAIN\user or DOMAIN:user values into just DOMAIN */
+
+ ch = strchr( domain, '\\' );
+ if ( !ch ) ch = strchr( domain, ':' );
+
+ if ( ch ) {
+ dom = ch_malloc( ch - domain + 1 );
+ strncpy( dom, domain, ch - domain );
+ dom[ch - domain] = '\0';
+ } else {
+ dom = ch_strdup( domain );
+ }
+
+ for ( ai = mappings; ai; ai = ai->next )
+ if ( strcasecmp( ai->domain, dom ) == 0 ) {
+ ret = ch_strdup( ai->realm );
+ break;
+ }
+
+ if ( !ai )
+ ret = default_realm ? ch_strdup( default_realm ) :
+ NULL; /* no mapping found */
+exit:
+ if ( dom ) ch_free( dom );
+ if ( ret &&
+ ( strncasecmp( ret, FILE_PREFIX, strlen( FILE_PREFIX ) ) == 0 ) ) {
+ char *p;
+
+ p = ret;
+ ret = ch_strdup( p + strlen( FILE_PREFIX ) );
+ ch_free( p );
+ if ( isfile ) *isfile = 1;
+ }
+
+ return ret;
+}
+
+static char *
+get_ldap_url( const char *realm, int isfile )
+{
+ char *ldap_url = NULL;
+ FILE *fp;
+
+ if ( !realm ) return NULL;
+
+ if ( !isfile ) {
+ if ( strstr( realm, "://" ) ) {
+ return ch_strdup( realm );
+ }
+
+ ldap_url = ch_malloc( 1 + strlen( LDAP_PREFIX ) + strlen( realm ) );
+ sprintf( ldap_url, "%s%s", LDAP_PREFIX, realm );
+ return ldap_url;
+ }
+
+ fp = fopen( realm, "r" );
+ if ( !fp ) {
+ char ebuf[128];
+ int saved_errno = errno;
+ Debug( LDAP_DEBUG_TRACE, "remoteauth: "
+ "Unable to open realm file (%s)\n",
+ sock_errstr( saved_errno, ebuf, sizeof(ebuf) ) );
+ return NULL;
+ }
+ /*
+ * Read each line in the file and return a URL of the form
+ * "ldap://<line1> ldap://<line2> ... ldap://<lineN>"
+ * which can be passed to ldap_initialize.
+ */
+ while ( !feof( fp ) ) {
+ char line[512], *p;
+
+ p = fgets( line, sizeof(line), fp );
+ if ( !p ) continue;
+
+ /* terminate line at first whitespace */
+ for ( p = line; *p; p++ )
+ if ( isspace( *p ) ) {
+ *p = '\0';
+ break;
+ }
+
+ if ( ldap_url ) {
+ char *nu;
+
+ nu = ch_malloc( strlen( ldap_url ) + 2 + strlen( LDAP_PREFIX ) +
+ strlen( line ) );
+
+ if ( strstr( line, "://" ) ) {
+ sprintf( nu, "%s %s", ldap_url, line );
+ } else {
+ sprintf( nu, "%s %s%s", ldap_url, LDAP_PREFIX, line );
+ }
+ ch_free( ldap_url );
+ ldap_url = nu;
+ } else {
+ ldap_url = ch_malloc( 1 + strlen( line ) + strlen( LDAP_PREFIX ) );
+ if ( strstr( line, "://" ) ) {
+ strcpy( ldap_url, line );
+ } else {
+ sprintf( ldap_url, "%s%s", LDAP_PREFIX, line );
+ }
+ }
+ }
+
+ fclose( fp );
+
+ return ldap_url;
+}
+
+static void
+trace_remoteauth_parameters( ad_private *ap )
+{
+ ad_info *pad_info;
+ struct berval bv;
+
+ if ( !ap ) return;
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_dn_attribute: %s\n",
+ ap->dn ? ap->dn : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_domain_attribute: %s\n",
+ ap->domain_attr ? ap->domain_attr : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_default_realm: %s\n",
+ ap->default_realm ? ap->default_realm : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_default_domain: %s\n",
+ ap->default_domain ? ap->default_domain : "NULL" );
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_retry_count: %d\n", ap->retry_count );
+
+ bindconf_tls_unparse( &ap->ad_tls, &bv );
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_tls:%s\n", bv.bv_val );
+ ch_free( bv.bv_val );
+
+ pad_info = ap->mappings;
+ while ( pad_info ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_mappings(%s,%s)\n",
+ pad_info->domain ? pad_info->domain : "NULL",
+ pad_info->realm ? pad_info->realm : "NULL" );
+ pad_info = pad_info->next;
+ }
+
+ return;
+}
+
+static int
+remoteauth_conn_cb(
+ LDAP *ld,
+ Sockbuf *sb,
+ LDAPURLDesc *srv,
+ struct sockaddr *addr,
+ struct ldap_conncb *ctx )
+{
+ ad_private *ap = ctx->lc_arg;
+ ad_pin *pin = NULL;
+ char *host;
+
+ host = srv->lud_host;
+ if ( !host || !*host ) {
+ host = "localhost";
+ }
+
+ for ( pin = ap->pins; pin; pin = pin->next ) {
+ if ( !strcasecmp( host, pin->hostname ) ) break;
+ }
+
+ if ( pin ) {
+ int rc = ldap_set_option( ld, LDAP_OPT_X_TLS_PEERKEY_HASH, pin->pin );
+ if ( rc == LDAP_SUCCESS ) {
+ return 0;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: "
+ "TLS Peerkey hash could not be set to '%s': %d\n",
+ pin->pin, rc );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: "
+ "No TLS Peerkey hash found for host '%s'\n",
+ host );
+ }
+
+ return -1;
+}
+
+static void
+remoteauth_conn_delcb( LDAP *ld, Sockbuf *sb, struct ldap_conncb *ctx )
+{
+ return;
+}
+
+static int
+remoteauth_bind( Operation *op, SlapReply *rs )
+{
+ Entry *e;
+ int rc;
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ ad_private *ap = (ad_private *)on->on_bi.bi_private;
+ Attribute *a_dom, *a_dn;
+ struct ldap_conncb ad_conncb = { .lc_add = remoteauth_conn_cb,
+ .lc_del = remoteauth_conn_delcb,
+ .lc_arg = ap };
+ struct berval dn = { 0 };
+ char *dom_val, *realm = NULL;
+ char *ldap_url = NULL;
+ LDAP *ld = NULL;
+ int protocol = LDAP_VERSION3, isfile = 0;
+ int tries = 0;
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ trace_remoteauth_parameters( ap );
+ }
+
+ if ( op->orb_method != LDAP_AUTH_SIMPLE )
+ return SLAP_CB_CONTINUE; /* only do password auth */
+
+ /* Can't handle root via this mechanism */
+ if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) return SLAP_CB_CONTINUE;
+
+ if ( !ap->up_set ) {
+ const char *txt = NULL;
+
+ if ( slap_str2ad( UP_STR, &ap->up_ad, &txt ) )
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "userPassword attr undefined: %s\n",
+ txt ? txt : "" );
+ ap->up_set = 1;
+ }
+
+ if ( !ap->up_ad ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "password attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* userPassword not defined */
+ }
+
+ if ( !ap->dn ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "remote DN attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* no mapped DN attribute */
+ }
+
+ if ( !ap->domain_attr ) {
+ Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: "
+ "domain attribute not configured\n" );
+ return SLAP_CB_CONTINUE; /* no way to know domain */
+ }
+
+ 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;
+
+ rc = SLAP_CB_CONTINUE;
+ /* if userPassword is defined in entry, skip to the end */
+ if ( attr_find( e->e_attrs, ap->up_ad ) ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "user has a password, skipping\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+ a_dom = attr_find( e->e_attrs, ap->domain_ad );
+ if ( !a_dom )
+ dom_val = ap->default_domain;
+ else {
+ dom_val = a_dom->a_vals[0].bv_val;
+ }
+
+ if ( !dom_val ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "user has no domain nor do we have a default, skipping\n",
+ op->o_log_prefix );
+ goto exit; /* user has no domain */
+ }
+
+ realm = get_realm( dom_val, ap->mappings, ap->default_realm, &isfile );
+ if ( !realm ) goto exit;
+
+ a_dn = attr_find( e->e_attrs, ap->dn_ad );
+ if ( !a_dn ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "no remote DN found on user\n",
+ op->o_log_prefix );
+ goto exit; /* user has no DN for the other directory */
+ }
+
+ ber_dupbv_x( &dn, a_dn->a_vals, op->o_tmpmemctx );
+ be_entry_release_r( op, e );
+ e = NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "(realm, dn) = (%s, %s)\n",
+ op->o_log_prefix, realm, dn.bv_val );
+
+ ldap_url = get_ldap_url( realm, isfile );
+ if ( !ldap_url ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "No LDAP URL obtained\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+retry:
+ rc = ldap_initialize( &ld, ldap_url );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "Cannot initialize %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit; /* user has no DN for the other directory */
+ }
+
+ ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol );
+
+#ifdef HAVE_TLS
+ rc = bindconf_tls_set( &ap->ad_tls, ld );
+ if ( rc ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "bindconf_tls_set failed\n",
+ op->o_log_prefix );
+ goto exit;
+ }
+
+ if ( ap->pins ) {
+ if ( (rc = ldap_set_option( ld, LDAP_OPT_CONNECT_CB, &ad_conncb )) !=
+ LDAP_SUCCESS ) {
+ goto exit;
+ }
+ }
+
+ if ( (rc = ldap_connect( ld )) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "Cannot connect to %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit;
+ }
+
+ if ( ap->ad_tls.sb_tls && !ldap_tls_inplace( ld ) ) {
+ if ( (rc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "LDAP TLS failed %s: %s\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ) );
+ goto exit;
+ }
+ }
+
+#endif /* HAVE_TLS */
+
+ rc = ldap_sasl_bind_s( ld, dn.bv_val, LDAP_SASL_SIMPLE,
+ &op->oq_bind.rb_cred, NULL, NULL, NULL );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( ap->store_on_success ) {
+ const char *txt;
+
+ Operation op2 = *op;
+ SlapReply r2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ Modifications m = {};
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_callback = &cb;
+ op2.orm_modlist = &m;
+ op2.orm_no_opattrs = 0;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ m.sml_op = LDAP_MOD_ADD;
+ m.sml_flags = 0;
+ m.sml_next = NULL;
+ m.sml_type = ap->up_ad->ad_cname;
+ m.sml_desc = ap->up_ad;
+ m.sml_numvals = 1;
+ m.sml_values = op->o_tmpcalloc(
+ sizeof(struct berval), 2, op->o_tmpmemctx );
+
+ slap_passwd_hash( &op->oq_bind.rb_cred, &m.sml_values[0], &txt );
+ if ( m.sml_values[0].bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: "
+ "password hashing for '%s' failed, storing password in "
+ "plain text\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+ ber_dupbv( &m.sml_values[0], &op->oq_bind.rb_cred );
+ }
+
+ /*
+ * If this server is a shadow 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) ) {
+ op2.o_bd = frontendDB;
+ } else {
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+ }
+
+ if ( op2.o_bd->be_modify( &op2, &r2 ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: "
+ "attempt to store password in entry '%s' failed, "
+ "ignoring\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+ }
+ ch_free( m.sml_values[0].bv_val );
+ }
+ goto exit;
+ }
+
+ if ( rc == LDAP_INVALID_CREDENTIALS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "ldap_sasl_bind_s (%s) failed: invalid credentials\n",
+ op->o_log_prefix, ldap_url );
+ goto exit;
+ }
+
+ if ( tries < ap->retry_count ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "ldap_sasl_bind_s failed %s: %s (try #%d)\n",
+ op->o_log_prefix, ldap_url, ldap_err2string( rc ), tries );
+ if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL );
+ tries++;
+ goto retry;
+ } else
+ goto exit;
+
+exit:
+ if ( dn.bv_val ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( e ) {
+ be_entry_release_r( op, e );
+ }
+ if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL );
+ if ( ldap_url ) ch_free( ldap_url );
+ if ( realm ) ch_free( realm );
+ if ( rc == SLAP_CB_CONTINUE ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "continue\n", op->o_log_prefix );
+ return rc;
+ } else {
+ /* for rc == 0, frontend sends result */
+ if ( rc ) {
+ if ( rc > 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "failed\n", op->o_log_prefix );
+ send_ldap_error( op, rs, rc, "remoteauth_bind failed" );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: "
+ "operations error\n", op->o_log_prefix );
+ send_ldap_error( op, rs, LDAP_OPERATIONS_ERROR,
+ "remoteauth_bind operations error" );
+ }
+ }
+
+ return rs->sr_err;
+ }
+}
+
+static int
+remoteauth_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ad_private *ap;
+
+ if ( SLAP_ISGLOBALOVERLAY(be) ) {
+ Debug( LDAP_DEBUG_ANY, "remoteauth_db_init: "
+ "remoteauth overlay must be instantiated within a "
+ "database.\n" );
+ return 1;
+ }
+
+ ap = ch_calloc( 1, sizeof(ad_private) );
+
+ ap->dn = NULL;
+ ap->dn_ad = NULL;
+ ap->domain_attr = NULL;
+ ap->domain_ad = NULL;
+
+ ap->up_ad = NULL;
+ ap->mappings = NULL;
+
+ ap->default_realm = NULL;
+ ap->default_domain = NULL;
+
+ ap->pins = NULL;
+
+ ap->up_set = 0;
+ ap->retry_count = 3;
+
+ on->on_bi.bi_private = ap;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+remoteauth_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ ad_private *ap = (ad_private *)on->on_bi.bi_private;
+ ad_info *ai = ap->mappings;
+
+ while ( ai ) {
+ ad_info *next = ai->next;
+
+ if ( ai->domain ) ch_free( ai->domain );
+ if ( ai->realm ) ch_free( ai->realm );
+
+ ch_free( ai );
+ ai = next;
+ }
+
+ if ( ap->dn ) ch_free( ap->dn );
+ if ( ap->default_domain ) ch_free( ap->default_domain );
+ if ( ap->default_realm ) ch_free( ap->default_realm );
+ if ( ap->domain_attr ) ch_free( ap->domain_attr );
+
+ bindconf_free( &ap->ad_tls );
+
+ ch_free( ap );
+
+ return 0;
+}
+
+static slap_overinst remoteauth;
+
+int
+remoteauth_initialize( void )
+{
+ int rc;
+
+ remoteauth.on_bi.bi_type = "remoteauth";
+ remoteauth.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+
+ remoteauth.on_bi.bi_cf_ocs = remoteauthocs;
+ rc = config_register_schema( remoteauthcfg, remoteauthocs );
+ if ( rc ) return rc;
+
+ remoteauth.on_bi.bi_db_init = remoteauth_db_init;
+ remoteauth.on_bi.bi_db_destroy = remoteauth_db_destroy;
+ remoteauth.on_bi.bi_op_bind = remoteauth_bind;
+
+ return overlay_register( &remoteauth );
+}
+
+#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return remoteauth_initialize();
+}
+#endif
diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c
new file mode 100644
index 0000000..15052e0
--- /dev/null
+++ b/servers/slapd/overlays/retcode.c
@@ -0,0 +1,1577 @@
+/* retcode.c - customizable response for client testing purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 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 "slap-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 = &sc;
+
+ 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;
+ }
+
+ 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|ARG_QUOTE|RC_PARENT, rc_cf_gen,
+ "( OLcfgOvAt:20.1 NAME 'olcRetcodeParent' "
+ "DESC '' "
+ "EQUALITY distinguishedNameMatch "
+ "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 '' "
+ "EQUALITY booleanMatch "
+ "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 '' "
+ "EQUALITY integerMatch "
+ "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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ }
+
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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';
+
+ /* We're marked X-ORDERED 'VALUES', valx might be valid */
+ for ( i = 0, rdip = &rd->rd_item;
+ *rdip && (c->valx < 0 || i < c->valx);
+ rdip = &(*rdip)->rdi_next, i++ )
+ /* go to position */ ;
+
+
+ rdi.rdi_next = *rdip;
+ *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" );
+ 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" );
+ 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..af10f6d
--- /dev/null
+++ b/servers/slapd/overlays/rwm.c
@@ -0,0 +1,2768 @@
+/* rwm.c - rewrite/remap operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2022 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 "slap-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;
+ }
+ if ( op->orr_newDN.bv_val != ros->orr_newDN.bv_val ) {
+ ch_free( op->orr_newDN.bv_val );
+ ch_free( op->orr_nnewDN.bv_val );
+ op->orr_newDN = ros->orr_newDN;
+ op->orr_nnewDN = ros->orr_nnewDN;
+ }
+ 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;
+ struct berval pdn, pndn;
+
+ 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;
+ }
+ pdn = newSup;
+ pndn = 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;
+ }
+ if ( !op->orr_newSup ) {
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+ }
+
+ /*
+ * Update the new DN
+ */
+ build_new_dn( &op->orr_newDN, &pdn, &op->orr_newrdn, op->o_tmpmemctx );
+ build_new_dn( &op->orr_nnewDN, &pndn, &op->orr_nnewrdn, op->o_tmpmemctx );
+
+ 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;
+ }
+
+ if ( op->orr_newDN.bv_val != roc->ros.orr_newDN.bv_val ) {
+ op->o_tmpfree( op->orr_newDN.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewDN.bv_val, op->o_tmpmemctx );
+ op->orr_newDN = roc->ros.orr_newDN;
+ op->orr_nnewDN = roc->ros.orr_nnewDN;
+ }
+ }
+
+ 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 struct berval *passwd_oid;
+
+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;
+ }
+
+ /* If we're fetching the target of a password mod, must let real DNs thru */
+ if ( op->o_tag == LDAP_REQ_EXTENDED && bvmatch( passwd_oid, &op->oq_extended.rs_reqoid ) ) {
+ 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 struct berval *passwd_oid = &exop_table[0].oid;
+
+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 );
+ 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 );
+ 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;
+ }
+
+ if ( op->o_tag == LDAP_REQ_ADD && op->ora_e ) {
+ /*
+ * Rewrite back the dn and attributes of the added entry op->ora_e
+ */
+ SlapReply rs2 = *rs;
+ rs2.sr_entry = op->ora_e;
+ rs2.sr_flags |= REP_ENTRY_MODIFIABLE;
+ return rwm_send_entry( op, &rs2 );
+ }
+
+ 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 );
+ 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 );
+ 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 );
+ 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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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 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 {
+ rc = slap_bv_x_ordered_unparse( rwmap->rwm_bva_rewrite, &c->rvalue_vals );
+ }
+ 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 ) {
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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 ) {
+ ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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 {
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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 ] );
+
+ ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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:;
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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 );
+ }
+
+ ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ ldap_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..6753737
--- /dev/null
+++ b/servers/slapd/overlays/rwm.h
@@ -0,0 +1,183 @@
+/* rwm.h - dn rewrite/attribute mapping header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 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
+
+/* 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..a1a9f36
--- /dev/null
+++ b/servers/slapd/overlays/rwmconf.c
@@ -0,0 +1,413 @@
+/* rwmconf.c - rewrite/map configuration file routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: line %d: source attributeType '%s': %d (%s)\n",
+ fname, lineno, src, rc,
+ text ? text : "null" );
+ 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 ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: line %d: destination attributeType '%s': %d (%s)\n",
+ fname, lineno, dst, rc,
+ text ? text : "null" );
+ goto error_return;
+ }
+ }
+ mapping[1].m_src_ad = mapping[0].m_dst_ad;
+ }
+
+ if ( ( src[0] != '\0' && ldap_avl_find( map->map, (caddr_t)mapping, rwm_mapping_cmp ) != NULL)
+ || ldap_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 );
+ /* FIXME: free stuff */
+ goto error_return;
+ }
+
+ if ( src[0] != '\0' ) {
+ ldap_avl_insert( &map->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ }
+ ldap_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..c67e3cf
--- /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-2022 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 normalized 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..74ffd05
--- /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-2022 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;
+
+ ldap_avl_insert( &lm->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ ldap_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 *)ldap_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, ftmp;
+ 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:;
+ f = &ftmp;
+ 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..503d6a6
--- /dev/null
+++ b/servers/slapd/overlays/seqmod.c
@@ -0,0 +1,207 @@
+/* seqmod.c - sequenced modifies */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SEQMOD
+
+#include "slap.h"
+#include "slap-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 = ldap_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 {
+ ldap_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 = ldap_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 */
+ ldap_avl_insert( &sm->sm_mods, mt, sm_avl_cmp, ldap_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_flags = SLAPO_BFLAG_SINGLE;
+ 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..828782a
--- /dev/null
+++ b/servers/slapd/overlays/sssvlv.c
@@ -0,0 +1,1439 @@
+/* sssvlv.c - server side sort / virtual list view */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2022 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 <ldap_avl.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "slap-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
+{
+ TAvlnode *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>") );
+
+ 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 ) {
+ TAvlnode *cur_node, *next_node;
+ cur_node = so->so_tree;
+ while ( cur_node ) {
+ next_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT );
+ ch_free( cur_node->avl_data );
+ ber_memfree( cur_node );
+
+ cur_node = next_node;
+ }
+ } else {
+ ldap_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)
+{
+ TAvlnode *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 = ldap_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 = ldap_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 = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ } else {
+ cur_node = ldap_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 = ldap_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 = ldap_tavl_find3( so->so_tree, sn, node_cmp, &j );
+ /* didn't find >= match */
+ if ( j > 0 ) {
+ if ( cur_node )
+ cur_node = ldap_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 = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ dir = TAVL_DIR_LEFT;
+ } else {
+ tmp_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ }
+ for (i=0; tmp_node != cur_node;
+ tmp_node = ldap_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 = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ } else {
+ i = 0;
+ }
+ for ( ; i<vc->vc_before; i++ ) {
+ tmp_node = ldap_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 = ldap_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 )
+{
+ TAvlnode *cur_node = so->so_tree;
+ TAvlnode *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 = ldap_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
+ * successfully 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 */
+ TAvlnode *start_node = ldap_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 */
+ ldap_tavl_insert(&(so->so_tree), sn, node_insert, ldap_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 );
+ }
+ }
+ 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 );
+ }
+ }
+
+ *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>") );
+ 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' "
+ "EQUALITY integerMatch "
+ "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' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SSSVLV_DEFAULT_MAX_KEYS } },
+ { "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' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL,
+ { .v_int = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN } },
+ { 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 );
+ 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 );
+#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_flags = SLAPO_BFLAG_SINGLE;
+ 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" );
+ }
+
+ 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..f208846
--- /dev/null
+++ b/servers/slapd/overlays/syncprov.c
@@ -0,0 +1,4412 @@
+/* $OpenLDAP$ */
+/* syncprov.c - syncrepl provider */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was 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 "slap-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;
+ void *s_pool_cookie;
+ 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;
+ TAvlnode *sl_entries;
+ ldap_pvt_thread_rdwr_t sl_mutex;
+} sessionlog;
+
+/* Accesslog callback data */
+typedef struct syncprov_accesslog_deletes {
+ Operation *op;
+ SlapReply *rs;
+ sync_control *srs;
+ BerVarray ctxcsn;
+ int numcsns, *sids;
+ Avlnode *uuids;
+ BerVarray uuid_list;
+ int ndel, list_len;
+ char *uuid_buf;
+} syncprov_accesslog_deletes;
+
+/* The main state for this overlay */
+typedef struct syncprov_info_t {
+ syncops *si_ops;
+ struct berval si_contextdn;
+ struct berval si_logbase;
+ 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];
+
+static AttributeDescription *ad_reqType, *ad_reqResult, *ad_reqDN,
+ *ad_reqEntryUUID, *ad_minCSN, *ad_reqNewDN;
+
+/* 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 );
+ 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 );
+ 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 );
+ 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 );
+ return LDAP_OTHER;
+ }
+ }
+
+ ret = ber_flatten2( ber, &rspdata, 0 );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "syncprov_sendinfo: ber_flatten2 failed (%d)\n",
+ ret );
+ 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
+sp_uuid_cmp( const void *l, const void *r )
+{
+ const struct berval *left = l, *right = r;
+
+ return ber_bvcmp( left, right );
+}
+
+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 );
+ }
+ 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 );
+ 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 : "" );
+
+again:
+ 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;
+
+ 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. */
+ Debug( LDAP_DEBUG_SYNC, "%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;
+ resinfo *ri = sr->s_info;
+ int freeit = 0;
+
+ ldap_pvt_thread_mutex_lock( &ri->ri_mutex );
+ for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) {
+ if (*st == sr) {
+ *st = sr->s_rilist;
+ if ( !sr->s_info->ri_list )
+ freeit = 1;
+ sr->s_info = NULL;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &ri->ri_mutex );
+ if ( freeit ) {
+ ldap_pvt_thread_mutex_destroy( &ri->ri_mutex );
+ if ( ri->ri_e )
+ entry_free( ri->ri_e );
+ if ( !BER_BVISNULL( &ri->ri_cookie ))
+ ch_free( ri->ri_cookie.bv_val );
+ ch_free( ri );
+ }
+}
+
+#define FS_UNLINK 1
+#define FS_LOCK 2
+#define FS_DEFER 4
+
+#define FSR_NOTFREE 0
+#define FSR_DIDFREE 1
+#define FSR_CANFREE 2
+
+static int
+syncprov_free_syncop( syncops *so, int flags )
+{
+ syncres *sr, *srnext;
+ GroupAssertion *ga, *gnext;
+
+ if ( flags & FS_LOCK )
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ /* already being freed, or still in use */
+ if ( !so->s_inuse || so->s_inuse > 1 ) {
+ if ( flags & FS_LOCK )
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ if ( !( flags & FS_DEFER ) && so->s_inuse )
+ so->s_inuse--;
+ return FSR_NOTFREE;
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+
+ /* caller wants to cleanup other stuff before actual free */
+ if ( flags & FS_DEFER )
+ return FSR_CANFREE;
+
+ if (( flags & FS_UNLINK ) && so->s_si ) {
+ 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 FSR_DIDFREE;
+}
+
+/* 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, NULL );
+
+#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 );
+ }
+#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 );
+ 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 );
+ }
+ } else {
+ /* set rc so we don't do a new qstart */
+ rc = 1;
+ }
+
+ 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 );
+ }
+
+ return rc;
+}
+
+static int
+syncprov_drop_psearch( syncops *so, int lock );
+
+/* 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, flag, frc;
+
+ 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;
+ operation_counter_init( op, 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 );
+
+ /* if an error occurred, or no responses left, task is no longer queued */
+ if ( !rc && !so->s_res )
+ rc = 1;
+
+ flag = FS_UNLINK;
+ if ( rc && op->o_abandon )
+ flag = FS_DEFER;
+
+ /* decrement use count... */
+ frc = syncprov_free_syncop( so, flag );
+ if ( frc == FSR_NOTFREE ) {
+ if ( rc )
+ /* if we didn't unlink, and task is no longer queued, clear flag */
+ so->s_flags ^= PS_TASK_QUEUED;
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ }
+
+ /* if we got abandoned while processing, cleanup now */
+ if ( frc == FSR_CANFREE ) {
+ syncprov_drop_psearch( 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_submit2( &connection_pool,
+ syncprov_qtask, so, &so->s_pool_cookie );
+}
+
+/* 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, NULL );
+ }
+ 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 ? 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, FS_LOCK );
+}
+
+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;
+ }
+ }
+ /* if task is active, it must drop itself */
+ if ( !( so->s_flags & PS_TASK_QUEUED ))
+ 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;
+ BackendDB *b0 = op->o_bd, db;
+
+ fc.fdn = saveit ? &op->o_req_ndn : &opc->sndn;
+ if ( !saveit && op->o_tag == LDAP_REQ_DELETE ) {
+ /* Delete succeeded, there is no entry */
+ } else 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 ) {
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ ber_dupbv_x( &opc->sdn, &op->orr_newDN, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sndn, &op->orr_nnewDN, op->o_tmpmemctx );
+ } else {
+ 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 );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "%srecording uuid for dn=%s on opc=%p\n",
+ op->o_log_prefix, a ? "" : "not ", opc->sdn.bv_val, opc );
+ }
+
+ 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, "%s syncprov_matchops: "
+ "skipping original sid %03x\n",
+ ss->s_op->o_log_prefix, opc->osid );
+ continue;
+ }
+
+ /* Don't send ops back to the messenger */
+ if ( opc->rsid > 0 && opc->rsid == ss->s_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: "
+ "skipping relayed sid %03x\n",
+ ss->s_op->o_log_prefix, opc->rsid );
+ 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;
+ }
+ }
+ }
+
+ rc = LDAP_COMPARE_FALSE;
+ if ( e && !is_entry_glue( e ) && 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, "%s syncprov_matchops: "
+ "sid %03x fscope %d rc %d\n",
+ ss->s_op->o_log_prefix, 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, FS_LOCK ) ) {
+ *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 );
+ }
+ 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, FS_LOCK|FS_UNLINK );
+ 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 );
+ ldap_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;
+
+ if ( opc->ssres.s_info ) {
+ free_resinfo( &opc->ssres );
+ }
+ 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 );
+
+ 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 ) {
+ ldap_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 = ldap_tavl_insert( &sl->sl_entries, se, syncprov_sessionlog_cmp, ldap_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 ) {
+ TAvlnode *edge = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT );
+ while ( sl->sl_num > sl->sl_size ) {
+ int i;
+ TAvlnode *next = ldap_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 );
+ slap_insert_csn_sids( (struct sync_cookie *)sl,
+ i, se->se_sid, &se->se_csn );
+ } else {
+ Debug( LDAP_DEBUG_SYNC, "%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 );
+ }
+ ldap_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;
+}
+
+/*
+ * Check whether the last nmods UUIDs in the uuids list exist in the database
+ * and (still) match the op filter, zero out the bv_len of any that still exist
+ * and return the number of UUIDs we have confirmed are gone now.
+ */
+static int
+check_uuidlist_presence(
+ Operation *op,
+ struct berval *uuids,
+ int len,
+ int nmods )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ Operation fop = *op;
+ SlapReply frs = { REP_RESULT };
+ Filter mf, af;
+ AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
+ slap_callback cb = {0};
+ int i, mods = nmods;
+
+ 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 = &af;
+
+ cb.sc_response = playlog_cb;
+
+ fop.o_bd->bd_info = (BackendInfo *)on->on_info;
+ for ( i=0; i<nmods; i++ ) {
+ mf.f_av_value = uuids[ len - 1 - i ];
+ cb.sc_private = NULL;
+ fop.ors_slimit = 1;
+
+ if ( BER_BVISEMPTY( &mf.f_av_value ) ) {
+ mods--;
+ continue;
+ }
+
+ rs_reinit( &frs, REP_RESULT );
+ fop.o_bd->be_search( &fop, &frs );
+ if ( cb.sc_private ) {
+ uuids[ len - 1 - i ].bv_len = 0;
+ mods--;
+ }
+ }
+ fop.o_bd->bd_info = (BackendInfo *)on;
+
+ return mods;
+}
+
+/*
+ * On each entry we get from the DB:
+ * - if it's an ADD, skip
+ * - check we've not handled it yet, skip if we have
+ * - check if it's a DELETE or missing from the DB now
+ * - send a new syncinfo entry
+ * - remember we've handled it already
+ *
+ * If we exhaust the list, clear it, forgetting entries we've handled so far.
+ */
+static int
+syncprov_accesslog_uuid_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ syncprov_accesslog_deletes *uuid_progress = sc->sc_private;
+ Attribute *a, *attrs;
+ sync_control *srs = uuid_progress->srs;
+ struct berval *bv, csn[2] = {}, uuid[2] = {},
+ add = BER_BVC("add"),
+ delete = BER_BVC("delete"),
+ modrdn = BER_BVC("modrdn");
+ int cmp, sid, i, is_delete = 0, rc;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return rs->sr_err;
+ }
+ attrs = rs->sr_entry->e_attrs;
+
+ a = attr_find( attrs, ad_reqType );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &add ) ) {
+ return rs->sr_err;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &delete ) ) {
+ is_delete = 1;
+ }
+
+ if ( bvmatch( &a->a_nvals[0], &modrdn ) ) {
+ a = attr_find( attrs, ad_reqDN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ /* Was it present in the first place? If not, skip: */
+ if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) {
+ return rs->sr_err;
+ }
+
+ a = attr_find( attrs, ad_reqNewDN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+
+ /* Has it gone away? */
+ if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) {
+ is_delete = 1;
+ }
+ }
+
+ /*
+ * Only pick entries that are both:
+ */
+ a = attr_find( attrs, slap_schema.si_ad_entryCSN );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+ csn[0] = a->a_nvals[0];
+
+ sid = slap_parse_csn_sid( &csn[0] );
+
+ /*
+ * newer than cookieCSN (srs->sr_state.ctxcsn)
+ */
+ cmp = 1;
+ for ( i=0; i<srs->sr_state.numcsns; i++ ) {
+ if ( sid == srs->sr_state.sids[i] ) {
+ cmp = ber_bvcmp( &csn[0], &srs->sr_state.ctxcsn[i] );
+ break;
+ }
+ }
+ if ( cmp <= 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "cmp %d, csn %s too old\n",
+ op->o_log_prefix, cmp, csn[0].bv_val );
+ return rs->sr_err;
+ }
+
+ /*
+ * not newer than snapshot ctxcsn (uuid_progress->ctxcsn)
+ */
+ cmp = 0;
+ for ( i=0; i<uuid_progress->numcsns; i++ ) {
+ if ( sid == uuid_progress->sids[i] ) {
+ cmp = ber_bvcmp( &csn[0], &uuid_progress->ctxcsn[i] );
+ break;
+ }
+ }
+ if ( cmp > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "cmp %d, csn %s too new\n",
+ op->o_log_prefix, cmp, csn[0].bv_val );
+ return rs->sr_err;
+ }
+
+ a = attr_find( attrs, ad_reqEntryUUID );
+ if ( !a || a->a_numvals == 0 ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ return rs->sr_err;
+ }
+ uuid[0] = a->a_nvals[0];
+
+ bv = ldap_avl_find( uuid_progress->uuids, uuid, sp_uuid_cmp );
+ if ( bv ) {
+ /* Already checked or sent, no change */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "uuid %s already checked\n",
+ op->o_log_prefix, a->a_vals[0].bv_val );
+ return rs->sr_err;
+ }
+
+ if ( !is_delete ) {
+ is_delete = check_uuidlist_presence( uuid_progress->op, uuid, 1, 1 );
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: "
+ "uuid %s is %s present\n",
+ op->o_log_prefix, a->a_vals[0].bv_val,
+ is_delete ? "no longer" : "still" );
+
+ i = uuid_progress->ndel++;
+
+ bv = &uuid_progress->uuid_list[i];
+ bv->bv_val = &uuid_progress->uuid_buf[i*UUID_LEN];
+ bv->bv_len = a->a_nvals[0].bv_len;
+ AC_MEMCPY( bv->bv_val, a->a_nvals[0].bv_val, a->a_nvals[0].bv_len );
+
+ rc = ldap_avl_insert( &uuid_progress->uuids, bv, sp_uuid_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+
+ if ( is_delete ) {
+ struct berval cookie;
+
+ slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn,
+ srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn );
+ syncprov_sendinfo( uuid_progress->op, uuid_progress->rs,
+ LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( uuid_progress->ndel >= uuid_progress->list_len ) {
+ int ndel;
+
+ assert( uuid_progress->ndel == uuid_progress->list_len );
+ ndel = ldap_avl_free( uuid_progress->uuids, NULL );
+ assert( ndel == uuid_progress->ndel );
+ uuid_progress->uuids = NULL;
+ uuid_progress->ndel = 0;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+syncprov_play_sessionlog( Operation *op, SlapReply *rs, sync_control *srs,
+ BerVarray ctxcsn, int numcsns, int *sids,
+ struct berval *mincsn, int minsid )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ sessionlog *sl = si->si_logs;
+ int i, j, ndel, num, nmods, mmods, do_play = 0, rc = -1;
+ BerVarray uuids, csns;
+ struct berval uuid[2] = {}, csn[2] = {};
+ slog_entry *se;
+ TAvlnode *entry;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ struct berval delcsn[2];
+
+ 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 ) {
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ return rc;
+ }
+ assert( sl->sl_num > 0 );
+
+ 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 ) {
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+ return rc;
+ }
+
+ num = sl->sl_num;
+ i = 0;
+ nmods = 0;
+ sl->sl_playing++;
+ ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex );
+
+ uuids = op->o_tmpalloc( (num) * sizeof( struct berval ) +
+ num * UUID_LEN, op->o_tmpmemctx );
+ uuids[0].bv_val = (char *)(uuids + num);
+ csns = op->o_tmpalloc( (num) * sizeof( struct berval ) +
+ num * LDAP_PVT_CSNSTR_BUFSIZE, op->o_tmpmemctx );
+ csns[0].bv_val = (char *)(csns + num);
+
+ 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 = ldap_tavl_find3( sl->sl_entries, &te, syncprov_sessionlog_cmp, &ndel );
+ }
+ if ( ndel > 0 && entry )
+ entry = ldap_tavl_next( entry, TAVL_DIR_LEFT );
+ /* if none, just start at beginning */
+ if ( !entry )
+ entry = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT );
+
+ do {
+ char uuidstr[40] = {};
+ 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_play_sessionlog: "
+ "cmp %d, csn %s too new, we're finished\n",
+ op->o_log_prefix, ndel, se->se_csn.bv_val );
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ break;
+ }
+ if ( se->se_tag == LDAP_REQ_DELETE ) {
+ j = i;
+ i++;
+ } 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;
+
+ csns[j].bv_val = csns[0].bv_val + (j * LDAP_PVT_CSNSTR_BUFSIZE);
+ AC_MEMCPY(csns[j].bv_val, se->se_csn.bv_val, se->se_csn.bv_len);
+ csns[j].bv_len = se->se_csn.bv_len;
+ /* We're printing it */
+ csns[j].bv_val[csns[j].bv_len] = '\0';
+
+ if ( LogTest( LDAP_DEBUG_SYNC ) ) {
+ lutil_uuidstr_from_normalized( uuids[j].bv_val, uuids[j].bv_len,
+ uuidstr, 40 );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: "
+ "picking a %s entry uuid=%s cookie=%s\n",
+ op->o_log_prefix, se->se_tag == LDAP_REQ_DELETE ? "deleted" : "modified",
+ uuidstr, csns[j].bv_val );
+ }
+ ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex );
+ } while ( (entry = ldap_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;
+ }
+ }
+ }
+
+ /* Check mods now */
+ if ( mmods ) {
+ check_uuidlist_presence( op, uuids, num, nmods );
+ }
+
+ /* ITS#8768 Send entries sorted by CSN order */
+ i = j = 0;
+ while ( i < ndel || j < nmods ) {
+ struct berval cookie;
+ int index;
+
+ /* Skip over duplicate mods */
+ if ( j < nmods && BER_BVISEMPTY( &uuids[ num - 1 - j ] ) ) {
+ j++;
+ continue;
+ }
+ index = num - 1 - j;
+
+ if ( i >= ndel ) {
+ j++;
+ } else if ( j >= nmods ) {
+ index = i++;
+ /* Take the oldest by CSN order */
+ } else if ( ber_bvcmp( &csns[index], &csns[i] ) < 0 ) {
+ j++;
+ } else {
+ index = i++;
+ }
+
+ uuid[0] = uuids[index];
+ csn[0] = csns[index];
+
+ slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn,
+ srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn );
+ if ( LogTest( LDAP_DEBUG_SYNC ) ) {
+ char uuidstr[40];
+ lutil_uuidstr_from_normalized( uuid[0].bv_val, uuid[0].bv_len,
+ uuidstr, 40 );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: "
+ "sending a new disappearing entry uuid=%s cookie=%s\n",
+ op->o_log_prefix, uuidstr, cookie.bv_val );
+ }
+
+ /* TODO: we might batch those that share the same CSN (think present
+ * phase), but would have to limit how many we send out at once */
+ syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 );
+ }
+ op->o_tmpfree( uuids, op->o_tmpmemctx );
+ op->o_tmpfree( csns, op->o_tmpmemctx );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_play_accesslog( Operation *op, SlapReply *rs, sync_control *srs,
+ BerVarray ctxcsn, int numcsns, int *sids,
+ struct berval *mincsn, int minsid )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ Operation fop;
+ SlapReply frs = { REP_RESULT };
+ slap_callback cb = {};
+ Filter *f;
+ syncprov_accesslog_deletes uuid_progress = {
+ .op = op,
+ .rs = rs,
+ .srs = srs,
+ .ctxcsn = ctxcsn,
+ .numcsns = numcsns,
+ .sids = sids,
+ };
+ struct berval oldestcsn = BER_BVNULL, newestcsn = BER_BVNULL,
+ basedn, filterpattern = BER_BVC(
+ "(&"
+ "(entryCSN>=%s)"
+ "(entryCSN<=%s)"
+ "(reqResult=0)"
+ "(|"
+ "(reqDN:dnSubtreeMatch:=%s)"
+ "(reqNewDN:dnSubtreeMatch:=%s)"
+ ")"
+ "(|"
+ "(objectclass=auditWriteObject)"
+ "(objectclass=auditExtended)"
+ "))" );
+ BackendDB *db;
+ Entry *e;
+ Attribute *a;
+ int *minsids, i, j = 0, rc = -1;
+
+ assert( !BER_BVISNULL( &si->si_logbase ) );
+
+ db = select_backend( &si->si_logbase, 0 );
+ if ( !db ) {
+ Debug( LDAP_DEBUG_ANY, "%s syncprov_play_accesslog: "
+ "No database configured to hold accesslog dn=%s\n",
+ op->o_log_prefix, si->si_logbase.bv_val );
+ return LDAP_NO_SUCH_OBJECT;
+ }
+
+ fop = *op;
+ fop.o_sync_mode = 0;
+ fop.o_bd = db;
+ rc = be_entry_get_rw( &fop, &si->si_logbase, NULL, ad_minCSN, 0, &e );
+ if ( rc ) {
+ return rc;
+ }
+
+ a = attr_find( e->e_attrs, ad_minCSN );
+ if ( !a ) {
+ be_entry_release_rw( &fop, e, 0 );
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ /*
+ * If we got here:
+ * - the consumer's cookie (srs->sr_state.ctxcsn) has the same sids in the
+ * same order as ctxcsn
+ * - at least one of the cookie's csns is older than its ctxcsn counterpart
+ *
+ * Now prepare the filter, we want it to be the union of all the intervals
+ * between the cookie and our contextCSN for each sid. Right now, we can't
+ * specify them separately, so just pick the boundary CSNs of non-empty
+ * intervals as a conservative overestimate.
+ *
+ * Also check accesslog can actually serve this query based on what's
+ * stored in minCSN.
+ */
+
+ assert( srs->sr_state.numcsns == numcsns );
+
+ minsids = slap_parse_csn_sids( a->a_nvals, a->a_numvals, op->o_tmpmemctx );
+ slap_sort_csn_sids( a->a_nvals, minsids, a->a_numvals, op->o_tmpmemctx );
+ for ( i=0, j=0; i < numcsns; i++ ) {
+ assert( srs->sr_state.sids[i] == sids[i] );
+ if ( ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[i] ) >= 0 ) {
+ /* Consumer is up to date for this sid */
+ continue;
+ }
+ for ( ; j < a->a_numvals && minsids[j] < sids[i]; j++ )
+ /* Find the right minCSN, if present */;
+ if ( j == a->a_numvals || minsids[j] != sids[i] ||
+ ber_bvcmp( &srs->sr_state.ctxcsn[i], &a->a_nvals[j] ) < 0 ) {
+ /* Consumer is missing changes for a sid and minCSN indicates we
+ * can't replay all relevant history */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: "
+ "accesslog information inadequate for log replay on csn=%s\n",
+ op->o_log_prefix, srs->sr_state.ctxcsn[i].bv_val );
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ be_entry_release_rw( &fop, e, 0 );
+ return 1;
+ }
+ if ( BER_BVISEMPTY( &oldestcsn ) ||
+ ber_bvcmp( &oldestcsn, &srs->sr_state.ctxcsn[i] ) > 0 ) {
+ oldestcsn = srs->sr_state.ctxcsn[i];
+ }
+ if ( BER_BVISEMPTY( &newestcsn ) ||
+ ber_bvcmp( &newestcsn, &ctxcsn[i] ) < 0 ) {
+ newestcsn = ctxcsn[i];
+ }
+ }
+ assert( !BER_BVISEMPTY( &oldestcsn ) && !BER_BVISEMPTY( &newestcsn ) &&
+ ber_bvcmp( &oldestcsn, &newestcsn ) < 0 );
+ slap_sl_free( minsids, op->o_tmpmemctx );
+
+ filter_escape_value_x( &op->o_req_ndn, &basedn, fop.o_tmpmemctx );
+ /* filter_escape_value_x sets output to BVNULL if input value is empty,
+ * supply our own copy */
+ if ( BER_BVISEMPTY( &basedn ) ) {
+ basedn.bv_val = "";
+ }
+ fop.o_req_ndn = fop.o_req_dn = si->si_logbase;
+ fop.ors_filterstr.bv_val = fop.o_tmpalloc(
+ filterpattern.bv_len +
+ oldestcsn.bv_len + newestcsn.bv_len + 2 * basedn.bv_len,
+ fop.o_tmpmemctx );
+ fop.ors_filterstr.bv_len = sprintf( fop.ors_filterstr.bv_val,
+ filterpattern.bv_val,
+ oldestcsn.bv_val, newestcsn.bv_val, basedn.bv_val, basedn.bv_val );
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: "
+ "prepared filter '%s', base='%s'\n",
+ op->o_log_prefix, fop.ors_filterstr.bv_val, si->si_logbase.bv_val );
+ f = str2filter_x( &fop, fop.ors_filterstr.bv_val );
+ assert( f != NULL );
+ fop.ors_filter = f;
+
+ if ( !BER_BVISEMPTY( &basedn ) ) {
+ fop.o_tmpfree( basedn.bv_val, fop.o_tmpmemctx );
+ }
+ be_entry_release_rw( &fop, e, 0 );
+
+ /*
+ * Allocate memory for list_len uuids for use by the callback, populate
+ * with entries that we have sent or checked still match the filter.
+ * A disappearing entry gets its uuid sent as a delete.
+ *
+ * in the callback, we need:
+ * - original op and rs so we can send the message
+ * - sync_control
+ * - the uuid buffer and list and their length
+ * - number of uuids we already have in the list
+ * - the lookup structure so we don't have to check/send a uuid twice
+ * (AVL?)
+ */
+ uuid_progress.list_len = SLAP_SYNCUUID_SET_SIZE;
+ uuid_progress.uuid_list = fop.o_tmpalloc( (uuid_progress.list_len) * sizeof(struct berval), fop.o_tmpmemctx );
+ uuid_progress.uuid_buf = fop.o_tmpalloc( (uuid_progress.list_len) * UUID_LEN, fop.o_tmpmemctx );
+
+ cb.sc_private = &uuid_progress;
+ cb.sc_response = syncprov_accesslog_uuid_cb;
+
+ fop.o_callback = &cb;
+
+ rc = fop.o_bd->be_search( &fop, &frs );
+
+ ldap_avl_free( uuid_progress.uuids, NULL );
+ fop.o_tmpfree( uuid_progress.uuid_buf, fop.o_tmpmemctx );
+ fop.o_tmpfree( uuid_progress.uuid_list, fop.o_tmpmemctx );
+ fop.o_tmpfree( fop.ors_filterstr.bv_val, fop.o_tmpmemctx );
+ filter_free_x( &fop, f, 1 );
+
+ return rc;
+}
+
+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 ) {
+ syncprov_matchops( op, opc, 0 );
+ }
+
+ /* 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 = ldap_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_pausewait( &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 );
+ ldap_avl_insert( &si->si_mods, mt, sp_avl_cmp, ldap_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;
+}
+
+static int
+syncprov_search_response( Operation *op, SlapReply *rs )
+{
+ searchstate *ss = op->o_callback->sc_private;
+ slap_overinst *on = ss->ss_on;
+ syncops *so = ss->ss_so;
+ 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 );
+ return SLAP_CB_CONTINUE;
+ }
+ if ( is_entry_glue( rs->sr_entry ) ) {
+ return LDAP_SUCCESS;
+ }
+ 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 ( !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 ) {
+ Debug( LDAP_DEBUG_SYNC, "%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 ) {
+ Debug( LDAP_DEBUG_SYNC, "%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, NULL );
+ 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, NULL );
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: cookie=%s\n",
+ op->o_log_prefix, cookie.bv_val );
+ }
+
+ /* Is this a regular refresh?
+ * Note: refresh never gets here if there were no changes
+ */
+ if ( !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 */
+ rs->sr_err = SLAPD_NO_REPLY;
+ 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( &so->s_mutex );
+ /* Turn off the refreshing flag */
+ so->s_flags ^= PS_IS_REFRESHING;
+
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: "
+ "detaching op\n", op->o_log_prefix );
+ syncprov_detach_op( op, so, on );
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ /* If there are queued responses, fire them off */
+ if ( so->s_res )
+ syncprov_qstart( so );
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return rs->sr_err;
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_search_cb( Operation *op, SlapReply *rs )
+{
+ /*
+ * Prevent the glue overlay from processing subordinates when it is
+ * configured (explicitly or implicitly) below the syncprov overlay.
+ */
+ if ( rs->sr_type == REP_RESULT )
+ op->o_no_subordinate_glue = 1;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_search_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_INTERMEDIATE ||
+ rs->sr_err == SLAPD_ABANDON || op->o_abandon ) {
+ searchstate *ss = op->o_callback->sc_private;
+ if ( ss && ss->ss_numcsns ) {
+ ber_bvarray_free_x( ss->ss_ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( ss->ss_sids, op->o_tmpmemctx );
+ }
+ slap_freeself_cb( op, rs );
+ }
+ 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 > SLAP_CONTROL_IGNORED ) {
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_response = syncprov_search_cb;
+ cb->sc_cleanup = syncprov_search_cleanup;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+
+ 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 = &sc;
+ rs->sr_err = syncprov_findbase( op, &fc );
+ op->o_callback = cb;
+ ldap_pvt_thread_mutex_destroy( &so.s_mutex );
+
+ /* Special case, if client knows nothing, nor do we, keep going */
+ if ( srs->sr_state.numcsns == 0 && rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "both our DB and client empty, ignoring NO_SUCH_OBJECT\n",
+ op->o_log_prefix );
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ 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 ) {
+aband:
+ ch_free( sop->s_base.bv_val );
+ ch_free( sop );
+ return SLAPD_ABANDON;
+ }
+ if ( !ldap_pvt_thread_pool_pausewait( &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 );
+ goto aband;
+ }
+ 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 );
+ }
+
+ /* 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 );
+
+ 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!";
+ Debug( LDAP_DEBUG_SYNC, "%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->s_base.bv_val );
+ ch_free( sop );
+ }
+ rs->sr_ctrls = NULL;
+ send_ldap_result( op, rs );
+ if ( numcsns ) {
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ }
+ 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;
+ if ( numcsns ) {
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ }
+ return rs->sr_err;
+ }
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "no change, skipping log replay\n",
+ op->o_log_prefix );
+ goto shortcut;
+ }
+
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ do_present = 0;
+ if ( syncprov_play_accesslog( op, rs, srs, ctxcsn,
+ numcsns, sids, &mincsn, minsid ) ) {
+ do_present = SS_PRESENT;
+ }
+ } else if ( si->si_logs ) {
+ do_present = 0;
+ if ( syncprov_play_sessionlog( op, rs, srs, ctxcsn,
+ numcsns, sids, &mincsn, minsid ) ) {
+ do_present = SS_PRESENT;
+ }
+ } else if ( ad_minCSN != NULL && si->si_nopres && si->si_usehint ) {
+ /* We are instructed to trust minCSN if it exists. */
+ Entry *e;
+ Attribute *a = NULL;
+ int rc;
+
+ /*
+ * ITS#9580 FIXME: when we've figured out and split the
+ * sessionlog/deltalog tracking, use the appropriate attribute
+ */
+ rc = overlay_entry_get_ov( op, &op->o_bd->be_nsuffix[0], NULL,
+ ad_minCSN, 0, &e, on );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ a = attr_find( e->e_attrs, ad_minCSN );
+ }
+
+ if ( a != NULL ) {
+ int *minsids;
+
+ minsids = slap_parse_csn_sids( a->a_vals, a->a_numvals, op->o_tmpmemctx );
+ slap_sort_csn_sids( a->a_vals, minsids, a->a_numvals, op->o_tmpmemctx );
+
+ for ( i=0, j=0; i < a->a_numvals; i++ ) {
+ while ( j < numcsns && minsids[i] > sids[j] ) j++;
+ if ( j < numcsns && minsids[i] == sids[j] &&
+ ber_bvcmp( &a->a_vals[i], &srs->sr_state.ctxcsn[j] ) <= 0 ) {
+ /* minCSN for this serverID is contained, keep going */
+ continue;
+ }
+ /*
+ * Log DB's minCSN claims we can only replay from a certain
+ * CSN for this serverID, but consumer's cookie hasn't met that
+ * threshold: they need to refresh
+ */
+ Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: "
+ "consumer not within recorded mincsn for DB's mincsn=%s\n",
+ op->o_log_prefix, a->a_vals[i].bv_val );
+ rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED;
+ rs->sr_text = "sync cookie is stale";
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ overlay_entry_release_ov( op, e, 0, on );
+ goto bailout;
+ }
+ slap_sl_free( minsids, op->o_tmpmemctx );
+ }
+ if ( e != NULL )
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ /*
+ * If sessionlog wasn't useful, see if we can find at least one entry
+ * that hasn't changed based on the cookie.
+ *
+ * TODO: Using mincsn only (rather than the whole cookie) will
+ * under-approximate the set of entries that haven't changed, but we
+ * can't look up CSNs by serverid with the current indexing support.
+ *
+ * As a result, dormant serverids in the cluster become mincsns and
+ * more likely to make syncprov_findcsn(,FIND_CSN,) fail -> triggering
+ * an expensive refresh...
+ */
+ if ( !do_present ) {
+ gotstate = 1;
+ } else 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 ) {
+ 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 );
+ 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 ( syncprov_findcsn( op, FIND_PRESENT, 0 ) != LDAP_SUCCESS )
+ 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_cleanup = syncprov_search_cleanup;
+ 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 );
+ 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;
+}
+
+static int
+syncprov_setup_accesslog(void)
+{
+ const char *text;
+ int rc = -1;
+
+ if ( !ad_reqType ) {
+ if ( slap_str2ad( "reqType", &ad_reqType, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqType, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqResult ) {
+ if ( slap_str2ad( "reqResult", &ad_reqResult, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqResult, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqDN ) {
+ if ( slap_str2ad( "reqDN", &ad_reqDN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqDN, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqEntryUUID ) {
+ if ( slap_str2ad( "reqEntryUUID", &ad_reqEntryUUID, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqEntryUUID, "
+ "is accesslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_reqNewDN ) {
+ if ( slap_str2ad( "reqNewDN", &ad_reqNewDN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute reqNewDN, "
+ "is accessslog configured?\n" );
+ return rc;
+ }
+ }
+
+ if ( !ad_minCSN ) {
+ if ( slap_str2ad( "minCSN", &ad_minCSN, &text ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: "
+ "couldn't get definition for attribute minCSN, "
+ "is accessslog configured?\n" );
+ return rc;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+enum {
+ SP_CHKPT = 1,
+ SP_SESSL,
+ SP_NOPRES,
+ SP_USEHINT,
+ SP_LOGDB
+};
+
+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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY integerMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-sessionlog-source", NULL, 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|SP_LOGDB,
+ sp_cf_gen, "( OLcfgOvAt:1.5 NAME 'olcSpSessionlogSource' "
+ "DESC 'On startup, try loading sessionlog from this subtree' "
+ "SYNTAX OMsDN 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 "
+ "$ olcSpSessionlogSource "
+ ") )",
+ 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;
+ case SP_LOGDB:
+ if ( BER_BVISEMPTY( &si->si_logbase ) ) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &si->si_logbase );
+ value_add_one( &c->rvalue_nvals, &si->si_logbase );
+ }
+ 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:
+ if ( si->si_logs )
+ si->si_logs->sl_size = 0;
+ break;
+ case SP_NOPRES:
+ si->si_nopres = 0;
+ break;
+ case SP_USEHINT:
+ si->si_usehint = 0;
+ break;
+ case SP_LOGDB:
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ ch_free( si->si_logbase.bv_val );
+ BER_BVZERO( &si->si_logbase );
+ }
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ return ARG_BAD_CONF;
+ }
+ if ( size && !BER_BVISNULL( &si->si_logbase ) ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring "
+ "internal sessionlog, accesslog source has already been "
+ "configured, this results in wasteful operation\n" );
+ }
+ sl = si->si_logs;
+ if ( !sl ) {
+ if ( !size ) break;
+ 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;
+ if ( si->si_usehint ) {
+ /* Consider we might be a delta provider, but it's ok if not */
+ (void)syncprov_setup_accesslog();
+ }
+ break;
+ case SP_LOGDB:
+ if ( si->si_logs ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring "
+ "accesslog source, internal sessionlog has already been "
+ "configured, this results in wasteful operation\n" );
+ }
+ if ( CONFIG_ONLINE_ADD( c ) ) {
+ if ( !select_backend( &c->value_ndn, 0 ) ) {
+ 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;
+ break;
+ }
+ ch_free( c->value_ndn.bv_val );
+ }
+ si->si_logbase = c->value_ndn;
+ rc = syncprov_setup_accesslog();
+ ch_free( c->value_dn.bv_val );
+ 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" );
+ 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 );
+
+ 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 );
+
+ /* 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];
+ }
+
+ if ( !BER_BVISNULL( &si->si_logbase ) ) {
+ BackendDB *db = select_backend( &si->si_logbase, 0 );
+ if ( !db ) {
+ Debug( LDAP_DEBUG_ANY, "syncprov_db_open: "
+ "configured accesslog database dn='%s' not present\n",
+ si->si_logbase.bv_val );
+ return -1;
+ }
+ }
+
+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;
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ send_ldap_result( so->s_op, &rs );
+ sonext=so->s_next;
+ if ( so->s_flags & PS_TASK_QUEUED )
+ ldap_pvt_thread_pool_retract( so->s_pool_cookie );
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ if ( !syncprov_drop_psearch( so, 0 ))
+ so->s_si = NULL;
+ }
+ 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" );
+ 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;
+
+ ldap_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 );
+ if ( si->si_logbase.bv_val )
+ ch_free( si->si_logbase.bv_val );
+ 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 );
+ return rc;
+ }
+
+ syncprov.on_bi.bi_type = "syncprov";
+ syncprov.on_bi.bi_flags = SLAPO_BFLAG_SINGLE;
+ 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..09b12dc
--- /dev/null
+++ b/servers/slapd/overlays/translucent.c
@@ -0,0 +1,1497 @@
+/* translucent.c - translucent proxy module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 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 "slap-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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY caseIgnoreMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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' "
+ "EQUALITY booleanMatch "
+ "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" );
+
+ 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 ))
+ config_push_cleanup( ca, 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" );
+
+ /* 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.be_cf_ocs )
+ config_build_entry( op, rs, cei, ca, &bv,
+ ov->db.be_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;
+ }
+
+ /* cn=config values could be deleted later, we only want one name
+ * per value for valx to match. */
+ if ( c->op != SLAP_CONFIG_ADD && strchr( c->argv[1], ',' ) ) {
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s: "
+ "Supplying multiple attribute names in a single value is "
+ "unsupported and will be disallowed in a future version\n",
+ c->log, c->argv[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 );
+ 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 );
+
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+
+ 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" );
+ 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 );
+ *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" );
+ 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 );
+ 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 );
+
+ 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;
+ TAvlnode *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 );
+
+ 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 = ldap_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) ) {
+ ldap_tavl_insert( &tc->list, re, entry_dn_cmp, ldap_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_EXT:
+ if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) {
+ AttributeAssertion *nava;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+
+ nava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx );
+ *nava = *f->f_ava;
+ n->f_ava = nava;
+
+ ber_dupbv_x( &n->f_av_value, &f->f_av_value, op->o_tmpmemctx );
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) {
+ SubstringsAssertion *nsub;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+
+ nsub = op->o_tmpalloc( sizeof(SubstringsAssertion), op->o_tmpmemctx );
+ *nsub = *f->f_sub;
+ n->f_sub = nsub;
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ))
+ ber_dupbv_x( &n->f_sub_initial, &f->f_sub_initial, op->o_tmpmemctx );
+
+ ber_bvarray_dup_x( &n->f_sub_any, f->f_sub_any, op->o_tmpmemctx );
+
+ if ( !BER_BVISNULL( &f->f_sub_final ))
+ ber_dupbv_x( &n->f_sub_final, &f->f_sub_final, op->o_tmpmemctx );
+
+ 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;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ op->o_tmpfree( f->f_av_value.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( f->f_ava, op->o_tmpmemctx );
+ break;
+ default:
+ break;
+ }
+ op->o_tmpfree( f, op->o_tmpmemctx );
+}
+
+static int
+translucent_search_cleanup( Operation *op, SlapReply *rs )
+{
+ trans_ctx *tc = op->o_callback->sc_private;
+
+ op->ors_filter = tc->orig;
+ return LDAP_SUCCESS;
+}
+
+/*
+** 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 );
+
+ 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_cleanup = (slap_response *) translucent_search_cleanup;
+ cb.sc_private = &tc;
+
+ 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;
+
+ if ( fr || !fl ) {
+ Operation op2;
+ Opheader oh;
+
+ op2 = *op;
+ oh = *op->o_hdr;
+ oh.oh_conn = op->o_conn;
+ oh.oh_connid = op->o_connid;
+ op2.o_bd = &ov->db;
+ op2.o_hdr = &oh;
+ op2.o_extra = op->o_extra;
+ op2.o_callback = &cb;
+
+ tc.attrs = op->ors_attrs;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_all_attributes;
+ tc.step |= RMT_SIDE;
+ if ( fl ) {
+ tc.step |= USE_LIST;
+ op->ors_filter = fr;
+ filter2bv_x( op, fr, &op2.ors_filterstr );
+ }
+ rc = ov->db.bd_info->bi_op_search( &op2, rs );
+ if ( op->ors_attrs == slap_anlist_all_attributes )
+ op->ors_attrs = tc.attrs;
+ if ( fl ) {
+ op->o_tmpfree( op2.ors_filterstr.bv_val, op2.o_tmpmemctx );
+ }
+ }
+
+ cb.sc_next = op->o_callback;
+ op->o_callback = &cb;
+
+ 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->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 ) {
+ TAvlnode *av;
+
+ av = ldap_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 = ldap_tavl_next( av, TAVL_DIR_RIGHT );
+ }
+ ldap_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 );
+
+ 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 = &sc;
+ }
+
+ 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" );
+
+ 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] : "" );
+
+ /* 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" );
+
+ 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" );
+ 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" );
+
+ /* 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 );
+
+ 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" );
+
+ 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" );
+
+ 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_pcsn_st.be_pcsn_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" );
+
+ 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..561d86d
--- /dev/null
+++ b/servers/slapd/overlays/unique.c
@@ -0,0 +1,1549 @@
+/* unique.c - attribute uniqueness module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2022 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 "slap-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 */
+ char serial; /* serialize execution */
+} unique_domain;
+
+typedef struct unique_data_s {
+ struct unique_domain_s *domains;
+ struct unique_domain_s *legacy;
+ char legacy_strict_set;
+ ldap_pvt_thread_mutex_t serial_mutex;
+} 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_QUOTE|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 );
+ 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 );
+ }
+ }
+
+ 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 );
+ 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 ][serialize ]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 );
+
+ 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, "serialize ",
+ STRLENOF( "serialize " ) ) == 0 ) {
+ domain->serial = 1;
+ uri_start += STRLENOF( "serialize " );
+ }
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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:
+ if ( c->argc > 2 ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "unique config: "
+ "Supplying multiple names in a single %s value is unsupported "
+ "and will be disallowed in a future version\n",
+ c->argv[0] );
+ }
+ /* FALLTHRU */
+ 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 );
+ 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 );
+ 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 );
+ }
+ 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 = legacy->strict ? slap_true_bv : slap_false_bv;
+ 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 );
+ 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: /* fallthru */
+ 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 );
+ 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 *private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n" );
+
+ private = ch_calloc ( 1, sizeof ( unique_data ) );
+ ldap_pvt_thread_mutex_init( &private->serial_mutex );
+ on->on_bi.bi_private = private;
+
+ return 0;
+}
+
+static int
+unique_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ unique_data *private = on->on_bi.bi_private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n" );
+
+ if ( private ) {
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+
+ unique_free_domain ( domains );
+ unique_free_domain ( legacy );
+ ldap_pvt_thread_mutex_destroy( &private->serial_mutex );
+ ch_free ( private );
+ on->on_bi.bi_private = 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" );
+
+ 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;
+ char *errmsg;
+ int errmsgsize;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key->bv_val );
+
+ 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;
+ memset( nop->o_ctrlflag, 0, sizeof( nop->o_ctrlflag ));
+
+ 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);
+
+ 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");
+ rc = rs->sr_err;
+ } else if(uq.count) {
+ Debug(LDAP_DEBUG_TRACE, "=> unique_search found %d records\n", uq.count );
+
+ errmsgsize = sizeof("non-unique attributes found with ") + key->bv_len;
+ errmsg = op->o_tmpalloc(errmsgsize, op->o_tmpmemctx);
+ snprintf( errmsg, errmsgsize, "non-unique attributes found with %s", key->bv_val );
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, errmsg);
+ op->o_tmpfree(errmsg, op->o_tmpmemctx);
+ rc = rs->sr_err;
+ } else {
+ Debug(LDAP_DEBUG_TRACE, "=> unique_search found no records\n" );
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ op->o_tmpfree( key->bv_val, op->o_tmpmemctx );
+
+ return(rc);
+}
+
+static int
+unique_unlock(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_callback *sc = op->o_callback;
+ unique_data *private = sc->sc_private;
+
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+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;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n",
+ op->o_req_dn.bv_val );
+
+ if ( be_shadow_update( op ) || (
+ get_relax(op) > SLAP_CONTROL_IGNORED
+ && access_allowed( op, op->ora_e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) ) {
+ 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 );
+ 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;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* 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;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ 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;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n",
+ op->o_req_dn.bv_val );
+
+ if ( !op->orm_modlist ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_modify: got empty modify op\n" );
+ return rc;
+ }
+
+ if ( be_shadow_update( op ) ) {
+ return rc;
+ }
+ if ( get_relax(op) > 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 ) ) {
+ 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;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* 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;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ 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;
+ int locked = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n",
+ op->o_req_dn.bv_val, op->orr_newrdn.bv_val );
+
+ if ( be_shadow_update( op ) ) {
+ return rc;
+ }
+ if ( get_relax(op) > 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 ) ) {
+ 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;
+
+ if ( domain->serial && !locked ) {
+ ldap_pvt_thread_mutex_lock( &private->serial_mutex );
+ locked = 1;
+ }
+
+ /* 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;
+ }
+
+ if ( locked ) {
+ if ( rc != SLAP_CB_CONTINUE ) {
+ ldap_pvt_thread_mutex_unlock( &private->serial_mutex );
+ } else {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = unique_unlock;
+ cb->sc_private = private;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+ }
+ }
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ 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..3d998e2
--- /dev/null
+++ b/servers/slapd/overlays/valsort.c
@@ -0,0 +1,585 @@
+/* valsort.c - sort attribute values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2022 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 "slap-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, **vip;
+ 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);
+ }
+
+ for ( vip = &on->on_bi.bi_private; *vip; vip = &(*vip)->vi_next )
+ /* Get to the end */ ;
+
+ vi = ch_malloc( sizeof(valsort_info) );
+ *vi = vitmp;
+ vi->vi_next = *vip;
+ *vip = 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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 );
+ 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_flags = SLAPO_BFLAG_SINGLE;
+ 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 );
+ 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 */