summaryrefslogtreecommitdiffstats
path: root/servers/slapd/overlays
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--servers/slapd/overlays/Makefile.in156
-rw-r--r--servers/slapd/overlays/README5
-rw-r--r--servers/slapd/overlays/accesslog.c2389
-rw-r--r--servers/slapd/overlays/auditlog.c240
-rw-r--r--servers/slapd/overlays/collect.c439
-rw-r--r--servers/slapd/overlays/constraint.c1231
-rw-r--r--servers/slapd/overlays/dds.c2046
-rw-r--r--servers/slapd/overlays/deref.c585
-rw-r--r--servers/slapd/overlays/dyngroup.c228
-rw-r--r--servers/slapd/overlays/dynlist.c1574
-rw-r--r--servers/slapd/overlays/memberof.c2204
-rw-r--r--servers/slapd/overlays/overlays.c44
-rw-r--r--servers/slapd/overlays/pcache.c5777
-rw-r--r--servers/slapd/overlays/ppolicy.c2526
-rw-r--r--servers/slapd/overlays/refint.c1087
-rw-r--r--servers/slapd/overlays/retcode.c1571
-rw-r--r--servers/slapd/overlays/rwm.c2759
-rw-r--r--servers/slapd/overlays/rwm.h187
-rw-r--r--servers/slapd/overlays/rwmconf.c417
-rw-r--r--servers/slapd/overlays/rwmdn.c215
-rw-r--r--servers/slapd/overlays/rwmmap.c1347
-rw-r--r--servers/slapd/overlays/seqmod.c206
-rw-r--r--servers/slapd/overlays/slapover.txt158
-rw-r--r--servers/slapd/overlays/sssvlv.c1434
-rw-r--r--servers/slapd/overlays/syncprov.c3523
-rw-r--r--servers/slapd/overlays/translucent.c1416
-rw-r--r--servers/slapd/overlays/unique.c1464
-rw-r--r--servers/slapd/overlays/valsort.c580
28 files changed, 35808 insertions, 0 deletions
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in
new file mode 100644
index 0000000..40b6fa5
--- /dev/null
+++ b/servers/slapd/overlays/Makefile.in
@@ -0,0 +1,156 @@
+# Makefile.in for overlays
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2003-2018 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = overlays.c \
+ accesslog.c \
+ auditlog.c \
+ constraint.c \
+ dds.c \
+ deref.c \
+ dyngroup.c \
+ dynlist.c \
+ memberof.c \
+ pcache.c \
+ collect.c \
+ ppolicy.c \
+ refint.c \
+ retcode.c \
+ rwm.c rwmconf.c rwmdn.c rwmmap.c \
+ seqmod.c \
+ sssvlv.c \
+ syncprov.c \
+ translucent.c \
+ unique.c \
+ valsort.c
+OBJS = statover.o \
+ @SLAPD_STATIC_OVERLAYS@ \
+ overlays.o
+
+# Add here the objs that are needed by overlays, but do not make it
+# into SLAPD_STATIC_OVERLAYS...
+OBJDEP=rwm.o rwmconf.o rwmdn.o rwmmap.o
+
+LTONLY_MOD = $(LTONLY_mod)
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+MOD_DEFS = -DSLAPD_IMPORT
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBRARY = ../liboverlays.a
+PROGRAMS = @SLAPD_DYNAMIC_OVERLAYS@
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+static: $(LIBRARY)
+
+dynamic: $(PROGRAMS)
+
+accesslog.la : accesslog.lo
+ $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS)
+
+auditlog.la : auditlog.lo
+ $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS)
+
+constraint.la : constraint.lo
+ $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS)
+
+dds.la : dds.lo
+ $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS)
+
+deref.la : deref.lo
+ $(LTLINK_MOD) -module -o $@ deref.lo version.lo $(LINK_LIBS)
+
+dyngroup.la : dyngroup.lo
+ $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS)
+
+dynlist.la : dynlist.lo
+ $(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS)
+
+memberof.la : memberof.lo
+ $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS)
+
+pcache.la : pcache.lo
+ $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS)
+
+collect.la : collect.lo
+ $(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS)
+
+ppolicy.la : ppolicy.lo
+ $(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS)
+
+refint.la : refint.lo
+ $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS)
+
+retcode.la : retcode.lo
+ $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS)
+
+rwm_x.o: rwm.o rwmconf.o rwmdn.o rwmmap.o
+ $(LD) -r -o $@ rwm.o rwmconf.o rwmdn.o rwmmap.o
+
+rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo
+ $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS)
+
+seqmod.la : seqmod.lo
+ $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS)
+
+sssvlv.la : sssvlv.lo
+ $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS)
+
+syncprov.la : syncprov.lo
+ $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS)
+
+translucent.la : translucent.lo
+ $(LTLINK_MOD) -module -o $@ translucent.lo version.lo $(LINK_LIBS)
+
+unique.la : unique.lo
+ $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS)
+
+valsort.la : valsort.lo
+ $(LTLINK_MOD) -module -o $@ valsort.lo version.lo $(LINK_LIBS)
+
+install-local: $(PROGRAMS)
+ @if test -n "$?" ; then \
+ $(MKDIR) $(DESTDIR)$(moduledir); \
+ $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\
+ fi
+
+MKDEPFLAG = -l
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+ $(LTCOMPILE_MOD) $<
+
+statover.o: statover.c $(srcdir)/../slap.h
+
+$(LIBRARY): $(OBJS) version.lo
+ $(AR) rs $@ $(OBJS)
+
+# Must fixup depends for non-libtool objects
+depend-local: depend-common
+ @if test -n "$(OBJS)"; then \
+ OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \
+ SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \
+ mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \
+ $(RM) Makefile.bak; fi
+
+veryclean-local:
+ $(RM) statover.c
+
diff --git a/servers/slapd/overlays/README b/servers/slapd/overlays/README
new file mode 100644
index 0000000..e426e4b
--- /dev/null
+++ b/servers/slapd/overlays/README
@@ -0,0 +1,5 @@
+This directory contains a number of SLAPD overlays, some
+project-maintained, some not. Some are generally usable,
+others are purely experimental. Additional overlays can
+be found in the contrib/slapd-modules directory.
+
diff --git a/servers/slapd/overlays/accesslog.c b/servers/slapd/overlays/accesslog.c
new file mode 100644
index 0000000..ca83568
--- /dev/null
+++ b/servers/slapd/overlays/accesslog.c
@@ -0,0 +1,2389 @@
+/* accesslog.c - log operations for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2018 The OpenLDAP Foundation.
+ * Portions copyright 2004-2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_ACCESSLOG
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+
+#define LOG_OP_ADD 0x001
+#define LOG_OP_DELETE 0x002
+#define LOG_OP_MODIFY 0x004
+#define LOG_OP_MODRDN 0x008
+#define LOG_OP_COMPARE 0x010
+#define LOG_OP_SEARCH 0x020
+#define LOG_OP_BIND 0x040
+#define LOG_OP_UNBIND 0x080
+#define LOG_OP_ABANDON 0x100
+#define LOG_OP_EXTENDED 0x200
+#define LOG_OP_UNKNOWN 0x400
+
+#define LOG_OP_WRITES (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN)
+#define LOG_OP_READS (LOG_OP_COMPARE|LOG_OP_SEARCH)
+#define LOG_OP_SESSION (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON)
+#define LOG_OP_ALL (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \
+ LOG_OP_EXTENDED|LOG_OP_UNKNOWN)
+
+typedef struct log_attr {
+ struct log_attr *next;
+ AttributeDescription *attr;
+} log_attr;
+
+typedef struct log_base {
+ struct log_base *lb_next;
+ slap_mask_t lb_ops;
+ struct berval lb_base;
+ struct berval lb_line;
+} log_base;
+
+typedef struct log_info {
+ BackendDB *li_db;
+ struct berval li_db_suffix;
+ slap_mask_t li_ops;
+ int li_age;
+ int li_cycle;
+ struct re_s *li_task;
+ Filter *li_oldf;
+ Entry *li_old;
+ log_attr *li_oldattrs;
+ struct berval li_uuid;
+ int li_success;
+ log_base *li_bases;
+ ldap_pvt_thread_rmutex_t li_op_rmutex;
+ ldap_pvt_thread_mutex_t li_log_mutex;
+} log_info;
+
+static ConfigDriver log_cf_gen;
+
+enum {
+ LOG_DB = 1,
+ LOG_OPS,
+ LOG_PURGE,
+ LOG_SUCCESS,
+ LOG_OLD,
+ LOG_OLDATTR,
+ LOG_BASE
+};
+
+static ConfigTable log_cfats[] = {
+ { "logdb", "suffix", 2, 2, 0, ARG_DN|ARG_MAGIC|LOG_DB,
+ log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' "
+ "DESC 'Suffix of database for log content' "
+ "SUP distinguishedName SINGLE-VALUE )", NULL, NULL },
+ { "logops", "op|writes|reads|session|all", 2, 0, 0,
+ ARG_MAGIC|LOG_OPS,
+ log_cf_gen, "( OLcfgOvAt:4.2 NAME 'olcAccessLogOps' "
+ "DESC 'Operation types to log' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "logpurge", "age> <interval", 3, 3, 0, ARG_MAGIC|LOG_PURGE,
+ log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' "
+ "DESC 'Log cleanup parameters' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logsuccess", NULL, 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|LOG_SUCCESS,
+ log_cf_gen, "( OLcfgOvAt:4.4 NAME 'olcAccessLogSuccess' "
+ "DESC 'Log successful ops only' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "logold", "filter", 2, 2, 0, ARG_MAGIC|LOG_OLD,
+ log_cf_gen, "( OLcfgOvAt:4.5 NAME 'olcAccessLogOld' "
+ "DESC 'Log old values when modifying entries matching the filter' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "logoldattr", "attrs", 2, 0, 0, ARG_MAGIC|LOG_OLDATTR,
+ log_cf_gen, "( OLcfgOvAt:4.6 NAME 'olcAccessLogOldAttr' "
+ "DESC 'Log old values of these attributes even if unmodified' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "logbase", "op|writes|reads|session|all< <baseDN", 3, 3, 0,
+ ARG_MAGIC|LOG_BASE,
+ log_cf_gen, "( OLcfgOvAt:4.7 NAME 'olcAccessLogBase' "
+ "DESC 'Operation types to log under a specific branch' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs log_cfocs[] = {
+ { "( OLcfgOvOc:4.1 "
+ "NAME 'olcAccessLogConfig' "
+ "DESC 'Access log configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcAccessLogDB "
+ "MAY ( olcAccessLogOps $ olcAccessLogPurge $ olcAccessLogSuccess $ "
+ "olcAccessLogOld $ olcAccessLogOldAttr $ olcAccessLogBase ) )",
+ Cft_Overlay, log_cfats },
+ { NULL }
+};
+
+static slap_verbmasks logops[] = {
+ { BER_BVC("all"), LOG_OP_ALL },
+ { BER_BVC("writes"), LOG_OP_WRITES },
+ { BER_BVC("session"), LOG_OP_SESSION },
+ { BER_BVC("reads"), LOG_OP_READS },
+ { BER_BVC("add"), LOG_OP_ADD },
+ { BER_BVC("delete"), LOG_OP_DELETE },
+ { BER_BVC("modify"), LOG_OP_MODIFY },
+ { BER_BVC("modrdn"), LOG_OP_MODRDN },
+ { BER_BVC("compare"), LOG_OP_COMPARE },
+ { BER_BVC("search"), LOG_OP_SEARCH },
+ { BER_BVC("bind"), LOG_OP_BIND },
+ { BER_BVC("unbind"), LOG_OP_UNBIND },
+ { BER_BVC("abandon"), LOG_OP_ABANDON },
+ { BER_BVC("extended"), LOG_OP_EXTENDED },
+ { BER_BVC("unknown"), LOG_OP_UNKNOWN },
+ { BER_BVNULL, 0 }
+};
+
+/* Start with "add" in logops */
+#define EN_OFFSET 4
+
+enum {
+ LOG_EN_ADD = 0,
+ LOG_EN_DELETE,
+ LOG_EN_MODIFY,
+ LOG_EN_MODRDN,
+ LOG_EN_COMPARE,
+ LOG_EN_SEARCH,
+ LOG_EN_BIND,
+ LOG_EN_UNBIND,
+ LOG_EN_ABANDON,
+ LOG_EN_EXTENDED,
+ LOG_EN_UNKNOWN,
+ LOG_EN__COUNT
+};
+
+static ObjectClass *log_ocs[LOG_EN__COUNT], *log_container,
+ *log_oc_read, *log_oc_write;
+
+#define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5"
+
+#define LOG_SCHEMA_AT LOG_SCHEMA_ROOT ".1"
+#define LOG_SCHEMA_OC LOG_SCHEMA_ROOT ".2"
+#define LOG_SCHEMA_SYN LOG_SCHEMA_ROOT ".3"
+
+static AttributeDescription *ad_reqDN, *ad_reqStart, *ad_reqEnd, *ad_reqType,
+ *ad_reqSession, *ad_reqResult, *ad_reqAuthzID, *ad_reqControls,
+ *ad_reqRespControls, *ad_reqMethod, *ad_reqAssertion, *ad_reqNewRDN,
+ *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod,
+ *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries,
+ *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData,
+ *ad_reqId, *ad_reqMessage, *ad_reqVersion, *ad_reqDerefAliases,
+ *ad_reqReferral, *ad_reqOld, *ad_auditContext, *ad_reqEntryUUID;
+
+static int
+logSchemaControlValidate(
+ Syntax *syntax,
+ struct berval *val );
+
+char *mrControl[] = {
+ "objectIdentifierFirstComponentMatch",
+ NULL
+};
+
+static struct {
+ char *oid;
+ slap_syntax_defs_rec syn;
+ char **mrs;
+} lsyntaxes[] = {
+ { LOG_SCHEMA_SYN ".1" ,
+ { "( " LOG_SCHEMA_SYN ".1 DESC 'Control' )",
+ SLAP_SYNTAX_HIDE,
+ NULL,
+ logSchemaControlValidate,
+ NULL },
+ mrControl },
+ { NULL }
+};
+
+static struct {
+ char *at;
+ AttributeDescription **ad;
+} lattrs[] = {
+ { "( " LOG_SCHEMA_AT ".1 NAME 'reqDN' "
+ "DESC 'Target DN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqDN },
+ { "( " LOG_SCHEMA_AT ".2 NAME 'reqStart' "
+ "DESC 'Start time of request' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE )", &ad_reqStart },
+ { "( " LOG_SCHEMA_AT ".3 NAME 'reqEnd' "
+ "DESC 'End time of request' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE )", &ad_reqEnd },
+ { "( " LOG_SCHEMA_AT ".4 NAME 'reqType' "
+ "DESC 'Type of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqType },
+ { "( " LOG_SCHEMA_AT ".5 NAME 'reqSession' "
+ "DESC 'Session ID of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqSession },
+ { "( " LOG_SCHEMA_AT ".6 NAME 'reqAuthzID' "
+ "DESC 'Authorization ID of requestor' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqAuthzID },
+ { "( " LOG_SCHEMA_AT ".7 NAME 'reqResult' "
+ "DESC 'Result code of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqResult },
+ { "( " LOG_SCHEMA_AT ".8 NAME 'reqMessage' "
+ "DESC 'Error text of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqMessage },
+ { "( " LOG_SCHEMA_AT ".9 NAME 'reqReferral' "
+ "DESC 'Referrals returned for request' "
+ "SUP labeledURI )", &ad_reqReferral },
+ { "( " LOG_SCHEMA_AT ".10 NAME 'reqControls' "
+ "DESC 'Request controls' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX " LOG_SCHEMA_SYN ".1 "
+ "X-ORDERED 'VALUES' )", &ad_reqControls },
+ { "( " LOG_SCHEMA_AT ".11 NAME 'reqRespControls' "
+ "DESC 'Response controls of request' "
+ "EQUALITY objectIdentifierFirstComponentMatch "
+ "SYNTAX " LOG_SCHEMA_SYN ".1 "
+ "X-ORDERED 'VALUES' )", &ad_reqRespControls },
+ { "( " LOG_SCHEMA_AT ".12 NAME 'reqId' "
+ "DESC 'ID of Request to Abandon' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqId },
+ { "( " LOG_SCHEMA_AT ".13 NAME 'reqVersion' "
+ "DESC 'Protocol version of Bind request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqVersion },
+ { "( " LOG_SCHEMA_AT ".14 NAME 'reqMethod' "
+ "DESC 'Bind method of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqMethod },
+ { "( " LOG_SCHEMA_AT ".15 NAME 'reqAssertion' "
+ "DESC 'Compare Assertion of request' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqAssertion },
+ { "( " LOG_SCHEMA_AT ".16 NAME 'reqMod' "
+ "DESC 'Modifications of request' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString )", &ad_reqMod },
+ { "( " LOG_SCHEMA_AT ".17 NAME 'reqOld' "
+ "DESC 'Old values of entry before request completed' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString )", &ad_reqOld },
+ { "( " LOG_SCHEMA_AT ".18 NAME 'reqNewRDN' "
+ "DESC 'New RDN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewRDN },
+ { "( " LOG_SCHEMA_AT ".19 NAME 'reqDeleteOldRDN' "
+ "DESC 'Delete old RDN' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", &ad_reqDeleteOldRDN },
+ { "( " LOG_SCHEMA_AT ".20 NAME 'reqNewSuperior' "
+ "DESC 'New superior DN of request' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN "
+ "SINGLE-VALUE )", &ad_reqNewSuperior },
+ { "( " LOG_SCHEMA_AT ".21 NAME 'reqScope' "
+ "DESC 'Scope of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqScope },
+ { "( " LOG_SCHEMA_AT ".22 NAME 'reqDerefAliases' "
+ "DESC 'Disposition of Aliases in request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqDerefAliases },
+ { "( " LOG_SCHEMA_AT ".23 NAME 'reqAttrsOnly' "
+ "DESC 'Attributes and values of request' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", &ad_reqAttrsOnly },
+ { "( " LOG_SCHEMA_AT ".24 NAME 'reqFilter' "
+ "DESC 'Filter of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", &ad_reqFilter },
+ { "( " LOG_SCHEMA_AT ".25 NAME 'reqAttr' "
+ "DESC 'Attributes of request' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", &ad_reqAttr },
+ { "( " LOG_SCHEMA_AT ".26 NAME 'reqSizeLimit' "
+ "DESC 'Size limit of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqSizeLimit },
+ { "( " LOG_SCHEMA_AT ".27 NAME 'reqTimeLimit' "
+ "DESC 'Time limit of request' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqTimeLimit },
+ { "( " LOG_SCHEMA_AT ".28 NAME 'reqEntries' "
+ "DESC 'Number of entries returned' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", &ad_reqEntries },
+ { "( " LOG_SCHEMA_AT ".29 NAME 'reqData' "
+ "DESC 'Data of extended request' "
+ "EQUALITY octetStringMatch "
+ "SUBSTR octetStringSubstringsMatch "
+ "SYNTAX OMsOctetString "
+ "SINGLE-VALUE )", &ad_reqData },
+
+ /*
+ * from <draft-chu-ldap-logschema-01.txt>:
+ *
+
+ ( LOG_SCHEMA_AT .30 NAME 'auditContext'
+ DESC 'DN of auditContainer'
+ EQUALITY distinguishedNameMatch
+ SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
+ SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )
+
+ * - removed EQUALITY matchingRule
+ * - changed directoryOperation in dSAOperation
+ */
+ { "( " LOG_SCHEMA_AT ".30 NAME 'auditContext' "
+ "DESC 'DN of auditContainer' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )", &ad_auditContext },
+
+ /*
+ * ITS#6656
+ */
+ { "( " LOG_SCHEMA_AT ".31 NAME 'reqEntryUUID' "
+ "DESC 'UUID of entry' "
+ "EQUALITY UUIDMatch "
+ "ORDERING UUIDOrderingMatch "
+ "SYNTAX 1.3.6.1.1.16.1 "
+ "SINGLE-VALUE )", &ad_reqEntryUUID },
+ { NULL, NULL }
+};
+
+static struct {
+ char *ot;
+ ObjectClass **oc;
+} locs[] = {
+ { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' "
+ "DESC 'AuditLog container' "
+ "SUP top STRUCTURAL "
+ "MAY ( cn $ reqStart $ reqEnd ) )", &log_container },
+ { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' "
+ "DESC 'OpenLDAP request auditing' "
+ "SUP top STRUCTURAL "
+ "MUST ( reqStart $ reqType $ reqSession ) "
+ "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ "
+ "reqResult $ reqMessage $ reqReferral $ reqEntryUUID ) )",
+ &log_ocs[LOG_EN_UNBIND] },
+ { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' "
+ "DESC 'OpenLDAP read request record' "
+ "SUP auditObject STRUCTURAL )", &log_oc_read },
+ { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' "
+ "DESC 'OpenLDAP write request record' "
+ "SUP auditObject STRUCTURAL )", &log_oc_write },
+ { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' "
+ "DESC 'Abandon operation' "
+ "SUP auditObject STRUCTURAL "
+ "MUST reqId )", &log_ocs[LOG_EN_ABANDON] },
+ { "( " LOG_SCHEMA_OC ".5 NAME 'auditAdd' "
+ "DESC 'Add operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MUST reqMod )", &log_ocs[LOG_EN_ADD] },
+ { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' "
+ "DESC 'Bind operation' "
+ "SUP auditObject STRUCTURAL "
+ "MUST ( reqVersion $ reqMethod ) )", &log_ocs[LOG_EN_BIND] },
+ { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' "
+ "DESC 'Compare operation' "
+ "SUP auditReadObject STRUCTURAL "
+ "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] },
+ { "( " LOG_SCHEMA_OC ".8 NAME 'auditDelete' "
+ "DESC 'Delete operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MAY reqOld )", &log_ocs[LOG_EN_DELETE] },
+ { "( " LOG_SCHEMA_OC ".9 NAME 'auditModify' "
+ "DESC 'Modify operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MAY reqOld MUST reqMod )", &log_ocs[LOG_EN_MODIFY] },
+ { "( " LOG_SCHEMA_OC ".10 NAME 'auditModRDN' "
+ "DESC 'ModRDN operation' "
+ "SUP auditWriteObject STRUCTURAL "
+ "MUST ( reqNewRDN $ reqDeleteOldRDN ) "
+ "MAY ( reqNewSuperior $ reqMod $ reqOld ) )", &log_ocs[LOG_EN_MODRDN] },
+ { "( " LOG_SCHEMA_OC ".11 NAME 'auditSearch' "
+ "DESC 'Search operation' "
+ "SUP auditReadObject STRUCTURAL "
+ "MUST ( reqScope $ reqDerefAliases $ reqAttrsonly ) "
+ "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ "
+ "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] },
+ { "( " LOG_SCHEMA_OC ".12 NAME 'auditExtended' "
+ "DESC 'Extended operation' "
+ "SUP auditObject STRUCTURAL "
+ "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] },
+ { NULL, NULL }
+};
+
+#define RDNEQ "reqStart="
+
+/* Our time intervals are of the form [ddd+]hh:mm[:ss]
+ * If a field is present, it must be two digits. (Except for
+ * days, which can be arbitrary width.)
+ */
+static int
+log_age_parse(char *agestr)
+{
+ int t1, t2;
+ int gotdays = 0;
+ char *endptr;
+
+ t1 = strtol( agestr, &endptr, 10 );
+ /* Is there a days delimiter? */
+ if ( *endptr == '+' ) {
+ /* 32 bit time only covers about 68 years */
+ if ( t1 < 0 || t1 > 25000 )
+ return -1;
+ t1 *= 24;
+ gotdays = 1;
+ agestr = endptr + 1;
+ } else {
+ if ( agestr[2] != ':' ) {
+ /* No valid delimiter found, fail */
+ return -1;
+ }
+ t1 *= 60;
+ agestr += 3;
+ }
+
+ t2 = atoi( agestr );
+ t1 += t2;
+
+ if ( agestr[2] ) {
+ /* if there's a delimiter, it can only be a colon */
+ if ( agestr[2] != ':' )
+ return -1;
+ } else {
+ /* If we're at the end of the string, and we started with days,
+ * fail because we expected to find minutes too.
+ */
+ return gotdays ? -1 : t1 * 60;
+ }
+
+ agestr += 3;
+ t2 = atoi( agestr );
+
+ /* last field can only be seconds */
+ if ( agestr[2] && ( agestr[2] != ':' || !gotdays ))
+ return -1;
+ t1 *= 60;
+ t1 += t2;
+
+ if ( agestr[2] ) {
+ agestr += 3;
+ if ( agestr[2] )
+ return -1;
+ t1 *= 60;
+ t1 += atoi( agestr );
+ } else if ( gotdays ) {
+ /* only got days+hh:mm */
+ t1 *= 60;
+ }
+ return t1;
+}
+
+static void
+log_age_unparse( int age, struct berval *agebv, size_t size )
+{
+ int dd, hh, mm, ss, len;
+ char *ptr;
+
+ assert( size > 0 );
+
+ ss = age % 60;
+ age /= 60;
+ mm = age % 60;
+ age /= 60;
+ hh = age % 24;
+ age /= 24;
+ dd = age;
+
+ ptr = agebv->bv_val;
+
+ if ( dd ) {
+ len = snprintf( ptr, size, "%d+", dd );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ }
+ len = snprintf( ptr, size, "%02d:%02d", hh, mm );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ if ( ss ) {
+ len = snprintf( ptr, size, ":%02d", ss );
+ assert( len >= 0 && (unsigned) len < size );
+ size -= len;
+ ptr += len;
+ }
+
+ agebv->bv_len = ptr - agebv->bv_val;
+}
+
+static slap_callback nullsc;
+
+#define PURGE_INCREMENT 100
+
+typedef struct purge_data {
+ int slots;
+ int used;
+ BerVarray dn;
+ BerVarray ndn;
+ struct berval csn; /* an arbitrary old CSN */
+} purge_data;
+
+static int
+log_old_lookup( Operation *op, SlapReply *rs )
+{
+ purge_data *pd = op->o_callback->sc_private;
+ Attribute *a;
+
+ if ( rs->sr_type != REP_SEARCH) return 0;
+
+ if ( slapd_shutdown ) return 0;
+
+ /* Remember max CSN: should always be the last entry
+ * seen, since log entries are ordered chronologically...
+ */
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryCSN );
+ if ( a ) {
+ ber_len_t len = a->a_nvals[0].bv_len;
+ /* Paranoid len check, normalized CSNs are always the same length */
+ if ( len > LDAP_PVT_CSNSTR_BUFSIZE )
+ len = LDAP_PVT_CSNSTR_BUFSIZE;
+ if ( memcmp( a->a_nvals[0].bv_val, pd->csn.bv_val, len ) > 0 ) {
+ AC_MEMCPY( pd->csn.bv_val, a->a_nvals[0].bv_val, len );
+ pd->csn.bv_len = len;
+ }
+ }
+ if ( pd->used >= pd->slots ) {
+ pd->slots += PURGE_INCREMENT;
+ pd->dn = ch_realloc( pd->dn, pd->slots * sizeof( struct berval ));
+ pd->ndn = ch_realloc( pd->ndn, pd->slots * sizeof( struct berval ));
+ }
+ ber_dupbv( &pd->dn[pd->used], &rs->sr_entry->e_name );
+ ber_dupbv( &pd->ndn[pd->used], &rs->sr_entry->e_nname );
+ pd->used++;
+ return 0;
+}
+
+/* Periodically search for old entries in the log database and delete them */
+static void *
+accesslog_purge( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ struct log_info *li = rtask->arg;
+
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ SlapReply rs = {REP_RESULT};
+ slap_callback cb = { NULL, log_old_lookup, NULL, NULL, NULL };
+ Filter f;
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ purge_data pd = {0};
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE];
+ char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ time_t old = slap_get_time();
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ f.f_choice = LDAP_FILTER_LE;
+ f.f_ava = &ava;
+ f.f_next = NULL;
+
+ ava.aa_desc = ad_reqStart;
+ ava.aa_value.bv_val = timebuf;
+ ava.aa_value.bv_len = sizeof(timebuf);
+
+ old -= li->li_age;
+ slap_timestamp( &old, &ava.aa_value );
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_bd = li->li_db;
+ op->o_dn = li->li_db->be_rootdn;
+ op->o_ndn = li->li_db->be_rootndn;
+ op->o_req_dn = li->li_db->be_suffix[0];
+ op->o_req_ndn = li->li_db->be_nsuffix[0];
+ op->o_callback = &cb;
+ op->ors_scope = LDAP_SCOPE_ONELEVEL;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_filter = &f;
+ filter2bv_x( op, &f, &op->ors_filterstr );
+ op->ors_attrs = slap_anlist_no_attrs;
+ op->ors_attrsonly = 1;
+
+ pd.csn.bv_len = sizeof( csnbuf );
+ pd.csn.bv_val = csnbuf;
+ csnbuf[0] = '\0';
+ cb.sc_private = &pd;
+
+ op->o_bd->be_search( op, &rs );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+
+ if ( pd.used ) {
+ int i;
+
+ /* delete the expired entries */
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_callback = &nullsc;
+ op->o_csn = pd.csn;
+ op->o_dont_replicate = 1;
+
+ for (i=0; i<pd.used; i++) {
+ op->o_req_dn = pd.dn[i];
+ op->o_req_ndn = pd.ndn[i];
+ if ( !slapd_shutdown ) {
+ rs_reinit( &rs, REP_RESULT );
+ op->o_bd->be_delete( op, &rs );
+ }
+ ch_free( pd.ndn[i].bv_val );
+ ch_free( pd.dn[i].bv_val );
+ ldap_pvt_thread_pool_pausecheck( &connection_pool );
+ }
+ ch_free( pd.ndn );
+ ch_free( pd.dn );
+
+ {
+ Modifications mod;
+ struct berval bv[2];
+ rs_reinit( &rs, REP_RESULT );
+ /* update context's entryCSN to reflect oldest CSN */
+ mod.sml_numvals = 1;
+ mod.sml_values = bv;
+ bv[0] = pd.csn;
+ BER_BVZERO(&bv[1]);
+ mod.sml_nvalues = NULL;
+ mod.sml_desc = slap_schema.si_ad_entryCSN;
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = SLAP_MOD_INTERNAL;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->orm_no_opattrs = 1;
+ op->o_req_dn = li->li_db->be_suffix[0];
+ op->o_req_ndn = li->li_db->be_nsuffix[0];
+ op->o_no_schema_check = 1;
+ op->o_managedsait = SLAP_CONTROL_NONCRITICAL;
+ op->o_bd->be_modify( op, &rs );
+ if ( mod.sml_next ) {
+ slap_mods_free( mod.sml_next, 1 );
+ }
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+static int
+log_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ struct log_info *li = on->on_bi.bi_private;
+ int rc = 0;
+ slap_mask_t tmask = 0;
+ char agebuf[2*STRLENOF("ddddd+hh:mm:ss ")];
+ struct berval agebv, cyclebv;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch( c->type ) {
+ case LOG_DB:
+ if ( !BER_BVISEMPTY( &li->li_db_suffix )) {
+ value_add_one( &c->rvalue_vals, &li->li_db_suffix );
+ value_add_one( &c->rvalue_nvals, &li->li_db_suffix );
+ } else if ( li->li_db ) {
+ value_add_one( &c->rvalue_vals, li->li_db->be_suffix );
+ value_add_one( &c->rvalue_nvals, li->li_db->be_nsuffix );
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "accesslog: \"logdb <suffix>\" must be specified" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ break;
+ }
+ break;
+ case LOG_OPS:
+ rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals );
+ break;
+ case LOG_PURGE:
+ if ( !li->li_age ) {
+ rc = 1;
+ break;
+ }
+ agebv.bv_val = agebuf;
+ log_age_unparse( li->li_age, &agebv, sizeof( agebuf ) );
+ agebv.bv_val[agebv.bv_len] = ' ';
+ agebv.bv_len++;
+ cyclebv.bv_val = agebv.bv_val + agebv.bv_len;
+ log_age_unparse( li->li_cycle, &cyclebv, sizeof( agebuf ) - agebv.bv_len );
+ agebv.bv_len += cyclebv.bv_len;
+ value_add_one( &c->rvalue_vals, &agebv );
+ break;
+ case LOG_SUCCESS:
+ if ( li->li_success )
+ c->value_int = li->li_success;
+ else
+ rc = 1;
+ break;
+ case LOG_OLD:
+ if ( li->li_oldf ) {
+ filter2bv( li->li_oldf, &agebv );
+ ber_bvarray_add( &c->rvalue_vals, &agebv );
+ }
+ else
+ rc = 1;
+ break;
+ case LOG_OLDATTR:
+ if ( li->li_oldattrs ) {
+ log_attr *la;
+
+ for ( la = li->li_oldattrs; la; la=la->next )
+ value_add_one( &c->rvalue_vals, &la->attr->ad_cname );
+ }
+ else
+ rc = 1;
+ break;
+ case LOG_BASE:
+ if ( li->li_bases ) {
+ log_base *lb;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ value_add_one( &c->rvalue_vals, &lb->lb_line );
+ }
+ else
+ rc = 1;
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch( c->type ) {
+ case LOG_DB:
+ /* noop. this should always be a valid backend. */
+ break;
+ case LOG_OPS:
+ if ( c->valx < 0 ) {
+ li->li_ops = 0;
+ } else {
+ rc = verbs_to_mask( 1, &c->line, logops, &tmask );
+ if ( rc == 0 )
+ li->li_ops &= ~tmask;
+ }
+ break;
+ case LOG_PURGE:
+ if ( li->li_task ) {
+ struct re_s *re = li->li_task;
+ li->li_task = NULL;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ))
+ ldap_pvt_runqueue_stoptask( &slapd_rq, re );
+ ldap_pvt_runqueue_remove( &slapd_rq, re );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ li->li_age = 0;
+ li->li_cycle = 0;
+ break;
+ case LOG_SUCCESS:
+ li->li_success = 0;
+ break;
+ case LOG_OLD:
+ if ( li->li_oldf ) {
+ filter_free( li->li_oldf );
+ li->li_oldf = NULL;
+ }
+ break;
+ case LOG_OLDATTR:
+ if ( c->valx < 0 ) {
+ log_attr *la, *ln;
+
+ for ( la = li->li_oldattrs; la; la = ln ) {
+ ln = la->next;
+ ch_free( la );
+ }
+ } else {
+ log_attr *la = NULL, **lp;
+ int i;
+
+ for ( lp = &li->li_oldattrs, i=0; i < c->valx; i++ ) {
+ la = *lp;
+ lp = &la->next;
+ }
+ *lp = la->next;
+ ch_free( la );
+ }
+ break;
+ case LOG_BASE:
+ if ( c->valx < 0 ) {
+ log_base *lb, *ln;
+
+ for ( lb = li->li_bases; lb; lb = ln ) {
+ ln = lb->lb_next;
+ ch_free( lb );
+ }
+ } else {
+ log_base *lb = NULL, **lp;
+ int i;
+
+ for ( lp = &li->li_bases, i=0; i < c->valx; i++ ) {
+ lb = *lp;
+ lp = &lb->lb_next;
+ }
+ *lp = lb->lb_next;
+ ch_free( lb );
+ }
+ break;
+ }
+ break;
+ default:
+ switch( c->type ) {
+ case LOG_DB:
+ if ( CONFIG_ONLINE_ADD( c )) {
+ li->li_db = select_backend( &c->value_ndn, 0 );
+ if ( !li->li_db ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> no matching backend found for suffix",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n",
+ c->log, c->cr_msg, c->value_dn.bv_val );
+ rc = 1;
+ }
+ ch_free( c->value_ndn.bv_val );
+ } else {
+ li->li_db_suffix = c->value_ndn;
+ }
+ ch_free( c->value_dn.bv_val );
+ break;
+ case LOG_OPS:
+ rc = verbs_to_mask( c->argc, c->argv, logops, &tmask );
+ if ( rc == 0 )
+ li->li_ops |= tmask;
+ break;
+ case LOG_PURGE:
+ li->li_age = log_age_parse( c->argv[1] );
+ if ( li->li_age < 1 ) {
+ rc = 1;
+ } else {
+ li->li_cycle = log_age_parse( c->argv[2] );
+ if ( li->li_cycle < 1 ) {
+ rc = 1;
+ } else if ( slapMode & SLAP_SERVER_MODE ) {
+ struct re_s *re = li->li_task;
+ if ( re )
+ re->interval.tv_sec = li->li_cycle;
+ else {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ li->li_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ li->li_cycle, accesslog_purge, li,
+ "accesslog_purge", li->li_db ?
+ li->li_db->be_suffix[0].bv_val :
+ c->be->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ }
+ }
+ break;
+ case LOG_SUCCESS:
+ li->li_success = c->value_int;
+ break;
+ case LOG_OLD:
+ li->li_oldf = str2filter( c->argv[1] );
+ if ( !li->li_oldf ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "bad filter!" );
+ rc = 1;
+ }
+ break;
+ case LOG_OLDATTR: {
+ int i;
+ AttributeDescription *ad;
+ const char *text;
+
+ for ( i=1; i< c->argc; i++ ) {
+ ad = NULL;
+ if ( slap_str2ad( c->argv[i], &ad, &text ) == LDAP_SUCCESS ) {
+ log_attr *la = ch_malloc( sizeof( log_attr ));
+ la->attr = ad;
+ la->next = li->li_oldattrs;
+ li->li_oldattrs = la;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s <%s>: %s",
+ c->argv[0], c->argv[i], text );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ }
+ }
+ break;
+ case LOG_BASE: {
+ slap_mask_t m = 0;
+ rc = verbstring_to_mask( logops, c->argv[1], '|', &m );
+ if ( rc == 0 ) {
+ struct berval dn, ndn;
+ ber_str2bv( c->argv[2], 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+ if ( rc == 0 ) {
+ log_base *lb;
+ struct berval mbv;
+ char *ptr;
+ mask_to_verbstring( logops, m, '|', &mbv );
+ lb = ch_malloc( sizeof( log_base ) + mbv.bv_len + ndn.bv_len + 3 + 1 );
+ lb->lb_line.bv_val = (char *)(lb + 1);
+ lb->lb_line.bv_len = mbv.bv_len + ndn.bv_len + 3;
+ ptr = lutil_strcopy( lb->lb_line.bv_val, mbv.bv_val );
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ lb->lb_base.bv_val = ptr;
+ lb->lb_base.bv_len = ndn.bv_len;
+ ptr = lutil_strcopy( ptr, ndn.bv_val );
+ *ptr++ = '"';
+ lb->lb_ops = m;
+ lb->lb_next = li->li_bases;
+ li->li_bases = lb;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: %s",
+ c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ rc = ARG_BAD_CONF;
+ }
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid ops: %s",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ rc = ARG_BAD_CONF;
+ }
+ }
+ break;
+ }
+ break;
+ }
+ return rc;
+}
+
+static int
+logSchemaControlValidate(
+ Syntax *syntax,
+ struct berval *valp )
+{
+ struct berval val, bv;
+ ber_len_t i;
+ int rc = LDAP_SUCCESS;
+
+ assert( valp != NULL );
+
+ val = *valp;
+
+ /* check minimal size */
+ if ( val.bv_len < STRLENOF( "{*}" ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ val.bv_len--;
+
+ /* check SEQUENCE boundaries */
+ if ( val.bv_val[ 0 ] != '{' /*}*/ ||
+ val.bv_val[ val.bv_len ] != /*{*/ '}' )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ /* extract and check OID */
+ for ( i = 1; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ bv.bv_val = &val.bv_val[ i ];
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( ASCII_SPACE( val.bv_val[ i ] ) )
+ {
+ break;
+ }
+ }
+
+ bv.bv_len = &val.bv_val[ i ] - bv.bv_val;
+
+ rc = numericoidValidate( NULL, &bv );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( val.bv_val[ i ] != ' ' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ /* extract and check criticality */
+ if ( strncasecmp( &val.bv_val[ i ], "criticality ", STRLENOF( "criticality " ) ) == 0 )
+ {
+ i += STRLENOF( "criticality " );
+ for ( ; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ bv.bv_val = &val.bv_val[ i ];
+
+ for ( ; i < val.bv_len; i++ ) {
+ if ( ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ bv.bv_len = &val.bv_val[ i ] - bv.bv_val;
+
+ if ( !bvmatch( &bv, &slap_true_bv ) && !bvmatch( &bv, &slap_false_bv ) )
+ {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( val.bv_val[ i ] != ' ' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ /* extract and check controlValue */
+ if ( strncasecmp( &val.bv_val[ i ], "controlValue ", STRLENOF( "controlValue " ) ) == 0 )
+ {
+ ber_len_t valueStart, valueLen;
+
+ i += STRLENOF( "controlValue " );
+ for ( ; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ if ( val.bv_val[ i ] != '"' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ i++;
+ valueStart = i;
+
+ for ( ; i < val.bv_len; i++ ) {
+ if ( val.bv_val[ i ] == '"' ) {
+ break;
+ }
+
+ if ( !ASCII_HEX( val.bv_val[ i ] ) ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+ }
+
+ if ( val.bv_val[ i ] != '"' ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ valueLen = i - valueStart;
+ if ( (valueLen/2)*2 != valueLen ) {
+ return LDAP_INVALID_SYNTAX;
+ }
+
+ for ( i++; i < val.bv_len; i++ ) {
+ if ( !ASCII_SPACE( val.bv_val[ i ] ) ) {
+ break;
+ }
+ }
+
+ if ( i == val.bv_len ) {
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return LDAP_INVALID_SYNTAX;
+}
+
+static int
+accesslog_ctrls(
+ LDAPControl **ctrls,
+ BerVarray *valsp,
+ BerVarray *nvalsp,
+ void *memctx )
+{
+ long i, rc = 0;
+
+ assert( valsp != NULL );
+ assert( ctrls != NULL );
+
+ *valsp = NULL;
+ *nvalsp = NULL;
+
+ for ( i = 0; ctrls[ i ] != NULL; i++ ) {
+ struct berval idx,
+ oid,
+ noid,
+ bv;
+ char *ptr,
+ buf[ 32 ];
+
+ if ( ctrls[ i ]->ldctl_oid == NULL ) {
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ idx.bv_len = snprintf( buf, sizeof( buf ), "{%ld}", i );
+ idx.bv_val = buf;
+
+ ber_str2bv( ctrls[ i ]->ldctl_oid, 0, 0, &oid );
+ noid.bv_len = idx.bv_len + oid.bv_len;
+ ptr = noid.bv_val = ber_memalloc_x( noid.bv_len + 1, memctx );
+ ptr = lutil_strcopy( ptr, idx.bv_val );
+ ptr = lutil_strcopy( ptr, oid.bv_val );
+
+ bv.bv_len = idx.bv_len + STRLENOF( "{}" ) + oid.bv_len;
+
+ if ( ctrls[ i ]->ldctl_iscritical ) {
+ bv.bv_len += STRLENOF( " criticality TRUE" );
+ }
+
+ if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) {
+ bv.bv_len += STRLENOF( " controlValue \"\"" )
+ + 2 * ctrls[ i ]->ldctl_value.bv_len;
+ }
+
+ ptr = bv.bv_val = ber_memalloc_x( bv.bv_len + 1, memctx );
+ if ( ptr == NULL ) {
+ ber_bvarray_free( *valsp );
+ *valsp = NULL;
+ ber_bvarray_free( *nvalsp );
+ *nvalsp = NULL;
+ return LDAP_OTHER;
+ }
+
+ ptr = lutil_strcopy( ptr, idx.bv_val );
+
+ *ptr++ = '{' /*}*/ ;
+ ptr = lutil_strcopy( ptr, oid.bv_val );
+
+ if ( ctrls[ i ]->ldctl_iscritical ) {
+ ptr = lutil_strcopy( ptr, " criticality TRUE" );
+ }
+
+ if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) {
+ ber_len_t j;
+
+ ptr = lutil_strcopy( ptr, " controlValue \"" );
+ for ( j = 0; j < ctrls[ i ]->ldctl_value.bv_len; j++ ) {
+ *ptr++ = SLAP_ESCAPE_HI(ctrls[ i ]->ldctl_value.bv_val[ j ]);
+ *ptr++ = SLAP_ESCAPE_LO(ctrls[ i ]->ldctl_value.bv_val[ j ]);
+ }
+
+ *ptr++ = '"';
+ }
+
+ *ptr++ = '}';
+ *ptr = '\0';
+
+ ber_bvarray_add_x( valsp, &bv, memctx );
+ ber_bvarray_add_x( nvalsp, &noid, memctx );
+ }
+
+ return rc;
+
+}
+
+static Entry *accesslog_entry( Operation *op, SlapReply *rs,
+ log_info *li, int logop, Operation *op2 ) {
+
+ char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
+ char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8];
+
+ struct berval rdn, nrdn, timestamp, ntimestamp, bv;
+ slap_verbmasks *lo = logops+logop+EN_OFFSET;
+
+ Entry *e = entry_alloc();
+
+ strcpy( rdnbuf, RDNEQ );
+ rdn.bv_val = rdnbuf;
+ strcpy( nrdnbuf, RDNEQ );
+ nrdn.bv_val = nrdnbuf;
+
+ timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ);
+ timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ);
+ slap_timestamp( &op->o_time, &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_overinst *on = (slap_overinst *)op->o_callback->sc_private;
+ log_info *li = on->on_bi.bi_private;
+ Attribute *a, *last_attr;
+ Modifications *m;
+ struct berval *b, uuid = BER_BVNULL;
+ int i;
+ int logop;
+ slap_verbmasks *lo;
+ Entry *e = NULL, *old = NULL, *e_uuid = NULL;
+ char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8];
+ struct berval bv;
+ char *ptr;
+ BerVarray vals;
+ Operation op2 = {0};
+ SlapReply rs2 = {REP_RESULT};
+
+ {
+ slap_callback *sc = op->o_callback;
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree(sc, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED )
+ return SLAP_CB_CONTINUE;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+ if ( !( li->li_ops & lo->mask )) {
+ log_base *lb;
+
+ i = 0;
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* mutex and so were only set for write operations;
+ * if we got here, the operation must be logged */
+ if ( lo->mask & LOG_OP_WRITES ) {
+ slap_callback *cb;
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ ldap_pvt_thread_mutex_lock( &li->li_log_mutex );
+ old = li->li_old;
+ uuid = li->li_uuid;
+ li->li_old = NULL;
+ BER_BVZERO( &li->li_uuid );
+#ifdef RMUTEX_DEBUG
+ Debug( LDAP_DEBUG_SYNC,
+ "accesslog_response: unlocking rmutex for tid %x\n",
+ op->o_tid, 0, 0 );
+#endif
+ ldap_pvt_thread_rmutex_unlock( &li->li_op_rmutex, op->o_tid );
+ }
+
+ /* ignore these internal reads */
+ if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( li->li_success && rs->sr_err != LDAP_SUCCESS )
+ goto done;
+
+ e = accesslog_entry( op, rs, li, logop, &op2 );
+
+ 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 == 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: c_op = '+'; break;
+ case LDAP_MOD_DELETE: 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 == 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 ) {
+ *ptr++ = '-';
+ } else {
+ *ptr++ = '=';
+ }
+ *ptr = '\0';
+ i++;
+ }
+ /* ITS#6545: when the same attribute is edited multiple times,
+ * record the transition */
+ if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc &&
+ m->sml_op == m->sml_next->sml_op ) {
+ ber_str2bv( ":", STRLENOF(":"), 1, &vals[i] );
+ i++;
+ }
+ }
+
+ if ( i > 0 ) {
+ BER_BVZERO( &vals[i] );
+ a = attr_alloc( ad_reqMod );
+ a->a_numvals = i;
+ a->a_vals = vals;
+ a->a_nvals = vals;
+ last_attr->a_next = a;
+ last_attr = a;
+
+ } else {
+ ch_free( vals );
+ }
+
+ if ( old ) {
+ /* count all the vals */
+ i = 0;
+ for ( a = old->e_attrs; a != NULL; a = a->a_next ) {
+ if ( a->a_vals && a->a_flags ) {
+ i += a->a_numvals;
+ }
+ }
+ if ( i ) {
+ vals = ch_malloc( (i + 1) * sizeof( struct berval ) );
+ i = 0;
+ for ( a=old->e_attrs; a; a=a->a_next ) {
+ if ( a->a_vals && a->a_flags ) {
+ for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) {
+ accesslog_val2val( a->a_desc, b, 0, &vals[i] );
+ }
+ }
+ }
+ vals[i].bv_val = NULL;
+ vals[i].bv_len = 0;
+ a = attr_alloc( ad_reqOld );
+ a->a_numvals = i;
+ a->a_vals = vals;
+ a->a_nvals = vals;
+ last_attr->a_next = a;
+ }
+ }
+ if ( logop == LOG_EN_MODIFY ) {
+ break;
+ }
+
+ /* Now log the actual modRDN info */
+ attr_merge_one( e, ad_reqNewRDN, &op->orr_newrdn, &op->orr_nnewrdn );
+ attr_merge_one( e, ad_reqDeleteOldRDN, op->orr_deleteoldrdn ?
+ (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,
+ NULL );
+ if ( op->orr_newSup ) {
+ attr_merge_one( e, ad_reqNewSuperior, op->orr_newSup, op->orr_nnewSup );
+ }
+ break;
+
+ case LOG_EN_COMPARE:
+ bv.bv_len = op->orc_ava->aa_desc->ad_cname.bv_len + 1 +
+ op->orc_ava->aa_value.bv_len;
+ bv.bv_val = op->o_tmpalloc( bv.bv_len+1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( bv.bv_val, op->orc_ava->aa_desc->ad_cname.bv_val );
+ *ptr++ = '=';
+ AC_MEMCPY( ptr, op->orc_ava->aa_value.bv_val, op->orc_ava->aa_value.bv_len );
+ bv.bv_val[bv.bv_len] = '\0';
+ attr_merge_one( e, ad_reqAssertion, &bv, NULL );
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LOG_EN_SEARCH:
+ attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL );
+ attr_merge_one( e, ad_reqDerefAliases, &derefs[op->ors_deref], NULL );
+ attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ?
+ (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv,
+ NULL );
+ if ( !BER_BVISEMPTY( &op->ors_filterstr ))
+ attr_merge_normalize_one( e, ad_reqFilter, &op->ors_filterstr, op->o_tmpmemctx );
+ if ( op->ors_attrs ) {
+ int j;
+ /* count them */
+ for (i=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++)
+ ;
+ vals = op->o_tmpalloc( (i+1) * sizeof(struct berval),
+ op->o_tmpmemctx );
+ for (i=0, j=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) {
+ if (!BER_BVISEMPTY(&op->ors_attrs[i].an_name)) {
+ vals[j] = op->ors_attrs[i].an_name;
+ j++;
+ }
+ }
+ BER_BVZERO(&vals[j]);
+ attr_merge_normalize( e, ad_reqAttr, vals, op->o_tmpmemctx );
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ }
+ bv.bv_val = timebuf;
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", rs->sr_nentries );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqEntries, &bv, NULL );
+ } /* else? */
+
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_tlimit );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqTimeLimit, &bv, NULL );
+ } /* else? */
+
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_slimit );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqSizeLimit, &bv, NULL );
+ } /* else? */
+ break;
+
+ case LOG_EN_BIND:
+ bv.bv_val = timebuf;
+ bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->o_protocol );
+ if ( bv.bv_len < sizeof( timebuf ) ) {
+ attr_merge_one( e, ad_reqVersion, &bv, NULL );
+ } /* else? */
+ if ( op->orb_method == LDAP_AUTH_SIMPLE ) {
+ attr_merge_normalize_one( e, ad_reqMethod, &simple, op->o_tmpmemctx );
+ } else {
+ bv.bv_len = STRLENOF("SASL()") + op->orb_mech.bv_len;
+ bv.bv_val = op->o_tmpalloc( bv.bv_len + 1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( bv.bv_val, "SASL(" );
+ ptr = lutil_strcopy( ptr, op->orb_mech.bv_val );
+ *ptr++ = ')';
+ *ptr = '\0';
+ attr_merge_normalize_one( e, ad_reqMethod, &bv, op->o_tmpmemctx );
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LOG_EN_EXTENDED:
+ if ( op->ore_reqdata ) {
+ attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL );
+ }
+ break;
+
+ case LOG_EN_UNKNOWN:
+ /* we don't know its parameters, don't add any */
+ break;
+ }
+
+ if ( e_uuid || !BER_BVISNULL( &uuid ) ) {
+ struct berval *pbv = NULL;
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ pbv = &uuid;
+
+ } else {
+ a = attr_find( e_uuid->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a ) {
+ pbv = &a->a_vals[0];
+ }
+ }
+
+ if ( pbv ) {
+ attr_merge_normalize_one( e, ad_reqEntryUUID, pbv, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ ber_memfree( uuid.bv_val );
+ BER_BVZERO( &uuid );
+ }
+ }
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ op2.o_csn = op->o_csn;
+ /* contextCSN updates may still reach here */
+ op2.o_dont_replicate = op->o_dont_replicate;
+
+ if (( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn )) {
+ struct berval maxcsn;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ int foundit;
+ cbuf[0] = '\0';
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = sizeof(cbuf);
+ /* If there was a commit CSN on the main DB,
+ * we must propagate it to the log DB for its
+ * own syncprov. Otherwise, don't generate one.
+ */
+ slap_get_commit_csn( op, &maxcsn, &foundit );
+ if ( !BER_BVISEMPTY( &maxcsn ) ) {
+ slap_queue_csn( &op2, &op->o_csn );
+ } else {
+ attr_merge_normalize_one( e, slap_schema.si_ad_entryCSN,
+ &op->o_csn, op->o_tmpmemctx );
+ }
+ }
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "accesslog_response: got result 0x%x adding log entry %s\n",
+ rs2.sr_err, op2.o_req_dn.bv_val, 0 );
+ }
+ if ( e == op2.ora_e ) entry_free( e );
+ e = NULL;
+
+done:
+ if ( lo->mask & LOG_OP_WRITES )
+ ldap_pvt_thread_mutex_unlock( &li->li_log_mutex );
+ if ( old ) entry_free( old );
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_op_misc( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc;
+
+ sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ sc->sc_response = accesslog_response;
+ sc->sc_private = op->o_bd->bd_info;
+
+ if ( op->o_callback ) {
+ sc->sc_next = op->o_callback->sc_next;
+ op->o_callback->sc_next = sc;
+ } else {
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ slap_verbmasks *lo;
+ int logop;
+ int doit = 0;
+
+ /* These internal ops are not logged */
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ logop = accesslog_op2logop( op );
+ lo = logops+logop+EN_OFFSET;
+
+ if ( li->li_ops & lo->mask ) {
+ doit = 1;
+ } else {
+ log_base *lb;
+ for ( lb = li->li_bases; lb; lb = lb->lb_next )
+ if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) {
+ doit = 1;
+ break;
+ }
+ }
+
+ if ( doit ) {
+ slap_callback *cb = op->o_tmpcalloc( 1, sizeof( slap_callback ), op->o_tmpmemctx );
+ cb->sc_cleanup = accesslog_response;
+ cb->sc_response = accesslog_response;
+ cb->sc_private = on;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+#ifdef RMUTEX_DEBUG
+ Debug( LDAP_DEBUG_SYNC,
+ "accesslog_op_mod: locking rmutex for tid %x\n",
+ op->o_tid, 0, 0 );
+#endif
+ ldap_pvt_thread_rmutex_lock( &li->li_op_rmutex, op->o_tid );
+#ifdef RMUTEX_DEBUG
+ Debug( LDAP_DEBUG_STATS,
+ "accesslog_op_mod: locked rmutex for tid %x\n",
+ op->o_tid, 0, 0 );
+#endif
+ if ( li->li_oldf && ( op->o_tag == LDAP_REQ_DELETE ||
+ op->o_tag == LDAP_REQ_MODIFY ||
+ ( op->o_tag == LDAP_REQ_MODRDN && li->li_oldattrs )))
+ {
+ int rc;
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( e ) {
+ if ( test_filter( op, e, li->li_oldf ) == LDAP_COMPARE_TRUE )
+ li->li_old = entry_dup( e );
+ be_entry_release_rw( op, e, 0 );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ } else {
+ int rc;
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( e ) {
+ Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a ) {
+ ber_dupbv( &li->li_uuid, &a->a_vals[0] );
+ }
+ be_entry_release_rw( op, e, 0 );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* unbinds are broadcast to all backends; we only log it if this
+ * backend was used for the original bind.
+ */
+static int
+accesslog_unbind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ if ( op->o_conn->c_authz_backend == on->on_info->oi_origdb ) {
+ log_info *li = on->on_bi.bi_private;
+ Operation op2 = {0};
+ void *cids[SLAP_MAX_CIDS];
+ SlapReply rs2 = {REP_RESULT};
+ Entry *e;
+
+ if ( !( li->li_ops & LOG_OP_UNBIND )) {
+ log_base *lb;
+ int i = 0;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & LOG_OP_UNBIND ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ e = accesslog_entry( op, rs, li, LOG_EN_UNBIND, &op2 );
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ op2.o_controls = cids;
+ memset(cids, 0, sizeof( cids ));
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( e == op2.ora_e )
+ entry_free( e );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_abandon( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ Operation op2 = {0};
+ void *cids[SLAP_MAX_CIDS];
+ SlapReply rs2 = {REP_RESULT};
+ Entry *e;
+ char buf[64];
+ struct berval bv;
+
+ if ( !op->o_time )
+ return SLAP_CB_CONTINUE;
+
+ if ( !( li->li_ops & LOG_OP_ABANDON )) {
+ log_base *lb;
+ int i = 0;
+
+ for ( lb = li->li_bases; lb; lb=lb->lb_next )
+ if (( lb->lb_ops & LOG_OP_ABANDON ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) {
+ i = 1;
+ break;
+ }
+ if ( !i )
+ return SLAP_CB_CONTINUE;
+ }
+
+ e = accesslog_entry( op, rs, li, LOG_EN_ABANDON, &op2 );
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", op->orn_msgid );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ attr_merge_one( e, ad_reqId, &bv, NULL );
+ } /* else? */
+
+ op2.o_hdr = op->o_hdr;
+ op2.o_tag = LDAP_REQ_ADD;
+ op2.o_bd = li->li_db;
+ op2.o_dn = li->li_db->be_rootdn;
+ op2.o_ndn = li->li_db->be_rootndn;
+ op2.o_req_dn = e->e_name;
+ op2.o_req_ndn = e->e_nname;
+ op2.ora_e = e;
+ op2.o_callback = &nullsc;
+ op2.o_controls = cids;
+ memset(cids, 0, sizeof( cids ));
+
+ op2.o_bd->be_add( &op2, &rs2 );
+ if ( e == op2.ora_e )
+ entry_free( e );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+accesslog_operational( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ log_info *li = on->on_bi.bi_private;
+
+ if ( op->o_sync != SLAP_CONTROL_NONE )
+ return SLAP_CB_CONTINUE;
+
+ if ( rs->sr_entry != NULL
+ && dn_match( &op->o_bd->be_nsuffix[0], &rs->sr_entry->e_nname ) )
+ {
+ Attribute **ap;
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next )
+ /* just count */ ;
+
+ if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( ad_auditContext, rs->sr_attrs ) )
+ {
+ *ap = attr_alloc( ad_auditContext );
+ attr_valadd( *ap,
+ &li->li_db->be_suffix[0],
+ &li->li_db->be_nsuffix[0], 1 );
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst accesslog;
+
+static int
+accesslog_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = ch_calloc(1, sizeof(log_info));
+
+ on->on_bi.bi_private = li;
+ ldap_pvt_thread_rmutex_init( &li->li_op_rmutex );
+ ldap_pvt_thread_mutex_init( &li->li_log_mutex );
+ return 0;
+}
+
+static int
+accesslog_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+ log_attr *la;
+
+ if ( li->li_oldf )
+ filter_free( li->li_oldf );
+ for ( la=li->li_oldattrs; la; la=li->li_oldattrs ) {
+ li->li_oldattrs = la->next;
+ ch_free( la );
+ }
+ ldap_pvt_thread_mutex_destroy( &li->li_log_mutex );
+ ldap_pvt_thread_rmutex_destroy( &li->li_op_rmutex );
+ free( li );
+ return LDAP_SUCCESS;
+}
+
+/* Create the logdb's root entry if it's missing */
+static void *
+accesslog_db_root(
+ void *ctx,
+ void *arg )
+{
+ struct re_s *rtask = arg;
+ slap_overinst *on = rtask->arg;
+ log_info *li = on->on_bi.bi_private;
+
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+
+ Entry *e;
+ int rc;
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+ op->o_bd = li->li_db;
+ op->o_dn = li->li_db->be_rootdn;
+ op->o_ndn = li->li_db->be_rootndn;
+ rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e );
+
+ if ( e ) {
+ be_entry_release_rw( op, e, 0 );
+
+ } else {
+ SlapReply rs = {REP_RESULT};
+ struct berval rdn, nrdn, attr;
+ char *ptr;
+ AttributeDescription *ad = NULL;
+ const char *text = NULL;
+ Entry *e_ctx;
+ BackendDB db;
+
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, li->li_db->be_suffix );
+ ber_dupbv( &e->e_nname, li->li_db->be_nsuffix );
+
+ attr_merge_one( e, slap_schema.si_ad_objectClass,
+ &log_container->soc_cname, NULL );
+
+ dnRdn( &e->e_name, &rdn );
+ dnRdn( &e->e_nname, &nrdn );
+ ptr = ber_bvchr( &rdn, '=' );
+
+ assert( ptr != NULL );
+
+ attr.bv_val = rdn.bv_val;
+ attr.bv_len = ptr - rdn.bv_val;
+
+ slap_bv2ad( &attr, &ad, &text );
+
+ rdn.bv_val = ptr+1;
+ rdn.bv_len -= attr.bv_len + 1;
+ ptr = ber_bvchr( &nrdn, '=' );
+ nrdn.bv_len -= ptr - nrdn.bv_val + 1;
+ nrdn.bv_val = ptr+1;
+ attr_merge_one( e, ad, &rdn, &nrdn );
+
+ /* Get contextCSN from main DB */
+ op->o_bd = on->on_info->oi_origdb;
+ rc = be_entry_get_rw( op, op->o_bd->be_nsuffix, NULL,
+ slap_schema.si_ad_contextCSN, 0, &e_ctx );
+
+ if ( e_ctx ) {
+ Attribute *a;
+
+ a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ /* FIXME: contextCSN could have multiple values!
+ * should select the one with the server's SID */
+ attr_merge_one( e, slap_schema.si_ad_entryCSN,
+ &a->a_vals[0], &a->a_nvals[0] );
+ attr_merge( e, a->a_desc, a->a_vals, a->a_nvals );
+ }
+ be_entry_release_rw( op, e_ctx, 0 );
+ }
+ db = *li->li_db;
+ op->o_bd = &db;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->ora_e = e;
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ op->o_callback = &nullsc;
+ SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD;
+ rc = op->o_bd->be_add( op, &rs );
+ if ( e == op->ora_e )
+ entry_free( e );
+ }
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ ldap_pvt_runqueue_remove( &slapd_rq, rtask );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+static int
+accesslog_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ log_info *li = on->on_bi.bi_private;
+
+
+ if ( !BER_BVISEMPTY( &li->li_db_suffix )) {
+ li->li_db = select_backend( &li->li_db_suffix, 0 );
+ ch_free( li->li_db_suffix.bv_val );
+ BER_BVZERO( &li->li_db_suffix );
+ }
+ if ( li->li_db == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog: \"logdb <suffix>\" missing or invalid.\n",
+ 0, 0, 0 );
+ return 1;
+ }
+
+ if ( slapMode & SLAP_TOOL_MODE )
+ return 0;
+
+ if ( BER_BVISEMPTY( &li->li_db->be_rootndn )) {
+ ber_dupbv( &li->li_db->be_rootdn, li->li_db->be_suffix );
+ ber_dupbv( &li->li_db->be_rootndn, li->li_db->be_nsuffix );
+ }
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, 3600, accesslog_db_root, on,
+ "accesslog_db_root", li->li_db->be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return 0;
+}
+
+int accesslog_initialize()
+{
+ int i, rc;
+
+ accesslog.on_bi.bi_type = "accesslog";
+ accesslog.on_bi.bi_db_init = accesslog_db_init;
+ accesslog.on_bi.bi_db_destroy = accesslog_db_destroy;
+ accesslog.on_bi.bi_db_open = accesslog_db_open;
+
+ accesslog.on_bi.bi_op_add = accesslog_op_mod;
+ accesslog.on_bi.bi_op_bind = accesslog_op_misc;
+ accesslog.on_bi.bi_op_compare = accesslog_op_misc;
+ accesslog.on_bi.bi_op_delete = accesslog_op_mod;
+ accesslog.on_bi.bi_op_modify = accesslog_op_mod;
+ accesslog.on_bi.bi_op_modrdn = accesslog_op_mod;
+ accesslog.on_bi.bi_op_search = accesslog_op_misc;
+ accesslog.on_bi.bi_extended = accesslog_op_misc;
+ accesslog.on_bi.bi_op_unbind = accesslog_unbind;
+ accesslog.on_bi.bi_op_abandon = accesslog_abandon;
+ accesslog.on_bi.bi_operational = accesslog_operational;
+
+ accesslog.on_bi.bi_cf_ocs = log_cfocs;
+
+ nullsc.sc_response = slap_null_cb;
+
+ rc = config_register_schema( log_cfats, log_cfocs );
+ if ( rc ) return rc;
+
+ /* log schema integration */
+ for ( i=0; lsyntaxes[i].oid; i++ ) {
+ int code;
+
+ code = register_syntax( &lsyntaxes[ i ].syn );
+ if ( code != 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_syntax failed\n",
+ 0, 0, 0 );
+ return code;
+ }
+
+ if ( lsyntaxes[i].mrs != NULL ) {
+ code = mr_make_syntax_compat_with_mrs(
+ lsyntaxes[i].oid, lsyntaxes[i].mrs );
+ if ( code < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: "
+ "mr_make_syntax_compat_with_mrs "
+ "failed\n",
+ 0, 0, 0 );
+ return code;
+ }
+ }
+ }
+
+ for ( i=0; lattrs[i].at; i++ ) {
+ int code;
+
+ code = register_at( lattrs[i].at, lattrs[i].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_at failed\n",
+ 0, 0, 0 );
+ return -1;
+ }
+#ifndef LDAP_DEVEL
+ (*lattrs[i].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+#endif
+ }
+
+ for ( i=0; locs[i].ot; i++ ) {
+ int code;
+
+ code = register_oc( locs[i].ot, locs[i].oc, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "accesslog_init: register_oc failed\n",
+ 0, 0, 0 );
+ return -1;
+ }
+#ifndef LDAP_DEVEL
+ (*locs[i].oc)->soc_flags |= SLAP_OC_HIDE;
+#endif
+ }
+
+ return overlay_register(&accesslog);
+}
+
+#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return accesslog_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_ACCESSLOG */
diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c
new file mode 100644
index 0000000..3c87b61
--- /dev/null
+++ b/servers/slapd/overlays/auditlog.c
@@ -0,0 +1,240 @@
+/* auditlog.c - log modifications for audit/history purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2018 The OpenLDAP Foundation.
+ * Portions copyright 2004-2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_AUDITLOG
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+#include "config.h"
+#include "ldif.h"
+
+typedef struct auditlog_data {
+ ldap_pvt_thread_mutex_t ad_mutex;
+ char *ad_logfile;
+} auditlog_data;
+
+static ConfigTable auditlogcfg[] = {
+ { "auditlog", "filename", 2, 2, 0,
+ ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(auditlog_data, ad_logfile),
+ "( OLcfgOvAt:15.1 NAME 'olcAuditlogFile' "
+ "DESC 'Filename for auditlogging' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs auditlogocs[] = {
+ { "( OLcfgOvOc:15.1 "
+ "NAME 'olcAuditlogConfig' "
+ "DESC 'Auditlog configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcAuditlogFile ) )",
+ Cft_Overlay, auditlogcfg },
+ { NULL, 0, NULL }
+};
+
+static int fprint_ldif(FILE *f, char *name, char *val, ber_len_t len) {
+ char *s;
+ if((s = ldif_put(LDIF_PUT_VALUE, name, val, len)) == NULL)
+ return(-1);
+ fputs(s, f);
+ ber_memfree(s);
+ return(0);
+}
+
+static int auditlog_response(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ auditlog_data *ad = on->on_bi.bi_private;
+ FILE *f;
+ Attribute *a;
+ Modifications *m;
+ struct berval *b, *who = NULL, peername;
+ char *what, *whatm, *suffix;
+ time_t stamp;
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+
+ if ( !ad->ad_logfile ) return SLAP_CB_CONTINUE;
+
+/*
+** add or modify: use modifiersName if present
+**
+*/
+ switch(op->o_tag) {
+ case LDAP_REQ_MODRDN: what = "modrdn"; break;
+ case LDAP_REQ_DELETE: what = "delete"; break;
+ case LDAP_REQ_ADD:
+ what = "add";
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ if( a->a_desc == slap_schema.si_ad_modifiersName ) {
+ who = &a->a_vals[0];
+ break;
+ }
+ break;
+ case LDAP_REQ_MODIFY:
+ what = "modify";
+ for(m = op->orm_modlist; m; m = m->sml_next)
+ if( m->sml_desc == slap_schema.si_ad_modifiersName &&
+ ( m->sml_op == LDAP_MOD_ADD ||
+ m->sml_op == LDAP_MOD_REPLACE )) {
+ who = &m->sml_values[0];
+ break;
+ }
+ break;
+ default:
+ return SLAP_CB_CONTINUE;
+ }
+
+ suffix = op->o_bd->be_suffix[0].bv_len ? op->o_bd->be_suffix[0].bv_val :
+ "global";
+
+/*
+** note: this means requestor's dn when modifiersName is null
+*/
+ if ( !who )
+ who = &op->o_dn;
+
+ peername = op->o_conn->c_peer_name;
+ ldap_pvt_thread_mutex_lock(&ad->ad_mutex);
+ if((f = fopen(ad->ad_logfile, "a")) == NULL) {
+ ldap_pvt_thread_mutex_unlock(&ad->ad_mutex);
+ return SLAP_CB_CONTINUE;
+ }
+
+ stamp = slap_get_time();
+ fprintf(f, "# %s %ld %s%s%s %s conn=%ld\n",
+ what, (long)stamp, suffix, who ? " " : "", who ? who->bv_val : "",
+ peername.bv_val ? peername.bv_val: "", op->o_conn->c_connid);
+
+ if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) &&
+ (!who || !dn_match( who, &op->o_conn->c_dn )))
+ fprintf(f, "# realdn: %s\n", op->o_conn->c_dn.bv_val );
+
+ fprintf(f, "dn: %s\nchangetype: %s\n",
+ op->o_req_dn.bv_val, what);
+
+ switch(op->o_tag) {
+ case LDAP_REQ_ADD:
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ if((b = a->a_vals) != NULL)
+ for(i = 0; b[i].bv_val; i++)
+ fprint_ldif(f, a->a_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len);
+ break;
+
+ case LDAP_REQ_MODIFY:
+ for(m = op->orm_modlist; m; m = m->sml_next) {
+ switch(m->sml_op & LDAP_MOD_OP) {
+ case LDAP_MOD_ADD: whatm = "add"; break;
+ case LDAP_MOD_REPLACE: whatm = "replace"; break;
+ case LDAP_MOD_DELETE: whatm = "delete"; break;
+ case LDAP_MOD_INCREMENT: whatm = "increment"; break;
+ default:
+ fprintf(f, "# MOD_TYPE_UNKNOWN:%02x\n", m->sml_op & LDAP_MOD_OP);
+ continue;
+ }
+ fprintf(f, "%s: %s\n", whatm, m->sml_desc->ad_cname.bv_val);
+ if((b = m->sml_values) != NULL)
+ for(i = 0; b[i].bv_val; i++)
+ fprint_ldif(f, m->sml_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len);
+ fprintf(f, "-\n");
+ }
+ break;
+
+ case LDAP_REQ_MODRDN:
+ fprintf(f, "newrdn: %s\ndeleteoldrdn: %s\n",
+ op->orr_newrdn.bv_val, op->orr_deleteoldrdn ? "1" : "0");
+ if(op->orr_newSup) fprintf(f, "newsuperior: %s\n", op->orr_newSup->bv_val);
+ break;
+
+ case LDAP_REQ_DELETE:
+ /* nothing else needed */
+ break;
+ }
+
+ fprintf(f, "# end %s %ld\n\n", what, (long)stamp);
+
+ fclose(f);
+ ldap_pvt_thread_mutex_unlock(&ad->ad_mutex);
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst auditlog;
+
+static int
+auditlog_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ auditlog_data *ad = ch_calloc(1, sizeof(auditlog_data));
+
+ on->on_bi.bi_private = ad;
+ ldap_pvt_thread_mutex_init( &ad->ad_mutex );
+ return 0;
+}
+
+static int
+auditlog_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ auditlog_data *ad = on->on_bi.bi_private;
+
+ ldap_pvt_thread_mutex_destroy( &ad->ad_mutex );
+ free( ad->ad_logfile );
+ free( ad );
+ return 0;
+}
+
+int auditlog_initialize() {
+ int rc;
+
+ auditlog.on_bi.bi_type = "auditlog";
+ auditlog.on_bi.bi_db_init = auditlog_db_init;
+ auditlog.on_bi.bi_db_destroy = auditlog_db_destroy;
+ auditlog.on_response = auditlog_response;
+
+ auditlog.on_bi.bi_cf_ocs = auditlogocs;
+ rc = config_register_schema( auditlogcfg, auditlogocs );
+ if ( rc ) return rc;
+
+ return overlay_register(&auditlog);
+}
+
+#if SLAPD_OVER_AUDITLOG == SLAPD_MOD_DYNAMIC && defined(PIC)
+int
+init_module( int argc, char *argv[] )
+{
+ return auditlog_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_AUDITLOG */
diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c
new file mode 100644
index 0000000..722b9f6
--- /dev/null
+++ b/servers/slapd/overlays/collect.c
@@ -0,0 +1,439 @@
+/* collect.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_COLLECT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+
+#include "lutil.h"
+
+/* This is a cheap hack to implement a collective attribute.
+ *
+ * This demonstration overlay looks for a specified attribute in an
+ * ancestor of a given entry and adds that attribute to the given
+ * entry when it is returned in a search response. It takes no effect
+ * for any other operations. If the ancestor does not exist, there
+ * is no effect. If no attribute was configured, there is no effect.
+ */
+
+typedef struct collect_info {
+ struct collect_info *ci_next;
+ struct berval ci_dn;
+ int ci_ad_num;
+ AttributeDescription *ci_ad[1];
+} collect_info;
+
+static int collect_cf( ConfigArgs *c );
+
+static ConfigTable collectcfg[] = {
+ { "collectinfo", "dn> <attribute", 3, 3, 0,
+ ARG_MAGIC, collect_cf,
+ "( OLcfgOvAt:19.1 NAME 'olcCollectInfo' "
+ "DESC 'DN of entry and attribute to distribute' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs collectocs[] = {
+ { "( OLcfgOvOc:19.1 "
+ "NAME 'olcCollectConfig' "
+ "DESC 'Collective Attribute configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcCollectInfo )",
+ Cft_Overlay, collectcfg },
+ { NULL, 0, NULL }
+};
+
+/*
+ * inserts a collect_info into on->on_bi.bi_private taking into account
+ * order. this means longer dn's (i.e. more specific dn's) will be found
+ * first when searching, allowing some limited overlap of dn's
+ */
+static void
+insert_ordered( slap_overinst *on, collect_info *ci ) {
+ collect_info *find = on->on_bi.bi_private;
+ collect_info *prev = NULL;
+ int found = 0;
+
+ while (!found) {
+ if (find == NULL) {
+ if (prev == NULL) {
+ /* base case - empty list */
+ on->on_bi.bi_private = ci;
+ ci->ci_next = NULL;
+ } else {
+ /* final case - end of list */
+ prev->ci_next = ci;
+ ci->ci_next = NULL;
+ }
+ found = 1;
+ } else if (find->ci_dn.bv_len < ci->ci_dn.bv_len) {
+ /* insert into list here */
+ if (prev == NULL) {
+ /* entry is head of list */
+ ci->ci_next = on->on_bi.bi_private;
+ on->on_bi.bi_private = ci;
+ } else {
+ /* entry is not head of list */
+ prev->ci_next = ci;
+ ci->ci_next = find;
+ }
+ found = 1;
+ } else {
+ /* keep looking */
+ prev = find;
+ find = find->ci_next;
+ }
+ }
+}
+
+static int
+collect_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ int rc = 1, idx;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ {
+ collect_info *ci;
+ for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) {
+ struct berval bv;
+ char *ptr;
+ int len;
+
+ /* calculate the length & malloc memory */
+ bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" ");
+ for (idx=0; idx<ci->ci_ad_num; idx++) {
+ bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len;
+ if (idx<(ci->ci_ad_num-1)) {
+ bv.bv_len++;
+ }
+ }
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ /* copy the value and update len */
+ len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ",
+ ci->ci_dn.bv_val);
+ ptr = bv.bv_val + len;
+ for (idx=0; idx<ci->ci_ad_num; idx++) {
+ ptr = lutil_strncopy( ptr,
+ ci->ci_ad[idx]->ad_cname.bv_val,
+ ci->ci_ad[idx]->ad_cname.bv_len);
+ if (idx<(ci->ci_ad_num-1)) {
+ *ptr++ = ',';
+ }
+ }
+ *ptr = '\0';
+ bv.bv_len = ptr - bv.bv_val;
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx == -1 ) {
+ /* Delete entire attribute */
+ collect_info *ci;
+ while (( ci = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ } else {
+ /* Delete just one value */
+ collect_info **cip, *ci;
+ int i;
+ cip = (collect_info **)&on->on_bi.bi_private;
+ ci = *cip;
+ for ( i=0; i < c->valx; i++ ) {
+ cip = &ci->ci_next;
+ ci = *cip;
+ }
+ *cip = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ {
+ collect_info *ci;
+ struct berval bv, dn;
+ const char *text;
+ int idx, count=0;
+ char *arg;
+
+ /* count delimiters in attribute argument */
+ arg = strtok(c->argv[2], ",");
+ while (arg!=NULL) {
+ count++;
+ arg = strtok(NULL, ",");
+ }
+
+ /* validate and normalize dn */
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ /* check for duplicate DNs */
+ for ( ci = (collect_info *)on->on_bi.bi_private; ci;
+ ci = ci->ci_next ) {
+ /* If new DN is longest, there are no possible matches */
+ if ( dn.bv_len > ci->ci_dn.bv_len ) {
+ ci = NULL;
+ break;
+ }
+ if ( bvmatch( &dn, &ci->ci_dn )) {
+ break;
+ }
+ }
+ if ( ci ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s DN already configured: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ /* allocate config info with room for attribute array */
+ ci = ch_malloc( sizeof( collect_info ) +
+ sizeof( AttributeDescription * ) * count );
+
+ /* load attribute description for attribute list */
+ arg = c->argv[2];
+ for( idx=0; idx<count; idx++) {
+ ci->ci_ad[idx] = NULL;
+
+ if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s attribute description unknown: \"%s\"",
+ c->argv[0], arg);
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ ch_free( ci );
+ return ARG_BAD_CONF;
+ }
+ while(*arg!='\0') {
+ arg++; /* skip to end of argument */
+ }
+ if (idx<count-1) {
+ arg++; /* skip inner delimiters */
+ }
+ }
+
+ /* The on->on_bi.bi_private pointer can be used for
+ * anything this instance of the overlay needs.
+ */
+ ci->ci_ad[count] = NULL;
+ ci->ci_ad_num = count;
+ ci->ci_dn = dn;
+
+ /* creates list of ci's ordered by dn length */
+ insert_ordered ( on, ci );
+
+ /* New ci wasn't simply appended to end, adjust its
+ * position in the config entry's a_vals
+ */
+ if ( c->ca_entry && ci->ci_next ) {
+ Attribute *a = attr_find( c->ca_entry->e_attrs,
+ collectcfg[0].ad );
+ if ( a ) {
+ struct berval bv, nbv;
+ collect_info *c2 = (collect_info *)on->on_bi.bi_private;
+ int i, j;
+ for ( i=0; c2 != ci; i++, c2 = c2->ci_next );
+ bv = a->a_vals[a->a_numvals-1];
+ nbv = a->a_nvals[a->a_numvals-1];
+ for ( j=a->a_numvals-1; j>i; j-- ) {
+ a->a_vals[j] = a->a_vals[j-1];
+ a->a_nvals[j] = a->a_nvals[j-1];
+ }
+ a->a_vals[j] = bv;
+ a->a_nvals[j] = nbv;
+ }
+ }
+
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static int
+collect_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ collect_info *ci;
+
+ while (( ci = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ci->ci_next;
+ ch_free( ci->ci_dn.bv_val );
+ ch_free( ci );
+ }
+ return 0;
+}
+
+static int
+collect_modify( Operation *op, SlapReply *rs)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ collect_info *ci = on->on_bi.bi_private;
+ Modifications *ml;
+ char errMsg[100];
+ int idx;
+
+ for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) {
+ for (; ci; ci=ci->ci_next ) {
+ /* Is this entry an ancestor of this collectinfo ? */
+ if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) {
+ /* this collectinfo does not match */
+ continue;
+ }
+
+ /* Is this entry the same as the template DN ? */
+ if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) {
+ /* all changes in this ci are allowed */
+ continue;
+ }
+
+ /* check for collect attributes - disallow modify if present */
+ for(idx=0; idx<ci->ci_ad_num; idx++) {
+ if (ml->sml_desc == ci->ci_ad[idx]) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ snprintf( errMsg, sizeof( errMsg ),
+ "cannot change virtual attribute '%s'",
+ ci->ci_ad[idx]->ad_cname.bv_val);
+ rs->sr_text = errMsg;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+ }
+
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+collect_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ collect_info *ci = on->on_bi.bi_private;
+
+ /* If we've been configured and the current response is
+ * a search entry
+ */
+ if ( ci && rs->sr_type == REP_SEARCH ) {
+ int rc;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ for (; ci; ci=ci->ci_next ) {
+ int idx=0;
+
+ /* Is this entry an ancestor of this collectinfo ? */
+ if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+ /* collectinfo does not match */
+ continue;
+ }
+
+ /* Is this entry the same as the template DN ? */
+ if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) {
+ /* dont apply change to parent */
+ continue;
+ }
+
+ /* The current entry may live in a cache, so
+ * don't modify it directly. Make a copy and
+ * work with that instead.
+ */
+ rs_entry2modifiable( op, rs, on );
+
+ /* Loop for each attribute in this collectinfo */
+ for(idx=0; idx<ci->ci_ad_num; idx++) {
+ BerVarray vals = NULL;
+
+ /* Extract the values of the desired attribute from
+ * the ancestor entry */
+ rc = backend_attribute( op, NULL, &ci->ci_dn,
+ ci->ci_ad[idx], &vals, ACL_READ );
+
+ /* If there are any values, merge them into the
+ * current search result
+ */
+ if ( vals ) {
+ attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx],
+ vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+ }
+ }
+
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_overinst collect;
+
+int collect_initialize() {
+ int code;
+
+ collect.on_bi.bi_type = "collect";
+ collect.on_bi.bi_db_destroy = collect_destroy;
+ collect.on_bi.bi_op_modify = collect_modify;
+ collect.on_response = collect_response;
+
+ collect.on_bi.bi_cf_ocs = collectocs;
+ code = config_register_schema( collectcfg, collectocs );
+ if ( code ) return code;
+
+ return overlay_register( &collect );
+}
+
+#if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return collect_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_COLLECT */
diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c
new file mode 100644
index 0000000..5c65b42
--- /dev/null
+++ b/servers/slapd/overlays/constraint.c
@@ -0,0 +1,1231 @@
+/* $OpenLDAP$ */
+/* constraint.c - Overlay to constrain attributes to certain values */
+/*
+ * Copyright 2003-2004 Hewlett-Packard Company
+ * Copyright 2007 Emmanuel Dreyfus
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/*
+ * Authors: Neil Dunbar <neil.dunbar@hp.com>
+ * Emmannuel Dreyfus <manu@netbsd.org>
+ */
+#include "portable.h"
+
+#ifdef SLAPD_OVER_CONSTRAINT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include <ac/regex.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "config.h"
+
+/*
+ * This overlay limits the values which can be placed into an
+ * attribute, over and above the limits placed by the schema.
+ *
+ * It traps only LDAP adds and modify commands (and only seeks to
+ * control the add and modify value mods of a modify)
+ */
+
+#define REGEX_STR "regex"
+#define URI_STR "uri"
+#define SET_STR "set"
+#define SIZE_STR "size"
+#define COUNT_STR "count"
+
+/*
+ * Linked list of attribute constraints which we should enforce.
+ * This is probably a sub optimal structure - some form of sorted
+ * array would be better if the number of attributes contrained is
+ * likely to be much bigger than 4 or 5. We stick with a list for
+ * the moment.
+ */
+
+typedef struct constraint {
+ struct constraint *ap_next;
+ AttributeDescription **ap;
+
+ LDAPURLDesc *restrict_lud;
+ struct berval restrict_ndn;
+ Filter *restrict_filter;
+ struct berval restrict_val;
+
+ int type;
+ regex_t *re;
+ LDAPURLDesc *lud;
+ int set;
+ size_t size;
+ size_t count;
+ AttributeDescription **attrs;
+ struct berval val; /* constraint value */
+ struct berval dn;
+ struct berval filter;
+} constraint;
+
+enum {
+ CONSTRAINT_ATTRIBUTE = 1,
+ CONSTRAINT_COUNT,
+ CONSTRAINT_SIZE,
+ CONSTRAINT_REGEX,
+ CONSTRAINT_SET,
+ CONSTRAINT_URI,
+};
+
+static ConfigDriver constraint_cf_gen;
+
+static ConfigTable constraintcfg[] = {
+ { "constraint_attribute", "attribute[list]> (regex|uri|set|size|count) <value> [<restrict URI>]",
+ 4, 0, 0, ARG_MAGIC | CONSTRAINT_ATTRIBUTE, constraint_cf_gen,
+ "( OLcfgOvAt:13.1 NAME 'olcConstraintAttribute' "
+ "DESC 'constraint for list of attributes' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs constraintocs[] = {
+ { "( OLcfgOvOc:13.1 "
+ "NAME 'olcConstraintConfig' "
+ "DESC 'Constraint overlay configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcConstraintAttribute ) )",
+ Cft_Overlay, constraintcfg },
+ { NULL, 0, NULL }
+};
+
+static void
+constraint_free( constraint *cp, int freeme )
+{
+ if (cp->restrict_lud)
+ ldap_free_urldesc(cp->restrict_lud);
+ if (!BER_BVISNULL(&cp->restrict_ndn))
+ ch_free(cp->restrict_ndn.bv_val);
+ if (cp->restrict_filter != NULL && cp->restrict_filter != slap_filter_objectClass_pres)
+ filter_free(cp->restrict_filter);
+ if (!BER_BVISNULL(&cp->restrict_val))
+ ch_free(cp->restrict_val.bv_val);
+ if (cp->re) {
+ regfree(cp->re);
+ ch_free(cp->re);
+ }
+ if (!BER_BVISNULL(&cp->val))
+ ch_free(cp->val.bv_val);
+ if (cp->lud)
+ ldap_free_urldesc(cp->lud);
+ if (cp->attrs)
+ ch_free(cp->attrs);
+ if (cp->ap)
+ ch_free(cp->ap);
+ if (freeme)
+ ch_free(cp);
+}
+
+static int
+constraint_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)(c->bi);
+ constraint *cn = on->on_bi.bi_private, *cp;
+ struct berval bv;
+ int i, rc = 0;
+ constraint ap = { NULL };
+ const char *text = NULL;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE:
+ for (cp=cn; cp; cp=cp->ap_next) {
+ char *s;
+ char *tstr = NULL;
+ int quotes = 0, numeric = 0;
+ int j;
+ size_t val;
+ char val_buf[SLAP_TEXT_BUFLEN] = { '\0' };
+
+ bv.bv_len = STRLENOF(" ");
+ for (j = 0; cp->ap[j]; j++) {
+ bv.bv_len += cp->ap[j]->ad_cname.bv_len;
+ }
+
+ /* room for commas */
+ bv.bv_len += j - 1;
+
+ switch (cp->type) {
+ case CONSTRAINT_COUNT:
+ tstr = COUNT_STR;
+ val = cp->count;
+ numeric = 1;
+ break;
+ case CONSTRAINT_SIZE:
+ tstr = SIZE_STR;
+ val = cp->size;
+ numeric = 1;
+ break;
+ case CONSTRAINT_REGEX:
+ tstr = REGEX_STR;
+ quotes = 1;
+ break;
+ case CONSTRAINT_SET:
+ tstr = SET_STR;
+ quotes = 1;
+ break;
+ case CONSTRAINT_URI:
+ tstr = URI_STR;
+ quotes = 1;
+ break;
+ default:
+ abort();
+ }
+
+ bv.bv_len += strlen(tstr);
+ bv.bv_len += cp->val.bv_len + 2*quotes;
+
+ if (cp->restrict_lud != NULL) {
+ bv.bv_len += cp->restrict_val.bv_len + STRLENOF(" restrict=\"\"");
+ }
+
+ if (numeric) {
+ int len = snprintf(val_buf, sizeof(val_buf), "%zu", val);
+ if (len <= 0) {
+ /* error */
+ return -1;
+ }
+ bv.bv_len += len;
+ }
+
+ s = bv.bv_val = ch_malloc(bv.bv_len + 1);
+
+ s = lutil_strncopy( s, cp->ap[0]->ad_cname.bv_val, cp->ap[0]->ad_cname.bv_len );
+ for (j = 1; cp->ap[j]; j++) {
+ *s++ = ',';
+ s = lutil_strncopy( s, cp->ap[j]->ad_cname.bv_val, cp->ap[j]->ad_cname.bv_len );
+ }
+ *s++ = ' ';
+ s = lutil_strcopy( s, tstr );
+ *s++ = ' ';
+ if (numeric) {
+ s = lutil_strcopy( s, val_buf );
+ } else {
+ if ( quotes ) *s++ = '"';
+ s = lutil_strncopy( s, cp->val.bv_val, cp->val.bv_len );
+ if ( quotes ) *s++ = '"';
+ }
+ if (cp->restrict_lud != NULL) {
+ s = lutil_strcopy( s, " restrict=\"" );
+ s = lutil_strncopy( s, cp->restrict_val.bv_val, cp->restrict_val.bv_len );
+ *s++ = '"';
+ }
+ *s = '\0';
+
+ rc = value_add_one( &c->rvalue_vals, &bv );
+ if (rc == LDAP_SUCCESS)
+ rc = value_add_one( &c->rvalue_nvals, &bv );
+ ch_free(bv.bv_val);
+ if (rc) return rc;
+ }
+ break;
+ default:
+ abort();
+ break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE:
+ if (!cn) break; /* nothing to do */
+
+ if (c->valx < 0) {
+ /* zap all constraints */
+ while (cn) {
+ cp = cn->ap_next;
+ constraint_free( cn, 1 );
+ cn = cp;
+ }
+
+ on->on_bi.bi_private = NULL;
+ } else {
+ constraint **cpp;
+
+ /* zap constraint numbered 'valx' */
+ for(i=0, cp = cn, cpp = &cn;
+ (cp) && (i<c->valx);
+ i++, cpp = &cp->ap_next, cp = *cpp);
+
+ if (cp) {
+ /* zap cp, and join cpp to cp->ap_next */
+ *cpp = cp->ap_next;
+ constraint_free( cp, 1 );
+ }
+ on->on_bi.bi_private = cn;
+ }
+ break;
+
+ default:
+ abort();
+ break;
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ switch (c->type) {
+ case CONSTRAINT_ATTRIBUTE: {
+ int j;
+ char **attrs = ldap_str2charray( c->argv[1], "," );
+
+ for ( j = 0; attrs[j]; j++)
+ /* just count */ ;
+ ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 );
+ for ( j = 0; attrs[j]; j++) {
+ if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s\n", c->argv[0], attrs[j], text );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+
+ if ( strcasecmp( c->argv[2], REGEX_STR ) == 0) {
+ int err;
+
+ ap.type = CONSTRAINT_REGEX;
+ ap.re = ch_malloc( sizeof(regex_t) );
+ if ((err = regcomp( ap.re,
+ c->argv[3], REG_EXTENDED )) != 0) {
+ char errmsg[1024];
+
+ regerror( err, ap.re, errmsg, sizeof(errmsg) );
+ ch_free(ap.re);
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Illegal regular expression \"%s\": Error %s",
+ c->argv[0], c->argv[1], c->argv[3], errmsg);
+ ap.re = NULL;
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+ } else if ( strcasecmp( c->argv[2], SIZE_STR ) == 0 ) {
+ size_t size;
+ char *endptr;
+
+ ap.type = CONSTRAINT_SIZE;
+ ap.size = strtoull(c->argv[3], &endptr, 10);
+ if ( *endptr )
+ rc = ARG_BAD_CONF;
+ } else if ( strcasecmp( c->argv[2], COUNT_STR ) == 0 ) {
+ size_t count;
+ char *endptr;
+
+ ap.type = CONSTRAINT_COUNT;
+ ap.count = strtoull(c->argv[3], &endptr, 10);
+ if ( *endptr )
+ rc = ARG_BAD_CONF;
+ } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) {
+ int err;
+
+ ap.type = CONSTRAINT_URI;
+ err = ldap_url_parse(c->argv[3], &ap.lud);
+ if ( err != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Invalid URI \"%s\"",
+ c->argv[0], c->argv[1], c->argv[3]);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if (ap.lud->lud_host != NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unsupported hostname in URI \"%s\"",
+ c->argv[0], c->argv[1], c->argv[3]);
+ ldap_free_urldesc(ap.lud);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ for ( i=0; ap.lud->lud_attrs[i]; i++);
+ /* FIXME: This is worthless without at least one attr */
+ if ( i ) {
+ ap.attrs = ch_malloc( (i+1)*sizeof(AttributeDescription *));
+ for ( i=0; ap.lud->lud_attrs[i]; i++) {
+ ap.attrs[i] = NULL;
+ if ( slap_str2ad( ap.lud->lud_attrs[i], &ap.attrs[i], &text ) ) {
+ ch_free( ap.attrs );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s\n", c->argv[0], ap.lud->lud_attrs[i], text );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+ ap.attrs[i] = NULL;
+ }
+
+ if (ap.lud->lud_dn == NULL) {
+ ap.lud->lud_dn = ch_strdup("");
+ } else {
+ struct berval dn, ndn;
+
+ ber_str2bv( ap.lud->lud_dn, 0, 0, &dn );
+ if (dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) ) {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: URI %s DN normalization failed",
+ c->argv[0], c->argv[1], c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ldap_memfree( ap.lud->lud_dn );
+ ap.lud->lud_dn = ndn.bv_val;
+ }
+
+ if (ap.lud->lud_filter == NULL) {
+ ap.lud->lud_filter = ch_strdup("objectClass=*");
+ } else if ( ap.lud->lud_filter[0] == '(' ) {
+ ber_len_t len = strlen( ap.lud->lud_filter );
+ if ( ap.lud->lud_filter[len - 1] != ')' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: invalid URI filter: %s",
+ c->argv[0], c->argv[1], ap.lud->lud_filter );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ AC_MEMCPY( &ap.lud->lud_filter[0], &ap.lud->lud_filter[1], len - 2 );
+ ap.lud->lud_filter[len - 2] = '\0';
+ }
+
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+
+ } else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) {
+ ap.set = 1;
+ ber_str2bv( c->argv[3], 0, 1, &ap.val );
+ ap.type = CONSTRAINT_SET;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Unknown constraint type: %s",
+ c->argv[0], c->argv[1], c->argv[2] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if ( c->argc > 4 ) {
+ int argidx;
+
+ for ( argidx = 4; argidx < c->argc; argidx++ ) {
+ if ( strncasecmp( c->argv[argidx], "restrict=", STRLENOF("restrict=") ) == 0 ) {
+ int err;
+ char *arg = c->argv[argidx] + STRLENOF("restrict=");
+
+ err = ldap_url_parse(arg, &ap.restrict_lud);
+ if ( err != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: Invalid restrict URI \"%s\"",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if (ap.restrict_lud->lud_host != NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unsupported hostname in restrict URI \"%s\"",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ if ( ap.restrict_lud->lud_attrs != NULL ) {
+ if ( ap.restrict_lud->lud_attrs[0] != '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: attrs not allowed in restrict URI %s\n",
+ c->argv[0], c->argv[1], arg);
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ ldap_memvfree((void *)ap.restrict_lud->lud_attrs);
+ ap.restrict_lud->lud_attrs = NULL;
+ }
+
+ if (ap.restrict_lud->lud_dn != NULL) {
+ if (ap.restrict_lud->lud_dn[0] == '\0') {
+ ldap_memfree(ap.restrict_lud->lud_dn);
+ ap.restrict_lud->lud_dn = NULL;
+
+ } else {
+ struct berval dn, ndn;
+ int j;
+
+ ber_str2bv(ap.restrict_lud->lud_dn, 0, 0, &dn);
+ if (dnNormalize(0, NULL, NULL, &dn, &ndn, NULL)) {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI %s DN normalization failed",
+ c->argv[0], c->argv[1], arg );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ assert(c->be != NULL);
+ if (c->be->be_nsuffix == NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI requires suffix",
+ c->argv[0], c->argv[1] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ for ( j = 0; !BER_BVISNULL(&c->be->be_nsuffix[j]); j++) {
+ if (dnIsSuffix(&ndn, &c->be->be_nsuffix[j])) break;
+ }
+
+ if (BER_BVISNULL(&c->be->be_nsuffix[j])) {
+ /* error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI DN %s not within database naming context(s)",
+ c->argv[0], c->argv[1], dn.bv_val );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+
+ ap.restrict_ndn = ndn;
+ }
+ }
+
+ if (ap.restrict_lud->lud_filter != NULL) {
+ ap.restrict_filter = str2filter(ap.restrict_lud->lud_filter);
+ if (ap.restrict_filter == NULL) {
+ /* error */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: restrict URI filter %s invalid",
+ c->argv[0], c->argv[1], ap.restrict_lud->lud_filter );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+
+ ber_str2bv(c->argv[argidx] + STRLENOF("restrict="), 0, 1, &ap.restrict_val);
+
+ } else {
+ /* cleanup */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s %s: unrecognized arg #%d (%s)",
+ c->argv[0], c->argv[1], argidx, c->argv[argidx] );
+ rc = ARG_BAD_CONF;
+ goto done;
+ }
+ }
+ }
+
+done:;
+ if ( rc == LDAP_SUCCESS ) {
+ constraint *a2 = ch_calloc( sizeof(constraint), 1 );
+ a2->ap_next = on->on_bi.bi_private;
+ a2->ap = ap.ap;
+ a2->type = ap.type;
+ a2->re = ap.re;
+ a2->val = ap.val;
+ a2->lud = ap.lud;
+ a2->set = ap.set;
+ a2->size = ap.size;
+ a2->count = ap.count;
+ if ( a2->lud ) {
+ ber_str2bv(a2->lud->lud_dn, 0, 0, &a2->dn);
+ ber_str2bv(a2->lud->lud_filter, 0, 0, &a2->filter);
+ }
+ a2->attrs = ap.attrs;
+ a2->restrict_lud = ap.restrict_lud;
+ a2->restrict_ndn = ap.restrict_ndn;
+ a2->restrict_filter = ap.restrict_filter;
+ a2->restrict_val = ap.restrict_val;
+ on->on_bi.bi_private = a2;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ constraint_free( &ap, 0 );
+ }
+
+ ldap_memvfree((void**)attrs);
+ } break;
+ default:
+ abort();
+ break;
+ }
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+constraint_uri_cb( Operation *op, SlapReply *rs )
+{
+ if(rs->sr_type == REP_SEARCH) {
+ int *foundp = op->o_callback->sc_private;
+
+ *foundp = 1;
+
+ Debug(LDAP_DEBUG_TRACE, "==> constraint_uri_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);
+ }
+ return 0;
+}
+
+static int
+constraint_violation( constraint *c, struct berval *bv, Operation *op )
+{
+ if ((!c) || (!bv)) return LDAP_SUCCESS;
+
+ switch (c->type) {
+ case CONSTRAINT_SIZE:
+ if (bv->bv_len > c->size)
+ return LDAP_CONSTRAINT_VIOLATION; /* size violation */
+ break;
+ case CONSTRAINT_REGEX:
+ if (regexec(c->re, bv->bv_val, 0, NULL, 0) == REG_NOMATCH)
+ return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */
+ break;
+ case CONSTRAINT_URI: {
+ Operation nop = *op;
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ slap_callback cb = { 0 };
+ int i;
+ int found = 0;
+ int rc;
+ size_t len;
+ struct berval filterstr;
+ char *ptr;
+
+ cb.sc_response = constraint_uri_cb;
+ cb.sc_private = &found;
+
+ nop.o_protocol = LDAP_VERSION3;
+ nop.o_tag = LDAP_REQ_SEARCH;
+ nop.o_time = slap_get_time();
+ if (c->lud->lud_dn) {
+ struct berval dn;
+
+ ber_str2bv(c->lud->lud_dn, 0, 0, &dn);
+ nop.o_req_dn = dn;
+ nop.o_req_ndn = dn;
+ nop.o_bd = select_backend(&nop.o_req_ndn, 1 );
+ if (!nop.o_bd) {
+ return LDAP_NO_SUCH_OBJECT; /* unexpected error */
+ }
+ if (!nop.o_bd->be_search) {
+ return LDAP_OTHER; /* unexpected error */
+ }
+ } else {
+ nop.o_req_dn = nop.o_bd->be_nsuffix[0];
+ nop.o_req_ndn = nop.o_bd->be_nsuffix[0];
+ nop.o_bd = on->on_info->oi_origdb;
+ }
+ nop.o_do_not_cache = 1;
+ nop.o_callback = &cb;
+
+ nop.ors_scope = c->lud->lud_scope;
+ nop.ors_deref = LDAP_DEREF_NEVER;
+ nop.ors_slimit = SLAP_NO_LIMIT;
+ nop.ors_tlimit = SLAP_NO_LIMIT;
+ nop.ors_limit = NULL;
+
+ nop.ors_attrsonly = 0;
+ nop.ors_attrs = slap_anlist_no_attrs;
+
+ len = STRLENOF("(&(") +
+ c->filter.bv_len +
+ STRLENOF(")(|");
+
+ for (i = 0; c->attrs[i]; i++) {
+ len += STRLENOF("(") +
+ c->attrs[i]->ad_cname.bv_len +
+ STRLENOF("=") +
+ bv->bv_len +
+ STRLENOF(")");
+ }
+
+ len += STRLENOF("))");
+ filterstr.bv_len = len;
+ filterstr.bv_val = op->o_tmpalloc(len + 1, op->o_tmpmemctx);
+
+ ptr = filterstr.bv_val +
+ snprintf(filterstr.bv_val, len, "(&(%s)(|", c->lud->lud_filter);
+ for (i = 0; c->attrs[i]; i++) {
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, c->attrs[i]->ad_cname.bv_val );
+ *ptr++ = '=';
+ ptr = lutil_strcopy( ptr, bv->bv_val );
+ *ptr++ = ')';
+ }
+ *ptr++ = ')';
+ *ptr++ = ')';
+ *ptr++ = '\0';
+
+ nop.ors_filterstr = filterstr;
+ nop.ors_filter = str2filter_x(&nop, filterstr.bv_val);
+ if ( nop.ors_filter == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s constraint_violation uri filter=\"%s\" invalid\n",
+ op->o_log_prefix, filterstr.bv_val, 0 );
+ rc = LDAP_OTHER;
+
+ } else {
+ SlapReply nrs = { REP_RESULT };
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri filter = %s\n",
+ filterstr.bv_val, 0, 0);
+
+ rc = nop.o_bd->be_search( &nop, &nrs );
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_violation uri rc = %d, found = %d\n",
+ rc, found, 0);
+ }
+ op->o_tmpfree(filterstr.bv_val, op->o_tmpmemctx);
+
+ if ((rc != LDAP_SUCCESS) && (rc != LDAP_NO_SUCH_OBJECT)) {
+ return rc; /* unexpected error */
+ }
+
+ if (!found)
+ return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */
+ break;
+ }
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static char *
+print_message( struct berval *errtext, AttributeDescription *a )
+{
+ char *ret;
+ int sz;
+
+ sz = errtext->bv_len + sizeof(" on ") + a->ad_cname.bv_len;
+ ret = ch_malloc(sz);
+ snprintf( ret, sz, "%s on %s", errtext->bv_val, a->ad_cname.bv_val );
+ return ret;
+}
+
+static unsigned
+constraint_count_attr(Entry *e, AttributeDescription *ad)
+{
+ struct Attribute *a;
+
+ if ((a = attr_find(e->e_attrs, ad)) != NULL)
+ return a->a_numvals;
+ return 0;
+}
+
+static int
+constraint_check_restrict( Operation *op, constraint *c, Entry *e )
+{
+ assert( c->restrict_lud != NULL );
+
+ if ( c->restrict_lud->lud_dn != NULL ) {
+ int diff = e->e_nname.bv_len - c->restrict_ndn.bv_len;
+
+ if ( diff < 0 ) {
+ return 0;
+ }
+
+ if ( c->restrict_lud->lud_scope == LDAP_SCOPE_BASE ) {
+ return bvmatch( &e->e_nname, &c->restrict_ndn );
+ }
+
+ if ( !dnIsSuffix( &e->e_nname, &c->restrict_ndn ) ) {
+ return 0;
+ }
+
+ if ( c->restrict_lud->lud_scope != LDAP_SCOPE_SUBTREE ) {
+ struct berval pdn;
+
+ if ( diff == 0 ) {
+ return 0;
+ }
+
+ dnParent( &e->e_nname, &pdn );
+
+ if ( c->restrict_lud->lud_scope == LDAP_SCOPE_ONELEVEL
+ && pdn.bv_len != c->restrict_ndn.bv_len )
+ {
+ return 0;
+ }
+ }
+ }
+
+ if ( c->restrict_filter != NULL ) {
+ int rc;
+ struct berval save_dn = op->o_dn, save_ndn = op->o_ndn;
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ rc = test_filter( op, e, c->restrict_filter );
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+
+ if ( rc != LDAP_COMPARE_TRUE ) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int
+constraint_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Attribute *a;
+ constraint *c = on->on_bi.bi_private, *cp;
+ BerVarray b = NULL;
+ int i;
+ struct berval rsv = BER_BVC("add breaks constraint");
+ int rc = 0;
+ char *msg = NULL;
+
+ if (get_relax(op) || SLAPD_SYNC_IS_SYNCCONN( op->o_connid )) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ((a = op->ora_e->e_attrs) == NULL) {
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "constraint_add: no attrs");
+ return(rs->sr_err);
+ }
+
+ for(; a; a = a->a_next ) {
+ /* we don't constrain operational attributes */
+ if (is_at_operational(a->a_desc->ad_type)) continue;
+
+ for(cp = c; cp; cp = cp->ap_next) {
+ int j;
+ for (j = 0; cp->ap[j]; j++) {
+ if (cp->ap[j] == a->a_desc) break;
+ }
+ if (cp->ap[j] == NULL) continue;
+ if ((b = a->a_vals) == NULL) continue;
+
+ if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, op->ora_e) == 0) {
+ continue;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_add, "
+ "a->a_numvals = %u, cp->count = %lu\n",
+ a->a_numvals, (unsigned long) cp->count, 0);
+
+ switch (cp->type) {
+ case CONSTRAINT_COUNT:
+ if (a->a_numvals > cp->count)
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ case CONSTRAINT_SET:
+ if (acl_match_set(&cp->val, op, op->ora_e, NULL) == 0)
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ break;
+ default:
+ for ( i = 0; b[i].bv_val; i++ ) {
+ rc = constraint_violation( cp, &b[i], op );
+ if ( rc ) {
+ goto add_violation;
+ }
+ }
+ }
+ if ( rc )
+ goto add_violation;
+
+ }
+ }
+
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+
+add_violation:
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ if (rc == LDAP_CONSTRAINT_VIOLATION ) {
+ msg = print_message( &rsv, a->a_desc );
+ }
+ send_ldap_error(op, rs, rc, msg );
+ ch_free(msg);
+ return (rs->sr_err);
+}
+
+
+static int
+constraint_check_count_violation( Modifications *m, Entry *target_entry, constraint *cp )
+{
+ BerVarray b = NULL;
+ unsigned ce = 0;
+ unsigned ca;
+ int j;
+
+ for ( j = 0; cp->ap[j]; j++ ) {
+ /* Get this attribute count */
+ if ( target_entry )
+ ce = constraint_count_attr( target_entry, cp->ap[j] );
+
+ for( ; m; m = m->sml_next ) {
+ if ( cp->ap[j] == m->sml_desc ) {
+ ca = m->sml_numvals;
+ switch ( m->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL:
+ if ( !ca || ca > ce ) {
+ ce = 0;
+ } else {
+ /* No need to check for values' validity. Invalid values
+ * cause the whole transaction to die anyway. */
+ ce -= ca;
+ }
+ break;
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD:
+ ce += ca;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ ce = ca;
+ break;
+
+#if 0
+ /* TODO */
+ case handle SLAP_MOD_ADD_IF_NOT_PRESENT:
+#endif
+
+ default:
+ /* impossible! assert? */
+ return 1;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_check_count_violation ce = %u, "
+ "ca = %u, cp->count = %lu\n",
+ ce, ca, (unsigned long) cp->count);
+ }
+ }
+ }
+
+ return ( ce > cp->count );
+}
+
+static int
+constraint_update( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Backend *be = op->o_bd;
+ constraint *c = on->on_bi.bi_private, *cp;
+ Entry *target_entry = NULL, *target_entry_copy = NULL;
+ Modifications *modlist, *m;
+ BerVarray b = NULL;
+ int i;
+ struct berval rsv = BER_BVC("modify breaks constraint");
+ int rc;
+ char *msg = NULL;
+ int is_v;
+
+ if (get_relax(op) || SLAPD_SYNC_IS_SYNCCONN( op->o_connid )) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_MODIFY:
+ modlist = op->orm_modlist;
+ break;
+
+ case LDAP_REQ_MODRDN:
+ modlist = op->orr_modlist;
+ break;
+
+ default:
+ /* impossible! assert? */
+ return LDAP_OTHER;
+ }
+
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_update()\n", 0,0,0);
+ if ((m = modlist) == NULL) {
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "constraint_update() got null modlist");
+ return(rs->sr_err);
+ }
+
+ op->o_bd = on->on_info->oi_origdb;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry );
+ op->o_bd = be;
+
+ /* let the backend send the error */
+ if ( target_entry == NULL )
+ return SLAP_CB_CONTINUE;
+
+ /* Do we need to count attributes? */
+ for(cp = c; cp; cp = cp->ap_next) {
+ if (cp->type == CONSTRAINT_COUNT) {
+ if (cp->restrict_lud && constraint_check_restrict(op, cp, target_entry) == 0) {
+ continue;
+ }
+
+ is_v = constraint_check_count_violation(m, target_entry, cp);
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> constraint_update is_v: %d\n", is_v, 0, 0);
+
+ if (is_v) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto mod_violation;
+ }
+ }
+ }
+
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ for(;m; m = m->sml_next) {
+ unsigned ce = 0;
+
+ if (is_at_operational( m->sml_desc->ad_type )) continue;
+
+ if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) &&
+ (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) &&
+ (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE))
+ continue;
+ /* we only care about ADD and REPLACE modifications */
+ /* and DELETE are used to track attribute count */
+ if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL))
+ continue;
+
+ for(cp = c; cp; cp = cp->ap_next) {
+ int j;
+ for (j = 0; cp->ap[j]; j++) {
+ if (cp->ap[j] == m->sml_desc) {
+ break;
+ }
+ }
+ if (cp->ap[j] == NULL) continue;
+
+ if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, target_entry) == 0) {
+ continue;
+ }
+
+ /* DELETE are to be ignored beyond this point */
+ if (( m->sml_op & LDAP_MOD_OP ) == LDAP_MOD_DELETE)
+ continue;
+
+ for ( i = 0; b[i].bv_val; i++ ) {
+ rc = constraint_violation( cp, &b[i], op );
+ if ( rc ) {
+ goto mod_violation;
+ }
+ }
+
+ if (cp->type == CONSTRAINT_SET && target_entry) {
+ if (target_entry_copy == NULL) {
+ Modifications *ml;
+
+ target_entry_copy = entry_dup(target_entry);
+
+ /* if rename, set the new entry's name
+ * (in normalized form only) */
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ struct berval pdn, ndn = BER_BVNULL;
+
+ if ( op->orr_nnewSup ) {
+ pdn = *op->orr_nnewSup;
+
+ } else {
+ dnParent( &target_entry_copy->e_nname, &pdn );
+ }
+
+ build_new_dn( &ndn, &pdn, &op->orr_nnewrdn, NULL );
+
+ ber_memfree( target_entry_copy->e_nname.bv_val );
+ target_entry_copy->e_nname = ndn;
+ ber_bvreplace( &target_entry_copy->e_name, &ndn );
+ }
+
+ /* apply modifications, in an attempt
+ * to estimate what the entry would
+ * look like in case all modifications
+ * pass */
+ for ( ml = modlist; ml; ml = ml->sml_next ) {
+ Modification *mod = &ml->sml_mod;
+ const char *text;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+ int err;
+
+ switch ( mod->sm_op ) {
+ case LDAP_MOD_ADD:
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_DELETE:
+ err = modify_delete_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_REPLACE:
+ err = modify_replace_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ err = modify_increment_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ break;
+
+ case SLAP_MOD_SOFTADD:
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_SOFTADD;
+ if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) {
+ err = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_SOFTDEL:
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_delete_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_SOFTDEL;
+ if ( err == LDAP_NO_SUCH_ATTRIBUTE ) {
+ err = LDAP_SUCCESS;
+ }
+ break;
+
+ case SLAP_MOD_ADD_IF_NOT_PRESENT:
+ if ( attr_find( target_entry_copy->e_attrs, mod->sm_desc ) ) {
+ err = LDAP_SUCCESS;
+ break;
+ }
+ mod->sm_op = LDAP_MOD_ADD;
+ err = modify_add_values( target_entry_copy,
+ mod, get_permissiveModify(op),
+ &text, textbuf, textlen );
+ mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT;
+ break;
+
+ default:
+ err = LDAP_OTHER;
+ break;
+ }
+
+ if ( err != LDAP_SUCCESS ) {
+ rc = err;
+ goto mod_violation;
+ }
+ }
+ }
+
+ if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto mod_violation;
+ }
+ }
+ }
+ }
+
+ if (target_entry) {
+ op->o_bd = on->on_info->oi_origdb;
+ be_entry_release_r(op, target_entry);
+ op->o_bd = be;
+ }
+
+ if (target_entry_copy) {
+ entry_free(target_entry_copy);
+ }
+
+ return SLAP_CB_CONTINUE;
+
+mod_violation:
+ /* violation */
+ if (target_entry) {
+ op->o_bd = on->on_info->oi_origdb;
+ be_entry_release_r(op, target_entry);
+ op->o_bd = be;
+ }
+
+ if (target_entry_copy) {
+ entry_free(target_entry_copy);
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)(on->on_info);
+ if ( rc == LDAP_CONSTRAINT_VIOLATION ) {
+ msg = print_message( &rsv, m->sml_desc );
+ }
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, msg );
+ ch_free(msg);
+ return (rs->sr_err);
+}
+
+static int
+constraint_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ constraint *ap, *a2;
+
+ for ( ap = on->on_bi.bi_private; ap; ap = a2 ) {
+ a2 = ap->ap_next;
+ constraint_free( ap, 1 );
+ }
+
+ return 0;
+}
+
+static slap_overinst constraint_ovl;
+
+#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC
+static
+#endif
+int
+constraint_initialize( void ) {
+ int rc;
+
+ constraint_ovl.on_bi.bi_type = "constraint";
+ constraint_ovl.on_bi.bi_db_destroy = constraint_destroy;
+ constraint_ovl.on_bi.bi_op_add = constraint_add;
+ constraint_ovl.on_bi.bi_op_modify = constraint_update;
+ constraint_ovl.on_bi.bi_op_modrdn = constraint_update;
+
+ constraint_ovl.on_bi.bi_private = NULL;
+
+ constraint_ovl.on_bi.bi_cf_ocs = constraintocs;
+ rc = config_register_schema( constraintcfg, constraintocs );
+ if (rc) return rc;
+
+ return overlay_register( &constraint_ovl );
+}
+
+#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return constraint_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_CONSTRAINT) */
+
diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c
new file mode 100644
index 0000000..7bb5186
--- /dev/null
+++ b/servers/slapd/overlays/dds.c
@@ -0,0 +1,2046 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2005-2006 SysNet s.n.c.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.n.c.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DDS
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+
+#include "config.h"
+
+#define DDS_RF2589_MAX_TTL (31557600) /* 1 year + 6 hours */
+#define DDS_RF2589_DEFAULT_TTL (86400) /* 1 day */
+#define DDS_DEFAULT_INTERVAL (3600) /* 1 hour */
+
+typedef struct dds_info_t {
+ unsigned di_flags;
+#define DDS_FOFF (0x1U) /* is this really needed? */
+#define DDS_SET(di, f) ( (di)->di_flags & (f) )
+
+#define DDS_OFF(di) DDS_SET( (di), DDS_FOFF )
+
+ time_t di_max_ttl;
+ time_t di_min_ttl;
+ time_t di_default_ttl;
+#define DDS_DEFAULT_TTL(di) \
+ ( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl )
+
+ time_t di_tolerance;
+
+ /* expire check interval and task */
+ time_t di_interval;
+#define DDS_INTERVAL(di) \
+ ( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL )
+ struct re_s *di_expire_task;
+
+ /* allows to limit the maximum number of dynamic objects */
+ ldap_pvt_thread_mutex_t di_mutex;
+ int di_num_dynamicObjects;
+ int di_max_dynamicObjects;
+
+ /* used to advertize the dynamicSubtrees in the root DSE,
+ * and to select the database in the expiration task */
+ BerVarray di_suffix;
+ BerVarray di_nsuffix;
+} dds_info_t;
+
+static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH );
+static AttributeDescription *ad_entryExpireTimestamp;
+
+/* list of expired DNs */
+typedef struct dds_expire_t {
+ struct berval de_ndn;
+ struct dds_expire_t *de_next;
+} dds_expire_t;
+
+typedef struct dds_cb_t {
+ dds_expire_t *dc_ndnlist;
+} dds_cb_t;
+
+static int
+dds_expire_cb( Operation *op, SlapReply *rs )
+{
+ dds_cb_t *dc = (dds_cb_t *)op->o_callback->sc_private;
+ dds_expire_t *de;
+ int rc;
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ /* alloc list and buffer for berval all in one */
+ de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1,
+ op->o_tmpmemctx );
+
+ de->de_next = dc->dc_ndnlist;
+ dc->dc_ndnlist = de;
+
+ de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len;
+ de->de_ndn.bv_val = (char *)&de[ 1 ];
+ AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val,
+ rs->sr_entry->e_nname.bv_len + 1 );
+ rc = 0;
+ break;
+
+ case REP_SEARCHREF:
+ case REP_RESULT:
+ rc = rs->sr_err;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ return rc;
+}
+
+static int
+dds_expire( void *ctx, dds_info_t *di )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback sc = { 0 };
+ dds_cb_t dc = { 0 };
+ dds_expire_t *de = NULL, **dep;
+ SlapReply rs = { REP_RESULT };
+
+ time_t expire;
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval ts;
+
+ int ndeletes, ntotdeletes;
+
+ int rc;
+ char *extra = "";
+
+ connection_fake_init2( &conn, &opbuf, ctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+ op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0 );
+
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_tlimit = DDS_INTERVAL( di )/2 + 1;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ expire = slap_get_time() - di->di_tolerance;
+ ts.bv_val = tsbuf;
+ ts.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &ts );
+
+ op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" )
+ + slap_schema.si_oc_dynamicObject->soc_cname.bv_len
+ + ad_entryExpireTimestamp->ad_cname.bv_len
+ + ts.bv_len;
+ op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+ snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+ "(&(objectClass=%s)(%s<=%s))",
+ slap_schema.si_oc_dynamicObject->soc_cname.bv_val,
+ ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val );
+
+ op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+ if ( op->ors_filter == NULL ) {
+ rs.sr_err = LDAP_OTHER;
+ goto done_search;
+ }
+
+ op->o_callback = &sc;
+ sc.sc_response = dds_expire_cb;
+ sc.sc_private = &dc;
+
+ (void)op->o_bd->bd_info->bi_op_search( op, &rs );
+
+done_search:;
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+
+ rc = rs.sr_err;
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* (ITS#5267) database not created yet? */
+ rs.sr_err = LDAP_SUCCESS;
+ extra = " (ignored)";
+ /* fallthru */
+
+ default:
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS expired objects lookup failed err=%d%s\n",
+ rc, extra );
+ goto done;
+ }
+
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_callback = &sc;
+ sc.sc_response = slap_null_cb;
+ sc.sc_private = NULL;
+
+ for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL && ndeletes > 0; ) {
+ ndeletes = 0;
+
+ for ( dep = &dc.dc_ndnlist; *dep != NULL; ) {
+ de = *dep;
+
+ op->o_req_dn = de->de_ndn;
+ op->o_req_ndn = de->de_ndn;
+ (void)op->o_bd->bd_info->bi_op_delete( op, &rs );
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS dn=\"%s\" expired.\n",
+ de->de_ndn.bv_val );
+ ndeletes++;
+ break;
+
+ case LDAP_NOT_ALLOWED_ON_NONLEAF:
+ Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "DDS dn=\"%s\" is non-leaf; "
+ "deferring.\n",
+ de->de_ndn.bv_val );
+ dep = &de->de_next;
+ de = NULL;
+ break;
+
+ default:
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "DDS dn=\"%s\" err=%d; "
+ "deferring.\n",
+ de->de_ndn.bv_val, rs.sr_err );
+ break;
+ }
+
+ if ( de != NULL ) {
+ *dep = de->de_next;
+ op->o_tmpfree( de, op->o_tmpmemctx );
+ }
+ }
+
+ ntotdeletes += ndeletes;
+ }
+
+ rs.sr_err = LDAP_SUCCESS;
+
+ Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS expired=%d\n", ntotdeletes );
+
+done:;
+ return rs.sr_err;
+}
+
+static void *
+dds_expire_fn( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ dds_info_t *di = rtask->arg;
+
+ assert( di->di_expire_task == rtask );
+
+ (void)dds_expire( ctx, di );
+
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+/* frees the callback */
+static int
+dds_freeit_cb( Operation *op, SlapReply *rs )
+{
+ op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* updates counter - installed on add/delete only if required */
+static int
+dds_counter_cb( Operation *op, SlapReply *rs )
+{
+ assert( rs->sr_type == REP_RESULT );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ dds_info_t *di = op->o_callback->sc_private;
+
+ ldap_pvt_thread_mutex_lock( &di->di_mutex );
+ switch ( op->o_tag ) {
+ case LDAP_REQ_DELETE:
+ assert( di->di_num_dynamicObjects > 0 );
+ di->di_num_dynamicObjects--;
+ break;
+
+ case LDAP_REQ_ADD:
+ assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects );
+ di->di_num_dynamicObjects++;
+ break;
+
+ default:
+ assert( 0 );
+ }
+ ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+ }
+
+ return dds_freeit_cb( op, rs );
+}
+
+static int
+dds_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int is_dynamicObject;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ is_dynamicObject = is_entry_dynamicObject( op->ora_e );
+
+ /* FIXME: do not allow this right now, pending clarification */
+ if ( is_dynamicObject ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ if ( is_entry_referral( op->ora_e ) ) {
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "a referral cannot be a dynamicObject";
+
+ } else if ( is_entry_alias( op->ora_e ) ) {
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ rs->sr_text = "an alias cannot be a dynamicObject";
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+
+ /* we don't allow dynamicObjects to have static subordinates */
+ if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) {
+ struct berval p_ndn;
+ Entry *e = NULL;
+ int rc;
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ dnParent( &op->o_req_ndn, &p_ndn );
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &p_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ if ( !is_dynamicObject ) {
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+
+ } else {
+ rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" );
+ }
+ }
+
+ be_entry_release_r( op, e );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+ op->o_bd->bd_info = bi;
+ }
+
+ /* handle dynamic object operational attr(s) */
+ if ( is_dynamicObject ) {
+ time_t ttl, expire;
+ char ttlbuf[STRLENOF("31557600") + 1];
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval bv;
+
+ if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) {
+ ldap_pvt_thread_mutex_lock( &di->di_mutex );
+ rs->sr_err = ( di->di_max_dynamicObjects &&
+ di->di_num_dynamicObjects >= di->di_max_dynamicObjects );
+ ldap_pvt_thread_mutex_unlock( &di->di_mutex );
+ if ( rs->sr_err ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "too many dynamicObjects in context" );
+ return rs->sr_err;
+ }
+ }
+
+ ttl = DDS_DEFAULT_TTL( di );
+
+ /* assert because should be checked at configure */
+ assert( ttl <= DDS_RF2589_MAX_TTL );
+
+ bv.bv_val = ttlbuf;
+ bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl );
+ assert( bv.bv_len < sizeof( ttlbuf ) );
+
+ /* FIXME: apparently, values in op->ora_e are malloc'ed
+ * on the thread's slab; works fine by chance,
+ * only because the attribute doesn't exist yet. */
+ assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL );
+ attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv );
+
+ expire = slap_get_time() + ttl;
+ bv.bv_val = tsbuf;
+ bv.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &bv );
+ assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL );
+ attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv );
+
+ /* if required, install counter callback */
+ if ( di->di_max_dynamicObjects > 0) {
+ slap_callback *sc;
+
+ sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+ sc->sc_cleanup = dds_freeit_cb;
+ sc->sc_response = dds_counter_cb;
+ sc->sc_private = di;
+ sc->sc_next = op->o_callback;
+ sc->sc_writewait = 0;
+
+ op->o_callback = sc;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ /* if required, install counter callback */
+ if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) {
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+
+ /* FIXME: couldn't the entry be added before deletion? */
+ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+ slap_callback *sc;
+
+ be_entry_release_r( op, e );
+ e = NULL;
+
+ sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx );
+ sc->sc_cleanup = dds_freeit_cb;
+ sc->sc_response = dds_counter_cb;
+ sc->sc_private = di;
+ sc->sc_writewait = 0;
+ sc->sc_next = op->o_callback;
+
+ op->o_callback = sc;
+ }
+ op->o_bd->bd_info = bi;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = (dds_info_t *)on->on_bi.bi_private;
+ Modifications *mod;
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int was_dynamicObject = 0,
+ is_dynamicObject = 0;
+ struct berval bv_entryTtl = BER_BVNULL;
+ time_t entryTtl = 0;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* bv_entryTtl stores the string representation of the entryTtl
+ * across modifies for consistency checks of the final value;
+ * the bv_val points to a static buffer; the bv_len is zero when
+ * the attribute is deleted.
+ * entryTtl stores the integer representation of the entryTtl;
+ * its value is -1 when the attribute is deleted; it is 0 only
+ * if no modifications of the entryTtl occurred, as an entryTtl
+ * of 0 is invalid. */
+ bv_entryTtl.bv_val = textbuf;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e );
+ if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) {
+ Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl );
+
+ /* the value of the entryTtl is saved for later checks */
+ if ( a != NULL ) {
+ unsigned long ttl;
+ int rc;
+
+ bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len;
+ AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len );
+ bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+ rc = lutil_atoul( &ttl, bv_entryTtl.bv_val );
+ assert( rc == 0 );
+ entryTtl = (time_t)ttl;
+ }
+
+ be_entry_release_r( op, e );
+ e = NULL;
+ was_dynamicObject = is_dynamicObject = 1;
+ }
+ op->o_bd->bd_info = bi;
+
+ rs->sr_err = LDAP_SUCCESS;
+ for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
+ if ( mod->sml_desc == slap_schema.si_ad_objectClass ) {
+ int i;
+ ObjectClass *oc;
+
+ switch ( mod->sml_op ) {
+ case LDAP_MOD_DELETE:
+ if ( mod->sml_values == NULL ) {
+ is_dynamicObject = 0;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+ oc = oc_bvfind( &mod->sml_values[ i ] );
+ if ( oc == slap_schema.si_oc_dynamicObject ) {
+ is_dynamicObject = 0;
+ break;
+ }
+ }
+
+ break;
+
+ case LDAP_MOD_REPLACE:
+ if ( mod->sml_values == NULL ) {
+ is_dynamicObject = 0;
+ break;
+ }
+ /* fallthru */
+
+ case LDAP_MOD_ADD:
+ for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) {
+ oc = oc_bvfind( &mod->sml_values[ i ] );
+ if ( oc == slap_schema.si_oc_dynamicObject ) {
+ is_dynamicObject = 1;
+ break;
+ }
+ }
+ break;
+ }
+
+ } else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) {
+ unsigned long uttl;
+ time_t ttl;
+ int rc;
+
+ switch ( mod->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* FIXME? */
+ if ( mod->sml_values != NULL ) {
+ if ( BER_BVISEMPTY( &bv_entryTtl )
+ || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) )
+ {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ }
+ goto done;
+ }
+ }
+ bv_entryTtl.bv_len = 0;
+ entryTtl = -1;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ bv_entryTtl.bv_len = 0;
+ entryTtl = -1;
+ /* fallthru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* FIXME? */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */
+ assert( mod->sml_values != NULL );
+ assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) );
+
+ if ( !BER_BVISEMPTY( &bv_entryTtl ) ) {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_text = "attribute 'entryTtl' cannot have multiple values";
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ }
+ goto done;
+ }
+
+ rc = lutil_atoul( &uttl, mod->sml_values[ 0 ].bv_val );
+ ttl = (time_t)uttl;
+ assert( rc == 0 );
+ if ( ttl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+ goto done;
+ }
+
+ if ( ttl <= 0 || ttl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+ goto done;
+ }
+
+ entryTtl = ttl;
+ bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len;
+ AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len );
+ bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0';
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ if ( BER_BVISEMPTY( &bv_entryTtl ) ) {
+ rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ } else {
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ rs->sr_text = "modify/increment: entryTtl: no such attribute";
+ }
+ goto done;
+ }
+
+ entryTtl++;
+ if ( entryTtl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+
+ } else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit";
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_text = NULL;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+
+ }
+ goto done;
+ }
+
+ bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl );
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ } else if ( mod->sml_desc == ad_entryExpireTimestamp ) {
+ /* should have been trapped earlier */
+ assert( mod->sml_flags & SLAP_MOD_INTERNAL );
+ }
+ }
+
+done:;
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int rc;
+
+ /* FIXME: this could be allowed when the Relax control is used...
+ * in that case:
+ *
+ * TODO
+ *
+ * static => dynamic:
+ * entryTtl must be provided; add
+ * entryExpireTimestamp accordingly
+ *
+ * dynamic => static:
+ * entryTtl must be removed; remove
+ * entryTimestamp accordingly
+ *
+ * ... but we need to make sure that there are no subordinate
+ * issues...
+ */
+ rc = is_dynamicObject - was_dynamicObject;
+ if ( rc ) {
+#if 0 /* fix subordinate issues first */
+ if ( get_relax( op ) ) {
+ switch ( rc ) {
+ case -1:
+ /* need to delete entryTtl to have a consistent entry */
+ if ( entryTtl != -1 ) {
+ rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion";
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+
+ case 1:
+ /* need to add entryTtl to have a consistent entry */
+ if ( entryTtl <= 0 ) {
+ rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition";
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+ break;
+ }
+
+ } else
+#endif
+ {
+ switch ( rc ) {
+ case -1:
+ rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry";
+ break;
+
+ case 1:
+ rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject";
+ break;
+ }
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ slap_schema.si_ad_entry, NULL, ACL_DISCLOSE );
+ if ( rc == LDAP_INSUFFICIENT_ACCESS ) {
+ rs->sr_text = NULL;
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+ }
+ }
+
+ if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) {
+ Modifications *tmpmod = NULL, **modp;
+
+ for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next )
+ ;
+
+ tmpmod = ch_calloc( 1, sizeof( Modifications ) );
+ tmpmod->sml_flags = SLAP_MOD_INTERNAL;
+ tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname;
+ tmpmod->sml_desc = ad_entryExpireTimestamp;
+
+ *modp = tmpmod;
+
+ if ( entryTtl == -1 ) {
+ /* delete entryExpireTimestamp */
+ tmpmod->sml_op = LDAP_MOD_DELETE;
+
+ } else {
+ time_t expire;
+ char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ struct berval bv;
+
+ /* keep entryExpireTimestamp consistent
+ * with entryTtl */
+ expire = slap_get_time() + entryTtl;
+ bv.bv_val = tsbuf;
+ bv.bv_len = sizeof( tsbuf );
+ slap_timestamp( &expire, &bv );
+
+ tmpmod->sml_op = LDAP_MOD_REPLACE;
+ value_add_one( &tmpmod->sml_values, &bv );
+ value_add_one( &tmpmod->sml_nvalues, &bv );
+ tmpmod->sml_numvals = 1;
+ }
+ }
+
+ if ( rs->sr_err ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dds_op_rename( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* we don't allow dynamicObjects to have static subordinates */
+ if ( op->orr_nnewSup != NULL ) {
+ Entry *e = NULL;
+ BackendInfo *bi = op->o_bd->bd_info;
+ int is_dynamicObject = 0,
+ rc;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ be_entry_release_r( op, e );
+ e = NULL;
+ is_dynamicObject = 1;
+ }
+
+ rc = be_entry_get_rw( op, op->orr_nnewSup,
+ slap_schema.si_oc_dynamicObject, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ if ( !is_dynamicObject ) {
+ /* return referral only if "disclose"
+ * is granted on the object */
+ if ( ! access_allowed( op, e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+
+ } else {
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "static entry cannot have dynamicObject as newSuperior" );
+ }
+ }
+ be_entry_release_r( op, e );
+ }
+ op->o_bd->bd_info = bi;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* entryTtl update for client */
+static int
+dds_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc;
+
+ if ( !DDS_OFF( di )
+ && rs->sr_type == REP_SEARCH
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) )
+ {
+ BerVarray vals = NULL;
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+ char ttlbuf[STRLENOF("31557600") + 1];
+ struct berval ttlvalue;
+ time_t ttl;
+ int len;
+
+ /* User already has access to entryTtl, skip ACL checks on
+ * entryExpireTimestamp */
+ rc = backend_attribute( op, NULL, &rs->sr_entry->e_nname,
+ ad_entryExpireTimestamp, &vals, ACL_NONE );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ assert( vals[0].bv_val[vals[0].bv_len] == '\0' );
+ if ( lutil_parsetime( vals[0].bv_val, &tm ) ) {
+ goto done;
+ }
+
+ lutil_tm2time( &tm, &tt );
+ ttl = tt.tt_sec - op->o_time;
+ ttl = (ttl < 0) ? 0 : ttl;
+ assert( ttl <= DDS_RF2589_MAX_TTL );
+
+ len = snprintf( ttlbuf, sizeof(ttlbuf), "%ld", ttl );
+ if ( len < 0 )
+ {
+ goto done;
+ }
+ ttlvalue.bv_val = ttlbuf;
+ ttlvalue.bv_len = len;
+
+ rs_entry2modifiable( op, rs, on );
+
+ if ( attr_delete( &rs->sr_entry->e_attrs,
+ slap_schema.si_ad_entryTtl ) )
+ {
+ goto done;
+ }
+ if ( attr_merge_normalize_one( rs->sr_entry,
+ slap_schema.si_ad_entryTtl,
+ &ttlvalue, op->o_tmpmemctx ) )
+ {
+ goto done;
+ }
+
+done:;
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+slap_parse_refresh(
+ struct berval *in,
+ struct berval *ndn,
+ time_t *ttl,
+ const char **text,
+ void *ctx )
+{
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval reqdata = BER_BVNULL;
+ int tmp;
+
+ *text = NULL;
+
+ if ( ndn ) {
+ BER_BVZERO( ndn );
+ }
+
+ if ( in == NULL || in->bv_len == 0 ) {
+ *text = "empty request data field in refresh exop";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_dupbv_x( &reqdata, in, ctx );
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, &reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+
+ if ( tag == LBER_ERROR ) {
+ Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) {
+ Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ if ( ndn ) {
+ struct berval dn;
+
+ tag = ber_scanf( ber, "m", &dn );
+ if ( tag == LBER_ERROR ) {
+ Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: DN parse failed.\n" );
+ goto decoding_error;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ *text = "invalid DN in refresh exop request data";
+ goto done;
+ }
+
+ } else {
+ tag = ber_scanf( ber, "x" /* "m" */ );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) {
+ Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error.\n" );
+ goto decoding_error;
+ }
+
+ tag = ber_scanf( ber, "i", &tmp );
+ if ( tag == LBER_ERROR ) {
+ Log0( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: TTL parse failed.\n" );
+ goto decoding_error;
+ }
+
+ if ( ttl ) {
+ *ttl = tmp;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag != LBER_DEFAULT || len != 0 ) {
+decoding_error:;
+ Log1( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR,
+ "slap_parse_refresh: decoding error, len=%ld\n",
+ (long)len );
+ rc = LDAP_PROTOCOL_ERROR;
+ *text = "data decoding error";
+
+done:;
+ if ( ndn && !BER_BVISNULL( ndn ) ) {
+ slap_sl_free( ndn->bv_val, ctx );
+ BER_BVZERO( ndn );
+ }
+ }
+
+ if ( !BER_BVISNULL( &reqdata ) ) {
+ ber_memfree_x( reqdata.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+static int
+dds_op_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( DDS_OFF( di ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) {
+ Entry *e = NULL;
+ time_t ttl;
+ BackendDB db = *op->o_bd;
+ SlapReply rs2 = { REP_RESULT };
+ Operation op2 = *op;
+ slap_callback sc = { 0 };
+ Modifications ttlmod = { { 0 } };
+ struct berval ttlvalues[ 2 ];
+ char ttlbuf[STRLENOF("31557600") + 1];
+
+ rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl,
+ &rs->sr_text, NULL );
+ assert( rs->sr_err == LDAP_SUCCESS );
+
+ if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = "invalid time-to-live for dynamicObject";
+ return rs->sr_err;
+ }
+
+ if ( ttl > di->di_max_ttl ) {
+ /* FIXME: I don't understand if this has to be an error,
+ * or an indication that the requested Ttl has been
+ * shortened to di->di_max_ttl >= 1 day */
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ rs->sr_text = "time-to-live for dynamicObject exceeds limit";
+ return rs->sr_err;
+ }
+
+ if ( di->di_min_ttl && ttl < di->di_min_ttl ) {
+ ttl = di->di_min_ttl;
+ }
+
+ /* This does not apply to multi-master 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 );
+
+ Log3( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO,
+ "%s REFRESH dn=\"%s\" TTL=%ld\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val, ttl );
+ }
+
+ ber_free_buf( ber );
+ }
+
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+enum {
+ DDS_STATE = 1,
+ DDS_MAXTTL,
+ DDS_MINTTL,
+ DDS_DEFAULTTTL,
+ DDS_INTERVAL,
+ DDS_TOLERANCE,
+ DDS_MAXDYNAMICOBJS,
+
+ DDS_LAST
+};
+
+static ConfigDriver dds_cfgen;
+#if 0
+static ConfigLDAPadd dds_ldadd;
+static ConfigCfAdd dds_cfadd;
+#endif
+
+static ConfigTable dds_cfg[] = {
+ { "dds-state", "on|off",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen,
+ "( OLcfgOvAt:9.1 NAME 'olcDDSstate' "
+ "DESC 'RFC2589 Dynamic directory services state' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-max-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen,
+ "( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' "
+ "DESC 'RFC2589 Dynamic directory services max TTL' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-min-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen,
+ "( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' "
+ "DESC 'RFC2589 Dynamic directory services min TTL' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-default-ttl", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen,
+ "( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' "
+ "DESC 'RFC2589 Dynamic directory services default TTL' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-interval", "interval",
+ 2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen,
+ "( OLcfgOvAt:9.5 NAME 'olcDDSinterval' "
+ "DESC 'RFC2589 Dynamic directory services expiration "
+ "task run interval' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-tolerance", "ttl",
+ 2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen,
+ "( OLcfgOvAt:9.6 NAME 'olcDDStolerance' "
+ "DESC 'RFC2589 Dynamic directory services additional "
+ "TTL in expiration scheduling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )", NULL, NULL },
+ { "dds-max-dynamicObjects", "num",
+ 2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen,
+ "( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' "
+ "DESC 'RFC2589 Dynamic directory services max number of dynamic objects' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dds_ocs[] = {
+ { "( OLcfgOvOc:9.1 "
+ "NAME 'olcDDSConfig' "
+ "DESC 'RFC2589 Dynamic directory services configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcDDSstate "
+ "$ olcDDSmaxTtl "
+ "$ olcDDSminTtl "
+ "$ olcDDSdefaultTtl "
+ "$ olcDDSinterval "
+ "$ olcDDStolerance "
+ "$ olcDDSmaxDynamicObjects "
+ " ) "
+ ")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ },
+ { NULL, 0, NULL }
+};
+
+#if 0
+static int
+dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ return LDAP_SUCCESS;
+}
+
+static int
+dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ return 0;
+}
+#endif
+
+static int
+dds_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc = 0;
+ unsigned long t;
+
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+
+ switch( c->type ) {
+ case DDS_STATE:
+ c->value_int = !DDS_OFF( di );
+ break;
+
+ case DDS_MAXTTL:
+ lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case DDS_MINTTL:
+ if ( di->di_min_ttl ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_DEFAULTTTL:
+ if ( di->di_default_ttl ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_INTERVAL:
+ if ( di->di_interval ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_interval );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_TOLERANCE:
+ if ( di->di_tolerance ) {
+ lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ if ( di->di_max_dynamicObjects > 0 ) {
+ c->value_int = di->di_max_dynamicObjects;
+
+ } else {
+ rc = 1;
+ }
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case DDS_STATE:
+ di->di_flags &= ~DDS_FOFF;
+ break;
+
+ case DDS_MAXTTL:
+ di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+ break;
+
+ case DDS_MINTTL:
+ di->di_min_ttl = 0;
+ break;
+
+ case DDS_DEFAULTTTL:
+ di->di_default_ttl = 0;
+ break;
+
+ case DDS_INTERVAL:
+ di->di_interval = 0;
+ break;
+
+ case DDS_TOLERANCE:
+ di->di_tolerance = 0;
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ di->di_max_dynamicObjects = 0;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ switch ( c->type ) {
+ case DDS_STATE:
+ if ( c->value_int ) {
+ di->di_flags &= ~DDS_FOFF;
+
+ } else {
+ di->di_flags |= DDS_FOFF;
+ }
+ break;
+
+ case DDS_MAXTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-max-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-max-ttl=%lu; must be between %d and %d",
+ t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ di->di_max_ttl = (time_t)t;
+ break;
+
+ case DDS_MINTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-min-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-min-ttl=%lu",
+ t );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t == 0 ) {
+ di->di_min_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ } else {
+ di->di_min_ttl = (time_t)t;
+ }
+ break;
+
+ case DDS_DEFAULTTTL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-default-ttl \"%s\"",
+ c->argv[ 1 ] );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-default-ttl=%lu",
+ t );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t == 0 ) {
+ di->di_default_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ } else {
+ di->di_default_ttl = (time_t)t;
+ }
+ break;
+
+ case DDS_INTERVAL:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-interval \"%s\"",
+ c->argv[ 1 ] );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-interval=%lu",
+ t );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t < 60 ) {
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE,
+ "%s: dds-interval=%lu may be too small.\n",
+ c->log, t );
+ }
+
+ di->di_interval = (time_t)t;
+ if ( di->di_expire_task ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+ }
+ di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di );
+ ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ break;
+
+ case DDS_TOLERANCE:
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "DDS unable to parse dds-tolerance \"%s\"",
+ c->argv[ 1 ] );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( t > DDS_RF2589_MAX_TTL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-tolerance=%lu",
+ t );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ di->di_tolerance = (time_t)t;
+ break;
+
+ case DDS_MAXDYNAMICOBJS:
+ if ( c->value_int < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "DDS invalid dds-max-dynamicObjects=%d", c->value_int );
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ di->di_max_dynamicObjects = c->value_int;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+dds_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di;
+ BackendInfo *bi = on->on_info->oi_orig;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Log0( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS cannot be used as global overlay.\n" );
+ return 1;
+ }
+
+ /* check support for required functions */
+ /* FIXME: some could be provided by other overlays in between */
+ if ( bi->bi_op_add == NULL /* object creation */
+ || bi->bi_op_delete == NULL /* object deletion */
+ || bi->bi_op_modify == NULL /* object refresh */
+ || bi->bi_op_search == NULL /* object expiration */
+ || bi->bi_entry_get_rw == NULL ) /* object type/existence checking */
+ {
+ Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS backend \"%s\" does not provide "
+ "required functionality.\n",
+ bi->bi_type );
+ return 1;
+ }
+
+ di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) );
+ on->on_bi.bi_private = di;
+
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+
+ ldap_pvt_thread_mutex_init( &di->di_mutex );
+
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC;
+
+ return 0;
+}
+
+/* adds dynamicSubtrees to root DSE */
+static int
+dds_entry_info( void *arg, Entry *e )
+{
+ dds_info_t *di = (dds_info_t *)arg;
+
+ attr_merge( e, slap_schema.si_ad_dynamicSubtrees,
+ di->di_suffix, di->di_nsuffix );
+
+ return 0;
+}
+
+/* callback that counts the returned entries, since the search
+ * does not get to the point in slap_send_search_entries where
+ * the actual count occurs */
+static int
+dds_count_cb( Operation *op, SlapReply *rs )
+{
+ int *nump = (int *)op->o_callback->sc_private;
+
+ switch ( rs->sr_type ) {
+ case REP_SEARCH:
+ (*nump)++;
+ break;
+
+ case REP_SEARCHREF:
+ case REP_RESULT:
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ return 0;
+}
+
+/* count dynamic objects existing in the database at startup */
+static int
+dds_count( void *ctx, BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = (dds_info_t *)on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback sc = { 0 };
+ SlapReply rs = { REP_RESULT };
+
+ int rc;
+ char *extra = "";
+
+ connection_fake_init2( &conn, &opbuf, ctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+
+ op->o_bd = be;
+
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" )
+ + slap_schema.si_oc_dynamicObject->soc_cname.bv_len;
+ op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx );
+ snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1,
+ "(objectClass=%s)",
+ slap_schema.si_oc_dynamicObject->soc_cname.bv_val );
+
+ op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val );
+ if ( op->ors_filter == NULL ) {
+ rs.sr_err = LDAP_OTHER;
+ goto done_search;
+ }
+
+ op->o_callback = &sc;
+ sc.sc_response = dds_count_cb;
+ sc.sc_private = &di->di_num_dynamicObjects;
+ di->di_num_dynamicObjects = 0;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ (void)op->o_bd->bd_info->bi_op_search( op, &rs );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+done_search:;
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+
+ rc = rs.sr_err;
+ switch ( rs.sr_err ) {
+ case LDAP_SUCCESS:
+ Log1( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "DDS non-expired=%d\n",
+ di->di_num_dynamicObjects );
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* (ITS#5267) database not created yet? */
+ rs.sr_err = LDAP_SUCCESS;
+ extra = " (ignored)";
+ /* fallthru */
+
+ default:
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS non-expired objects lookup failed err=%d%s\n",
+ rc, extra );
+ break;
+ }
+
+ return rs.sr_err;
+}
+
+static int
+dds_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+ int rc = 0;
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ if ( slapMode & SLAP_TOOL_MODE )
+ return 0;
+
+ if ( DDS_OFF( di ) ) {
+ goto done;
+ }
+
+ if ( SLAP_SINGLE_SHADOW( be ) ) {
+ Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS incompatible with shadow database \"%s\".\n",
+ be->be_suffix[ 0 ].bv_val );
+ return 1;
+ }
+
+ if ( di->di_max_ttl == 0 ) {
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ }
+
+ if ( di->di_min_ttl == 0 ) {
+ di->di_max_ttl = DDS_RF2589_DEFAULT_TTL;
+ }
+
+ di->di_suffix = be->be_suffix;
+ di->di_nsuffix = be->be_nsuffix;
+
+ /* count the dynamic objects first */
+ rc = dds_count( thrctx, be );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = 1;
+ goto done;
+ }
+
+ /* start expire task */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq,
+ DDS_INTERVAL( di ),
+ dds_expire_fn, di, "dds_expire_fn",
+ be->be_suffix[ 0 ].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ /* register dinamicSubtrees root DSE info support */
+ rc = entry_info_register( dds_entry_info, (void *)di );
+
+done:;
+
+ return rc;
+}
+
+static int
+dds_db_close(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ /* stop expire task */
+ if ( di && di->di_expire_task ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ di->di_expire_task = NULL;
+ }
+
+ (void)entry_info_unregister( dds_entry_info, (void *)di );
+
+ return 0;
+}
+
+static int
+dds_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ dds_info_t *di = on->on_bi.bi_private;
+
+ if ( di != NULL ) {
+ ldap_pvt_thread_mutex_destroy( &di->di_mutex );
+
+ free( di );
+ }
+
+ return 0;
+}
+
+static int
+slap_exop_refresh(
+ Operation *op,
+ SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL,
+ &rs->sr_text, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ Log2( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO,
+ "%s REFRESH dn=\"%s\"\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val );
+ op->o_req_dn = op->o_req_ndn;
+
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL ) {
+ send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT,
+ "no global superior knowledge" );
+ goto done;
+ }
+
+ if ( !SLAP_DYNAMIC( op->o_bd ) ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support dynamic directory services" );
+ goto done;
+ }
+
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&slap_EXOP_REFRESH );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if ( op->o_bd->be_extended == NULL ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support extended operations" );
+ goto done;
+ }
+
+ op->o_bd->be_extended( op, rs );
+
+done:;
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ BER_BVZERO( &op->o_req_dn );
+ }
+ op->o_bd = bd;
+
+ return rs->sr_err;
+}
+
+static slap_overinst dds;
+
+static int do_not_load_exop;
+static int do_not_replace_exop;
+static int do_not_load_schema;
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+int
+dds_initialize()
+{
+ int rc = 0;
+ int i, code;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( DDS_LAST );
+
+ if ( !do_not_load_schema ) {
+ static struct {
+ char *desc;
+ slap_mask_t flags;
+ AttributeDescription **ad;
+ } s_at[] = {
+ { "( 1.3.6.1.4.1.4203.666.1.57 "
+ "NAME ( 'entryExpireTimestamp' ) "
+ "DESC 'RFC2589 OpenLDAP extension: expire time of a dynamic object, "
+ "computed as now + entryTtl' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+ "NO-USER-MODIFICATION "
+ "USAGE dSAOperation )",
+ SLAP_AT_HIDE,
+ &ad_entryExpireTimestamp },
+ { NULL }
+ };
+
+ for ( i = 0; s_at[ i ].desc != NULL; i++ ) {
+ code = register_at( s_at[ i ].desc, s_at[ i ].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "dds_initialize: register_at failed\n", 0, 0, 0 );
+ return code;
+ }
+ (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+ }
+
+ if ( !do_not_load_exop ) {
+ rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH,
+ SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh,
+ !do_not_replace_exop );
+ if ( rc != LDAP_SUCCESS ) {
+ Log1( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS unable to register refresh exop: %d.\n",
+ rc );
+ return rc;
+ }
+ }
+
+ dds.on_bi.bi_type = "dds";
+
+ dds.on_bi.bi_db_init = dds_db_init;
+ dds.on_bi.bi_db_open = dds_db_open;
+ dds.on_bi.bi_db_close = dds_db_close;
+ dds.on_bi.bi_db_destroy = dds_db_destroy;
+
+ dds.on_bi.bi_op_add = dds_op_add;
+ dds.on_bi.bi_op_delete = dds_op_delete;
+ dds.on_bi.bi_op_modify = dds_op_modify;
+ dds.on_bi.bi_op_modrdn = dds_op_rename;
+ dds.on_bi.bi_extended = dds_op_extended;
+ dds.on_response = dds_response;
+
+ dds.on_bi.bi_cf_ocs = dds_ocs;
+
+ rc = config_register_schema( dds_cfg, dds_ocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &dds );
+}
+
+#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ int i;
+
+ for ( i = 0; i < argc; i++ ) {
+ char *arg = argv[ i ];
+ int no = 0;
+
+ if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) {
+ arg += STRLENOF( "no-" );
+ no = 1;
+ }
+
+ if ( strcasecmp( arg, "exop" ) == 0 ) {
+ do_not_load_exop = no;
+
+ } else if ( strcasecmp( arg, "replace" ) == 0 ) {
+ do_not_replace_exop = no;
+
+ } else if ( strcasecmp( arg, "schema" ) == 0 ) {
+ do_not_load_schema = no;
+
+ } else {
+ Log2( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR,
+ "DDS unknown module arg[#%d]=\"%s\".\n",
+ i, argv[ i ] );
+ return 1;
+ }
+ }
+
+ return dds_initialize();
+}
+#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_DDS) */
diff --git a/servers/slapd/overlays/deref.c b/servers/slapd/overlays/deref.c
new file mode 100644
index 0000000..599ca18
--- /dev/null
+++ b/servers/slapd/overlays/deref.c
@@ -0,0 +1,585 @@
+/* deref.c - dereference overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1998-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2008 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati
+ * for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DEREF
+
+#include <stdio.h>
+
+#include "ac/string.h"
+#include "ac/socket.h"
+
+#include "slap.h"
+#include "config.h"
+
+#include "lutil.h"
+
+/*
+ * 1. Specification
+ *
+ * 1.1. Request
+ *
+ * controlValue ::= SEQUENCE OF derefSpec DerefSpec
+ *
+ * DerefSpec ::= SEQUENCE {
+ * derefAttr attributeDescription, ; DN-valued
+ * attributes AttributeList }
+ *
+ * AttributeList ::= SEQUENCE OF attr AttributeDescription
+ *
+ * derefAttr MUST be unique within controlValue
+ *
+ *
+ * 1.2. Response
+ *
+ * controlValue ::= SEQUENCE OF DerefRes
+ *
+ * From RFC 4511:
+ * PartialAttribute ::= SEQUENCE {
+ * type AttributeDescription,
+ * vals SET OF value AttributeValue }
+ *
+ * PartialAttributeList ::= SEQUENCE OF
+ * partialAttribute PartialAttribute
+ *
+ * DerefRes ::= SEQUENCE {
+ * derefAttr AttributeDescription,
+ * derefVal LDAPDN,
+ * attrVals [0] PartialAttributeList OPTIONAL }
+ *
+ * If vals is empty, partialAttribute is omitted.
+ * If all vals in attrVals are empty, attrVals is omitted.
+ *
+ * 2. Examples
+ *
+ * 2.1. Example
+ *
+ * 2.1.1. Request
+ *
+ * { { member, { GUID, SID } }, { memberOf, { GUID, SID } } }
+ *
+ * 2.1.2. Response
+ *
+ * { { memberOf, "cn=abartlet,cn=users,dc=abartlet,dc=net",
+ * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fa" ] },
+ * { SID, [ "S-1-2-3-2345" ] } } },
+ * { memberOf, "cn=ando,cn=users,dc=sys-net,dc=it",
+ * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fb" ] },
+ * { SID, [ "S-1-2-3-2346" ] } } } }
+ *
+ * 2.2. Example
+ *
+ * 2.2.1. Request
+ *
+ * { { member, { cn, uid, drink } } }
+ *
+ * 2.2.2. Response
+ *
+ * { { member, "cn=ando,cn=users,dc=sys-net,dc=it",
+ * { { cn, [ "ando", "Pierangelo Masarati" ] },
+ * { uid, [ "ando" ] } } },
+ * { member, "dc=sys-net,dc=it" } }
+ *
+ *
+ * 3. Security considerations
+ *
+ * The control result must not disclose information the client's
+ * identity could not have accessed directly by performing the related
+ * search operations. The presence of a derefVal in the control
+ * response does not imply neither the existence of nor any access
+ * privilege to the corresponding entry. It is merely a consequence
+ * of the read access the client's identity has on the corresponding
+ * attribute's value.
+ */
+
+#define o_deref o_ctrlflag[deref_cid]
+#define o_ctrlderef o_controls[deref_cid]
+
+typedef struct DerefSpec {
+ AttributeDescription *ds_derefAttr;
+ AttributeDescription **ds_attributes;
+ int ds_nattrs;
+ struct DerefSpec *ds_next;
+} DerefSpec;
+
+typedef struct DerefVal {
+ struct berval dv_derefSpecVal;
+ BerVarray *dv_attrVals;
+} DerefVal;
+
+typedef struct DerefRes {
+ DerefSpec dr_spec;
+ DerefVal *dr_vals;
+ struct DerefRes *dr_next;
+} DerefRes;
+
+typedef struct deref_cb_t {
+ slap_overinst *dc_on;
+ DerefSpec *dc_ds;
+} deref_cb_t;
+
+static int deref_cid;
+static slap_overinst deref;
+static int ov_count;
+
+static int
+deref_parseCtrl (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_len_t len;
+ char *last;
+ DerefSpec *dshead = NULL, **dsp = &dshead;
+ BerVarray attributes = NULL;
+
+ if ( op->o_deref != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Dereference control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Dereference control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Dereference control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ for ( tag = ber_first_element( ber, &len, &last );
+ tag != LBER_DEFAULT;
+ tag = ber_next_element( ber, &len, last ) )
+ {
+ struct berval derefAttr;
+ DerefSpec *ds, *dstmp;
+ const char *text;
+ int rc;
+ ber_len_t cnt = sizeof(struct berval);
+ ber_len_t off = 0;
+
+ if ( ber_scanf( ber, "{m{M}}", &derefAttr, &attributes, &cnt, off ) == LBER_ERROR
+ || !cnt )
+ {
+ rs->sr_text = "Dereference control: derefSpec decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ ds = (DerefSpec *)op->o_tmpcalloc( 1,
+ sizeof(DerefSpec) + sizeof(AttributeDescription *)*(cnt + 1),
+ op->o_tmpmemctx );
+ ds->ds_attributes = (AttributeDescription **)&ds[ 1 ];
+ ds->ds_nattrs = cnt;
+
+ rc = slap_bv2ad( &derefAttr, &ds->ds_derefAttr, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = "Dereference control: derefAttr decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ for ( dstmp = dshead; dstmp && dstmp != ds; dstmp = dstmp->ds_next ) {
+ if ( dstmp->ds_derefAttr == ds->ds_derefAttr ) {
+ rs->sr_text = "Dereference control: derefAttr must be unique within control";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ }
+
+ if ( !( ds->ds_derefAttr->ad_type->sat_syntax->ssyn_flags & SLAP_SYNTAX_DN )) {
+ if ( ctrl->ldctl_iscritical ) {
+ rs->sr_text = "Dereference control: derefAttr syntax not distinguishedName";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ goto justcleanup;
+ }
+
+ for ( cnt = 0; !BER_BVISNULL( &attributes[ cnt ] ); cnt++ ) {
+ rc = slap_bv2ad( &attributes[ cnt ], &ds->ds_attributes[ cnt ], &text );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_text = "Dereference control: attribute decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+ }
+
+ ber_memfree_x( attributes, op->o_tmpmemctx );
+ attributes = NULL;
+
+ *dsp = ds;
+ dsp = &ds->ds_next;
+ }
+
+ op->o_ctrlderef = (void *)dshead;
+
+ op->o_deref = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+justcleanup:;
+ for ( ; dshead; ) {
+ DerefSpec *dsnext = dshead->ds_next;
+ op->o_tmpfree( dshead, op->o_tmpmemctx );
+ dshead = dsnext;
+ }
+ }
+
+ if ( attributes != NULL ) {
+ ber_memfree_x( attributes, op->o_tmpmemctx );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+deref_cleanup( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) {
+ op->o_tmpfree( op->o_callback, op->o_tmpmemctx );
+ op->o_callback = NULL;
+
+ op->o_tmpfree( op->o_ctrlderef, op->o_tmpmemctx );
+ op->o_ctrlderef = NULL;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+deref_response( Operation *op, SlapReply *rs )
+{
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *) &berbuf;
+ deref_cb_t *dc = (deref_cb_t *)op->o_callback->sc_private;
+ DerefSpec *ds;
+ DerefRes *dr, *drhead = NULL, **drp = &drhead;
+ struct berval bv = BER_BVNULL;
+ int nDerefRes = 0, nDerefVals = 0, nAttrs = 0, nVals = 0;
+ struct berval ctrlval;
+ LDAPControl *ctrl, *ctrlsp[2];
+ AccessControlState acl_state = ACL_STATE_INIT;
+ static char dummy = '\0';
+ Entry *ebase;
+ int i;
+
+ rc = overlay_entry_get_ov( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &ebase, dc->dc_on );
+ if ( rc != LDAP_SUCCESS || ebase == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( ds = dc->dc_ds; ds; ds = ds->ds_next ) {
+ Attribute *a = attr_find( ebase->e_attrs, ds->ds_derefAttr );
+
+ if ( a != NULL ) {
+ DerefVal *dv;
+ BerVarray *bva;
+
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc,
+ NULL, ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+
+ dr = op->o_tmpcalloc( 1,
+ sizeof( DerefRes ) + ( sizeof( DerefVal ) + sizeof( BerVarray * ) * ds->ds_nattrs ) * ( a->a_numvals + 1 ),
+ op->o_tmpmemctx );
+ dr->dr_spec = *ds;
+ dv = dr->dr_vals = (DerefVal *)&dr[ 1 ];
+ bva = (BerVarray *)&dv[ a->a_numvals + 1 ];
+
+ bv.bv_len += ds->ds_derefAttr->ad_cname.bv_len;
+ nAttrs++;
+ nDerefRes++;
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e = NULL;
+
+ dv[ i ].dv_attrVals = bva;
+ bva += ds->ds_nattrs;
+
+
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc,
+ &a->a_nvals[ i ], ACL_READ, &acl_state ) )
+ {
+ dv[ i ].dv_derefSpecVal.bv_val = &dummy;
+ continue;
+ }
+
+ ber_dupbv_x( &dv[ i ].dv_derefSpecVal, &a->a_vals[ i ], op->o_tmpmemctx );
+ bv.bv_len += dv[ i ].dv_derefSpecVal.bv_len;
+ nVals++;
+ nDerefVals++;
+
+ rc = overlay_entry_get_ov( op, &a->a_nvals[ i ], NULL, NULL, 0, &e, dc->dc_on );
+ if ( rc == LDAP_SUCCESS && e != NULL ) {
+ int j;
+
+ if ( access_allowed( op, e, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ for ( j = 0; j < ds->ds_nattrs; j++ ) {
+ Attribute *aa;
+
+ if ( !access_allowed( op, e, ds->ds_attributes[ j ], NULL,
+ ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+
+ aa = attr_find( e->e_attrs, ds->ds_attributes[ j ] );
+ if ( aa != NULL ) {
+ unsigned k, h, last = aa->a_numvals;
+
+ ber_bvarray_dup_x( &dv[ i ].dv_attrVals[ j ],
+ aa->a_vals, op->o_tmpmemctx );
+
+ bv.bv_len += ds->ds_attributes[ j ]->ad_cname.bv_len;
+
+ for ( k = 0, h = 0; k < aa->a_numvals; k++ ) {
+ if ( !access_allowed( op, e,
+ aa->a_desc,
+ &aa->a_nvals[ k ],
+ ACL_READ, &acl_state ) )
+ {
+ op->o_tmpfree( dv[ i ].dv_attrVals[ j ][ h ].bv_val,
+ op->o_tmpmemctx );
+ dv[ i ].dv_attrVals[ j ][ h ] = dv[ i ].dv_attrVals[ j ][ --last ];
+ BER_BVZERO( &dv[ i ].dv_attrVals[ j ][ last ] );
+ continue;
+ }
+ bv.bv_len += dv[ i ].dv_attrVals[ j ][ h ].bv_len;
+ nVals++;
+ h++;
+ }
+ nAttrs++;
+ }
+ }
+ }
+
+ overlay_entry_release_ov( op, e, 0, dc->dc_on );
+ }
+ }
+
+ *drp = dr;
+ drp = &dr->dr_next;
+ }
+ }
+ overlay_entry_release_ov( op, ebase, 0, dc->dc_on );
+
+ if ( drhead == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* cook the control value */
+ bv.bv_len += nVals * sizeof(struct berval)
+ + nAttrs * sizeof(struct berval)
+ + nDerefVals * sizeof(DerefVal)
+ + nDerefRes * sizeof(DerefRes);
+ bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx );
+
+ ber_init2( ber, &bv, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ rc = ber_printf( ber, "{" /*}*/ );
+ for ( dr = drhead; dr != NULL; dr = dr->dr_next ) {
+ for ( i = 0; !BER_BVISNULL( &dr->dr_vals[ i ].dv_derefSpecVal ); i++ ) {
+ int j, first = 1;
+
+ if ( dr->dr_vals[ i ].dv_derefSpecVal.bv_val == &dummy ) {
+ continue;
+ }
+
+ rc = ber_printf( ber, "{OO" /*}*/,
+ &dr->dr_spec.ds_derefAttr->ad_cname,
+ &dr->dr_vals[ i ].dv_derefSpecVal );
+ op->o_tmpfree( dr->dr_vals[ i ].dv_derefSpecVal.bv_val, op->o_tmpmemctx );
+ for ( j = 0; j < dr->dr_spec.ds_nattrs; j++ ) {
+ if ( dr->dr_vals[ i ].dv_attrVals[ j ] != NULL ) {
+ if ( first ) {
+ rc = ber_printf( ber, "t{" /*}*/,
+ (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) );
+ first = 0;
+ }
+ rc = ber_printf( ber, "{O[W]}",
+ &dr->dr_spec.ds_attributes[ j ]->ad_cname,
+ dr->dr_vals[ i ].dv_attrVals[ j ] );
+ op->o_tmpfree( dr->dr_vals[ i ].dv_attrVals[ j ],
+ op->o_tmpmemctx );
+ }
+ }
+ if ( !first ) {
+ rc = ber_printf( ber, /*{{*/ "}N}" );
+ } else {
+ rc = ber_printf( ber, /*{*/ "}" );
+ }
+ }
+ }
+ rc = ber_printf( ber, /*{*/ "}" );
+ if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) {
+ if ( op->o_deref == SLAP_CONTROL_CRITICAL ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ goto cleanup;
+ }
+
+ ctrl = op->o_tmpcalloc( 1,
+ sizeof( LDAPControl ) + ctrlval.bv_len + 1,
+ op->o_tmpmemctx );
+ ctrl->ldctl_value.bv_val = (char *)&ctrl[ 1 ];
+ ctrl->ldctl_oid = LDAP_CONTROL_X_DEREF;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_len = ctrlval.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len );
+ ctrl->ldctl_value.bv_val[ ctrl->ldctl_value.bv_len ] = '\0';
+
+ ber_free_buf( ber );
+
+ ctrlsp[0] = ctrl;
+ ctrlsp[1] = NULL;
+ slap_add_ctrls( op, rs, ctrlsp );
+
+ rc = SLAP_CB_CONTINUE;
+
+cleanup:;
+ /* release all */
+ for ( ; drhead != NULL; ) {
+ DerefRes *drnext = drhead->dr_next;
+ op->o_tmpfree( drhead, op->o_tmpmemctx );
+ drhead = drnext;
+ }
+
+ } else if ( rs->sr_type == REP_RESULT ) {
+ rc = deref_cleanup( op, rs );
+ }
+
+ return rc;
+}
+
+static int
+deref_op_search( Operation *op, SlapReply *rs )
+{
+ if ( op->o_deref ) {
+ slap_callback *sc;
+ deref_cb_t *dc;
+
+ sc = op->o_tmpcalloc( 1, sizeof( slap_callback ) + sizeof( deref_cb_t ), op->o_tmpmemctx );
+
+ dc = (deref_cb_t *)&sc[ 1 ];
+ dc->dc_on = (slap_overinst *)op->o_bd->bd_info;
+ dc->dc_ds = (DerefSpec *)op->o_ctrlderef;
+
+ sc->sc_response = deref_response;
+ sc->sc_cleanup = deref_cleanup;
+ sc->sc_private = (void *)dc;
+
+ sc->sc_next = op->o_callback->sc_next;
+ op->o_callback->sc_next = sc;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+deref_db_init( BackendDB *be, ConfigReply *cr)
+{
+ if ( ov_count == 0 ) {
+ int rc;
+
+ rc = register_supported_control2( LDAP_CONTROL_X_DEREF,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ deref_parseCtrl,
+ 1, /* replace */
+ &deref_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "deref_init: Failed to register control (%d)\n",
+ rc, 0, 0 );
+ return rc;
+ }
+ }
+ ov_count++;
+ return LDAP_SUCCESS;
+}
+
+static int
+deref_db_open( BackendDB *be, ConfigReply *cr)
+{
+ return overlay_register_control( be, LDAP_CONTROL_X_DEREF );
+}
+
+#ifdef SLAP_CONFIG_DELETE
+static int
+deref_db_destroy( BackendDB *be, ConfigReply *cr)
+{
+ ov_count--;
+ overlay_unregister_control( be, LDAP_CONTROL_X_DEREF );
+ if ( ov_count == 0 ) {
+ unregister_supported_control( LDAP_CONTROL_X_DEREF );
+ }
+ return 0;
+}
+#endif /* SLAP_CONFIG_DELETE */
+
+int
+deref_initialize(void)
+{
+ deref.on_bi.bi_type = "deref";
+ deref.on_bi.bi_db_init = deref_db_init;
+ deref.on_bi.bi_db_open = deref_db_open;
+#ifdef SLAP_CONFIG_DELETE
+ deref.on_bi.bi_db_destroy = deref_db_destroy;
+#endif /* SLAP_CONFIG_DELETE */
+ deref.on_bi.bi_op_search = deref_op_search;
+
+ return overlay_register( &deref );
+}
+
+#if SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return deref_initialize();
+}
+#endif /* SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_DEREF */
diff --git a/servers/slapd/overlays/dyngroup.c b/servers/slapd/overlays/dyngroup.c
new file mode 100644
index 0000000..a6a6d86
--- /dev/null
+++ b/servers/slapd/overlays/dyngroup.c
@@ -0,0 +1,228 @@
+/* dyngroup.c - Demonstration of overlay code */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2018 The OpenLDAP Foundation.
+ * Copyright 2003 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DYNGROUP
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "config.h"
+
+/* This overlay extends the Compare operation to detect members of a
+ * dynamic group. It has no effect on any other operations. It must
+ * be configured with a pair of attributes to trigger on, e.g.
+ * attrpair member memberURL
+ * will cause compares on "member" to trigger a compare on "memberURL".
+ */
+
+typedef struct adpair {
+ struct adpair *ap_next;
+ AttributeDescription *ap_mem;
+ AttributeDescription *ap_uri;
+} adpair;
+
+static int dgroup_cf( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ int rc = 1;
+
+ switch( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ {
+ adpair *ap;
+ for ( ap = on->on_bi.bi_private; ap; ap = ap->ap_next ) {
+ struct berval bv;
+ char *ptr;
+ bv.bv_len = ap->ap_mem->ad_cname.bv_len + 1 +
+ ap->ap_uri->ad_cname.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( bv.bv_val, ap->ap_mem->ad_cname.bv_val );
+ *ptr++ = ' ';
+ strcpy( ptr, ap->ap_uri->ad_cname.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx == -1 ) {
+ adpair *ap;
+ while (( ap = on->on_bi.bi_private )) {
+ on->on_bi.bi_private = ap->ap_next;
+ ch_free( ap );
+ }
+ } else {
+ adpair **app, *ap;
+ int i;
+ app = (adpair **)&on->on_bi.bi_private;
+ for (i=0; i<=c->valx; i++, app = &ap->ap_next) {
+ ap = *app;
+ }
+ *app = ap->ap_next;
+ ch_free( ap );
+ }
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ case LDAP_MOD_ADD:
+ {
+ adpair ap = { NULL, NULL, NULL }, *a2;
+ const char *text;
+ if ( slap_str2ad( c->argv[1], &ap.ap_mem, &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ if ( slap_str2ad( c->argv[2], &ap.ap_uri, &text ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"",
+ c->argv[0], c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ /* The on->on_bi.bi_private pointer can be used for
+ * anything this instance of the overlay needs.
+ */
+ a2 = ch_malloc( sizeof(adpair) );
+ a2->ap_next = on->on_bi.bi_private;
+ a2->ap_mem = ap.ap_mem;
+ a2->ap_uri = ap.ap_uri;
+ on->on_bi.bi_private = a2;
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static ConfigTable dgroupcfg[] = {
+ { "attrpair", "member-attribute> <URL-attribute", 3, 3, 0,
+ ARG_MAGIC, dgroup_cf,
+ "( OLcfgOvAt:17.1 NAME 'olcDGAttrPair' "
+ "DESC 'Member and MemberURL attribute pair' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dgroupocs[] = {
+ { "( OLcfgOvOc:17.1 "
+ "NAME 'olcDGConfig' "
+ "DESC 'Dynamic Group configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcDGAttrPair )",
+ Cft_Overlay, dgroupcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+dyngroup_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ adpair *ap = on->on_bi.bi_private;
+
+ /* If we've been configured and the current response is
+ * what we're looking for...
+ */
+ if ( ap && op->o_tag == LDAP_REQ_COMPARE &&
+ rs->sr_err == LDAP_NO_SUCH_ATTRIBUTE ) {
+
+ for (;ap;ap=ap->ap_next) {
+ if ( op->oq_compare.rs_ava->aa_desc == ap->ap_mem ) {
+ /* This compare is for one of the attributes we're
+ * interested in. We'll use slapd's existing dyngroup
+ * evaluator to get the answer we want.
+ */
+ int cache = op->o_do_not_cache;
+
+ op->o_do_not_cache = 1;
+ rs->sr_err = backend_group( op, NULL, &op->o_req_ndn,
+ &op->oq_compare.rs_ava->aa_value, NULL, ap->ap_uri );
+ op->o_do_not_cache = cache;
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ break;
+ }
+ break;
+ }
+ }
+ }
+ /* Default is to just fall through to the normal processing */
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dyngroup_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ adpair *ap, *a2;
+
+ for ( ap = on->on_bi.bi_private; ap; ap = a2 ) {
+ a2 = ap->ap_next;
+ ch_free( ap );
+ }
+ return 0;
+}
+
+static slap_overinst dyngroup;
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+int dyngroup_initialize() {
+ int code;
+
+ dyngroup.on_bi.bi_type = "dyngroup";
+ dyngroup.on_bi.bi_db_destroy = dyngroup_destroy;
+ dyngroup.on_response = dyngroup_response;
+
+ dyngroup.on_bi.bi_cf_ocs = dgroupocs;
+ code = config_register_schema( dgroupcfg, dgroupocs );
+ if ( code ) return code;
+
+ return overlay_register( &dyngroup );
+}
+
+#if SLAPD_OVER_DYNGROUP == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return dyngroup_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_DYNGROUP) */
diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c
new file mode 100644
index 0000000..6293889
--- /dev/null
+++ b/servers/slapd/overlays/dynlist.c
@@ -0,0 +1,1574 @@
+/* dynlist.c - dynamic list overlay */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2004-2005 Pierangelo Masarati.
+ * Portions Copyright 2008 Emmanuel Dreyfus.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati
+ * for SysNet s.n.c., for inclusion in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DYNLIST
+
+#if SLAPD_OVER_DYNGROUP != SLAPD_MOD_STATIC
+#define TAKEOVER_DYNGROUP
+#endif
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+
+static AttributeDescription *ad_dgIdentity, *ad_dgAuthz;
+
+typedef struct dynlist_map_t {
+ AttributeDescription *dlm_member_ad;
+ AttributeDescription *dlm_mapped_ad;
+ struct dynlist_map_t *dlm_next;
+} dynlist_map_t;
+
+typedef struct dynlist_info_t {
+ ObjectClass *dli_oc;
+ AttributeDescription *dli_ad;
+ struct dynlist_map_t *dli_dlm;
+ struct berval dli_uri;
+ LDAPURLDesc *dli_lud;
+ struct berval dli_uri_nbase;
+ Filter *dli_uri_filter;
+ struct berval dli_default_filter;
+ struct dynlist_info_t *dli_next;
+} dynlist_info_t;
+
+#define DYNLIST_USAGE \
+ "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad> ...]\": "
+
+static dynlist_info_t *
+dynlist_is_dynlist_next( Operation *op, SlapReply *rs, dynlist_info_t *old_dli )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_info_t *dli;
+
+ Attribute *a;
+
+ if ( old_dli == NULL ) {
+ dli = (dynlist_info_t *)on->on_bi.bi_private;
+
+ } else {
+ dli = old_dli->dli_next;
+ }
+
+ a = attrs_find( rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass );
+ if ( a == NULL ) {
+ /* FIXME: objectClass must be present; for non-storage
+ * backends, like back-ldap, it needs to be added
+ * to the requested attributes */
+ return NULL;
+ }
+
+ for ( ; dli; dli = dli->dli_next ) {
+ if ( dli->dli_lud != NULL ) {
+ /* check base and scope */
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase )
+ && !dnIsSuffixScope( &rs->sr_entry->e_nname,
+ &dli->dli_uri_nbase,
+ dli->dli_lud->lud_scope ) )
+ {
+ continue;
+ }
+
+ /* check filter */
+ if ( dli->dli_uri_filter && test_filter( op, rs->sr_entry, dli->dli_uri_filter ) != LDAP_COMPARE_TRUE ) {
+ continue;
+ }
+ }
+
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &dli->dli_oc->soc_cname, NULL,
+ op->o_tmpmemctx ) == 0 )
+ {
+ return dli;
+ }
+ }
+
+ return NULL;
+}
+
+static int
+dynlist_make_filter( Operation *op, Entry *e, const char *url, struct berval *oldf, struct berval *newf )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private;
+
+ char *ptr;
+ int needBrackets = 0;
+
+ assert( oldf != NULL );
+ assert( newf != NULL );
+ assert( !BER_BVISNULL( oldf ) );
+ assert( !BER_BVISEMPTY( oldf ) );
+
+ if ( oldf->bv_val[0] != '(' ) {
+ Debug( LDAP_DEBUG_ANY, "%s: dynlist, DN=\"%s\": missing brackets in URI=\"%s\" filter\n",
+ op->o_log_prefix, e->e_name.bv_val, url );
+ needBrackets = 2;
+ }
+
+ newf->bv_len = STRLENOF( "(&(!(objectClass=" "))" ")" )
+ + dli->dli_oc->soc_cname.bv_len + oldf->bv_len + needBrackets;
+ newf->bv_val = op->o_tmpalloc( newf->bv_len + 1, op->o_tmpmemctx );
+ if ( newf->bv_val == NULL ) {
+ return -1;
+ }
+ ptr = lutil_strcopy( newf->bv_val, "(&(!(objectClass=" );
+ ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "))" );
+ if ( needBrackets ) *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, oldf->bv_val );
+ if ( needBrackets ) *ptr++ = ')';
+ ptr = lutil_strcopy( ptr, ")" );
+ newf->bv_len = ptr - newf->bv_val;
+
+ return 0;
+}
+
+/* dynlist_sc_update() callback info set by dynlist_prepare_entry() */
+typedef struct dynlist_sc_t {
+ dynlist_info_t *dlc_dli;
+ Entry *dlc_e;
+} dynlist_sc_t;
+
+static int
+dynlist_sc_update( Operation *op, SlapReply *rs )
+{
+ Entry *e;
+ Attribute *a;
+ int opattrs,
+ userattrs;
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ dynlist_sc_t *dlc;
+ dynlist_map_t *dlm;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return 0;
+ }
+
+ dlc = (dynlist_sc_t *)op->o_callback->sc_private;
+ e = dlc->dlc_e;
+
+ assert( e != NULL );
+ assert( rs->sr_entry != NULL );
+
+ /* test access to entry */
+ if ( !access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ goto done;
+ }
+
+ /* if there is only one member_ad, and it's not mapped,
+ * consider it as old-style member listing */
+ dlm = dlc->dlc_dli->dli_dlm;
+ if ( dlm && dlm->dlm_mapped_ad == NULL && dlm->dlm_next == NULL ) {
+ /* if access allowed, try to add values, emulating permissive
+ * control to silently ignore duplicates */
+ if ( access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry,
+ NULL, ACL_READ, NULL ) )
+ {
+ Modification mod;
+ const char *text = NULL;
+ char textbuf[1024];
+ struct berval vals[ 2 ], nvals[ 2 ];
+
+ vals[ 0 ] = rs->sr_entry->e_name;
+ BER_BVZERO( &vals[ 1 ] );
+ nvals[ 0 ] = rs->sr_entry->e_nname;
+ BER_BVZERO( &nvals[ 1 ] );
+
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = dlm->dlm_member_ad;
+ mod.sm_type = dlm->dlm_member_ad->ad_cname;
+ mod.sm_values = vals;
+ mod.sm_nvalues = nvals;
+ mod.sm_numvals = 1;
+
+ (void)modify_add_values( e, &mod, /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+ }
+
+ goto done;
+ }
+
+ opattrs = SLAP_OPATTRS( rs->sr_attr_flags );
+ userattrs = SLAP_USERATTRS( rs->sr_attr_flags );
+
+ for ( a = rs->sr_entry->e_attrs; a != NULL; a = a->a_next ) {
+ BerVarray vals, nvals = NULL;
+ int i, j,
+ is_oc = a->a_desc == slap_schema.si_ad_objectClass;
+
+ /* if attribute is not requested, skip it */
+ if ( rs->sr_attrs == NULL ) {
+ if ( is_at_operational( a->a_desc->ad_type ) ) {
+ continue;
+ }
+
+ } else {
+ if ( is_at_operational( a->a_desc->ad_type ) ) {
+ if ( !opattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) )
+ {
+ continue;
+ }
+
+ } else {
+ if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) )
+ {
+ continue;
+ }
+ }
+ }
+
+ /* test access to attribute */
+ if ( op->ors_attrsonly ) {
+ if ( !access_allowed( op, rs->sr_entry, a->a_desc, NULL,
+ ACL_READ, &acl_state ) )
+ {
+ continue;
+ }
+ }
+
+ /* single-value check: keep first only */
+ if ( is_at_single_value( a->a_desc->ad_type ) ) {
+ if ( attr_find( e->e_attrs, a->a_desc ) != NULL ) {
+ continue;
+ }
+ }
+
+ /* test access to attribute */
+ i = a->a_numvals;
+
+ vals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ if ( a->a_nvals != a->a_vals ) {
+ nvals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ }
+
+ for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) {
+ if ( is_oc ) {
+ ObjectClass *soc = oc_bvfind( &a->a_vals[i] );
+
+ if ( soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) {
+ continue;
+ }
+ }
+
+ if ( access_allowed( op, rs->sr_entry, a->a_desc,
+ &a->a_nvals[i], ACL_READ, &acl_state ) )
+ {
+ vals[j] = a->a_vals[i];
+ if ( nvals ) {
+ nvals[j] = a->a_nvals[i];
+ }
+ j++;
+ }
+ }
+
+ /* if access allowed, try to add values, emulating permissive
+ * control to silently ignore duplicates */
+ if ( j != 0 ) {
+ Modification mod;
+ const char *text = NULL;
+ char textbuf[1024];
+ dynlist_map_t *dlm;
+ AttributeDescription *ad;
+
+ BER_BVZERO( &vals[j] );
+ if ( nvals ) {
+ BER_BVZERO( &nvals[j] );
+ }
+
+ ad = a->a_desc;
+ for ( dlm = dlc->dlc_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == a->a_desc ) {
+ if ( dlm->dlm_mapped_ad ) {
+ ad = dlm->dlm_mapped_ad;
+ }
+ break;
+ }
+ }
+
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = ad;
+ mod.sm_type = ad->ad_cname;
+ mod.sm_values = vals;
+ mod.sm_nvalues = nvals;
+ mod.sm_numvals = j;
+
+ (void)modify_add_values( e, &mod, /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+ }
+
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+ if ( nvals ) {
+ op->o_tmpfree( nvals, op->o_tmpmemctx );
+ }
+ }
+
+done:;
+ if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) {
+ entry_free( rs->sr_entry );
+ rs->sr_entry = NULL;
+ rs->sr_flags &= ~REP_ENTRY_MASK;
+ }
+
+ return 0;
+}
+
+static int
+dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
+{
+ Attribute *a, *id = NULL;
+ slap_callback cb = { 0 };
+ Operation o = *op;
+ struct berval *url;
+ Entry *e;
+ int opattrs,
+ userattrs;
+ dynlist_sc_t dlc = { 0 };
+ dynlist_map_t *dlm;
+
+ a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad );
+ if ( a == NULL ) {
+ /* FIXME: error? */
+ return SLAP_CB_CONTINUE;
+ }
+
+ opattrs = SLAP_OPATTRS( rs->sr_attr_flags );
+ userattrs = SLAP_USERATTRS( rs->sr_attr_flags );
+
+ /* Don't generate member list if it wasn't requested */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+ if ( userattrs || ad_inlist( ad, rs->sr_attrs ) )
+ break;
+ }
+ if ( dli->dli_dlm && !dlm )
+ return SLAP_CB_CONTINUE;
+
+ if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) {
+ Attribute *authz = NULL;
+
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op )
+ && ( authz = attrs_find( rs->sr_entry->e_attrs, ad_dgAuthz ) ) )
+ {
+ if ( slap_sasl_matches( op, authz->a_nvals,
+ &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS )
+ {
+ return SLAP_CB_CONTINUE;
+ }
+ }
+
+ o.o_dn = id->a_vals[0];
+ o.o_ndn = id->a_nvals[0];
+ o.o_groups = NULL;
+ }
+
+ e = rs->sr_entry;
+ /* ensure e is modifiable, but do not replace
+ * sr_entry yet since we have pointers into it */
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ e = entry_dup( rs->sr_entry );
+ }
+
+ dlc.dlc_e = e;
+ dlc.dlc_dli = dli;
+ cb.sc_private = &dlc;
+ cb.sc_response = dynlist_sc_update;
+
+ o.o_callback = &cb;
+ o.ors_deref = LDAP_DEREF_NEVER;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+
+ for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) {
+ LDAPURLDesc *lud = NULL;
+ int i, j;
+ struct berval dn;
+ int rc;
+
+ BER_BVZERO( &o.o_req_dn );
+ BER_BVZERO( &o.o_req_ndn );
+ o.ors_filter = NULL;
+ o.ors_attrs = NULL;
+ BER_BVZERO( &o.ors_filterstr );
+
+ if ( ldap_url_parse( url->bv_val, &lud ) != LDAP_URL_SUCCESS ) {
+ /* FIXME: error? */
+ continue;
+ }
+
+ if ( lud->lud_host != NULL ) {
+ /* FIXME: host not allowed; reject as illegal? */
+ Debug( LDAP_DEBUG_ANY, "dynlist_prepare_entry(\"%s\"): "
+ "illegal URI \"%s\"\n",
+ e->e_name.bv_val, url->bv_val, 0 );
+ goto cleanup;
+ }
+
+ if ( lud->lud_dn == NULL ) {
+ /* note that an empty base is not honored in terms
+ * of defaultSearchBase, because select_backend()
+ * is not aware of the defaultSearchBase option;
+ * this can be useful in case of a database serving
+ * the empty suffix */
+ BER_BVSTR( &dn, "" );
+
+ } else {
+ ber_str2bv( lud->lud_dn, 0, 0, &dn );
+ }
+ rc = dnPrettyNormal( NULL, &dn, &o.o_req_dn, &o.o_req_ndn, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ /* FIXME: error? */
+ goto cleanup;
+ }
+ o.ors_scope = lud->lud_scope;
+
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_mapped_ad != NULL ) {
+ break;
+ }
+ }
+
+ if ( dli->dli_dlm && !dlm ) {
+ /* if ( lud->lud_attrs != NULL ),
+ * the URL should be ignored */
+ o.ors_attrs = slap_anlist_no_attrs;
+
+ } else if ( lud->lud_attrs == NULL ) {
+ o.ors_attrs = rs->sr_attrs;
+
+ } else {
+ for ( i = 0; lud->lud_attrs[i]; i++)
+ /* just count */ ;
+
+ o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx );
+ for ( i = 0, j = 0; lud->lud_attrs[i]; i++) {
+ const char *text = NULL;
+
+ ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name );
+ o.ors_attrs[j].an_desc = NULL;
+ (void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text );
+ /* FIXME: ignore errors... */
+
+ if ( rs->sr_attrs == NULL ) {
+ if ( o.ors_attrs[j].an_desc != NULL &&
+ is_at_operational( o.ors_attrs[j].an_desc->ad_type ) )
+ {
+ continue;
+ }
+
+ } else {
+ if ( o.ors_attrs[j].an_desc != NULL &&
+ is_at_operational( o.ors_attrs[j].an_desc->ad_type ) )
+ {
+ if ( !opattrs ) {
+ continue;
+ }
+
+ if ( !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) {
+ /* lookup if mapped -- linear search,
+ * not very efficient unless list
+ * is very short */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) {
+ break;
+ }
+ }
+
+ if ( dlm == NULL ) {
+ continue;
+ }
+ }
+
+ } else {
+ if ( !userattrs &&
+ o.ors_attrs[j].an_desc != NULL &&
+ !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) )
+ {
+ /* lookup if mapped -- linear search,
+ * not very efficient unless list
+ * is very short */
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) {
+ break;
+ }
+ }
+
+ if ( dlm == NULL ) {
+ continue;
+ }
+ }
+ }
+ }
+
+ j++;
+ }
+
+ if ( j == 0 ) {
+ goto cleanup;
+ }
+
+ BER_BVZERO( &o.ors_attrs[j].an_name );
+ }
+
+ if ( lud->lud_filter == NULL ) {
+ ber_dupbv_x( &o.ors_filterstr,
+ &dli->dli_default_filter, op->o_tmpmemctx );
+
+ } else {
+ struct berval flt;
+ ber_str2bv( lud->lud_filter, 0, 0, &flt );
+ if ( dynlist_make_filter( op, rs->sr_entry, url->bv_val, &flt, &o.ors_filterstr ) ) {
+ /* error */
+ goto cleanup;
+ }
+ }
+ o.ors_filter = str2filter_x( op, o.ors_filterstr.bv_val );
+ if ( o.ors_filter == NULL ) {
+ goto cleanup;
+ }
+
+ o.o_bd = select_backend( &o.o_req_ndn, 1 );
+ if ( o.o_bd && o.o_bd->be_search ) {
+ SlapReply r = { REP_SEARCH };
+ r.sr_attr_flags = slap_attr_flags( o.ors_attrs );
+ (void)o.o_bd->be_search( &o, &r );
+ }
+
+cleanup:;
+ if ( id ) {
+ slap_op_groups_free( &o );
+ }
+ if ( o.ors_filter ) {
+ filter_free_x( &o, o.ors_filter, 1 );
+ }
+ if ( o.ors_attrs && o.ors_attrs != rs->sr_attrs
+ && o.ors_attrs != slap_anlist_no_attrs )
+ {
+ op->o_tmpfree( o.ors_attrs, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &o.o_req_dn ) ) {
+ op->o_tmpfree( o.o_req_dn.bv_val, op->o_tmpmemctx );
+ }
+ if ( !BER_BVISNULL( &o.o_req_ndn ) ) {
+ op->o_tmpfree( o.o_req_ndn.bv_val, op->o_tmpmemctx );
+ }
+ assert( BER_BVISNULL( &o.ors_filterstr )
+ || o.ors_filterstr.bv_val != lud->lud_filter );
+ op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx );
+ ldap_free_urldesc( lud );
+ }
+
+ if ( e != rs->sr_entry ) {
+ rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e );
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* dynlist_sc_compare_entry() callback set by dynlist_compare() */
+typedef struct dynlist_cc_t {
+ slap_callback dc_cb;
+# define dc_ava dc_cb.sc_private /* attr:val to compare with */
+ int *dc_res;
+} dynlist_cc_t;
+
+static int
+dynlist_sc_compare_entry( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+ dynlist_cc_t *dc = (dynlist_cc_t *)op->o_callback;
+ AttributeAssertion *ava = dc->dc_ava;
+ Attribute *a = attrs_find( rs->sr_entry->e_attrs, ava->aa_desc );
+
+ if ( a != NULL ) {
+ while ( LDAP_SUCCESS != attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &ava->aa_value, NULL, op->o_tmpmemctx )
+ && (a = attrs_find( a->a_next, ava->aa_desc )) != NULL )
+ ;
+ *dc->dc_res = a ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ }
+ }
+
+ return 0;
+}
+
+static int
+dynlist_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private;
+ Operation o = *op;
+ Entry *e = NULL;
+ dynlist_map_t *dlm;
+ BackendDB *be;
+
+ for ( ; dli != NULL; dli = dli->dli_next ) {
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next )
+ if ( op->oq_compare.rs_ava->aa_desc == dlm->dlm_member_ad )
+ break;
+
+ if ( dlm ) {
+ /* This compare is for one of the attributes we're
+ * interested in. We'll use slapd's existing dyngroup
+ * evaluator to get the answer we want.
+ */
+ BerVarray id = NULL, authz = NULL;
+
+ o.o_do_not_cache = 1;
+
+ if ( ad_dgIdentity && backend_attribute( &o, NULL, &o.o_req_ndn,
+ ad_dgIdentity, &id, ACL_READ ) == LDAP_SUCCESS )
+ {
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( id ) && !be_isroot( op )
+ && backend_attribute( &o, NULL, &o.o_req_ndn,
+ ad_dgAuthz, &authz, ACL_READ ) == LDAP_SUCCESS )
+ {
+
+ rs->sr_err = slap_sasl_matches( op, authz,
+ &o.o_ndn, &o.o_ndn );
+ ber_bvarray_free_x( authz, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+ o.o_dn = *id;
+ o.o_ndn = *id;
+ o.o_groups = NULL; /* authz changed, invalidate cached groups */
+ }
+
+ rs->sr_err = backend_group( &o, NULL, &o.o_req_ndn,
+ &o.oq_compare.rs_ava->aa_value, dli->dli_oc, dli->dli_ad );
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+
+ case LDAP_NO_SUCH_OBJECT:
+ /* NOTE: backend_group() returns noSuchObject
+ * if op_ndn does not exist; however, since
+ * dynamic list expansion means that the
+ * member attribute is virtually present, the
+ * non-existence of the asserted value implies
+ * the assertion is FALSE rather than
+ * UNDEFINED */
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ break;
+ }
+
+done:;
+ if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx );
+
+ return SLAP_CB_CONTINUE;
+ }
+ }
+
+ be = select_backend( &o.o_req_ndn, 1 );
+ if ( !be || !be->be_search ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) !=
+ LDAP_SUCCESS || e == NULL )
+ {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* check for dynlist objectClass; done if not found */
+ dli = (dynlist_info_t *)on->on_bi.bi_private;
+ while ( dli != NULL && !is_entry_objectclass_or_sub( e, dli->dli_oc ) ) {
+ dli = dli->dli_next;
+ }
+ if ( dli == NULL ) {
+ goto release;
+ }
+
+ if ( ad_dgIdentity ) {
+ Attribute *id = attrs_find( e->e_attrs, ad_dgIdentity );
+ if ( id ) {
+ Attribute *authz;
+
+ /* if not rootdn and dgAuthz is present,
+ * check if user can be authorized as dgIdentity */
+ if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op )
+ && ( authz = attrs_find( e->e_attrs, ad_dgAuthz ) ) )
+ {
+ if ( slap_sasl_matches( op, authz->a_nvals,
+ &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS )
+ {
+ goto release;
+ }
+ }
+
+ o.o_dn = id->a_vals[0];
+ o.o_ndn = id->a_nvals[0];
+ o.o_groups = NULL;
+ }
+ }
+
+ /* generate dynamic list with dynlist_response() and compare */
+ {
+ SlapReply r = { REP_SEARCH };
+ dynlist_cc_t dc = { { 0, dynlist_sc_compare_entry, 0, 0 }, 0 };
+ AttributeName an[2];
+
+ dc.dc_ava = op->orc_ava;
+ dc.dc_res = &rs->sr_err;
+ o.o_callback = (slap_callback *) &dc;
+
+ o.o_tag = LDAP_REQ_SEARCH;
+ o.ors_limit = NULL;
+ o.ors_tlimit = SLAP_NO_LIMIT;
+ o.ors_slimit = SLAP_NO_LIMIT;
+
+ o.ors_filterstr = *slap_filterstr_objectClass_pres;
+ o.ors_filter = (Filter *) slap_filter_objectClass_pres;
+
+ o.ors_scope = LDAP_SCOPE_BASE;
+ o.ors_deref = LDAP_DEREF_NEVER;
+ an[0].an_name = op->orc_ava->aa_desc->ad_cname;
+ an[0].an_desc = op->orc_ava->aa_desc;
+ BER_BVZERO( &an[1].an_name );
+ o.ors_attrs = an;
+ o.ors_attrsonly = 0;
+
+ o.o_acl_priv = ACL_COMPARE;
+
+ o.o_bd = be;
+ (void)be->be_search( &o, &r );
+
+ if ( o.o_dn.bv_val != op->o_dn.bv_val ) {
+ slap_op_groups_free( &o );
+ }
+ }
+
+release:;
+ if ( e != NULL ) {
+ overlay_entry_release_ov( &o, e, 0, on );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dynlist_response( Operation *op, SlapReply *rs )
+{
+ switch ( op->o_tag ) {
+ case LDAP_REQ_SEARCH:
+ if ( rs->sr_type == REP_SEARCH && !get_manageDSAit( op ) )
+ {
+ int rc = SLAP_CB_CONTINUE;
+ dynlist_info_t *dli = NULL;
+
+ while ( (dli = dynlist_is_dynlist_next( op, rs, dli )) != NULL ) {
+ rc = dynlist_prepare_entry( op, rs, dli );
+ }
+
+ return rc;
+ }
+ break;
+
+ case LDAP_REQ_COMPARE:
+ switch ( rs->sr_err ) {
+ /* NOTE: we waste a few cycles running the dynamic list
+ * also when the result is FALSE, which occurs if the
+ * dynamic entry itself contains the AVA attribute */
+ /* FIXME: this approach is less than optimal; a dedicated
+ * compare op should be implemented, that fetches the
+ * entry, checks if it has the appropriate objectClass
+ * and, in case, runs a compare thru all the URIs,
+ * stopping at the first positive occurrence; see ITS#3756 */
+ case LDAP_COMPARE_FALSE:
+ case LDAP_NO_SUCH_ATTRIBUTE:
+ return dynlist_compare( op, rs );
+ }
+ break;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+dynlist_build_def_filter( dynlist_info_t *dli )
+{
+ char *ptr;
+
+ dli->dli_default_filter.bv_len = STRLENOF( "(!(objectClass=" "))" )
+ + dli->dli_oc->soc_cname.bv_len;
+ dli->dli_default_filter.bv_val = ch_malloc( dli->dli_default_filter.bv_len + 1 );
+ if ( dli->dli_default_filter.bv_val == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: malloc failed.\n",
+ 0, 0, 0 );
+ return -1;
+ }
+
+ ptr = lutil_strcopy( dli->dli_default_filter.bv_val, "(!(objectClass=" );
+ ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "))" );
+
+ assert( ptr == &dli->dli_default_filter.bv_val[dli->dli_default_filter.bv_len] );
+
+ return 0;
+}
+
+enum {
+ DL_ATTRSET = 1,
+ DL_ATTRPAIR,
+ DL_ATTRPAIR_COMPAT,
+ DL_LAST
+};
+
+static ConfigDriver dl_cfgen;
+
+/* XXXmanu 255 is the maximum arguments we allow. Can we go beyond? */
+static ConfigTable dlcfg[] = {
+ { "dynlist-attrset", "group-oc> [uri] <URL-ad> <[mapped:]member-ad> [...]",
+ 3, 0, 0, ARG_MAGIC|DL_ATTRSET, dl_cfgen,
+ "( OLcfgOvAt:8.1 NAME 'olcDlAttrSet' "
+ "DESC 'Dynamic list: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "dynlist-attrpair", "member-ad> <URL-ad",
+ 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR, dl_cfgen,
+ NULL, NULL, NULL },
+#ifdef TAKEOVER_DYNGROUP
+ { "attrpair", "member-ad> <URL-ad",
+ 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR_COMPAT, dl_cfgen,
+ NULL, NULL, NULL },
+#endif
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs dlocs[] = {
+ { "( OLcfgOvOc:8.1 "
+ "NAME 'olcDynamicList' "
+ "DESC 'Dynamic list configuration' "
+ "SUP olcOverlayConfig "
+ "MAY olcDLattrSet )",
+ Cft_Overlay, dlcfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+dl_cfgen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private;
+
+ int rc = 0, i;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case DL_ATTRSET:
+ for ( i = 0; dli; i++, dli = dli->dli_next ) {
+ struct berval bv;
+ char *ptr = c->cr_msg;
+ dynlist_map_t *dlm;
+
+ assert( dli->dli_oc != NULL );
+ assert( dli->dli_ad != NULL );
+
+ /* FIXME: check buffer overflow! */
+ ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ SLAP_X_ORDERED_FMT "%s", i,
+ dli->dli_oc->soc_cname.bv_val );
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strncopy( ptr, dli->dli_uri.bv_val,
+ dli->dli_uri.bv_len );
+ *ptr++ = '"';
+ }
+
+ *ptr++ = ' ';
+ ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val,
+ dli->dli_ad->ad_cname.bv_len );
+
+ for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+ ptr[ 0 ] = ' ';
+ ptr++;
+ if ( dlm->dlm_mapped_ad ) {
+ ptr = lutil_strcopy( ptr, dlm->dlm_mapped_ad->ad_cname.bv_val );
+ ptr[ 0 ] = ':';
+ ptr++;
+ }
+
+ ptr = lutil_strcopy( ptr, dlm->dlm_member_ad->ad_cname.bv_val );
+ }
+
+ bv.bv_val = c->cr_msg;
+ bv.bv_len = ptr - bv.bv_val;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case DL_ATTRPAIR_COMPAT:
+ case DL_ATTRPAIR:
+ rc = 1;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case DL_ATTRSET:
+ if ( c->valx < 0 ) {
+ dynlist_info_t *dli_next;
+
+ for ( dli_next = dli; dli_next; dli = dli_next ) {
+ dynlist_map_t *dlm = dli->dli_dlm;
+ dynlist_map_t *dlm_next;
+
+ dli_next = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+ }
+
+ on->on_bi.bi_private = NULL;
+
+ } else {
+ dynlist_info_t **dlip;
+ dynlist_map_t *dlm;
+ dynlist_map_t *dlm_next;
+
+ for ( i = 0, dlip = (dynlist_info_t **)&on->on_bi.bi_private;
+ i < c->valx; i++ )
+ {
+ if ( *dlip == NULL ) {
+ return 1;
+ }
+ dlip = &(*dlip)->dli_next;
+ }
+
+ dli = *dlip;
+ *dlip = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ dlm = dli->dli_dlm;
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+
+ dli = (dynlist_info_t *)on->on_bi.bi_private;
+ }
+ break;
+
+ case DL_ATTRPAIR_COMPAT:
+ case DL_ATTRPAIR:
+ rc = 1;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ switch( c->type ) {
+ case DL_ATTRSET: {
+ dynlist_info_t **dlip,
+ *dli_next = NULL;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ int attridx = 2;
+ LDAPURLDesc *lud = NULL;
+ struct berval nbase = BER_BVNULL;
+ Filter *filter = NULL;
+ struct berval uri = BER_BVNULL;
+ dynlist_map_t *dlm = NULL, *dlml = NULL;
+ const char *text;
+
+ oc = oc_find( c->argv[ 1 ] );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to find ObjectClass \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 ) {
+ if ( ldap_url_parse( c->argv[ attridx ], &lud ) != LDAP_URL_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to parse URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_host != NULL ) {
+ if ( lud->lud_host[0] == '\0' ) {
+ ch_free( lud->lud_host );
+ lud->lud_host = NULL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "host not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+ }
+
+ if ( lud->lud_attrs != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "attrs not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_exts != NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "extensions not allowed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( lud->lud_dn != NULL && lud->lud_dn[ 0 ] != '\0' ) {
+ struct berval dn;
+ ber_str2bv( lud->lud_dn, 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &nbase, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "DN normalization failed in URI \"%s\"",
+ c->argv[ attridx ] );
+ goto done_uri;
+ }
+ }
+
+ if ( lud->lud_filter != NULL && lud->lud_filter[ 0 ] != '\0' ) {
+ filter = str2filter( lud->lud_filter );
+ if ( filter == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "filter parsing failed in URI \"%s\"",
+ c->argv[ attridx ] );
+ rc = 1;
+ goto done_uri;
+ }
+ }
+
+ ber_str2bv( c->argv[ attridx ], 0, 1, &uri );
+
+done_uri:;
+ if ( rc ) {
+ if ( lud ) {
+ ldap_free_urldesc( lud );
+ }
+
+ if ( !BER_BVISNULL( &nbase ) ) {
+ ber_memfree( nbase.bv_val );
+ }
+
+ if ( filter != NULL ) {
+ filter_free( filter );
+ }
+
+ while ( dlm != NULL ) {
+ dlml = dlm;
+ dlm = dlm->dlm_next;
+ ch_free( dlml );
+ }
+
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+
+ return rc;
+ }
+
+ attridx++;
+ }
+
+ rc = slap_str2ad( c->argv[ attridx ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "unable to find AttributeDescription \"%s\"",
+ c->argv[ attridx ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ rc = 1;
+ goto done_uri;
+ }
+
+ if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE
+ "AttributeDescription \"%s\" "
+ "must be a subtype of \"labeledURI\"",
+ c->argv[ attridx ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ rc = 1;
+ goto done_uri;
+ }
+
+ attridx++;
+
+ for ( i = attridx; i < c->argc; i++ ) {
+ char *arg;
+ char *cp;
+ AttributeDescription *member_ad = NULL;
+ AttributeDescription *mapped_ad = NULL;
+ dynlist_map_t *dlmp;
+
+
+ /*
+ * If no mapped attribute is given, dn is used
+ * for backward compatibility.
+ */
+ arg = c->argv[i];
+ if ( ( cp = strchr( arg, ':' ) ) != NULL ) {
+ struct berval bv;
+ ber_str2bv( arg, cp - arg, 0, &bv );
+ rc = slap_bv2ad( &bv, &mapped_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find mapped AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ rc = 1;
+ goto done_uri;
+ }
+ arg = cp + 1;
+ }
+
+ rc = slap_str2ad( arg, &member_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "unable to find AttributeDescription #%d \"%s\"\n",
+ i - 3, c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ rc = 1;
+ goto done_uri;
+ }
+
+ dlmp = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) );
+ if ( dlm == NULL ) {
+ dlm = dlmp;
+ }
+ dlmp->dlm_member_ad = member_ad;
+ dlmp->dlm_mapped_ad = mapped_ad;
+ dlmp->dlm_next = NULL;
+
+ if ( dlml != NULL )
+ dlml->dlm_next = dlmp;
+ dlml = dlmp;
+ }
+
+ if ( c->valx > 0 ) {
+ int i;
+
+ for ( i = 0, dlip = (dynlist_info_t **)&on->on_bi.bi_private;
+ i < c->valx; i++ )
+ {
+ if ( *dlip == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "invalid index {%d}\n",
+ c->valx );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ rc = 1;
+ goto done_uri;
+ }
+ dlip = &(*dlip)->dli_next;
+ }
+ dli_next = *dlip;
+
+ } else {
+ for ( dlip = (dynlist_info_t **)&on->on_bi.bi_private;
+ *dlip; dlip = &(*dlip)->dli_next )
+ /* goto last */;
+ }
+
+ *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) );
+
+ (*dlip)->dli_oc = oc;
+ (*dlip)->dli_ad = ad;
+ (*dlip)->dli_dlm = dlm;
+ (*dlip)->dli_next = dli_next;
+
+ (*dlip)->dli_lud = lud;
+ (*dlip)->dli_uri_nbase = nbase;
+ (*dlip)->dli_uri_filter = filter;
+ (*dlip)->dli_uri = uri;
+
+ rc = dynlist_build_def_filter( *dlip );
+
+ } break;
+
+ case DL_ATTRPAIR_COMPAT:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: \"attrpair\" only supported for limited "
+ "backward compatibility with overlay \"dyngroup\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ /* fallthru */
+
+ case DL_ATTRPAIR: {
+ dynlist_info_t **dlip;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL,
+ *member_ad = NULL;
+ const char *text;
+
+ oc = oc_find( "groupOfURLs" );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find default ObjectClass \"groupOfURLs\"" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ rc = slap_str2ad( c->argv[ 1 ], &member_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find AttributeDescription \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ rc = slap_str2ad( c->argv[ 2 ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "unable to find AttributeDescription \"%s\"\n",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ DYNLIST_USAGE
+ "AttributeDescription \"%s\" "
+ "must be a subtype of \"labeledURI\"",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ for ( dlip = (dynlist_info_t **)&on->on_bi.bi_private;
+ *dlip; dlip = &(*dlip)->dli_next )
+ {
+ /*
+ * The same URL attribute / member attribute pair
+ * cannot be repeated, but we enforce this only
+ * when the member attribute is unique. Performing
+ * the check for multiple values would require
+ * sorting and comparing the lists, which is left
+ * as a future improvement
+ */
+ if ( (*dlip)->dli_ad == ad &&
+ (*dlip)->dli_dlm->dlm_next == NULL &&
+ member_ad == (*dlip)->dli_dlm->dlm_member_ad ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"dynlist-attrpair <member-ad> <URL-ad>\": "
+ "URL attributeDescription \"%s\" already mapped.\n",
+ ad->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+#if 0
+ /* make it a warning... */
+ return 1;
+#endif
+ }
+ }
+
+ *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) );
+
+ (*dlip)->dli_oc = oc;
+ (*dlip)->dli_ad = ad;
+ (*dlip)->dli_dlm = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) );
+ (*dlip)->dli_dlm->dlm_member_ad = member_ad;
+ (*dlip)->dli_dlm->dlm_mapped_ad = NULL;
+
+ rc = dynlist_build_def_filter( *dlip );
+
+ } break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+dynlist_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private;
+ ObjectClass *oc = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text;
+ int rc;
+
+ if ( dli == NULL ) {
+ dli = ch_calloc( 1, sizeof( dynlist_info_t ) );
+ on->on_bi.bi_private = (void *)dli;
+ }
+
+ for ( ; dli; dli = dli->dli_next ) {
+ if ( dli->dli_oc == NULL ) {
+ if ( oc == NULL ) {
+ oc = oc_find( "groupOfURLs" );
+ if ( oc == NULL ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch objectClass \"groupOfURLs\"" );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg, 0, 0 );
+ return 1;
+ }
+ }
+
+ dli->dli_oc = oc;
+ }
+
+ if ( dli->dli_ad == NULL ) {
+ if ( ad == NULL ) {
+ rc = slap_str2ad( "memberURL", &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"memberURL\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg, 0, 0 );
+ return 1;
+ }
+ }
+
+ dli->dli_ad = ad;
+ }
+
+ if ( BER_BVISNULL( &dli->dli_default_filter ) ) {
+ rc = dynlist_build_def_filter( dli );
+ if ( rc != 0 ) {
+ return rc;
+ }
+ }
+ }
+
+ if ( ad_dgIdentity == NULL ) {
+ rc = slap_str2ad( "dgIdentity", &ad_dgIdentity, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"dgIdentity\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg, 0, 0 );
+ /* Just a warning */
+ }
+ }
+
+ if ( ad_dgAuthz == NULL ) {
+ rc = slap_str2ad( "dgAuthz", &ad_dgAuthz, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( cr->msg, sizeof( cr->msg),
+ "unable to fetch attributeDescription \"dgAuthz\": %d (%s)",
+ rc, text );
+ Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg, 0, 0 );
+ /* Just a warning */
+ }
+ }
+
+ return 0;
+}
+
+static int
+dynlist_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+
+ if ( on->on_bi.bi_private ) {
+ dynlist_info_t *dli = (dynlist_info_t *)on->on_bi.bi_private,
+ *dli_next;
+
+ for ( dli_next = dli; dli_next; dli = dli_next ) {
+ dynlist_map_t *dlm;
+ dynlist_map_t *dlm_next;
+
+ dli_next = dli->dli_next;
+
+ if ( !BER_BVISNULL( &dli->dli_uri ) ) {
+ ch_free( dli->dli_uri.bv_val );
+ }
+
+ if ( dli->dli_lud != NULL ) {
+ ldap_free_urldesc( dli->dli_lud );
+ }
+
+ if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) {
+ ber_memfree( dli->dli_uri_nbase.bv_val );
+ }
+
+ if ( dli->dli_uri_filter != NULL ) {
+ filter_free( dli->dli_uri_filter );
+ }
+
+ ch_free( dli->dli_default_filter.bv_val );
+
+ dlm = dli->dli_dlm;
+ while ( dlm != NULL ) {
+ dlm_next = dlm->dlm_next;
+ ch_free( dlm );
+ dlm = dlm_next;
+ }
+ ch_free( dli );
+ }
+ }
+
+ return 0;
+}
+
+static slap_overinst dynlist = { { NULL } };
+#ifdef TAKEOVER_DYNGROUP
+static char *obsolete_names[] = {
+ "dyngroup",
+ NULL
+};
+#endif
+
+#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC */
+int
+dynlist_initialize(void)
+{
+ int rc = 0;
+
+ dynlist.on_bi.bi_type = "dynlist";
+
+#ifdef TAKEOVER_DYNGROUP
+ /* makes dynlist incompatible with dyngroup */
+ dynlist.on_bi.bi_obsolete_names = obsolete_names;
+#endif
+
+ dynlist.on_bi.bi_db_config = config_generic_wrapper;
+ dynlist.on_bi.bi_db_open = dynlist_db_open;
+ dynlist.on_bi.bi_db_destroy = dynlist_db_destroy;
+
+ dynlist.on_response = dynlist_response;
+
+ dynlist.on_bi.bi_cf_ocs = dlocs;
+
+ rc = config_register_schema( dlcfg, dlocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &dynlist );
+}
+
+#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return dynlist_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_DYNLIST */
diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c
new file mode 100644
index 0000000..54c2468
--- /dev/null
+++ b/servers/slapd/overlays/memberof.c
@@ -0,0 +1,2204 @@
+/* memberof.c - back-reference for group membership */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software, sponsored by SysNet s.r.l.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_MEMBEROF
+
+#include <stdio.h>
+
+#include "ac/string.h"
+#include "ac/socket.h"
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+
+/*
+ * Glossary:
+ *
+ * GROUP a group object (an entry with GROUP_OC
+ * objectClass)
+ * MEMBER a member object (an entry whose DN is
+ * listed as MEMBER_AT value of a GROUP)
+ * GROUP_OC the objectClass of the group object
+ * (default: groupOfNames)
+ * MEMBER_AT the membership attribute, DN-valued;
+ * note: nameAndOptionalUID is tolerated
+ * as soon as the optionalUID is absent
+ * (default: member)
+ * MEMBER_OF reverse membership attribute
+ * (default: memberOf)
+ *
+ * - add:
+ * - if the entry that is being added is a GROUP,
+ * the MEMBER_AT defined as values of the add operation
+ * get the MEMBER_OF value directly from the request.
+ *
+ * if configured to do so, the MEMBER objects do not exist,
+ * and no relax control is issued, either:
+ * - fail
+ * - drop non-existing members
+ * (by default: don't muck with values)
+ *
+ * - if (configured to do so,) the referenced GROUP exists,
+ * the relax control is set and the user has
+ * "manage" privileges, allow to add MEMBER_OF values to
+ * generic entries.
+ *
+ * - modify:
+ * - if the entry being modified is a GROUP_OC and the
+ * MEMBER_AT attribute is modified, the MEMBER_OF value
+ * of the (existing) MEMBER_AT entries that are affected
+ * is modified according to the request:
+ * - if a MEMBER is removed from the group,
+ * delete the corresponding MEMBER_OF
+ * - if a MEMBER is added to a group,
+ * add the corresponding MEMBER_OF
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP_OC, and we need to check, from the
+ * modification list, if the MEMBER_AT attribute is being
+ * affected, and what MEMBER_AT values are affected.
+ *
+ * if configured to do so, the entries corresponding to
+ * the MEMBER_AT values do not exist, and no relax control
+ * is issued, either:
+ * - fail
+ * - drop non-existing members
+ * (by default: don't muck with values)
+ *
+ * - if configured to do so, the referenced GROUP exists,
+ * (the relax control is set) and the user has
+ * "manage" privileges, allow to add MEMBER_OF values to
+ * generic entries; the change is NOT automatically reflected
+ * in the MEMBER attribute of the GROUP referenced
+ * by the value of MEMBER_OF; a separate modification,
+ * with or without relax control, needs to be performed.
+ *
+ * - modrdn:
+ * - if the entry being renamed is a GROUP, the MEMBER_OF
+ * value of the (existing) MEMBER objects is modified
+ * accordingly based on the newDN of the GROUP.
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP; the list of MEMBER objects is obtained from
+ * the database.
+ *
+ * Non-existing MEMBER objects are ignored, since the
+ * MEMBER_AT is not being addressed by the operation.
+ *
+ * - if the entry being renamed has the MEMBER_OF attribute,
+ * the corresponding MEMBER value must be modified in the
+ * respective group entries.
+ *
+ *
+ * - delete:
+ * - if the entry being deleted is a GROUP, the (existing)
+ * MEMBER objects are modified accordingly; a copy of the
+ * values of the MEMBER_AT is saved and, if the delete
+ * succeeds, the MEMBER_OF value of the (existing) MEMBER
+ * objects is deleted.
+ *
+ * We need to determine, from the database, if it is
+ * a GROUP.
+ *
+ * Non-existing MEMBER objects are ignored, since the entry
+ * is being deleted.
+ *
+ * - if the entry being deleted has the MEMBER_OF attribute,
+ * the corresponding value of the MEMBER_AT must be deleted
+ * from the respective GROUP entries.
+ */
+
+#define SLAPD_MEMBEROF_ATTR "memberOf"
+
+static AttributeDescription *ad_member;
+static AttributeDescription *ad_memberOf;
+
+static ObjectClass *oc_group;
+
+static slap_overinst memberof;
+
+typedef struct memberof_t {
+ struct berval mo_dn;
+ struct berval mo_ndn;
+
+ ObjectClass *mo_oc_group;
+ AttributeDescription *mo_ad_member;
+ AttributeDescription *mo_ad_memberof;
+
+ struct berval mo_groupFilterstr;
+ AttributeAssertion mo_groupAVA;
+ Filter mo_groupFilter;
+
+ struct berval mo_memberFilterstr;
+ Filter mo_memberFilter;
+
+ unsigned mo_flags;
+#define MEMBEROF_NONE 0x00U
+#define MEMBEROF_FDANGLING_DROP 0x01U
+#define MEMBEROF_FDANGLING_ERROR 0x02U
+#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR)
+#define MEMBEROF_FREFINT 0x04U
+#define MEMBEROF_FREVERSE 0x08U
+
+ ber_int_t mo_dangling_err;
+
+#define MEMBEROF_CHK(mo,f) \
+ (((mo)->mo_flags & (f)) == (f))
+#define MEMBEROF_DANGLING_CHECK(mo) \
+ ((mo)->mo_flags & MEMBEROF_FDANGLING_MASK)
+#define MEMBEROF_DANGLING_DROP(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP)
+#define MEMBEROF_DANGLING_ERROR(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR)
+#define MEMBEROF_REFINT(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FREFINT)
+#define MEMBEROF_REVERSE(mo) \
+ MEMBEROF_CHK((mo),MEMBEROF_FREVERSE)
+} memberof_t;
+
+typedef enum memberof_is_t {
+ MEMBEROF_IS_NONE = 0x00,
+ MEMBEROF_IS_GROUP = 0x01,
+ MEMBEROF_IS_MEMBER = 0x02,
+ MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER)
+} memberof_is_t;
+
+typedef struct memberof_cookie_t {
+ AttributeDescription *ad;
+ BerVarray vals;
+ int foundit;
+} memberof_cookie_t;
+
+typedef struct memberof_cbinfo_t {
+ slap_overinst *on;
+ BerVarray member;
+ BerVarray memberof;
+ memberof_is_t what;
+} memberof_cbinfo_t;
+
+static void
+memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on )
+{
+ BackendInfo *bi = op->o_bd->bd_info;
+
+ if ( bi->bi_type == memberof.on_bi.bi_type )
+ op_target->o_bd->bd_info = (BackendInfo *)on->on_info;
+}
+
+static int
+memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ memberof_cookie_t *mc;
+
+ mc = (memberof_cookie_t *)op->o_callback->sc_private;
+ mc->foundit = 1;
+ }
+
+ return 0;
+}
+
+/*
+ * callback for internal search that saves the member attribute values
+ * of groups being deleted.
+ */
+static int
+memberof_saveMember_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ memberof_cookie_t *mc;
+ Attribute *a;
+
+ mc = (memberof_cookie_t *)op->o_callback->sc_private;
+ mc->foundit = 1;
+
+ assert( rs->sr_entry != NULL );
+ assert( rs->sr_entry->e_attrs != NULL );
+
+ a = attr_find( rs->sr_entry->e_attrs, mc->ad );
+ if ( a != NULL ) {
+ ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx );
+
+ assert( attr_find( a->a_next, mc->ad ) == NULL );
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * the delete hook performs an internal search that saves the member
+ * attribute values of groups being deleted.
+ */
+static int
+memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci )
+{
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Operation op2 = *op;
+ slap_callback cb = { 0 };
+ BackendInfo *bi = op->o_bd->bd_info;
+ AttributeName an[ 2 ];
+
+ memberof_is_t iswhat = MEMBEROF_IS_NONE;
+ memberof_cookie_t mc;
+
+ assert( mci->what != MEMBEROF_IS_NONE );
+
+ cb.sc_private = &mc;
+ if ( op->o_tag == LDAP_REQ_DELETE ) {
+ cb.sc_response = memberof_saveMember_cb;
+
+ } else {
+ cb.sc_response = memberof_isGroupOrMember_cb;
+ }
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_callback = &cb;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ BER_BVZERO( &an[ 1 ].an_name );
+ op2.ors_attrs = an;
+ op2.ors_attrsonly = 0;
+ op2.ors_limit = NULL;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+
+ if ( mci->what & MEMBEROF_IS_GROUP ) {
+ SlapReply rs2 = { REP_RESULT };
+
+ mc.ad = mo->mo_ad_member;
+ mc.foundit = 0;
+ mc.vals = NULL;
+ an[ 0 ].an_desc = mo->mo_ad_member;
+ an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+ op2.ors_filterstr = mo->mo_groupFilterstr;
+ op2.ors_filter = &mo->mo_groupFilter;
+ op2.o_do_not_cache = 1; /* internal search, don't log */
+
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_search( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+
+ if ( mc.foundit ) {
+ iswhat |= MEMBEROF_IS_GROUP;
+ if ( mc.vals ) mci->member = mc.vals;
+
+ }
+ }
+
+ if ( mci->what & MEMBEROF_IS_MEMBER ) {
+ SlapReply rs2 = { REP_RESULT };
+
+ mc.ad = mo->mo_ad_memberof;
+ mc.foundit = 0;
+ mc.vals = NULL;
+ an[ 0 ].an_desc = mo->mo_ad_memberof;
+ an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname;
+ op2.ors_filterstr = mo->mo_memberFilterstr;
+ op2.ors_filter = &mo->mo_memberFilter;
+ op2.o_do_not_cache = 1; /* internal search, don't log */
+
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_search( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+
+ if ( mc.foundit ) {
+ iswhat |= MEMBEROF_IS_MEMBER;
+ if ( mc.vals ) mci->memberof = mc.vals;
+
+ }
+ }
+
+ mci->what = iswhat;
+
+ return LDAP_SUCCESS;
+}
+
+/*
+ * response callback that adds memberof values when a group is modified.
+ */
+static void
+memberof_value_modify(
+ Operation *op,
+ struct berval *ndn,
+ AttributeDescription *ad,
+ struct berval *old_dn,
+ struct berval *old_ndn,
+ struct berval *new_dn,
+ struct berval *new_ndn )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Operation op2 = *op;
+ unsigned long opid = op->o_opid;
+ SlapReply rs2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ Modifications mod[ 2 ] = { { { 0 } } }, *ml;
+ struct berval values[ 4 ], nvalues[ 4 ];
+ int mcnt = 0;
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+
+ op2.o_req_dn = *ndn;
+ op2.o_req_ndn = *ndn;
+
+ op2.o_callback = &cb;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ op2.orm_modlist = NULL;
+
+ /* Internal ops, never replicate these */
+ op2.o_opid = 0; /* shared with op, saved above */
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+
+ if ( !BER_BVISNULL( &mo->mo_ndn ) ) {
+ ml = &mod[ mcnt ];
+ ml->sml_numvals = 1;
+ ml->sml_values = &values[ 0 ];
+ ml->sml_values[ 0 ] = mo->mo_dn;
+ BER_BVZERO( &ml->sml_values[ 1 ] );
+ ml->sml_nvalues = &nvalues[ 0 ];
+ ml->sml_nvalues[ 0 ] = mo->mo_ndn;
+ BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+ ml->sml_desc = slap_schema.si_ad_modifiersName;
+ ml->sml_type = ml->sml_desc->ad_cname;
+ ml->sml_op = LDAP_MOD_REPLACE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_next = op2.orm_modlist;
+ op2.orm_modlist = ml;
+
+ mcnt++;
+ }
+
+ ml = &mod[ mcnt ];
+ ml->sml_numvals = 1;
+ ml->sml_values = &values[ 2 ];
+ BER_BVZERO( &ml->sml_values[ 1 ] );
+ ml->sml_nvalues = &nvalues[ 2 ];
+ BER_BVZERO( &ml->sml_nvalues[ 1 ] );
+ ml->sml_desc = ad;
+ ml->sml_type = ml->sml_desc->ad_cname;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_next = op2.orm_modlist;
+ op2.orm_modlist = ml;
+
+ if ( new_ndn != NULL ) {
+ BackendInfo *bi = op2.o_bd->bd_info;
+ OpExtra oex;
+
+ assert( !BER_BVISNULL( new_dn ) );
+ assert( !BER_BVISNULL( new_ndn ) );
+
+ ml = &mod[ mcnt ];
+ ml->sml_op = LDAP_MOD_ADD;
+
+ ml->sml_values[ 0 ] = *new_dn;
+ ml->sml_nvalues[ 0 ] = *new_ndn;
+
+ oex.oe_key = (void *)&memberof;
+ LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_modify( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+ LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ),
+ "memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d",
+ op2.o_req_dn.bv_val, ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n",
+ op->o_log_prefix, buf, 0 );
+ }
+
+ assert( op2.orm_modlist == &mod[ mcnt ] );
+ assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] );
+ ml = op2.orm_modlist->sml_next;
+ if ( mcnt == 1 ) {
+ assert( ml == &mod[ 0 ] );
+ ml = ml->sml_next;
+ }
+ if ( ml != NULL ) {
+ slap_mods_free( ml, 1 );
+ }
+
+ mod[ 0 ].sml_next = NULL;
+ }
+
+ if ( old_ndn != NULL ) {
+ BackendInfo *bi = op2.o_bd->bd_info;
+ OpExtra oex;
+
+ assert( !BER_BVISNULL( old_dn ) );
+ assert( !BER_BVISNULL( old_ndn ) );
+
+ ml = &mod[ mcnt ];
+ ml->sml_op = LDAP_MOD_DELETE;
+
+ ml->sml_values[ 0 ] = *old_dn;
+ ml->sml_nvalues[ 0 ] = *old_ndn;
+
+ oex.oe_key = (void *)&memberof;
+ LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next);
+ memberof_set_backend( &op2, op, on );
+ (void)op->o_bd->be_modify( &op2, &rs2 );
+ op2.o_bd->bd_info = bi;
+ LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next);
+ if ( rs2.sr_err != LDAP_SUCCESS ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ),
+ "memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d",
+ op2.o_req_dn.bv_val, ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n",
+ op->o_log_prefix, buf, 0 );
+ }
+
+ assert( op2.orm_modlist == &mod[ mcnt ] );
+ ml = op2.orm_modlist->sml_next;
+ if ( mcnt == 1 ) {
+ assert( ml == &mod[ 0 ] );
+ ml = ml->sml_next;
+ }
+ if ( ml != NULL ) {
+ slap_mods_free( ml, 1 );
+ }
+ }
+ /* restore original opid */
+ op->o_opid = opid;
+
+ /* FIXME: if old_group_ndn doesn't exist, both delete __and__
+ * add will fail; better split in two operations, although
+ * not optimal in terms of performance. At least it would
+ * move towards self-repairing capabilities. */
+}
+
+static int
+memberof_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ memberof_cbinfo_t *mci = sc->sc_private;
+
+ op->o_callback = sc->sc_next;
+ if ( mci->memberof )
+ ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx );
+ if ( mci->member )
+ ber_bvarray_free_x( mci->member, op->o_tmpmemctx );
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+static int memberof_res_add( Operation *op, SlapReply *rs );
+static int memberof_res_delete( Operation *op, SlapReply *rs );
+static int memberof_res_modify( Operation *op, SlapReply *rs );
+static int memberof_res_modrdn( Operation *op, SlapReply *rs );
+
+static int
+memberof_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Attribute **ap, **map = NULL;
+ int rc = SLAP_CB_CONTINUE;
+ int i;
+ struct berval save_dn, save_ndn;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( op->ora_e->e_attrs == NULL ) {
+ /* FIXME: global overlay; need to deal with */
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "consistency checks not implemented when overlay "
+ "is instantiated as global.\n",
+ op->o_log_prefix, op->o_req_dn.bv_val, 0 );
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) {
+ Attribute *a = *ap;
+
+ if ( a->a_desc == mo->mo_ad_memberof ) {
+ map = ap;
+ break;
+ }
+ }
+ }
+
+ save_dn = op->o_dn;
+ save_ndn = op->o_ndn;
+
+ if ( MEMBEROF_DANGLING_CHECK( mo )
+ && !get_relax( op )
+ && is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) )
+ {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ for ( ap = &op->ora_e->e_attrs; *ap; ) {
+ Attribute *a = *ap;
+
+ if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) {
+ ap = &a->a_next;
+ continue;
+ }
+
+ assert( a->a_nvals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e = NULL;
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+ NULL, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS ) {
+ be_entry_release_r( op, e );
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as group member";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "member=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->ora_e->e_name.bv_val,
+ a->a_vals[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+ ber_memfree( a->a_vals[ i ].bv_val );
+ BER_BVZERO( &a->a_vals[ i ] );
+ if ( a->a_nvals != a->a_vals ) {
+ ber_memfree( a->a_nvals[ i ].bv_val );
+ BER_BVZERO( &a->a_nvals[ i ] );
+ }
+ a->a_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( a->a_nvals != a->a_vals ) {
+ AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+ }
+
+ /* If all values have been removed,
+ * remove the attribute itself. */
+ if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+ *ap = a->a_next;
+ attr_free( a );
+
+ } else {
+ ap = &a->a_next;
+ }
+ }
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ if ( map != NULL ) {
+ Attribute *a = *map;
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof,
+ &a->a_nvals[ i ], ACL_WADD,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &a->a_nvals[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( get_relax( op ) ) {
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->ora_e->e_name.bv_val,
+ a->a_nvals[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ );
+ ber_memfree( a->a_vals[ i ].bv_val );
+ BER_BVZERO( &a->a_vals[ i ] );
+ if ( a->a_nvals != a->a_vals ) {
+ ber_memfree( a->a_nvals[ i ].bv_val );
+ BER_BVZERO( &a->a_nvals[ i ] );
+ }
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( a->a_nvals != a->a_vals ) {
+ AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn, ACL_WADD, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) {
+ *map = a->a_next;
+ attr_free( a );
+ }
+ }
+
+ rc = SLAP_CB_CONTINUE;
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_add;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+done:;
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ return rc;
+}
+
+static int
+memberof_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_delete;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ mci->what = MEMBEROF_IS_GROUP;
+ if ( MEMBEROF_REFINT( mo ) ) {
+ mci->what = MEMBEROF_IS_BOTH;
+ }
+
+ memberof_isGroupOrMember( op, mci );
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+memberof_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ Modifications **mlp, **mmlp = NULL;
+ int rc = SLAP_CB_CONTINUE, save_member = 0;
+ struct berval save_dn, save_ndn;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci, mcis;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) {
+ Modifications *ml = *mlp;
+
+ if ( ml->sml_desc == mo->mo_ad_memberof ) {
+ mmlp = mlp;
+ break;
+ }
+ }
+ }
+
+ save_dn = op->o_dn;
+ save_ndn = op->o_ndn;
+ mcis.on = on;
+ mcis.what = MEMBEROF_IS_GROUP;
+
+ if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS
+ && ( mcis.what & MEMBEROF_IS_GROUP ) )
+ {
+ Modifications *ml;
+
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc == mo->mo_ad_member ) {
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case LDAP_MOD_REPLACE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ save_member = 1;
+ break;
+ }
+ }
+ }
+
+
+ if ( MEMBEROF_DANGLING_CHECK( mo )
+ && !get_relax( op ) )
+ {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ assert( op->orm_modlist != NULL );
+
+ for ( mlp = &op->orm_modlist; *mlp; ) {
+ Modifications *ml = *mlp;
+ int i;
+
+ if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) {
+ mlp = &ml->sml_next;
+ continue;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ /* we don't care about cancellations: if the value
+ * exists, fine; if it doesn't, we let the underlying
+ * database fail as appropriate; */
+ mlp = &ml->sml_next;
+ break;
+
+ case LDAP_MOD_REPLACE:
+ /* Handle this just like a delete (see above) */
+ if ( !ml->sml_values ) {
+ mlp = &ml->sml_next;
+ break;
+ }
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ /* NOTE: right now, the attributeType we use
+ * for member must have a normalized value */
+ assert( ml->sml_nvalues != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
+ continue;
+
+ if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e ) == LDAP_SUCCESS )
+ {
+ be_entry_release_r( op, e );
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as group member";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "member=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ i--;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+
+ } else {
+ mlp = &ml->sml_next;
+ }
+
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+ }
+ }
+
+ if ( mmlp != NULL ) {
+ Modifications *ml = *mmlp;
+ int i;
+ Entry *target;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn,
+ NULL, NULL, 0, &target );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ send_ldap_result( op, rs );
+ goto done;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ if ( ml->sml_nvalues != NULL ) {
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ &ml->sml_nvalues[ i ],
+ ACL_WDEL,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( get_relax( op ) ) {
+ continue;
+ }
+
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "deleting non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ }
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn,
+ ACL_WDEL, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mmlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ NULL,
+ ACL_WDEL, NULL );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ {
+ AccessControlState acl_state = ACL_STATE_INIT;
+
+ for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) {
+ Entry *e;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ /* access is checked with the original identity */
+ rc = access_allowed( op, target,
+ mo->mo_ad_memberof,
+ &ml->sml_nvalues[ i ],
+ ACL_WADD,
+ &acl_state );
+ if ( rc == 0 ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = NULL;
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ml->sml_nvalues[i], &save_ndn ))
+ continue;
+
+ rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ],
+ NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc != LDAP_SUCCESS ) {
+ if ( MEMBEROF_DANGLING_ERROR( mo ) ) {
+ rc = rs->sr_err = mo->mo_dangling_err;
+ rs->sr_text = "adding non-existing object "
+ "as memberof";
+ send_ldap_result( op, rs );
+ goto done2;
+ }
+
+ if ( MEMBEROF_DANGLING_DROP( mo ) ) {
+ int j;
+
+ Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): "
+ "memberof=\"%s\" does not exist (stripping...)\n",
+ op->o_log_prefix, op->o_req_ndn.bv_val,
+ ml->sml_nvalues[ i ].bv_val );
+
+ for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ );
+ ber_memfree( ml->sml_values[ i ].bv_val );
+ BER_BVZERO( &ml->sml_values[ i ] );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ ber_memfree( ml->sml_nvalues[ i ].bv_val );
+ BER_BVZERO( &ml->sml_nvalues[ i ] );
+ }
+ ml->sml_numvals--;
+ if ( j - i == 1 ) {
+ break;
+ }
+
+ AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ if ( ml->sml_nvalues != ml->sml_values ) {
+ AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ],
+ sizeof( struct berval ) * ( j - i ) );
+ }
+ i--;
+ }
+
+ continue;
+ }
+
+ /* access is checked with the original identity */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = access_allowed( op, e, mo->mo_ad_member,
+ &op->o_req_ndn,
+ ACL_WDEL, NULL );
+ be_entry_release_r( op, e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( !rc ) {
+ rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "insufficient access to object referenced by memberof";
+ send_ldap_result( op, rs );
+ goto done;
+ }
+ }
+
+ if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) {
+ *mmlp = ml->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ } break;
+
+ default:
+ assert( 0 );
+ }
+
+done2:;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, target );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_modify;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+ mci->what = mcis.what;
+
+ if ( save_member ) {
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ mo->mo_ad_member, &mci->member, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ rc = SLAP_CB_CONTINUE;
+
+done:;
+ op->o_dn = save_dn;
+ op->o_ndn = save_ndn;
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ return rc;
+}
+
+static int
+memberof_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slap_callback *sc;
+ memberof_cbinfo_t *mci;
+ OpExtra *oex;
+
+ LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) {
+ if ( oex->oe_key == (void *)&memberof )
+ return SLAP_CB_CONTINUE;
+ }
+
+ sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx );
+ sc->sc_private = sc+1;
+ sc->sc_response = memberof_res_modrdn;
+ sc->sc_cleanup = memberof_cleanup;
+ sc->sc_writewait = 0;
+ mci = sc->sc_private;
+ mci->on = on;
+ mci->member = NULL;
+ mci->memberof = NULL;
+
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds memberof values when a group is added.
+ */
+static int
+memberof_res_add( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ Attribute *ma;
+
+ ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof );
+ if ( ma != NULL ) {
+ /* relax is required to allow to add
+ * a non-existing member */
+ op->o_relax = SLAP_CONTROL_CRITICAL;
+
+ for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) {
+
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn ))
+ continue;
+
+ /* the modification is attempted
+ * with the original identity */
+ memberof_value_modify( op,
+ &ma->a_nvals[ i ], mo->mo_ad_member,
+ NULL, NULL, &op->o_req_dn, &op->o_req_ndn );
+ }
+ }
+ }
+
+ if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) {
+ Attribute *a;
+
+ for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member );
+ a != NULL;
+ a = attrs_find( a->a_next, mo->mo_ad_member ) )
+ {
+ for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) {
+ /* ITS#6670 Ignore member pointing to this entry */
+ if ( dn_match( &a->a_nvals[i], &op->o_req_ndn ))
+ continue;
+
+ memberof_value_modify( op,
+ &a->a_nvals[ i ],
+ mo->mo_ad_memberof,
+ NULL, NULL,
+ &op->o_req_dn,
+ &op->o_req_ndn );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that deletes memberof values when a group is deleted.
+ */
+static int
+memberof_res_delete( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ BerVarray vals;
+ int i;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ vals = mci->member;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+
+ if ( MEMBEROF_REFINT( mo ) ) {
+ vals = mci->memberof;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes memberof values when a group
+ * is modified.
+ */
+static int
+memberof_res_modify( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i, rc;
+ Modifications *ml, *mml = NULL;
+ BerVarray vals;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ if ( MEMBEROF_REVERSE( mo ) ) {
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc == mo->mo_ad_memberof ) {
+ mml = ml;
+ break;
+ }
+ }
+ }
+
+ if ( mml != NULL ) {
+ BerVarray vals = mml->sml_nvalues;
+
+ switch ( mml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+ /* delete all ... */
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &op->o_req_ndn,
+ mo->mo_ad_memberof, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */
+ assert( vals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ NULL, NULL,
+ &op->o_req_dn, &op->o_req_ndn );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+
+ if ( mci->what & MEMBEROF_IS_GROUP )
+ {
+ for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ if ( ml->sml_desc != mo->mo_ad_member ) {
+ continue;
+ }
+
+ switch ( ml->sml_op ) {
+ case LDAP_MOD_DELETE:
+ case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */
+ vals = ml->sml_nvalues;
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_REPLACE:
+ vals = mci->member;
+
+ /* delete all ... */
+ if ( vals != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ NULL, NULL );
+ }
+ }
+
+ if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) {
+ break;
+ }
+ /* fall thru */
+
+ case LDAP_MOD_ADD:
+ case SLAP_MOD_SOFTADD: /* ITS#7487 */
+ case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */
+ assert( ml->sml_nvalues != NULL );
+ vals = ml->sml_nvalues;
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ NULL, NULL,
+ &op->o_req_dn, &op->o_req_ndn );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ }
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * response callback that adds/deletes member values when a group member
+ * is renamed.
+ */
+static int
+memberof_res_modrdn( Operation *op, SlapReply *rs )
+{
+ memberof_cbinfo_t *mci = op->o_callback->sc_private;
+ slap_overinst *on = mci->on;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ struct berval newPDN, newDN = BER_BVNULL, newPNDN, newNDN;
+ int i, rc;
+ BerVarray vals;
+
+ struct berval save_dn, save_ndn;
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ mci->what = MEMBEROF_IS_GROUP;
+ if ( MEMBEROF_REFINT( mo ) ) {
+ mci->what |= MEMBEROF_IS_MEMBER;
+ }
+
+ if ( op->orr_nnewSup ) {
+ newPNDN = *op->orr_nnewSup;
+
+ } else {
+ dnParent( &op->o_req_ndn, &newPNDN );
+ }
+
+ build_new_dn( &newNDN, &newPNDN, &op->orr_nnewrdn, op->o_tmpmemctx );
+
+ save_dn = op->o_req_dn;
+ save_ndn = op->o_req_ndn;
+
+ op->o_req_dn = newNDN;
+ op->o_req_ndn = newNDN;
+ rc = memberof_isGroupOrMember( op, mci );
+ op->o_req_dn = save_dn;
+ op->o_req_ndn = save_ndn;
+
+ if ( rc != LDAP_SUCCESS || mci->what == MEMBEROF_IS_NONE ) {
+ goto done;
+ }
+
+ if ( op->orr_newSup ) {
+ newPDN = *op->orr_newSup;
+
+ } else {
+ dnParent( &op->o_req_dn, &newPDN );
+ }
+
+ build_new_dn( &newDN, &newPDN, &op->orr_newrdn, op->o_tmpmemctx );
+
+ if ( mci->what & MEMBEROF_IS_GROUP ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &newNDN,
+ mo->mo_ad_member, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_memberof,
+ &op->o_req_dn, &op->o_req_ndn,
+ &newDN, &newNDN );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+ if ( MEMBEROF_REFINT( mo ) && ( mci->what & MEMBEROF_IS_MEMBER ) ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = backend_attribute( op, NULL, &newNDN,
+ mo->mo_ad_memberof, &vals, ACL_READ );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc == LDAP_SUCCESS ) {
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ memberof_value_modify( op,
+ &vals[ i ], mo->mo_ad_member,
+ &op->o_req_dn, &op->o_req_ndn,
+ &newDN, &newNDN );
+ }
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+done:;
+ if ( !BER_BVISNULL( &newDN ) ) {
+ op->o_tmpfree( newDN.bv_val, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( newNDN.bv_val, op->o_tmpmemctx );
+
+ return SLAP_CB_CONTINUE;
+}
+
+
+static int
+memberof_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo;
+ const char *text = NULL;
+ int rc;
+
+ mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) );
+
+ /* safe default */
+ mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
+
+ if ( !ad_memberOf ) {
+ rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
+ "unable to find attribute=\"%s\": %s (%d)\n",
+ SLAPD_MEMBEROF_ATTR, text, rc );
+ return rc;
+ }
+ }
+
+ if ( !ad_member ) {
+ rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "memberof_db_init: "
+ "unable to find attribute=\"%s\": %s (%d)\n",
+ SLAPD_GROUP_ATTR, text, rc );
+ return rc;
+ }
+ }
+
+ if ( !oc_group ) {
+ oc_group = oc_find( SLAPD_GROUP_CLASS );
+ if ( oc_group == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "memberof_db_init: "
+ "unable to find objectClass=\"%s\"\n",
+ SLAPD_GROUP_CLASS, 0, 0 );
+ return 1;
+ }
+ }
+
+ on->on_bi.bi_private = (void *)mo;
+
+ return 0;
+}
+
+enum {
+ MO_DN = 1,
+ MO_DANGLING,
+ MO_REFINT,
+ MO_GROUP_OC,
+ MO_MEMBER_AD,
+ MO_MEMBER_OF_AD,
+#if 0
+ MO_REVERSE,
+#endif
+
+ MO_DANGLING_ERROR,
+
+ MO_LAST
+};
+
+static ConfigDriver mo_cf_gen;
+
+#define OID "1.3.6.1.4.1.7136.2.666.4"
+#define OIDAT OID ".1.1"
+#define OIDCFGAT OID ".1.2"
+#define OIDOC OID ".2.1"
+#define OIDCFGOC OID ".2.2"
+
+
+static ConfigTable mo_cfg[] = {
+ { "memberof-dn", "modifiersName",
+ 2, 2, 0, ARG_MAGIC|ARG_DN|MO_DN, mo_cf_gen,
+ "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' "
+ "DESC 'DN to be used as modifiersName' "
+ "SYNTAX OMsDN SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-dangling", "ignore|drop|error",
+ 2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen,
+ "( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' "
+ "DESC 'Behavior with respect to dangling members, "
+ "constrained to ignore, drop, error' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-refint", "true|FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen,
+ "( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' "
+ "DESC 'Take care of referential integrity' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-group-oc", "objectClass",
+ 2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen,
+ "( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' "
+ "DESC 'Group objectClass' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-member-ad", "member attribute",
+ 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen,
+ "( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' "
+ "DESC 'member attribute' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "memberof-memberof-ad", "memberOf attribute",
+ 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen,
+ "( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' "
+ "DESC 'memberOf attribute' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+#if 0
+ { "memberof-reverse", "true|FALSE",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen,
+ "( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' "
+ "DESC 'Take care of referential integrity "
+ "also when directly modifying memberOf' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )",
+ NULL, NULL },
+#endif
+
+ { "memberof-dangling-error", "error code",
+ 2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen,
+ "( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' "
+ "DESC 'Error code returned in case of dangling back reference' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs mo_ocs[] = {
+ { "( OLcfgOvOc:18.1 "
+ "NAME 'olcMemberOf' "
+ "DESC 'Member-of configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcMemberOfDN "
+ "$ olcMemberOfDangling "
+ "$ olcMemberOfDanglingError"
+ "$ olcMemberOfRefInt "
+ "$ olcMemberOfGroupOC "
+ "$ olcMemberOfMemberAD "
+ "$ olcMemberOfMemberOfAD "
+#if 0
+ "$ olcMemberOfReverse "
+#endif
+ ") "
+ ")",
+ Cft_Overlay, mo_cfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static slap_verbmasks dangling_mode[] = {
+ { BER_BVC( "ignore" ), MEMBEROF_NONE },
+ { BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP },
+ { BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR },
+ { BER_BVNULL, 0 }
+};
+
+static int
+memberof_make_group_filter( memberof_t *mo )
+{
+ char *ptr;
+
+ if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ ch_free( mo->mo_groupFilterstr.bv_val );
+ }
+
+ mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY;
+ mo->mo_groupFilter.f_ava = &mo->mo_groupAVA;
+
+ mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass;
+ mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname;
+
+ mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" )
+ + slap_schema.si_ad_objectClass->ad_cname.bv_len
+ + mo->mo_oc_group->soc_cname.bv_len;
+ ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 );
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val );
+ *ptr++ = '=';
+ ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val );
+ *ptr++ = ')';
+ *ptr = '\0';
+
+ return 0;
+}
+
+static int
+memberof_make_member_filter( memberof_t *mo )
+{
+ char *ptr;
+
+ if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ ch_free( mo->mo_memberFilterstr.bv_val );
+ }
+
+ mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT;
+ mo->mo_memberFilter.f_desc = mo->mo_ad_memberof;
+
+ mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" )
+ + mo->mo_ad_memberof->ad_cname.bv_len;
+ ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 );
+ *ptr++ = '(';
+ ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val );
+ ptr = lutil_strcopy( ptr, "=*)" );
+
+ return 0;
+}
+
+static int
+mo_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int i, rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch( c->type ) {
+ case MO_DN:
+ if ( mo->mo_dn.bv_val != NULL) {
+ value_add_one( &c->rvalue_vals, &mo->mo_dn );
+ value_add_one( &c->rvalue_nvals, &mo->mo_ndn );
+ }
+ break;
+
+ case MO_DANGLING:
+ enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case MO_DANGLING_ERROR:
+ if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err );
+ if ( bv.bv_len < sizeof( buf ) ) {
+ bv.bv_val = buf;
+ } else {
+ rc = 1;
+ break;
+ }
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ break;
+
+ case MO_REFINT:
+ c->value_int = MEMBEROF_REFINT( mo );
+ break;
+
+#if 0
+ case MO_REVERSE:
+ c->value_int = MEMBEROF_REVERSE( mo );
+ break;
+#endif
+
+ case MO_GROUP_OC:
+ if ( mo->mo_oc_group != NULL ){
+ value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname );
+ }
+ break;
+
+ case MO_MEMBER_AD:
+ if ( mo->mo_ad_member != NULL ){
+ value_add_one( &c->rvalue_vals, &mo->mo_ad_member->ad_cname );
+ }
+ break;
+
+ case MO_MEMBER_OF_AD:
+ if ( mo->mo_ad_memberof != NULL ){
+ value_add_one( &c->rvalue_vals, &mo->mo_ad_memberof->ad_cname );
+ }
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case MO_DN:
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ BER_BVZERO( &mo->mo_dn );
+ BER_BVZERO( &mo->mo_ndn );
+ }
+ break;
+
+ case MO_DANGLING:
+ mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
+ break;
+
+ case MO_DANGLING_ERROR:
+ mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION;
+ break;
+
+ case MO_REFINT:
+ mo->mo_flags &= ~MEMBEROF_FREFINT;
+ break;
+
+#if 0
+ case MO_REVERSE:
+ mo->mo_flags &= ~MEMBEROF_FREVERSE;
+ break;
+#endif
+
+ case MO_GROUP_OC:
+ mo->mo_oc_group = oc_group;
+ memberof_make_group_filter( mo );
+ break;
+
+ case MO_MEMBER_AD:
+ mo->mo_ad_member = ad_member;
+ break;
+
+ case MO_MEMBER_OF_AD:
+ mo->mo_ad_memberof = ad_memberOf;
+ memberof_make_member_filter( mo );
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ } else {
+ switch( c->type ) {
+ case MO_DN:
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ }
+ mo->mo_dn = c->value_dn;
+ mo->mo_ndn = c->value_ndn;
+ break;
+
+ case MO_DANGLING:
+ i = verb_to_mask( c->argv[ 1 ], dangling_mode );
+ if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) {
+ return 1;
+ }
+
+ mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK;
+ mo->mo_flags |= dangling_mode[ i ].mask;
+ break;
+
+ case MO_DANGLING_ERROR:
+ i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code );
+ if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) {
+ mo->mo_dangling_err = slap_ldap_response_code[ i ].mask;
+ } else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) {
+ return 1;
+ }
+ break;
+
+ case MO_REFINT:
+ if ( c->value_int ) {
+ mo->mo_flags |= MEMBEROF_FREFINT;
+
+ } else {
+ mo->mo_flags &= ~MEMBEROF_FREFINT;
+ }
+ break;
+
+#if 0
+ case MO_REVERSE:
+ if ( c->value_int ) {
+ mo->mo_flags |= MEMBEROF_FREVERSE;
+
+ } else {
+ mo->mo_flags &= ~MEMBEROF_FREVERSE;
+ }
+ break;
+#endif
+
+ case MO_GROUP_OC: {
+ ObjectClass *oc = NULL;
+
+ oc = oc_find( c->argv[ 1 ] );
+ if ( oc == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to find group objectClass=\"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ mo->mo_oc_group = oc;
+ memberof_make_group_filter( mo );
+ } break;
+
+ case MO_MEMBER_AD: {
+ AttributeDescription *ad = c->value_ad;
+
+ if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
+ && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "member attribute=\"%s\" must either "
+ "have DN (%s) or nameUID (%s) syntax",
+ c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ mo->mo_ad_member = ad;
+ } break;
+
+ case MO_MEMBER_OF_AD: {
+ AttributeDescription *ad = c->value_ad;
+
+ if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */
+ && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "memberof attribute=\"%s\" must either "
+ "have DN (%s) or nameUID (%s) syntax",
+ c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n",
+ c->log, c->cr_msg, 0 );
+ return 1;
+ }
+
+ mo->mo_ad_memberof = ad;
+ memberof_make_member_filter( mo );
+ } break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+memberof_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ int rc;
+
+ if ( !mo->mo_ad_memberof ) {
+ mo->mo_ad_memberof = ad_memberOf;
+ }
+
+ if ( ! mo->mo_ad_member ) {
+ mo->mo_ad_member = ad_member;
+ }
+
+ if ( ! mo->mo_oc_group ) {
+ mo->mo_oc_group = oc_group;
+ }
+
+ if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) {
+ ber_dupbv( &mo->mo_dn, &be->be_rootdn );
+ ber_dupbv( &mo->mo_ndn, &be->be_rootndn );
+ }
+
+ if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ memberof_make_group_filter( mo );
+ }
+
+ if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ memberof_make_member_filter( mo );
+ }
+
+ return 0;
+}
+
+static int
+memberof_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ memberof_t *mo = (memberof_t *)on->on_bi.bi_private;
+
+ if ( mo ) {
+ if ( !BER_BVISNULL( &mo->mo_dn ) ) {
+ ber_memfree( mo->mo_dn.bv_val );
+ ber_memfree( mo->mo_ndn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) {
+ ber_memfree( mo->mo_groupFilterstr.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) {
+ ber_memfree( mo->mo_memberFilterstr.bv_val );
+ }
+
+ ber_memfree( mo );
+ }
+
+ return 0;
+}
+
+static struct {
+ char *desc;
+ AttributeDescription **adp;
+} as[] = {
+ { "( 1.2.840.113556.1.2.102 "
+ "NAME 'memberOf' "
+ "DESC 'Group that the entry belongs to' "
+ "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' "
+ "EQUALITY distinguishedNameMatch " /* added */
+ "USAGE dSAOperation " /* added; questioned */
+ /* "NO-USER-MODIFICATION " */ /* add? */
+ "X-ORIGIN 'iPlanet Delegated Administrator' )",
+ &ad_memberOf },
+ { NULL }
+};
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+int
+memberof_initialize( void )
+{
+ int code, i;
+
+ for ( i = 0; as[ i ].desc != NULL; i++ ) {
+ code = register_at( as[ i ].desc, as[ i ].adp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "memberof_initialize: register_at #%d failed\n",
+ i, 0, 0 );
+ return code;
+ }
+ }
+
+ memberof.on_bi.bi_type = "memberof";
+
+ memberof.on_bi.bi_db_init = memberof_db_init;
+ memberof.on_bi.bi_db_open = memberof_db_open;
+ memberof.on_bi.bi_db_destroy = memberof_db_destroy;
+
+ memberof.on_bi.bi_op_add = memberof_op_add;
+ memberof.on_bi.bi_op_delete = memberof_op_delete;
+ memberof.on_bi.bi_op_modify = memberof_op_modify;
+ memberof.on_bi.bi_op_modrdn = memberof_op_modrdn;
+
+ memberof.on_bi.bi_cf_ocs = mo_ocs;
+
+ code = config_register_schema( mo_cfg, mo_ocs );
+ if ( code ) return code;
+
+ return overlay_register( &memberof );
+}
+
+#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return memberof_initialize();
+}
+#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_MEMBEROF */
diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c
new file mode 100644
index 0000000..2f83fd7
--- /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-2018 The OpenLDAP Foundation.
+ * Copyright 2003 by Howard Chu.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#include "slap.h"
+
+extern OverlayInit slap_oinfo[];
+
+int
+overlay_init(void)
+{
+ int i, rc = 0;
+
+ for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) {
+ rc = slap_oinfo[i].ov_init();
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s overlay setup failed, err %d\n",
+ slap_oinfo[i].ov_type, rc, 0 );
+ break;
+ }
+ }
+
+ return rc;
+}
diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c
new file mode 100644
index 0000000..166ca48
--- /dev/null
+++ b/servers/slapd/overlays/pcache.c
@@ -0,0 +1,5777 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2003 IBM Corporation.
+ * Portions Copyright 2003-2009 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Apurva Kumar for inclusion
+ * in OpenLDAP Software and subsequently rewritten by Howard Chu.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_PROXYCACHE
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "ldap_rq.h"
+#include "avl.h"
+
+#include "../back-monitor/back-monitor.h"
+
+#include "config.h"
+
+#ifdef LDAP_DEVEL
+/*
+ * Control that allows to access the private DB
+ * instead of the public one
+ */
+#define PCACHE_CONTROL_PRIVDB "1.3.6.1.4.1.4203.666.11.9.5.1"
+
+/*
+ * Extended Operation that allows to remove a query from the cache
+ */
+#define PCACHE_EXOP_QUERY_DELETE "1.3.6.1.4.1.4203.666.11.9.6.1"
+
+/*
+ * Monitoring
+ */
+#define PCACHE_MONITOR
+#endif
+
+/* query cache structs */
+/* query */
+
+typedef struct Query_s {
+ Filter* filter; /* Search Filter */
+ struct berval base; /* Search Base */
+ int scope; /* Search scope */
+} Query;
+
+struct query_template_s;
+
+typedef struct Qbase_s {
+ Avlnode *scopes[4]; /* threaded AVL trees of cached queries */
+ struct berval base;
+ int queries;
+} Qbase;
+
+/* struct representing a cached query */
+typedef struct cached_query_s {
+ Filter *filter;
+ Filter *first;
+ Qbase *qbase;
+ int scope;
+ struct berval q_uuid; /* query identifier */
+ int q_sizelimit;
+ struct query_template_s *qtemp; /* template of the query */
+ time_t expiry_time; /* time till the query is considered invalid */
+ time_t refresh_time; /* time till the query is refreshed */
+ time_t bindref_time; /* time till the bind is refreshed */
+ int bind_refcnt; /* number of bind operation referencing this query */
+ unsigned long answerable_cnt; /* how many times it was answerable */
+ int refcnt; /* references since last refresh */
+ ldap_pvt_thread_mutex_t answerable_cnt_mutex;
+ struct cached_query_s *next; /* next query in the template */
+ struct cached_query_s *prev; /* previous query in the template */
+ struct cached_query_s *lru_up; /* previous query in the LRU list */
+ struct cached_query_s *lru_down; /* next query in the LRU list */
+ ldap_pvt_thread_rdwr_t rwlock;
+} CachedQuery;
+
+/*
+ * URL representation:
+ *
+ * ldap:///<base>??<scope>?<filter>?x-uuid=<uid>,x-template=<template>,x-attrset=<attrset>,x-expiry=<expiry>,x-refresh=<refresh>
+ *
+ * <base> ::= CachedQuery.qbase->base
+ * <scope> ::= CachedQuery.scope
+ * <filter> ::= filter2bv(CachedQuery.filter)
+ * <uuid> ::= CachedQuery.q_uuid
+ * <attrset> ::= CachedQuery.qtemp->attr_set_index
+ * <expiry> ::= CachedQuery.expiry_time
+ * <refresh> ::= CachedQuery.refresh_time
+ *
+ * quick hack: parse URI, call add_query() and then fix
+ * CachedQuery.expiry_time and CachedQuery.q_uuid
+ *
+ * NOTE: if the <attrset> changes, all stored URLs will be invalidated.
+ */
+
+/*
+ * Represents a set of projected attributes.
+ */
+
+struct attr_set {
+ struct query_template_s *templates;
+ AttributeName* attrs; /* specifies the set */
+ unsigned flags;
+#define PC_CONFIGURED (0x1)
+#define PC_REFERENCED (0x2)
+#define PC_GOT_OC (0x4)
+ int count; /* number of attributes */
+};
+
+/* struct representing a query template
+ * e.g. template string = &(cn=)(mail=)
+ */
+typedef struct query_template_s {
+ struct query_template_s *qtnext;
+ struct query_template_s *qmnext;
+
+ Avlnode* qbase;
+ CachedQuery* query; /* most recent query cached for the template */
+ CachedQuery* query_last; /* oldest query cached for the template */
+ ldap_pvt_thread_rdwr_t t_rwlock; /* Rd/wr lock for accessing queries in the template */
+ struct berval querystr; /* Filter string corresponding to the QT */
+ struct berval bindbase; /* base DN for Bind request */
+ struct berval bindfilterstr; /* Filter string for Bind request */
+ struct berval bindftemp; /* bind filter template */
+ Filter *bindfilter;
+ AttributeDescription **bindfattrs; /* attrs to substitute in ftemp */
+
+ int bindnattrs; /* number of bindfattrs */
+ int bindscope;
+ int attr_set_index; /* determines the projected attributes */
+ int no_of_queries; /* Total number of queries in the template */
+ time_t ttl; /* TTL for the queries of this template */
+ time_t negttl; /* TTL for negative results */
+ time_t limitttl; /* TTL for sizelimit exceeding results */
+ time_t ttr; /* time to refresh */
+ time_t bindttr; /* TTR for cached binds */
+ struct attr_set t_attrs; /* filter attrs + attr_set */
+} QueryTemplate;
+
+typedef enum {
+ PC_IGNORE = 0,
+ PC_POSITIVE,
+ PC_NEGATIVE,
+ PC_SIZELIMIT
+} pc_caching_reason_t;
+
+static const char *pc_caching_reason_str[] = {
+ "IGNORE",
+ "POSITIVE",
+ "NEGATIVE",
+ "SIZELIMIT",
+
+ NULL
+};
+
+struct query_manager_s;
+
+/* prototypes for functions for 1) query containment
+ * 2) query addition, 3) cache replacement
+ */
+typedef CachedQuery *(QCfunc)(Operation *op, struct query_manager_s*,
+ Query*, QueryTemplate*);
+typedef CachedQuery *(AddQueryfunc)(Operation *op, struct query_manager_s*,
+ Query*, QueryTemplate*, pc_caching_reason_t, int wlock);
+typedef void (CRfunc)(struct query_manager_s*, struct berval*);
+
+/* LDAP query cache */
+typedef struct query_manager_s {
+ struct attr_set* attr_sets; /* possible sets of projected attributes */
+ QueryTemplate* templates; /* cacheable templates */
+
+ CachedQuery* lru_top; /* top and bottom of LRU list */
+ CachedQuery* lru_bottom;
+
+ ldap_pvt_thread_mutex_t lru_mutex; /* mutex for accessing LRU list */
+
+ /* Query cache methods */
+ QCfunc *qcfunc; /* Query containment*/
+ CRfunc *crfunc; /* cache replacement */
+ AddQueryfunc *addfunc; /* add query */
+} query_manager;
+
+/* LDAP query cache manager */
+typedef struct cache_manager_s {
+ BackendDB db; /* underlying database */
+ unsigned long num_cached_queries; /* total number of cached queries */
+ unsigned long max_queries; /* upper bound on # of cached queries */
+ int save_queries; /* save cached queries across restarts */
+ int check_cacheability; /* check whether a query is cacheable */
+ int numattrsets; /* number of attribute sets */
+ int cur_entries; /* current number of entries cached */
+ int max_entries; /* max number of entries cached */
+ int num_entries_limit; /* max # of entries in a cacheable query */
+
+ char response_cb; /* install the response callback
+ * at the tail of the callback list */
+#define PCACHE_RESPONSE_CB_HEAD 0
+#define PCACHE_RESPONSE_CB_TAIL 1
+ char defer_db_open; /* defer open for online add */
+ char cache_binds; /* cache binds or just passthru */
+
+ time_t cc_period; /* interval between successive consistency checks (sec) */
+#define PCACHE_CC_PAUSED 1
+#define PCACHE_CC_OFFLINE 2
+ int cc_paused;
+ void *cc_arg;
+
+ ldap_pvt_thread_mutex_t cache_mutex;
+
+ query_manager* qm; /* query cache managed by the cache manager */
+
+#ifdef PCACHE_MONITOR
+ void *monitor_cb;
+ struct berval monitor_ndn;
+#endif /* PCACHE_MONITOR */
+} cache_manager;
+
+#ifdef PCACHE_MONITOR
+static int pcache_monitor_db_init( BackendDB *be );
+static int pcache_monitor_db_open( BackendDB *be );
+static int pcache_monitor_db_close( BackendDB *be );
+static int pcache_monitor_db_destroy( BackendDB *be );
+#endif /* PCACHE_MONITOR */
+
+static int pcache_debug;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+static int privDB_cid;
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static AttributeDescription *ad_queryId, *ad_cachedQueryURL;
+
+#ifdef PCACHE_MONITOR
+static AttributeDescription *ad_numQueries, *ad_numEntries;
+static ObjectClass *oc_olmPCache;
+#endif /* PCACHE_MONITOR */
+
+static struct {
+ char *name;
+ char *oid;
+} s_oid[] = {
+ { "PCacheOID", "1.3.6.1.4.1.4203.666.11.9.1" },
+ { "PCacheAttributes", "PCacheOID:1" },
+ { "PCacheObjectClasses", "PCacheOID:2" },
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ AttributeDescription **adp;
+} s_ad[] = {
+ { "( PCacheAttributes:1 "
+ "NAME 'pcacheQueryID' "
+ "DESC 'ID of query the entry belongs to, formatted as a UUID' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64} "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_queryId },
+ { "( PCacheAttributes:2 "
+ "NAME 'pcacheQueryURL' "
+ "DESC 'URI describing a cached query' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_cachedQueryURL },
+#ifdef PCACHE_MONITOR
+ { "( PCacheAttributes:3 "
+ "NAME 'pcacheNumQueries' "
+ "DESC 'Number of cached queries' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_numQueries },
+ { "( PCacheAttributes:4 "
+ "NAME 'pcacheNumEntries' "
+ "DESC 'Number of cached entries' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "NO-USER-MODIFICATION "
+ "USAGE directoryOperation )",
+ &ad_numEntries },
+#endif /* PCACHE_MONITOR */
+
+ { NULL }
+};
+
+static struct {
+ char *desc;
+ ObjectClass **ocp;
+} s_oc[] = {
+#ifdef PCACHE_MONITOR
+ /* augments an existing object, so it must be AUXILIARY */
+ { "( PCacheObjectClasses:1 "
+ "NAME ( 'olmPCache' ) "
+ "SUP top AUXILIARY "
+ "MAY ( "
+ "pcacheQueryURL "
+ "$ pcacheNumQueries "
+ "$ pcacheNumEntries "
+ " ) )",
+ &oc_olmPCache },
+#endif /* PCACHE_MONITOR */
+
+ { NULL }
+};
+
+static int
+filter2template(
+ Operation *op,
+ Filter *f,
+ struct berval *fstr );
+
+static CachedQuery *
+add_query(
+ Operation *op,
+ query_manager* qm,
+ Query* query,
+ QueryTemplate *templ,
+ pc_caching_reason_t why,
+ int wlock);
+
+static int
+remove_query_data(
+ Operation *op,
+ struct berval *query_uuid );
+
+/*
+ * Turn a cached query into its URL representation
+ */
+static int
+query2url( Operation *op, CachedQuery *q, struct berval *urlbv, int dolock )
+{
+ struct berval bv_scope,
+ bv_filter;
+ char attrset_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ expiry_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ refresh_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ answerable_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ],
+ *ptr;
+ ber_len_t attrset_len,
+ expiry_len,
+ refresh_len,
+ answerable_len;
+
+ if ( dolock ) {
+ ldap_pvt_thread_rdwr_rlock( &q->rwlock );
+ }
+
+ ldap_pvt_scope2bv( q->scope, &bv_scope );
+ filter2bv_x( op, q->filter, &bv_filter );
+ attrset_len = sprintf( attrset_buf,
+ "%lu", (unsigned long)q->qtemp->attr_set_index );
+ expiry_len = sprintf( expiry_buf,
+ "%lu", (unsigned long)q->expiry_time );
+ answerable_len = snprintf( answerable_buf, sizeof( answerable_buf ),
+ "%lu", q->answerable_cnt );
+ if ( q->refresh_time )
+ refresh_len = sprintf( refresh_buf,
+ "%lu", (unsigned long)q->refresh_time );
+ else
+ refresh_len = 0;
+
+ urlbv->bv_len = STRLENOF( "ldap:///" )
+ + q->qbase->base.bv_len
+ + STRLENOF( "??" )
+ + bv_scope.bv_len
+ + STRLENOF( "?" )
+ + bv_filter.bv_len
+ + STRLENOF( "?x-uuid=" )
+ + q->q_uuid.bv_len
+ + STRLENOF( ",x-attrset=" )
+ + attrset_len
+ + STRLENOF( ",x-expiry=" )
+ + expiry_len
+ + STRLENOF( ",x-answerable=" )
+ + answerable_len;
+ if ( refresh_len )
+ urlbv->bv_len += STRLENOF( ",x-refresh=" )
+ + refresh_len;
+
+ ptr = urlbv->bv_val = ber_memalloc_x( urlbv->bv_len + 1, op->o_tmpmemctx );
+ ptr = lutil_strcopy( ptr, "ldap:///" );
+ ptr = lutil_strcopy( ptr, q->qbase->base.bv_val );
+ ptr = lutil_strcopy( ptr, "??" );
+ ptr = lutil_strcopy( ptr, bv_scope.bv_val );
+ ptr = lutil_strcopy( ptr, "?" );
+ ptr = lutil_strcopy( ptr, bv_filter.bv_val );
+ ptr = lutil_strcopy( ptr, "?x-uuid=" );
+ ptr = lutil_strcopy( ptr, q->q_uuid.bv_val );
+ ptr = lutil_strcopy( ptr, ",x-attrset=" );
+ ptr = lutil_strcopy( ptr, attrset_buf );
+ ptr = lutil_strcopy( ptr, ",x-expiry=" );
+ ptr = lutil_strcopy( ptr, expiry_buf );
+ ptr = lutil_strcopy( ptr, ",x-answerable=" );
+ ptr = lutil_strcopy( ptr, answerable_buf );
+ if ( refresh_len ) {
+ ptr = lutil_strcopy( ptr, ",x-refresh=" );
+ ptr = lutil_strcopy( ptr, refresh_buf );
+ }
+
+ ber_memfree_x( bv_filter.bv_val, op->o_tmpmemctx );
+
+ if ( dolock ) {
+ ldap_pvt_thread_rdwr_runlock( &q->rwlock );
+ }
+
+ return 0;
+}
+
+/* Find and record the empty filter clauses */
+
+static int
+ftemp_attrs( struct berval *ftemp, struct berval *template,
+ AttributeDescription ***ret, const char **text )
+{
+ int i;
+ int attr_cnt=0;
+ struct berval bv;
+ char *p1, *p2, *t1;
+ AttributeDescription *ad;
+ AttributeDescription **descs = NULL;
+ char *temp2;
+
+ temp2 = ch_malloc( ftemp->bv_len + 1 );
+ p1 = ftemp->bv_val;
+ t1 = temp2;
+
+ *ret = NULL;
+
+ for (;;) {
+ while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' )
+ *t1++ = *p1++;
+
+ p2 = strchr( p1, '=' );
+ if ( !p2 ) {
+ if ( !descs ) {
+ ch_free( temp2 );
+ return -1;
+ }
+ break;
+ }
+ i = p2 - p1;
+ AC_MEMCPY( t1, p1, i );
+ t1 += i;
+ *t1++ = '=';
+
+ if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
+ bv.bv_val = p1;
+ bv.bv_len = p2 - p1;
+ ad = NULL;
+ i = slap_bv2ad( &bv, &ad, text );
+ if ( i ) {
+ ch_free( temp2 );
+ ch_free( descs );
+ return -1;
+ }
+ if ( *p2 == '<' || *p2 == '>' ) p2++;
+ if ( p2[1] != ')' ) {
+ p2++;
+ while ( *p2 != ')' ) p2++;
+ p1 = p2;
+ continue;
+ }
+
+ descs = (AttributeDescription **)ch_realloc(descs,
+ (attr_cnt + 2)*sizeof(AttributeDescription *));
+
+ descs[attr_cnt++] = ad;
+
+ p1 = p2+1;
+ }
+ *t1 = '\0';
+ descs[attr_cnt] = NULL;
+ *ret = descs;
+ template->bv_val = temp2;
+ template->bv_len = t1 - temp2;
+ return attr_cnt;
+}
+
+static int
+template_attrs( char *template, struct attr_set *set, AttributeName **ret,
+ const char **text )
+{
+ int got_oc = 0;
+ int alluser = 0;
+ int allop = 0;
+ int i;
+ int attr_cnt;
+ int t_cnt = 0;
+ struct berval bv;
+ char *p1, *p2;
+ AttributeDescription *ad;
+ AttributeName *attrs;
+
+ p1 = template;
+
+ *ret = NULL;
+
+ attrs = ch_calloc( set->count + 1, sizeof(AttributeName) );
+ for ( i=0; i < set->count; i++ )
+ attrs[i] = set->attrs[i];
+ attr_cnt = i;
+ alluser = an_find( attrs, slap_bv_all_user_attrs );
+ allop = an_find( attrs, slap_bv_all_operational_attrs );
+
+ for (;;) {
+ while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) p1++;
+ p2 = strchr( p1, '=' );
+ if ( !p2 )
+ break;
+ if ( p2[-1] == '<' || p2[-1] == '>' ) p2--;
+ bv.bv_val = p1;
+ bv.bv_len = p2 - p1;
+ ad = NULL;
+ i = slap_bv2ad( &bv, &ad, text );
+ if ( i ) {
+ ch_free( attrs );
+ return -1;
+ }
+ t_cnt++;
+
+ if ( ad == slap_schema.si_ad_objectClass )
+ got_oc = 1;
+
+ if ( is_at_operational(ad->ad_type)) {
+ if ( allop ) {
+ goto bottom;
+ }
+ } else if ( alluser ) {
+ goto bottom;
+ }
+ if ( !ad_inlist( ad, attrs )) {
+ attrs = (AttributeName *)ch_realloc(attrs,
+ (attr_cnt + 2)*sizeof(AttributeName));
+
+ attrs[attr_cnt].an_desc = ad;
+ attrs[attr_cnt].an_name = ad->ad_cname;
+ attrs[attr_cnt].an_oc = NULL;
+ attrs[attr_cnt].an_flags = 0;
+ BER_BVZERO( &attrs[attr_cnt+1].an_name );
+ attr_cnt++;
+ }
+
+bottom:
+ p1 = p2+2;
+ }
+ if ( !t_cnt ) {
+ *text = "couldn't parse template";
+ ch_free(attrs);
+ return -1;
+ }
+ if ( !got_oc && !( set->flags & PC_GOT_OC )) {
+ attrs = (AttributeName *)ch_realloc(attrs,
+ (attr_cnt + 2)*sizeof(AttributeName));
+
+ ad = slap_schema.si_ad_objectClass;
+ attrs[attr_cnt].an_desc = ad;
+ attrs[attr_cnt].an_name = ad->ad_cname;
+ attrs[attr_cnt].an_oc = NULL;
+ attrs[attr_cnt].an_flags = 0;
+ BER_BVZERO( &attrs[attr_cnt+1].an_name );
+ attr_cnt++;
+ }
+ *ret = attrs;
+ return attr_cnt;
+}
+
+/*
+ * Turn an URL representing a formerly cached query into a cached query,
+ * and try to cache it
+ */
+static int
+url2query(
+ char *url,
+ Operation *op,
+ query_manager *qm )
+{
+ Query query = { 0 };
+ QueryTemplate *qt;
+ CachedQuery *cq;
+ LDAPURLDesc *lud = NULL;
+ struct berval base,
+ tempstr = BER_BVNULL,
+ uuid = BER_BVNULL;
+ int attrset;
+ time_t expiry_time;
+ time_t refresh_time;
+ unsigned long answerable_cnt;
+ int i,
+ got = 0,
+#define GOT_UUID 0x1U
+#define GOT_ATTRSET 0x2U
+#define GOT_EXPIRY 0x4U
+#define GOT_ANSWERABLE 0x8U
+#define GOT_REFRESH 0x10U
+#define GOT_ALL (GOT_UUID|GOT_ATTRSET|GOT_EXPIRY|GOT_ANSWERABLE)
+ rc = 0;
+
+ rc = ldap_url_parse( url, &lud );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ return -1;
+ }
+
+ /* non-allowed fields */
+ if ( lud->lud_host != NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_attrs != NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ /* be pedantic */
+ if ( strcmp( lud->lud_scheme, "ldap" ) != 0 ) {
+ rc = 1;
+ goto error;
+ }
+
+ /* required fields */
+ if ( lud->lud_dn == NULL || lud->lud_dn[ 0 ] == '\0' ) {
+ rc = 1;
+ goto error;
+ }
+
+ switch ( lud->lud_scope ) {
+ case LDAP_SCOPE_BASE:
+ case LDAP_SCOPE_ONELEVEL:
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ break;
+
+ default:
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_filter == NULL || lud->lud_filter[ 0 ] == '\0' ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( lud->lud_exts == NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ for ( i = 0; lud->lud_exts[ i ] != NULL; i++ ) {
+ if ( strncmp( lud->lud_exts[ i ], "x-uuid=", STRLENOF( "x-uuid=" ) ) == 0 ) {
+ struct berval tmpUUID;
+ Syntax *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax;
+
+ if ( got & GOT_UUID ) {
+ rc = 1;
+ goto error;
+ }
+
+ ber_str2bv( &lud->lud_exts[ i ][ STRLENOF( "x-uuid=" ) ], 0, 0, &tmpUUID );
+ if ( !BER_BVISEMPTY( &tmpUUID ) ) {
+ rc = syn_UUID->ssyn_pretty( syn_UUID, &tmpUUID, &uuid, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto error;
+ }
+ }
+ got |= GOT_UUID;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-attrset=", STRLENOF( "x-attrset=" ) ) == 0 ) {
+ if ( got & GOT_ATTRSET ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoi( &attrset, &lud->lud_exts[ i ][ STRLENOF( "x-attrset=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ got |= GOT_ATTRSET;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-expiry=", STRLENOF( "x-expiry=" ) ) == 0 ) {
+ unsigned long l;
+
+ if ( got & GOT_EXPIRY ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-expiry=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ expiry_time = (time_t)l;
+ got |= GOT_EXPIRY;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-answerable=", STRLENOF( "x-answerable=" ) ) == 0 ) {
+ if ( got & GOT_ANSWERABLE ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &answerable_cnt, &lud->lud_exts[ i ][ STRLENOF( "x-answerable=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ got |= GOT_ANSWERABLE;
+
+ } else if ( strncmp( lud->lud_exts[ i ], "x-refresh=", STRLENOF( "x-refresh=" ) ) == 0 ) {
+ unsigned long l;
+
+ if ( got & GOT_REFRESH ) {
+ rc = 1;
+ goto error;
+ }
+
+ rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-refresh=" ) ] );
+ if ( rc ) {
+ goto error;
+ }
+ refresh_time = (time_t)l;
+ got |= GOT_REFRESH;
+
+ } else {
+ rc = -1;
+ goto error;
+ }
+ }
+
+ if ( got != GOT_ALL ) {
+ rc = 1;
+ goto error;
+ }
+
+ if ( !(got & GOT_REFRESH ))
+ refresh_time = 0;
+
+ /* ignore expired queries */
+ if ( expiry_time <= slap_get_time()) {
+ Operation op2 = *op;
+
+ memset( &op2.oq_search, 0, sizeof( op2.oq_search ) );
+
+ (void)remove_query_data( &op2, &uuid );
+
+ rc = 0;
+
+ } else {
+ ber_str2bv( lud->lud_dn, 0, 0, &base );
+ rc = dnNormalize( 0, NULL, NULL, &base, &query.base, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto error;
+ }
+ query.scope = lud->lud_scope;
+ query.filter = str2filter( lud->lud_filter );
+ if ( query.filter == NULL ) {
+ rc = -1;
+ goto error;
+ }
+
+ tempstr.bv_val = ch_malloc( strlen( lud->lud_filter ) + 1 );
+ tempstr.bv_len = 0;
+ if ( filter2template( op, query.filter, &tempstr ) ) {
+ ch_free( tempstr.bv_val );
+ rc = -1;
+ goto error;
+ }
+
+ /* check for query containment */
+ qt = qm->attr_sets[attrset].templates;
+ for ( ; qt; qt = qt->qtnext ) {
+ /* find if template i can potentially answer tempstr */
+ if ( bvmatch( &qt->querystr, &tempstr ) ) {
+ break;
+ }
+ }
+
+ if ( qt == NULL ) {
+ rc = 1;
+ goto error;
+ }
+
+ cq = add_query( op, qm, &query, qt, PC_POSITIVE, 0 );
+ if ( cq != NULL ) {
+ cq->expiry_time = expiry_time;
+ cq->refresh_time = refresh_time;
+ cq->q_uuid = uuid;
+ cq->answerable_cnt = answerable_cnt;
+ cq->refcnt = 0;
+
+ /* it's now into cq->filter */
+ BER_BVZERO( &uuid );
+ query.filter = NULL;
+
+ } else {
+ rc = 1;
+ }
+ }
+
+error:;
+ if ( query.filter != NULL ) filter_free( query.filter );
+ if ( !BER_BVISNULL( &tempstr ) ) ch_free( tempstr.bv_val );
+ if ( !BER_BVISNULL( &query.base ) ) ch_free( query.base.bv_val );
+ if ( !BER_BVISNULL( &uuid ) ) ch_free( uuid.bv_val );
+ if ( lud != NULL ) ldap_free_urldesc( lud );
+
+ return rc;
+}
+
+/* Return 1 for an added entry, else 0 */
+static int
+merge_entry(
+ Operation *op,
+ Entry *e,
+ int dup,
+ struct berval* query_uuid )
+{
+ int rc;
+ Modifications* modlist = NULL;
+ const char* text = NULL;
+ Attribute *attr;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+
+ SlapReply sreply = {REP_RESULT};
+
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+
+ if ( dup )
+ e = entry_dup( e );
+ attr = e->e_attrs;
+ e->e_attrs = NULL;
+
+ /* add queryId attribute */
+ attr_merge_one( e, ad_queryId, query_uuid, NULL );
+
+ /* append the attribute list from the fetched entry */
+ e->e_attrs->a_next = attr;
+
+ op->o_tag = LDAP_REQ_ADD;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+
+ op->ora_e = e;
+ op->o_req_dn = e->e_name;
+ op->o_req_ndn = e->e_nname;
+ rc = op->o_bd->be_add( op, &sreply );
+
+ if ( rc != LDAP_SUCCESS ) {
+ if ( rc == LDAP_ALREADY_EXISTS ) {
+ rs_reinit( &sreply, REP_RESULT );
+ slap_entry2mods( e, &modlist, &text, textbuf, textlen );
+ modlist->sml_op = LDAP_MOD_ADD;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = modlist;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_bd->be_modify( op, &sreply );
+ slap_mods_free( modlist, 1 );
+ } else if ( rc == LDAP_REFERRAL ||
+ rc == LDAP_NO_SUCH_OBJECT ) {
+ syncrepl_add_glue( op, e );
+ e = NULL;
+ rc = 1;
+ }
+ if ( e ) {
+ entry_free( e );
+ rc = 0;
+ }
+ } else {
+ if ( op->ora_e == e )
+ entry_free( e );
+ rc = 1;
+ }
+
+ return rc;
+}
+
+/* Length-ordered sort on normalized DNs */
+static int pcache_dn_cmp( const void *v1, const void *v2 )
+{
+ const Qbase *q1 = v1, *q2 = v2;
+
+ int rc = q1->base.bv_len - q2->base.bv_len;
+ if ( rc == 0 )
+ rc = strncmp( q1->base.bv_val, q2->base.bv_val, q1->base.bv_len );
+ return rc;
+}
+
+static int lex_bvcmp( struct berval *bv1, struct berval *bv2 )
+{
+ int len, dif;
+ dif = bv1->bv_len - bv2->bv_len;
+ len = bv1->bv_len;
+ if ( dif > 0 ) len -= dif;
+ len = memcmp( bv1->bv_val, bv2->bv_val, len );
+ if ( !len )
+ len = dif;
+ return len;
+}
+
+/* compare the current value in each filter */
+static int pcache_filter_cmp( Filter *f1, Filter *f2 )
+{
+ int rc, weight1, weight2;
+
+ switch( f1->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ weight1 = 0;
+ break;
+ case LDAP_FILTER_PRESENT:
+ weight1 = 1;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ weight1 = 2;
+ break;
+ default:
+ weight1 = 3;
+ }
+ switch( f2->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ weight2 = 0;
+ break;
+ case LDAP_FILTER_PRESENT:
+ weight2 = 1;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ weight2 = 2;
+ break;
+ default:
+ weight2 = 3;
+ }
+ rc = weight1 - weight2;
+ if ( !rc ) {
+ switch( weight1 ) {
+ case 0:
+ rc = pcache_filter_cmp( f1->f_and, f2->f_and );
+ break;
+ case 1:
+ break;
+ case 2:
+ rc = lex_bvcmp( &f1->f_av_value, &f2->f_av_value );
+ break;
+ case 3:
+ if ( f1->f_choice == LDAP_FILTER_SUBSTRINGS ) {
+ rc = 0;
+ if ( !BER_BVISNULL( &f1->f_sub_initial )) {
+ if ( !BER_BVISNULL( &f2->f_sub_initial )) {
+ rc = lex_bvcmp( &f1->f_sub_initial,
+ &f2->f_sub_initial );
+ } else {
+ rc = 1;
+ }
+ } else if ( !BER_BVISNULL( &f2->f_sub_initial )) {
+ rc = -1;
+ }
+ if ( rc ) break;
+ if ( f1->f_sub_any ) {
+ if ( f2->f_sub_any ) {
+ rc = lex_bvcmp( f1->f_sub_any,
+ f2->f_sub_any );
+ } else {
+ rc = 1;
+ }
+ } else if ( f2->f_sub_any ) {
+ rc = -1;
+ }
+ if ( rc ) break;
+ if ( !BER_BVISNULL( &f1->f_sub_final )) {
+ if ( !BER_BVISNULL( &f2->f_sub_final )) {
+ rc = lex_bvcmp( &f1->f_sub_final,
+ &f2->f_sub_final );
+ } else {
+ rc = 1;
+ }
+ } else if ( !BER_BVISNULL( &f2->f_sub_final )) {
+ rc = -1;
+ }
+ } else {
+ rc = lex_bvcmp( &f1->f_mr_value,
+ &f2->f_mr_value );
+ }
+ break;
+ }
+ while ( !rc ) {
+ f1 = f1->f_next;
+ f2 = f2->f_next;
+ if ( f1 || f2 ) {
+ if ( !f1 )
+ rc = -1;
+ else if ( !f2 )
+ rc = 1;
+ else {
+ rc = pcache_filter_cmp( f1, f2 );
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
+/* compare filters in each query */
+static int pcache_query_cmp( const void *v1, const void *v2 )
+{
+ const CachedQuery *q1 = v1, *q2 =v2;
+ return pcache_filter_cmp( q1->filter, q2->filter );
+}
+
+/* add query on top of LRU list */
+static void
+add_query_on_top (query_manager* qm, CachedQuery* qc)
+{
+ CachedQuery* top = qm->lru_top;
+
+ qm->lru_top = qc;
+
+ if (top)
+ top->lru_up = qc;
+ else
+ qm->lru_bottom = qc;
+
+ qc->lru_down = top;
+ qc->lru_up = NULL;
+ Debug( pcache_debug, "Base of added query = %s\n",
+ qc->qbase->base.bv_val, 0, 0 );
+}
+
+/* remove_query from LRU list */
+
+static void
+remove_query (query_manager* qm, CachedQuery* qc)
+{
+ CachedQuery* up;
+ CachedQuery* down;
+
+ if (!qc)
+ return;
+
+ up = qc->lru_up;
+ down = qc->lru_down;
+
+ if (!up)
+ qm->lru_top = down;
+
+ if (!down)
+ qm->lru_bottom = up;
+
+ if (down)
+ down->lru_up = up;
+
+ if (up)
+ up->lru_down = down;
+
+ qc->lru_up = qc->lru_down = NULL;
+}
+
+/* find and remove string2 from string1
+ * from start if position = 1,
+ * from end if position = 3,
+ * from anywhere if position = 2
+ * string1 is overwritten if position = 2.
+ */
+
+static int
+find_and_remove(struct berval* ber1, struct berval* ber2, int position)
+{
+ int ret=0;
+
+ if ( !ber2->bv_val )
+ return 1;
+ if ( !ber1->bv_val )
+ return 0;
+
+ switch( position ) {
+ case 1:
+ if ( ber1->bv_len >= ber2->bv_len && !memcmp( ber1->bv_val,
+ ber2->bv_val, ber2->bv_len )) {
+ ret = 1;
+ ber1->bv_val += ber2->bv_len;
+ ber1->bv_len -= ber2->bv_len;
+ }
+ break;
+ case 2: {
+ char *temp;
+ ber1->bv_val[ber1->bv_len] = '\0';
+ temp = strstr( ber1->bv_val, ber2->bv_val );
+ if ( temp ) {
+ strcpy( temp, temp+ber2->bv_len );
+ ber1->bv_len -= ber2->bv_len;
+ ret = 1;
+ }
+ break;
+ }
+ case 3:
+ if ( ber1->bv_len >= ber2->bv_len &&
+ !memcmp( ber1->bv_val+ber1->bv_len-ber2->bv_len, ber2->bv_val,
+ ber2->bv_len )) {
+ ret = 1;
+ ber1->bv_len -= ber2->bv_len;
+ }
+ break;
+ }
+ return ret;
+}
+
+
+static struct berval*
+merge_init_final(Operation *op, struct berval* init, struct berval* any,
+ struct berval* final)
+{
+ struct berval* merged, *temp;
+ int i, any_count, count;
+
+ for (any_count=0; any && any[any_count].bv_val; any_count++)
+ ;
+
+ count = any_count;
+
+ if (init->bv_val)
+ count++;
+ if (final->bv_val)
+ count++;
+
+ merged = (struct berval*)op->o_tmpalloc( (count+1)*sizeof(struct berval),
+ op->o_tmpmemctx );
+ temp = merged;
+
+ if (init->bv_val) {
+ ber_dupbv_x( temp, init, op->o_tmpmemctx );
+ temp++;
+ }
+
+ for (i=0; i<any_count; i++) {
+ ber_dupbv_x( temp, any, op->o_tmpmemctx );
+ temp++; any++;
+ }
+
+ if (final->bv_val){
+ ber_dupbv_x( temp, final, op->o_tmpmemctx );
+ temp++;
+ }
+ BER_BVZERO( temp );
+ return merged;
+}
+
+/* Each element in stored must be found in incoming. Incoming is overwritten.
+ */
+static int
+strings_containment(struct berval* stored, struct berval* incoming)
+{
+ struct berval* element;
+ int k=0;
+ int j, rc = 0;
+
+ for ( element=stored; element->bv_val != NULL; element++ ) {
+ for (j = k; incoming[j].bv_val != NULL; j++) {
+ if (find_and_remove(&(incoming[j]), element, 2)) {
+ k = j;
+ rc = 1;
+ break;
+ }
+ rc = 0;
+ }
+ if ( rc ) {
+ continue;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int
+substr_containment_substr(Operation *op, Filter* stored, Filter* incoming)
+{
+ int rc = 0;
+
+ struct berval init_incoming;
+ struct berval final_incoming;
+ struct berval *remaining_incoming = NULL;
+
+ if ((!(incoming->f_sub_initial.bv_val) && (stored->f_sub_initial.bv_val))
+ || (!(incoming->f_sub_final.bv_val) && (stored->f_sub_final.bv_val)))
+ return 0;
+
+ init_incoming = incoming->f_sub_initial;
+ final_incoming = incoming->f_sub_final;
+
+ if (find_and_remove(&init_incoming,
+ &(stored->f_sub_initial), 1) && find_and_remove(&final_incoming,
+ &(stored->f_sub_final), 3))
+ {
+ if (stored->f_sub_any == NULL) {
+ rc = 1;
+ goto final;
+ }
+ remaining_incoming = merge_init_final(op, &init_incoming,
+ incoming->f_sub_any, &final_incoming);
+ rc = strings_containment(stored->f_sub_any, remaining_incoming);
+ ber_bvarray_free_x( remaining_incoming, op->o_tmpmemctx );
+ }
+final:
+ return rc;
+}
+
+static int
+substr_containment_equality(Operation *op, Filter* stored, Filter* incoming)
+{
+ struct berval incoming_val[2];
+ int rc = 0;
+
+ incoming_val[1] = incoming->f_av_value;
+
+ if (find_and_remove(incoming_val+1,
+ &(stored->f_sub_initial), 1) && find_and_remove(incoming_val+1,
+ &(stored->f_sub_final), 3)) {
+ if (stored->f_sub_any == NULL){
+ rc = 1;
+ goto final;
+ }
+ ber_dupbv_x( incoming_val, incoming_val+1, op->o_tmpmemctx );
+ BER_BVZERO( incoming_val+1 );
+ rc = strings_containment(stored->f_sub_any, incoming_val);
+ op->o_tmpfree( incoming_val[0].bv_val, op->o_tmpmemctx );
+ }
+final:
+ return rc;
+}
+
+static Filter *
+filter_first( Filter *f )
+{
+ while ( f->f_choice == LDAP_FILTER_OR || f->f_choice == LDAP_FILTER_AND )
+ f = f->f_and;
+ return f;
+}
+
+typedef struct fstack {
+ struct fstack *fs_next;
+ Filter *fs_fs;
+ Filter *fs_fi;
+} fstack;
+
+static CachedQuery *
+find_filter( Operation *op, Avlnode *root, Filter *inputf, Filter *first )
+{
+ Filter* fs;
+ Filter* fi;
+ MatchingRule* mrule = NULL;
+ int res=0, eqpass= 0;
+ int ret, rc, dir;
+ Avlnode *ptr;
+ CachedQuery cq, *qc;
+ fstack *stack = NULL, *fsp;
+
+ cq.filter = inputf;
+ cq.first = first;
+
+ /* substring matches sort to the end, and we just have to
+ * walk the entire list.
+ */
+ if ( first->f_choice == LDAP_FILTER_SUBSTRINGS ) {
+ ptr = tavl_end( root, 1 );
+ dir = TAVL_DIR_LEFT;
+ } else {
+ ptr = tavl_find3( root, &cq, pcache_query_cmp, &ret );
+ dir = (first->f_choice == LDAP_FILTER_GE) ? TAVL_DIR_LEFT :
+ TAVL_DIR_RIGHT;
+ }
+
+ while (ptr) {
+ qc = ptr->avl_data;
+ fi = inputf;
+ fs = qc->filter;
+
+ /* an incoming substr query can only be satisfied by a cached
+ * substr query.
+ */
+ if ( first->f_choice == LDAP_FILTER_SUBSTRINGS &&
+ qc->first->f_choice != LDAP_FILTER_SUBSTRINGS )
+ break;
+
+ /* an incoming eq query can be satisfied by a cached eq or substr
+ * query
+ */
+ if ( first->f_choice == LDAP_FILTER_EQUALITY ) {
+ if ( eqpass == 0 ) {
+ if ( qc->first->f_choice != LDAP_FILTER_EQUALITY ) {
+nextpass: eqpass = 1;
+ ptr = tavl_end( root, 1 );
+ dir = TAVL_DIR_LEFT;
+ continue;
+ }
+ } else {
+ if ( qc->first->f_choice != LDAP_FILTER_SUBSTRINGS )
+ break;
+ }
+ }
+ do {
+ res=0;
+ switch (fs->f_choice) {
+ case LDAP_FILTER_EQUALITY:
+ if (fi->f_choice == LDAP_FILTER_EQUALITY)
+ mrule = fs->f_ava->aa_desc->ad_type->sat_equality;
+ else
+ ret = 1;
+ break;
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ mrule = fs->f_ava->aa_desc->ad_type->sat_ordering;
+ break;
+ default:
+ mrule = NULL;
+ }
+ if (mrule) {
+ const char *text;
+ rc = value_match(&ret, fs->f_ava->aa_desc, mrule,
+ SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
+ &(fi->f_ava->aa_value),
+ &(fs->f_ava->aa_value), &text);
+ if (rc != LDAP_SUCCESS) {
+ return NULL;
+ }
+ if ( fi==first && fi->f_choice==LDAP_FILTER_EQUALITY && ret )
+ goto nextpass;
+ }
+ switch (fs->f_choice) {
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_AND:
+ if ( fs->f_next ) {
+ /* save our stack position */
+ fsp = op->o_tmpalloc(sizeof(fstack), op->o_tmpmemctx);
+ fsp->fs_next = stack;
+ fsp->fs_fs = fs->f_next;
+ fsp->fs_fi = fi->f_next;
+ stack = fsp;
+ }
+ fs = fs->f_and;
+ fi = fi->f_and;
+ res=1;
+ break;
+ case LDAP_FILTER_SUBSTRINGS:
+ /* check if the equality query can be
+ * answered with cached substring query */
+ if ((fi->f_choice == LDAP_FILTER_EQUALITY)
+ && substr_containment_equality( op,
+ fs, fi))
+ res=1;
+ /* check if the substring query can be
+ * answered with cached substring query */
+ if ((fi->f_choice ==LDAP_FILTER_SUBSTRINGS
+ ) && substr_containment_substr( op,
+ fs, fi))
+ res= 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_PRESENT:
+ res=1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_EQUALITY:
+ if (ret == 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_GE:
+ if (mrule && ret >= 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_LE:
+ if (mrule && ret <= 0)
+ res = 1;
+ fs=fs->f_next;
+ fi=fi->f_next;
+ break;
+ case LDAP_FILTER_NOT:
+ res=0;
+ break;
+ default:
+ break;
+ }
+ if (!fs && !fi && stack) {
+ /* pop the stack */
+ fsp = stack;
+ stack = fsp->fs_next;
+ fs = fsp->fs_fs;
+ fi = fsp->fs_fi;
+ op->o_tmpfree(fsp, op->o_tmpmemctx);
+ }
+ } while((res) && (fi != NULL) && (fs != NULL));
+
+ if ( res )
+ return qc;
+ ptr = tavl_next( ptr, dir );
+ }
+ return NULL;
+}
+
+/* check whether query is contained in any of
+ * the cached queries in template
+ */
+static CachedQuery *
+query_containment(Operation *op, query_manager *qm,
+ Query *query,
+ QueryTemplate *templa)
+{
+ CachedQuery* qc;
+ int depth = 0, tscope;
+ Qbase qbase, *qbptr = NULL;
+ struct berval pdn;
+
+ if (query->filter != NULL) {
+ Filter *first;
+
+ Debug( pcache_debug, "Lock QC index = %p\n",
+ (void *) templa, 0, 0 );
+ qbase.base = query->base;
+
+ first = filter_first( query->filter );
+
+ ldap_pvt_thread_rdwr_rlock(&templa->t_rwlock);
+ for( ;; ) {
+ /* Find the base */
+ qbptr = avl_find( templa->qbase, &qbase, pcache_dn_cmp );
+ if ( qbptr ) {
+ tscope = query->scope;
+ /* Find a matching scope:
+ * match at depth 0 OK
+ * scope is BASE,
+ * one at depth 1 OK
+ * subord at depth > 0 OK
+ * subtree at any depth OK
+ * scope is ONE,
+ * subtree or subord at any depth OK
+ * scope is SUBORD,
+ * subtree or subord at any depth OK
+ * scope is SUBTREE,
+ * subord at depth > 0 OK
+ * subtree at any depth OK
+ */
+ for ( tscope = 0 ; tscope <= LDAP_SCOPE_CHILDREN; tscope++ ) {
+ switch ( query->scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( tscope == LDAP_SCOPE_BASE && depth ) continue;
+ if ( tscope == LDAP_SCOPE_ONE && depth != 1) continue;
+ if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue;
+ break;
+ case LDAP_SCOPE_ONE:
+ if ( tscope == LDAP_SCOPE_BASE )
+ tscope = LDAP_SCOPE_ONE;
+ if ( tscope == LDAP_SCOPE_ONE && depth ) continue;
+ if ( !depth ) break;
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ break;
+ case LDAP_SCOPE_SUBTREE:
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue;
+ break;
+ case LDAP_SCOPE_CHILDREN:
+ if ( tscope < LDAP_SCOPE_SUBTREE )
+ tscope = LDAP_SCOPE_SUBTREE;
+ break;
+ }
+ if ( !qbptr->scopes[tscope] ) continue;
+
+ /* Find filter */
+ qc = find_filter( op, qbptr->scopes[tscope],
+ query->filter, first );
+ if ( qc ) {
+ if ( qc->q_sizelimit ) {
+ ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock);
+ return NULL;
+ }
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if (qm->lru_top != qc) {
+ remove_query(qm, qc);
+ add_query_on_top(qm, qc);
+ }
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ return qc;
+ }
+ }
+ }
+ if ( be_issuffix( op->o_bd, &qbase.base ))
+ break;
+ /* Up a level */
+ dnParent( &qbase.base, &pdn );
+ qbase.base = pdn;
+ depth++;
+ }
+
+ Debug( pcache_debug,
+ "Not answerable: Unlock QC index=%p\n",
+ (void *) templa, 0, 0 );
+ ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock);
+ }
+ return NULL;
+}
+
+static void
+free_query (CachedQuery* qc)
+{
+ free(qc->q_uuid.bv_val);
+ filter_free(qc->filter);
+ ldap_pvt_thread_mutex_destroy(&qc->answerable_cnt_mutex);
+ ldap_pvt_thread_rdwr_destroy( &qc->rwlock );
+ memset(qc, 0, sizeof(*qc));
+ free(qc);
+}
+
+
+/* Add query to query cache, the returned Query is locked for writing */
+static CachedQuery *
+add_query(
+ Operation *op,
+ query_manager* qm,
+ Query* query,
+ QueryTemplate *templ,
+ pc_caching_reason_t why,
+ int wlock)
+{
+ CachedQuery* new_cached_query = (CachedQuery*) ch_malloc(sizeof(CachedQuery));
+ Qbase *qbase, qb;
+ Filter *first;
+ int rc;
+ time_t ttl = 0, ttr = 0;
+ time_t now;
+
+ new_cached_query->qtemp = templ;
+ BER_BVZERO( &new_cached_query->q_uuid );
+ new_cached_query->q_sizelimit = 0;
+
+ now = slap_get_time();
+ switch ( why ) {
+ case PC_POSITIVE:
+ ttl = templ->ttl;
+ if ( templ->ttr )
+ ttr = now + templ->ttr;
+ break;
+
+ case PC_NEGATIVE:
+ ttl = templ->negttl;
+ break;
+
+ case PC_SIZELIMIT:
+ ttl = templ->limitttl;
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ new_cached_query->expiry_time = now + ttl;
+ new_cached_query->refresh_time = ttr;
+ new_cached_query->bindref_time = 0;
+
+ new_cached_query->bind_refcnt = 0;
+ new_cached_query->answerable_cnt = 0;
+ new_cached_query->refcnt = 1;
+ ldap_pvt_thread_mutex_init(&new_cached_query->answerable_cnt_mutex);
+
+ new_cached_query->lru_up = NULL;
+ new_cached_query->lru_down = NULL;
+ Debug( pcache_debug, "Added query expires at %ld (%s)\n",
+ (long) new_cached_query->expiry_time,
+ pc_caching_reason_str[ why ], 0 );
+
+ new_cached_query->scope = query->scope;
+ new_cached_query->filter = query->filter;
+ new_cached_query->first = first = filter_first( query->filter );
+
+ ldap_pvt_thread_rdwr_init(&new_cached_query->rwlock);
+ if (wlock)
+ ldap_pvt_thread_rdwr_wlock(&new_cached_query->rwlock);
+
+ qb.base = query->base;
+
+ /* Adding a query */
+ Debug( pcache_debug, "Lock AQ index = %p\n",
+ (void *) templ, 0, 0 );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ qbase = avl_find( templ->qbase, &qb, pcache_dn_cmp );
+ if ( !qbase ) {
+ qbase = ch_calloc( 1, sizeof(Qbase) + qb.base.bv_len + 1 );
+ qbase->base.bv_len = qb.base.bv_len;
+ qbase->base.bv_val = (char *)(qbase+1);
+ memcpy( qbase->base.bv_val, qb.base.bv_val, qb.base.bv_len );
+ qbase->base.bv_val[qbase->base.bv_len] = '\0';
+ avl_insert( &templ->qbase, qbase, pcache_dn_cmp, avl_dup_error );
+ }
+ new_cached_query->next = templ->query;
+ new_cached_query->prev = NULL;
+ new_cached_query->qbase = qbase;
+ rc = tavl_insert( &qbase->scopes[query->scope], new_cached_query,
+ pcache_query_cmp, avl_dup_error );
+ if ( rc == 0 ) {
+ qbase->queries++;
+ if (templ->query == NULL)
+ templ->query_last = new_cached_query;
+ else
+ templ->query->prev = new_cached_query;
+ templ->query = new_cached_query;
+ templ->no_of_queries++;
+ } else {
+ ldap_pvt_thread_mutex_destroy(&new_cached_query->answerable_cnt_mutex);
+ if (wlock)
+ ldap_pvt_thread_rdwr_wunlock(&new_cached_query->rwlock);
+ ldap_pvt_thread_rdwr_destroy( &new_cached_query->rwlock );
+ ch_free( new_cached_query );
+ new_cached_query = find_filter( op, qbase->scopes[query->scope],
+ query->filter, first );
+ filter_free( query->filter );
+ query->filter = NULL;
+ }
+ Debug( pcache_debug, "TEMPLATE %p QUERIES++ %d\n",
+ (void *) templ, templ->no_of_queries, 0 );
+
+ /* Adding on top of LRU list */
+ if ( rc == 0 ) {
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ add_query_on_top(qm, new_cached_query);
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ }
+ Debug( pcache_debug, "Unlock AQ index = %p \n",
+ (void *) templ, 0, 0 );
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+
+ return rc == 0 ? new_cached_query : NULL;
+}
+
+static void
+remove_from_template (CachedQuery* qc, QueryTemplate* template)
+{
+ if (!qc->prev && !qc->next) {
+ template->query_last = template->query = NULL;
+ } else if (qc->prev == NULL) {
+ qc->next->prev = NULL;
+ template->query = qc->next;
+ } else if (qc->next == NULL) {
+ qc->prev->next = NULL;
+ template->query_last = qc->prev;
+ } else {
+ qc->next->prev = qc->prev;
+ qc->prev->next = qc->next;
+ }
+ tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp );
+ qc->qbase->queries--;
+ if ( qc->qbase->queries == 0 ) {
+ avl_delete( &template->qbase, qc->qbase, pcache_dn_cmp );
+ ch_free( qc->qbase );
+ qc->qbase = NULL;
+ }
+
+ template->no_of_queries--;
+}
+
+/* remove bottom query of LRU list from the query cache */
+/*
+ * NOTE: slight change in functionality.
+ *
+ * - if result->bv_val is NULL, the query at the bottom of the LRU
+ * is removed
+ * - otherwise, the query whose UUID is *result is removed
+ * - if not found, result->bv_val is zeroed
+ */
+static void
+cache_replacement(query_manager* qm, struct berval *result)
+{
+ CachedQuery* bottom;
+ QueryTemplate *temp;
+
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ if ( BER_BVISNULL( result ) ) {
+ bottom = qm->lru_bottom;
+
+ if (!bottom) {
+ Debug ( pcache_debug,
+ "Cache replacement invoked without "
+ "any query in LRU list\n", 0, 0, 0 );
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ return;
+ }
+
+ } else {
+ for ( bottom = qm->lru_bottom;
+ bottom != NULL;
+ bottom = bottom->lru_up )
+ {
+ if ( bvmatch( result, &bottom->q_uuid ) ) {
+ break;
+ }
+ }
+
+ if ( !bottom ) {
+ Debug ( pcache_debug,
+ "Could not find query with uuid=\"%s\""
+ "in LRU list\n", result->bv_val, 0, 0 );
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ BER_BVZERO( result );
+ return;
+ }
+ }
+
+ temp = bottom->qtemp;
+ remove_query(qm, bottom);
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+
+ *result = bottom->q_uuid;
+ BER_BVZERO( &bottom->q_uuid );
+
+ Debug( pcache_debug, "Lock CR index = %p\n", (void *) temp, 0, 0 );
+ ldap_pvt_thread_rdwr_wlock(&temp->t_rwlock);
+ remove_from_template(bottom, temp);
+ Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n",
+ (void *) temp, temp->no_of_queries, 0 );
+ Debug( pcache_debug, "Unlock CR index = %p\n", (void *) temp, 0, 0 );
+ ldap_pvt_thread_rdwr_wunlock(&temp->t_rwlock);
+ free_query(bottom);
+}
+
+struct query_info {
+ struct query_info *next;
+ struct berval xdn;
+ int del;
+};
+
+static int
+remove_func (
+ Operation *op,
+ SlapReply *rs
+)
+{
+ Attribute *attr;
+ struct query_info *qi;
+ int count = 0;
+
+ if ( rs->sr_type != REP_SEARCH ) return 0;
+
+ attr = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ if ( attr == NULL ) return 0;
+
+ count = attr->a_numvals;
+ assert( count > 0 );
+ qi = op->o_tmpalloc( sizeof( struct query_info ), op->o_tmpmemctx );
+ qi->next = op->o_callback->sc_private;
+ op->o_callback->sc_private = qi;
+ ber_dupbv_x( &qi->xdn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ qi->del = ( count == 1 );
+
+ return 0;
+}
+
+static int
+remove_query_data(
+ Operation *op,
+ struct berval *query_uuid )
+{
+ struct query_info *qi, *qnext;
+ char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ Filter filter = {LDAP_FILTER_EQUALITY};
+ SlapReply sreply = {REP_RESULT};
+ slap_callback cb = { NULL, remove_func, NULL, NULL };
+ int deleted = 0;
+
+ op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str),
+ "(%s=%s)", ad_queryId->ad_cname.bv_val, query_uuid->bv_val);
+ filter.f_ava = &ava;
+ filter.f_av_desc = ad_queryId;
+ filter.f_av_value = *query_uuid;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ op->ors_filter = &filter;
+ op->ors_filterstr.bv_val = filter_str;
+ op->ors_filterstr.bv_len = strlen(filter_str);
+ op->ors_attrs = NULL;
+ op->ors_attrsonly = 0;
+
+ op->o_bd->be_search( op, &sreply );
+
+ for ( qi=cb.sc_private; qi; qi=qnext ) {
+ qnext = qi->next;
+
+ op->o_req_dn = qi->xdn;
+ op->o_req_ndn = qi->xdn;
+ rs_reinit( &sreply, REP_RESULT );
+
+ if ( qi->del ) {
+ Debug( pcache_debug, "DELETING ENTRY TEMPLATE=%s\n",
+ query_uuid->bv_val, 0, 0 );
+
+ op->o_tag = LDAP_REQ_DELETE;
+
+ if (op->o_bd->be_delete(op, &sreply) == LDAP_SUCCESS) {
+ deleted++;
+ }
+
+ } else {
+ Modifications mod;
+ struct berval vals[2];
+
+ vals[0] = *query_uuid;
+ vals[1].bv_val = NULL;
+ vals[1].bv_len = 0;
+ mod.sml_op = LDAP_MOD_DELETE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_queryId;
+ mod.sml_type = ad_queryId->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+ Debug( pcache_debug,
+ "REMOVING TEMP ATTR : TEMPLATE=%s\n",
+ query_uuid->bv_val, 0, 0 );
+
+ op->orm_modlist = &mod;
+
+ op->o_bd->be_modify( op, &sreply );
+ }
+ op->o_tmpfree( qi->xdn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( qi, op->o_tmpmemctx );
+ }
+ return deleted;
+}
+
+static int
+get_attr_set(
+ AttributeName* attrs,
+ query_manager* qm,
+ int num
+);
+
+static int
+filter2template(
+ Operation *op,
+ Filter *f,
+ struct berval *fstr )
+{
+ AttributeDescription *ad;
+ int len, ret;
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_GE:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(>=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s>=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_LE:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(<=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s<=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_APPROX:
+ ad = f->f_av_desc;
+ len = STRLENOF( "(~=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s~=)", ad->ad_cname.bv_val);
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ ad = f->f_sub_desc;
+ len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ len = STRLENOF( "(=*)" ) + ad->ad_cname.bv_len;
+ ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=*)", ad->ad_cname.bv_val );
+ assert( ret == len );
+ fstr->bv_len += len;
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ int rc = 0;
+ fstr->bv_val[fstr->bv_len++] = '(';
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ fstr->bv_val[fstr->bv_len] = '&';
+ break;
+ case LDAP_FILTER_OR:
+ fstr->bv_val[fstr->bv_len] = '|';
+ break;
+ case LDAP_FILTER_NOT:
+ fstr->bv_val[fstr->bv_len] = '!';
+ break;
+ }
+ fstr->bv_len++;
+
+ for ( f = f->f_list; f != NULL; f = f->f_next ) {
+ rc = filter2template( op, f, fstr );
+ if ( rc ) break;
+ }
+ fstr->bv_val[fstr->bv_len++] = ')';
+ fstr->bv_val[fstr->bv_len] = '\0';
+
+ return rc;
+ }
+
+ default:
+ /* a filter should at least have room for "()",
+ * an "=" and for a 1-char attr */
+ strcpy( fstr->bv_val, "(?=)" );
+ fstr->bv_len += STRLENOF("(?=)");
+ return -1;
+ }
+
+ return 0;
+}
+
+#define BI_HASHED 0x01
+#define BI_DIDCB 0x02
+#define BI_LOOKUP 0x04
+
+struct search_info;
+
+typedef struct bindinfo {
+ cache_manager *bi_cm;
+ CachedQuery *bi_cq;
+ QueryTemplate *bi_templ;
+ struct search_info *bi_si;
+ int bi_flags;
+ slap_callback bi_cb;
+} bindinfo;
+
+struct search_info {
+ slap_overinst *on;
+ Query query;
+ QueryTemplate *qtemp;
+ AttributeName* save_attrs; /* original attributes, saved for response */
+ int swap_saved_attrs;
+ int max;
+ int over;
+ int count;
+ int slimit;
+ int slimit_exceeded;
+ pc_caching_reason_t caching_reason;
+ Entry *head, *tail;
+ bindinfo *pbi;
+};
+
+static void
+remove_query_and_data(
+ Operation *op,
+ cache_manager *cm,
+ struct berval *uuid )
+{
+ query_manager* qm = cm->qm;
+
+ qm->crfunc( qm, uuid );
+ if ( !BER_BVISNULL( uuid ) ) {
+ int return_val;
+
+ Debug( pcache_debug,
+ "Removing query UUID %s\n",
+ uuid->bv_val, 0, 0 );
+ return_val = remove_query_data( op, uuid );
+ Debug( pcache_debug,
+ "QUERY REMOVED, SIZE=%d\n",
+ return_val, 0, 0);
+ ldap_pvt_thread_mutex_lock( &cm->cache_mutex );
+ cm->cur_entries -= return_val;
+ cm->num_cached_queries--;
+ Debug( pcache_debug,
+ "STORED QUERIES = %lu\n",
+ cm->num_cached_queries, 0, 0 );
+ ldap_pvt_thread_mutex_unlock( &cm->cache_mutex );
+ Debug( pcache_debug,
+ "QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries, 0, 0 );
+ }
+}
+
+/*
+ * Callback used to fetch queryId values based on entryUUID;
+ * used by pcache_remove_entries_from_cache()
+ */
+static int
+fetch_queryId_cb( Operation *op, SlapReply *rs )
+{
+ int rc = 0;
+
+ /* only care about searchEntry responses */
+ if ( rs->sr_type != REP_SEARCH ) {
+ return 0;
+ }
+
+ /* allow only one response per entryUUID */
+ if ( op->o_callback->sc_private != NULL ) {
+ rc = 1;
+
+ } else {
+ Attribute *a;
+
+ /* copy all queryId values into callback's private data */
+ a = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ if ( a != NULL ) {
+ BerVarray vals = NULL;
+
+ ber_bvarray_dup_x( &vals, a->a_nvals, op->o_tmpmemctx );
+ op->o_callback->sc_private = (void *)vals;
+ }
+ }
+
+ /* clear entry if required */
+ rs_flush_entry( op, rs, (slap_overinst *) op->o_bd->bd_info );
+
+ return rc;
+}
+
+/*
+ * Call that allows to remove a set of entries from the cache,
+ * by forcing the removal of all the related queries.
+ */
+int
+pcache_remove_entries_from_cache(
+ Operation *op,
+ cache_manager *cm,
+ BerVarray entryUUIDs )
+{
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation op2;
+ slap_callback sc = { 0 };
+ Filter f = { 0 };
+ char filtbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(entryUUID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+ int s, rc;
+
+ if ( op == NULL ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ connection_fake_init( &conn, &opbuf, thrctx );
+ op = &opbuf.ob_op;
+
+ } else {
+ op2 = *op;
+ op = &op2;
+ }
+
+ memset( &op->oq_search, 0, sizeof( op->oq_search ) );
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ f.f_choice = LDAP_FILTER_EQUALITY;
+ f.f_ava = &ava;
+ ava.aa_desc = slap_schema.si_ad_entryUUID;
+ op->ors_filter = &f;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ attrs[ 0 ].an_desc = ad_queryId;
+ attrs[ 0 ].an_name = ad_queryId->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+
+ op->o_req_dn = cm->db.be_suffix[ 0 ];
+ op->o_req_ndn = cm->db.be_nsuffix[ 0 ];
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+ op->o_bd = &cm->db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ sc.sc_response = fetch_queryId_cb;
+ op->o_callback = &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, 0, 0 );
+
+ for ( e=si->head; e; e=si->head ) {
+ si->head = e->e_private;
+ e->e_private = NULL;
+ while ( cm->cur_entries > (cm->max_entries) ) {
+ BER_BVZERO( &crp_uuid );
+ remove_query_and_data( op_tmp, cm, &crp_uuid );
+ }
+
+ return_val = merge_entry(op_tmp, e, 0, query_uuid);
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->cur_entries += return_val;
+ Debug( pcache_debug,
+ "ENTRY ADDED/MERGED, CACHED ENTRIES=%d\n",
+ cm->cur_entries, 0, 0 );
+ return_val = 0;
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+ }
+
+ return return_val;
+}
+
+static int
+pcache_op_cleanup( Operation *op, SlapReply *rs ) {
+ slap_callback *cb = op->o_callback;
+ struct search_info *si = cb->sc_private;
+ slap_overinst *on = si->on;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+
+ if ( rs->sr_type == REP_RESULT ||
+ op->o_abandon || rs->sr_err == SLAPD_ABANDON )
+ {
+ if ( si->swap_saved_attrs ) {
+ rs->sr_attrs = si->save_attrs;
+ op->ors_attrs = si->save_attrs;
+ }
+ if ( (op->o_abandon || rs->sr_err == SLAPD_ABANDON) &&
+ si->caching_reason == PC_IGNORE )
+ {
+ filter_free( si->query.filter );
+ if ( si->count ) {
+ /* duplicate query, free it */
+ Entry *e;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else if ( si->caching_reason != PC_IGNORE ) {
+ CachedQuery *qc = qm->addfunc(op, qm, &si->query,
+ si->qtemp, si->caching_reason, 1 );
+
+ if ( qc != NULL ) {
+ switch ( si->caching_reason ) {
+ case PC_POSITIVE:
+ cache_entries( op, &qc->q_uuid );
+ if ( si->pbi ) {
+ qc->bind_refcnt++;
+ si->pbi->bi_cq = qc;
+ }
+ break;
+
+ case PC_SIZELIMIT:
+ qc->q_sizelimit = rs->sr_nentries;
+ break;
+
+ case PC_NEGATIVE:
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ ldap_pvt_thread_rdwr_wunlock(&qc->rwlock);
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->num_cached_queries++;
+ Debug( pcache_debug, "STORED QUERIES = %lu\n",
+ cm->num_cached_queries, 0, 0 );
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+
+ /* If the consistency checker suspended itself,
+ * wake it back up
+ */
+ if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( cm->cc_paused == PCACHE_CC_PAUSED ) {
+ cm->cc_paused = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+
+ } else if ( si->count ) {
+ /* duplicate query, free it */
+ Entry *e;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else {
+ filter_free( si->query.filter );
+ }
+
+ op->o_callback = op->o_callback->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ struct search_info *si = op->o_callback->sc_private;
+
+ if ( si->swap_saved_attrs ) {
+ rs->sr_attrs = si->save_attrs;
+ rs->sr_attr_flags = slap_attr_flags( si->save_attrs );
+ op->ors_attrs = si->save_attrs;
+ }
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ Entry *e;
+
+ /* don't return more entries than requested by the client */
+ if ( si->slimit > 0 && rs->sr_nentries >= si->slimit ) {
+ si->slimit_exceeded = 1;
+ }
+
+ /* If we haven't exceeded the limit for this query,
+ * build a chain of answers to store. If we hit the
+ * limit, empty the chain and ignore the rest.
+ */
+ if ( !si->over ) {
+ slap_overinst *on = si->on;
+ cache_manager *cm = on->on_bi.bi_private;
+
+ /* check if the entry contains undefined
+ * attributes/objectClasses (ITS#5680) */
+ if ( cm->check_cacheability && test_filter( op, rs->sr_entry, si->query.filter ) != LDAP_COMPARE_TRUE ) {
+ Debug( pcache_debug, "%s: query not cacheable because of schema issues in DN \"%s\"\n",
+ op->o_log_prefix, rs->sr_entry->e_name.bv_val, 0 );
+ goto over;
+ }
+
+ /* check for malformed entries: attrs with no values */
+ {
+ Attribute *a = rs->sr_entry->e_attrs;
+ for (; a; a=a->a_next) {
+ if ( !a->a_numvals ) {
+ Debug( pcache_debug, "%s: query not cacheable because of attrs without values in DN \"%s\" (%s)\n",
+ op->o_log_prefix, rs->sr_entry->e_name.bv_val,
+ a->a_desc->ad_cname.bv_val );
+ goto over;
+ }
+ }
+ }
+
+ if ( si->count < si->max ) {
+ si->count++;
+ e = entry_dup( rs->sr_entry );
+ if ( !si->head ) si->head = e;
+ if ( si->tail ) si->tail->e_private = e;
+ si->tail = e;
+
+ } else {
+over:;
+ si->over = 1;
+ si->count = 0;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ si->tail = NULL;
+ }
+ }
+ if ( si->slimit_exceeded ) {
+ return 0;
+ }
+ } else if ( rs->sr_type == REP_RESULT ) {
+
+ if ( si->count ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ si->caching_reason = PC_POSITIVE;
+
+ } else if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED
+ && si->qtemp->limitttl )
+ {
+ Entry *e;
+
+ si->caching_reason = PC_SIZELIMIT;
+ for (;si->head; si->head=e) {
+ e = si->head->e_private;
+ si->head->e_private = NULL;
+ entry_free(si->head);
+ }
+ }
+
+ } else if ( si->qtemp->negttl && !si->count && !si->over &&
+ rs->sr_err == LDAP_SUCCESS )
+ {
+ si->caching_reason = PC_NEGATIVE;
+ }
+
+
+ if ( si->slimit_exceeded ) {
+ rs->sr_err = LDAP_SIZELIMIT_EXCEEDED;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* NOTE: this is a quick workaround to let pcache minimally interact
+ * with pagedResults. A more articulated solutions would be to
+ * perform the remote query without control and cache all results,
+ * performing the pagedResults search only within the client
+ * and the proxy. This requires pcache to understand pagedResults. */
+static int
+pcache_chk_controls(
+ Operation *op,
+ SlapReply *rs )
+{
+ const char *non = "";
+ const char *stripped = "";
+
+ switch( op->o_pagedresults ) {
+ case SLAP_CONTROL_NONCRITICAL:
+ non = "non-";
+ stripped = "; stripped";
+ /* fallthru */
+
+ case SLAP_CONTROL_CRITICAL:
+ Debug( pcache_debug, "%s: "
+ "%scritical pagedResults control "
+ "disabled with proxy cache%s.\n",
+ op->o_log_prefix, non, stripped );
+
+ slap_remove_control( op, rs, slap_cids.sc_pagedResults, NULL );
+ break;
+
+ default:
+ rs->sr_err = SLAP_CB_CONTINUE;
+ break;
+ }
+
+ return rs->sr_err;
+}
+
+static int
+pc_setpw( Operation *op, struct berval *pwd, cache_manager *cm )
+{
+ struct berval vals[2];
+
+ {
+ const char *text = NULL;
+ BER_BVZERO( &vals[0] );
+ slap_passwd_hash( pwd, &vals[0], &text );
+ if ( BER_BVISEMPTY( &vals[0] )) {
+ Debug( pcache_debug, "pc_setpw: hash failed %s\n",
+ text, 0, 0 );
+ return LDAP_OTHER;
+ }
+ }
+
+ BER_BVZERO( &vals[1] );
+
+ {
+ Modifications mod;
+ SlapReply sr = { REP_RESULT };
+ slap_callback cb = { 0, slap_null_cb, 0, 0 };
+ int rc;
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = 0;
+ mod.sml_desc = slap_schema.si_ad_userPassword;
+ mod.sml_type = mod.sml_desc->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->o_bd = &cm->db;
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ op->o_callback = &cb;
+ Debug( pcache_debug, "pc_setpw: CACHING BIND for %s\n",
+ op->o_req_dn.bv_val, 0, 0 );
+ rc = op->o_bd->be_modify( op, &sr );
+ ch_free( vals[0].bv_val );
+ return rc;
+ }
+}
+
+typedef struct bindcacheinfo {
+ slap_overinst *on;
+ CachedQuery *qc;
+} bindcacheinfo;
+
+static int
+pc_bind_save( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ bindcacheinfo *bci = op->o_callback->sc_private;
+ slap_overinst *on = bci->on;
+ cache_manager *cm = on->on_bi.bi_private;
+ CachedQuery *qc = bci->qc;
+ int delete = 0;
+
+ ldap_pvt_thread_rdwr_wlock( &qc->rwlock );
+ if ( qc->bind_refcnt-- ) {
+ Operation op2 = *op;
+ if ( pc_setpw( &op2, &op->orb_cred, cm ) == LDAP_SUCCESS )
+ bci->qc->bindref_time = op->o_time + bci->qc->qtemp->bindttr;
+ } else {
+ bci->qc = NULL;
+ delete = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &qc->rwlock );
+ if ( delete ) free_query(qc);
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static Filter *
+pc_bind_attrs( Operation *op, Entry *e, QueryTemplate *temp,
+ struct berval *fbv )
+{
+ int i, len = 0;
+ struct berval *vals, pres = BER_BVC("*");
+ char *p1, *p2;
+ Attribute *a;
+
+ vals = op->o_tmpalloc( temp->bindnattrs * sizeof( struct berval ),
+ op->o_tmpmemctx );
+
+ for ( i=0; i<temp->bindnattrs; i++ ) {
+ a = attr_find( e->e_attrs, temp->bindfattrs[i] );
+ if ( a && a->a_vals ) {
+ vals[i] = a->a_vals[0];
+ len += a->a_vals[0].bv_len;
+ } else {
+ vals[i] = pres;
+ }
+ }
+ fbv->bv_len = len + temp->bindftemp.bv_len;
+ fbv->bv_val = op->o_tmpalloc( fbv->bv_len + 1, op->o_tmpmemctx );
+
+ p1 = temp->bindftemp.bv_val;
+ p2 = fbv->bv_val;
+ i = 0;
+ while ( *p1 ) {
+ *p2++ = *p1;
+ if ( p1[0] == '=' && p1[1] == ')' ) {
+ AC_MEMCPY( p2, vals[i].bv_val, vals[i].bv_len );
+ p2 += vals[i].bv_len;
+ i++;
+ }
+ p1++;
+ }
+ *p2 = '\0';
+ op->o_tmpfree( vals, op->o_tmpmemctx );
+
+ /* FIXME: are we sure str2filter_x can't fail?
+ * caller needs to check */
+ {
+ Filter *f = str2filter_x( op, fbv->bv_val );
+ assert( f != NULL );
+ return f;
+ }
+}
+
+/* Check if the requested entry is from the cache and has a valid
+ * ttr and password hash
+ */
+static int
+pc_bind_search( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ bindinfo *pbi = op->o_callback->sc_private;
+
+ /* We only care if this is an already cached result and we're
+ * below the refresh time, or we're offline.
+ */
+ if ( pbi->bi_cq ) {
+ if (( pbi->bi_cm->cc_paused & PCACHE_CC_OFFLINE ) ||
+ op->o_time < pbi->bi_cq->bindref_time ) {
+ Attribute *a;
+
+ /* See if a recognized password is hashed here */
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_userPassword );
+ if ( a && a->a_vals[0].bv_val[0] == '{' &&
+ lutil_passwd_scheme( a->a_vals[0].bv_val ))
+ pbi->bi_flags |= BI_HASHED;
+ } else {
+ Debug( pcache_debug, "pc_bind_search: cache is stale, "
+ "reftime: %ld, current time: %ld\n",
+ pbi->bi_cq->bindref_time, op->o_time, 0 );
+ }
+ } else if ( pbi->bi_si ) {
+ /* This search result is going into the cache */
+ struct berval fbv;
+ Filter *f;
+
+ filter_free( pbi->bi_si->query.filter );
+ f = pc_bind_attrs( op, rs->sr_entry, pbi->bi_templ, &fbv );
+ op->o_tmpfree( fbv.bv_val, op->o_tmpmemctx );
+ pbi->bi_si->query.filter = filter_dup( f, NULL );
+ filter_free_x( op, f, 1 );
+ }
+ }
+ return 0;
+}
+
+/* We always want pc_bind_search to run after the search handlers */
+static int
+pc_bind_resp( Operation *op, SlapReply *rs )
+{
+ bindinfo *pbi = op->o_callback->sc_private;
+ if ( !( pbi->bi_flags & BI_DIDCB )) {
+ slap_callback *sc = op->o_callback;
+ while ( sc && sc->sc_response != pcache_response )
+ sc = sc->sc_next;
+ if ( !sc )
+ sc = op->o_callback;
+ pbi->bi_cb.sc_next = sc->sc_next;
+ sc->sc_next = &pbi->bi_cb;
+ pbi->bi_flags |= BI_DIDCB;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+#ifdef PCACHE_CONTROL_PRIVDB
+static int
+pcache_op_privdb(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ slap_callback *save_cb;
+ slap_op_t type;
+
+ /* skip if control is unset */
+ if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_CRITICAL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* The cache DB isn't open yet */
+ if ( cm->defer_db_open ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+ "pcachePrivDB: cacheDB not available" );
+ return rs->sr_err;
+ }
+
+ /* FIXME: might be a little bit exaggerated... */
+ if ( !be_isroot( op ) ) {
+ save_cb = op->o_callback;
+ op->o_callback = NULL;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "pcachePrivDB: operation not allowed" );
+ op->o_callback = save_cb;
+
+ return rs->sr_err;
+ }
+
+ /* map tag to operation */
+ type = slap_req2op( op->o_tag );
+ if ( type != SLAP_OP_LAST ) {
+ BI_op_func **func;
+ int rc;
+
+ /* execute, if possible */
+ func = &cm->db.be_bind;
+ if ( func[ type ] != NULL ) {
+ Operation op2 = *op;
+
+ op2.o_bd = &cm->db;
+
+ rc = func[ type ]( &op2, rs );
+ if ( type == SLAP_OP_BIND && rc == LDAP_SUCCESS ) {
+ op->o_conn->c_authz_cookie = cm->db.be_private;
+ }
+
+ return rs->sr_err;
+ }
+ }
+
+ /* otherwise fall back to error */
+ save_cb = op->o_callback;
+ op->o_callback = NULL;
+ send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM,
+ "operation not supported with pcachePrivDB control" );
+ op->o_callback = save_cb;
+
+ return rs->sr_err;
+}
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static int
+pcache_op_bind(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ QueryTemplate *temp;
+ Entry *e;
+ slap_callback cb = { 0 }, *sc;
+ bindinfo bi = { 0 };
+ bindcacheinfo *bci;
+ Operation op2;
+ int rc;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL )
+ return pcache_op_privdb( op, rs );
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+ /* Skip if we're not configured for Binds, or cache DB isn't open yet */
+ if ( !cm->cache_binds || cm->defer_db_open )
+ return SLAP_CB_CONTINUE;
+
+ /* First find a matching template with Bind info */
+ for ( temp=cm->qm->templates; temp; temp=temp->qmnext ) {
+ if ( temp->bindttr && dnIsSuffix( &op->o_req_ndn, &temp->bindbase ))
+ break;
+ }
+ /* Didn't find a suitable template, just passthru */
+ if ( !temp )
+ return SLAP_CB_CONTINUE;
+
+ /* See if the entry is already locally cached. If so, we can
+ * populate the query filter to retrieve the cached query. We
+ * need to check the bindrefresh time in the query.
+ */
+ op2 = *op;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.o_bd = &cm->db;
+ e = NULL;
+ rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, NULL, 0, &e );
+ if ( rc == LDAP_SUCCESS && e ) {
+ bi.bi_flags |= BI_LOOKUP;
+ op2.ors_filter = pc_bind_attrs( op, e, temp, &op2.ors_filterstr );
+ be_entry_release_r( &op2, e );
+ } else {
+ op2.ors_filter = temp->bindfilter;
+ op2.ors_filterstr = temp->bindfilterstr;
+ }
+
+ op2.o_bd = op->o_bd;
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_slimit = 1;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_limit = NULL;
+ op2.ors_attrs = cm->qm->attr_sets[temp->attr_set_index].attrs;
+ op2.ors_attrsonly = 0;
+
+ /* We want to invoke search at the same level of the stack
+ * as we're already at...
+ */
+ bi.bi_cm = cm;
+ bi.bi_templ = temp;
+
+ bi.bi_cb.sc_response = pc_bind_search;
+ bi.bi_cb.sc_private = &bi;
+ cb.sc_private = &bi;
+ cb.sc_response = pc_bind_resp;
+ op2.o_callback = &cb;
+ overlay_op_walk( &op2, rs, op_search, on->on_info, on );
+
+ /* OK, just bind locally */
+ if ( bi.bi_flags & BI_HASHED ) {
+ int delete = 0;
+ BackendDB *be = op->o_bd;
+ op->o_bd = &cm->db;
+
+ Debug( pcache_debug, "pcache_op_bind: CACHED BIND for %s\n",
+ op->o_req_dn.bv_val, 0, 0 );
+
+ if ( op->o_bd->be_bind( op, rs ) == LDAP_SUCCESS ) {
+ op->o_conn->c_authz_cookie = cm->db.be_private;
+ }
+ op->o_bd = be;
+ ldap_pvt_thread_rdwr_wlock( &bi.bi_cq->rwlock );
+ if ( !bi.bi_cq->bind_refcnt-- ) {
+ delete = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &bi.bi_cq->rwlock );
+ if ( delete ) free_query( bi.bi_cq );
+ return rs->sr_err;
+ }
+
+ /* We have a cached query to work with */
+ if ( bi.bi_cq ) {
+ sc = op->o_tmpalloc( sizeof(slap_callback) + sizeof(bindcacheinfo),
+ op->o_tmpmemctx );
+ sc->sc_response = pc_bind_save;
+ sc->sc_cleanup = NULL;
+ sc->sc_private = sc+1;
+ sc->sc_writewait = NULL;
+ bci = sc->sc_private;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ bci->on = on;
+ bci->qc = bi.bi_cq;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static slap_response refresh_merge;
+
+static int
+pcache_op_search(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+
+ int i = -1;
+
+ Query query;
+ QueryTemplate *qtemp = NULL;
+ bindinfo *pbi = NULL;
+
+ int attr_set = -1;
+ CachedQuery *answerable = NULL;
+ int cacheable = 0;
+
+ struct berval tempstr;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) {
+ return pcache_op_privdb( op, rs );
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+ /* The cache DB isn't open yet */
+ if ( cm->defer_db_open ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE,
+ "pcachePrivDB: cacheDB not available" );
+ return rs->sr_err;
+ }
+
+ /* pickup runtime ACL changes */
+ cm->db.be_acl = op->o_bd->be_acl;
+
+ {
+ /* See if we're processing a Bind request
+ * or a cache refresh */
+ slap_callback *cb = op->o_callback;
+
+ for ( ; cb; cb=cb->sc_next ) {
+ if ( cb->sc_response == pc_bind_resp ) {
+ pbi = cb->sc_private;
+ break;
+ }
+ if ( cb->sc_response == refresh_merge ) {
+ /* This is a refresh, do not search the cache */
+ return SLAP_CB_CONTINUE;
+ }
+ }
+ }
+
+ /* FIXME: cannot cache/answer requests with pagedResults control */
+
+ query.filter = op->ors_filter;
+
+ if ( pbi ) {
+ query.base = pbi->bi_templ->bindbase;
+ query.scope = pbi->bi_templ->bindscope;
+ attr_set = pbi->bi_templ->attr_set_index;
+ cacheable = 1;
+ qtemp = pbi->bi_templ;
+ if ( pbi->bi_flags & BI_LOOKUP )
+ answerable = qm->qcfunc(op, qm, &query, qtemp);
+
+ } else {
+ tempstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len+1,
+ op->o_tmpmemctx );
+ tempstr.bv_len = 0;
+ if ( filter2template( op, op->ors_filter, &tempstr ))
+ {
+ op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug( pcache_debug, "query template of incoming query = %s\n",
+ tempstr.bv_val, 0, 0 );
+
+ /* find attr set */
+ attr_set = get_attr_set(op->ors_attrs, qm, cm->numattrsets);
+
+ query.base = op->o_req_ndn;
+ query.scope = op->ors_scope;
+
+ /* check for query containment */
+ if (attr_set > -1) {
+ QueryTemplate *qt = qm->attr_sets[attr_set].templates;
+ for (; qt; qt = qt->qtnext ) {
+ /* find if template i can potentially answer tempstr */
+ if ( ber_bvstrcasecmp( &qt->querystr, &tempstr ) != 0 )
+ continue;
+ cacheable = 1;
+ qtemp = qt;
+ Debug( pcache_debug, "Entering QC, querystr = %s\n",
+ op->ors_filterstr.bv_val, 0, 0 );
+ answerable = qm->qcfunc(op, qm, &query, qt);
+
+ /* if != NULL, rlocks qtemp->t_rwlock */
+ if (answerable)
+ break;
+ }
+ }
+ op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx );
+ }
+
+ if (answerable) {
+ BackendDB *save_bd = op->o_bd;
+
+ ldap_pvt_thread_mutex_lock( &answerable->answerable_cnt_mutex );
+ answerable->answerable_cnt++;
+ /* we only care about refcnts if we're refreshing */
+ if ( answerable->refresh_time )
+ answerable->refcnt++;
+ Debug( pcache_debug, "QUERY ANSWERABLE (answered %lu times)\n",
+ answerable->answerable_cnt, 0, 0 );
+ ldap_pvt_thread_mutex_unlock( &answerable->answerable_cnt_mutex );
+
+ ldap_pvt_thread_rdwr_wlock(&answerable->rwlock);
+ if ( BER_BVISNULL( &answerable->q_uuid )) {
+ /* No entries cached, just an empty result set */
+ i = rs->sr_err = 0;
+ send_ldap_result( op, rs );
+ } else {
+ /* Let Bind know we used a cached query */
+ if ( pbi ) {
+ answerable->bind_refcnt++;
+ pbi->bi_cq = answerable;
+ }
+
+ op->o_bd = &cm->db;
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_TAIL ) {
+ slap_callback cb;
+ /* The cached entry was already processed by any
+ * other overlays, so don't let it get processed again.
+ *
+ * This loop removes over_back_response from the stack.
+ */
+ if ( overlay_callback_after_backover( op, &cb, 0) == 0 ) {
+ slap_callback **scp;
+ for ( scp = &op->o_callback; *scp != NULL;
+ scp = &(*scp)->sc_next ) {
+ if ( (*scp)->sc_next == &cb ) {
+ *scp = cb.sc_next;
+ break;
+ }
+ }
+ }
+ }
+ i = cm->db.bd_info->bi_op_search( op, rs );
+ }
+ ldap_pvt_thread_rdwr_wunlock(&answerable->rwlock);
+ /* locked by qtemp->qcfunc (query_containment) */
+ ldap_pvt_thread_rdwr_runlock(&qtemp->t_rwlock);
+ op->o_bd = save_bd;
+ return i;
+ }
+
+ Debug( pcache_debug, "QUERY NOT ANSWERABLE\n", 0, 0, 0 );
+
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ if (cm->num_cached_queries >= cm->max_queries) {
+ cacheable = 0;
+ }
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+
+ if (op->ors_attrsonly)
+ cacheable = 0;
+
+ if (cacheable) {
+ slap_callback *cb;
+ struct search_info *si;
+
+ Debug( pcache_debug, "QUERY CACHEABLE\n", 0, 0, 0 );
+ query.filter = filter_dup(op->ors_filter, NULL);
+
+ cb = op->o_tmpalloc( sizeof(*cb) + sizeof(*si), op->o_tmpmemctx );
+ cb->sc_response = pcache_response;
+ cb->sc_cleanup = pcache_op_cleanup;
+ cb->sc_private = (cb+1);
+ cb->sc_writewait = 0;
+ si = cb->sc_private;
+ si->on = on;
+ si->query = query;
+ si->qtemp = qtemp;
+ si->max = cm->num_entries_limit ;
+ si->over = 0;
+ si->count = 0;
+ si->slimit = 0;
+ si->slimit_exceeded = 0;
+ si->caching_reason = PC_IGNORE;
+ if ( op->ors_slimit > 0 && op->ors_slimit < cm->num_entries_limit ) {
+ si->slimit = op->ors_slimit;
+ op->ors_slimit = cm->num_entries_limit;
+ }
+ si->head = NULL;
+ si->tail = NULL;
+ si->swap_saved_attrs = 1;
+ si->save_attrs = op->ors_attrs;
+ si->pbi = pbi;
+ if ( pbi )
+ pbi->bi_si = si;
+
+ op->ors_attrs = qtemp->t_attrs.attrs;
+
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) {
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ } else {
+ slap_callback **pcb;
+
+ /* need to move the callback at the end, in case other
+ * overlays are present, so that the final entry is
+ * actually cached */
+ cb->sc_next = NULL;
+ for ( pcb = &op->o_callback; *pcb; pcb = &(*pcb)->sc_next );
+ *pcb = cb;
+ }
+
+ } else {
+ Debug( pcache_debug, "QUERY NOT CACHEABLE\n",
+ 0, 0, 0);
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+get_attr_set(
+ AttributeName* attrs,
+ query_manager* qm,
+ int num )
+{
+ int i = 0;
+ int count = 0;
+
+ if ( attrs ) {
+ for ( ; attrs[i].an_name.bv_val; i++ ) {
+ /* only count valid attribute names
+ * (searches ignore others, this overlay does the same) */
+ if ( attrs[i].an_desc ) {
+ count++;
+ }
+ }
+ }
+
+ /* recognize default or explicit single "*" */
+ if ( ! attrs ||
+ ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_all_user_attrs ) ) )
+ {
+ count = 1;
+ attrs = slap_anlist_all_user_attributes;
+
+ /* recognize implicit (no valid attributes) or explicit single "1.1" */
+ } else if ( count == 0 ||
+ ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_no_attrs ) ) )
+ {
+ count = 0;
+ attrs = NULL;
+ }
+
+ for ( i = 0; i < num; i++ ) {
+ AttributeName *a2;
+ int found = 1;
+
+ if ( count > qm->attr_sets[i].count ) {
+ if ( qm->attr_sets[i].count &&
+ bvmatch( &qm->attr_sets[i].attrs[0].an_name, slap_bv_all_user_attrs )) {
+ break;
+ }
+ continue;
+ }
+
+ if ( !count ) {
+ if ( !qm->attr_sets[i].count ) {
+ break;
+ }
+ continue;
+ }
+
+ for ( a2 = attrs; a2->an_name.bv_val; a2++ ) {
+ if ( !a2->an_desc && !bvmatch( &a2->an_name, slap_bv_all_user_attrs ) ) continue;
+
+ if ( !an_find( qm->attr_sets[i].attrs, &a2->an_name ) ) {
+ found = 0;
+ break;
+ }
+ }
+
+ if ( found ) {
+ break;
+ }
+ }
+
+ if ( i == num ) {
+ i = -1;
+ }
+
+ return i;
+}
+
+/* Refresh a cached query:
+ * 1: Replay the query on the remote DB and merge each entry into
+ * the local DB. Remember the DNs of each remote entry.
+ * 2: Search the local DB for all entries matching this queryID.
+ * Delete any entry whose DN is not in the list from (1).
+ */
+typedef struct dnlist {
+ struct dnlist *next;
+ struct berval dn;
+ char delete;
+} dnlist;
+
+typedef struct refresh_info {
+ dnlist *ri_dns;
+ dnlist *ri_tail;
+ dnlist *ri_dels;
+ BackendDB *ri_be;
+ CachedQuery *ri_q;
+} refresh_info;
+
+static dnlist *dnl_alloc( Operation *op, struct berval *bvdn )
+{
+ dnlist *dn = op->o_tmpalloc( sizeof(dnlist) + bvdn->bv_len + 1,
+ op->o_tmpmemctx );
+ dn->dn.bv_len = bvdn->bv_len;
+ dn->dn.bv_val = (char *)(dn+1);
+ AC_MEMCPY( dn->dn.bv_val, bvdn->bv_val, dn->dn.bv_len );
+ dn->dn.bv_val[dn->dn.bv_len] = '\0';
+ return dn;
+}
+
+static int
+refresh_merge( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ refresh_info *ri = op->o_callback->sc_private;
+ Entry *e;
+ dnlist *dnl;
+ slap_callback *ocb;
+ int rc;
+
+ ocb = op->o_callback;
+ /* Find local entry, merge */
+ op->o_bd = ri->ri_be;
+ rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e );
+ if ( rc != LDAP_SUCCESS || e == NULL ) {
+ /* No local entry, just add it. FIXME: we are not checking
+ * the cache entry limit here
+ */
+ merge_entry( op, rs->sr_entry, 1, &ri->ri_q->q_uuid );
+ } else {
+ /* Entry exists, update it */
+ Entry ne;
+ Attribute *a, **b;
+ Modifications *modlist, *mods = NULL;
+ const char* text = NULL;
+ char textbuf[SLAP_TEXT_BUFLEN];
+ size_t textlen = sizeof(textbuf);
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+
+ ne = *e;
+ b = &ne.e_attrs;
+ /* Get a copy of only the attrs we requested */
+ for ( a=e->e_attrs; a; a=a->a_next ) {
+ if ( ad_inlist( a->a_desc, rs->sr_attrs )) {
+ *b = attr_alloc( a->a_desc );
+ *(*b) = *a;
+ /* The actual values still belong to e */
+ (*b)->a_flags |= SLAP_ATTR_DONT_FREE_VALS |
+ SLAP_ATTR_DONT_FREE_DATA;
+ b = &((*b)->a_next);
+ }
+ }
+ *b = NULL;
+ slap_entry2mods( rs->sr_entry, &modlist, &text, textbuf, textlen );
+ syncrepl_diff_entry( op, ne.e_attrs, rs->sr_entry->e_attrs,
+ &mods, &modlist, 0 );
+ be_entry_release_r( op, e );
+ attrs_free( ne.e_attrs );
+ slap_mods_free( modlist, 1 );
+ /* mods is NULL if there are no changes */
+ if ( mods ) {
+ SlapReply rs2 = { REP_RESULT };
+ struct berval dn = op->o_req_dn;
+ struct berval ndn = op->o_req_ndn;
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = mods;
+ op->o_req_dn = rs->sr_entry->e_name;
+ op->o_req_ndn = rs->sr_entry->e_nname;
+ op->o_callback = &cb;
+ op->o_bd->be_modify( op, &rs2 );
+ rs->sr_err = rs2.sr_err;
+ rs_assert_done( &rs2 );
+ slap_mods_free( mods, 1 );
+ op->o_req_dn = dn;
+ op->o_req_ndn = ndn;
+ }
+ }
+
+ /* Add DN to list */
+ dnl = dnl_alloc( op, &rs->sr_entry->e_nname );
+ dnl->next = NULL;
+ if ( ri->ri_tail ) {
+ ri->ri_tail->next = dnl;
+ } else {
+ ri->ri_dns = dnl;
+ }
+ ri->ri_tail = dnl;
+ op->o_callback = ocb;
+ }
+ return 0;
+}
+
+static int
+refresh_purge( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ refresh_info *ri = op->o_callback->sc_private;
+ dnlist **dn;
+ int del = 1;
+
+ /* Did the entry exist on the remote? */
+ for ( dn=&ri->ri_dns; *dn; dn = &(*dn)->next ) {
+ if ( dn_match( &(*dn)->dn, &rs->sr_entry->e_nname )) {
+ dnlist *dnext = (*dn)->next;
+ op->o_tmpfree( *dn, op->o_tmpmemctx );
+ *dn = dnext;
+ del = 0;
+ break;
+ }
+ }
+ /* No, so put it on the list to delete */
+ if ( del ) {
+ Attribute *a;
+ dnlist *dnl = dnl_alloc( op, &rs->sr_entry->e_nname );
+ dnl->next = ri->ri_dels;
+ ri->ri_dels = dnl;
+ a = attr_find( rs->sr_entry->e_attrs, ad_queryId );
+ /* If ours is the only queryId, delete entry */
+ dnl->delete = ( a->a_numvals == 1 );
+ }
+ }
+ return 0;
+}
+
+static int
+refresh_query( Operation *op, CachedQuery *query, slap_overinst *on )
+{
+ SlapReply rs = {REP_RESULT};
+ slap_callback cb = { 0 };
+ refresh_info ri = { 0 };
+ char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ];
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ Filter filter = {LDAP_FILTER_EQUALITY};
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+ dnlist *dn;
+ int rc;
+
+ ldap_pvt_thread_mutex_lock( &query->answerable_cnt_mutex );
+ query->refcnt = 0;
+ ldap_pvt_thread_mutex_unlock( &query->answerable_cnt_mutex );
+
+ cb.sc_response = refresh_merge;
+ cb.sc_private = &ri;
+
+ /* cache DB */
+ ri.ri_be = op->o_bd;
+ ri.ri_q = query;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ op->o_callback = &cb;
+ op->o_do_not_cache = 1;
+
+ op->o_req_dn = query->qbase->base;
+ op->o_req_ndn = query->qbase->base;
+ op->ors_scope = query->scope;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ op->ors_filter = query->filter;
+ filter2bv_x( op, query->filter, &op->ors_filterstr );
+ op->ors_attrs = query->qtemp->t_attrs.attrs;
+ op->ors_attrsonly = 0;
+
+ op->o_bd = on->on_info->oi_origdb;
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc ) {
+ op->o_bd = ri.ri_be;
+ goto leave;
+ }
+
+ /* Get the DNs of all entries matching this query */
+ cb.sc_response = refresh_purge;
+
+ op->o_bd = ri.ri_be;
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str),
+ "(%s=%s)", ad_queryId->ad_cname.bv_val, query->q_uuid.bv_val);
+ filter.f_ava = &ava;
+ filter.f_av_desc = ad_queryId;
+ filter.f_av_value = query->q_uuid;
+ attrs[ 0 ].an_desc = ad_queryId;
+ attrs[ 0 ].an_name = ad_queryId->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+ rs_reinit( &rs, REP_RESULT );
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc ) goto leave;
+
+ while (( dn = ri.ri_dels )) {
+ op->o_req_dn = dn->dn;
+ op->o_req_ndn = dn->dn;
+ rs_reinit( &rs, REP_RESULT );
+ if ( dn->delete ) {
+ op->o_tag = LDAP_REQ_DELETE;
+ op->o_bd->be_delete( op, &rs );
+ } else {
+ Modifications mod;
+ struct berval vals[2];
+
+ vals[0] = query->q_uuid;
+ BER_BVZERO( &vals[1] );
+ mod.sml_op = LDAP_MOD_DELETE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_queryId;
+ mod.sml_type = ad_queryId->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_numvals = 1;
+ mod.sml_next = NULL;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = &mod;
+ op->o_bd->be_modify( op, &rs );
+ }
+ ri.ri_dels = dn->next;
+ op->o_tmpfree( dn, op->o_tmpmemctx );
+ }
+
+leave:
+ /* reset our local heap, we're done with it */
+ slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, op->o_threadctx, 1 );
+ return rc;
+}
+
+static void*
+consistency_check(
+ void *ctx,
+ void *arg )
+{
+ struct re_s *rtask = arg;
+ slap_overinst *on = rtask->arg;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+
+ CachedQuery *query, *qprev;
+ int return_val, pause = PCACHE_CC_PAUSED;
+ QueryTemplate *templ;
+
+ /* Don't expire anything when we're offline */
+ if ( cm->cc_paused & PCACHE_CC_OFFLINE ) {
+ pause = PCACHE_CC_OFFLINE;
+ goto leave;
+ }
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ op->o_bd = &cm->db;
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+
+ cm->cc_arg = arg;
+
+ for (templ = qm->templates; templ; templ=templ->qmnext) {
+ time_t ttl;
+ if ( !templ->query_last ) continue;
+ pause = 0;
+ op->o_time = slap_get_time();
+ if ( !templ->ttr ) {
+ ttl = templ->ttl;
+ if ( templ->negttl && templ->negttl < ttl )
+ ttl = templ->negttl;
+ if ( templ->limitttl && templ->limitttl < ttl )
+ ttl = templ->limitttl;
+ /* The oldest timestamp that needs expiration checking */
+ ttl += op->o_time;
+ }
+
+ for ( query=templ->query_last; query; query=qprev ) {
+ qprev = query->prev;
+ if ( query->refresh_time && query->refresh_time < op->o_time ) {
+ /* A refresh will extend the expiry if the query has been
+ * referenced, but not if it's unreferenced. If the
+ * expiration has been hit, then skip the refresh since
+ * we're just going to discard the result anyway.
+ */
+ if ( query->refcnt )
+ query->expiry_time = op->o_time + templ->ttl;
+ if ( query->expiry_time > op->o_time ) {
+ refresh_query( op, query, on );
+ continue;
+ }
+ }
+
+ if (query->expiry_time < op->o_time) {
+ int rem = 0;
+ Debug( pcache_debug, "Lock CR index = %p\n",
+ (void *) templ, 0, 0 );
+ ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock);
+ if ( query == templ->query_last ) {
+ rem = 1;
+ remove_from_template(query, templ);
+ Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n",
+ (void *) templ, templ->no_of_queries, 0 );
+ Debug( pcache_debug, "Unlock CR index = %p\n",
+ (void *) templ, 0, 0 );
+ }
+ if ( !rem ) {
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+ continue;
+ }
+ ldap_pvt_thread_mutex_lock(&qm->lru_mutex);
+ remove_query(qm, query);
+ ldap_pvt_thread_mutex_unlock(&qm->lru_mutex);
+ if ( BER_BVISNULL( &query->q_uuid ))
+ return_val = 0;
+ else
+ return_val = remove_query_data(op, &query->q_uuid);
+ Debug( pcache_debug, "STALE QUERY REMOVED, SIZE=%d\n",
+ return_val, 0, 0 );
+ ldap_pvt_thread_mutex_lock(&cm->cache_mutex);
+ cm->cur_entries -= return_val;
+ cm->num_cached_queries--;
+ Debug( pcache_debug, "STORED QUERIES = %lu\n",
+ cm->num_cached_queries, 0, 0 );
+ ldap_pvt_thread_mutex_unlock(&cm->cache_mutex);
+ Debug( pcache_debug,
+ "STALE QUERY REMOVED, CACHE ="
+ "%d entries\n",
+ cm->cur_entries, 0, 0 );
+ ldap_pvt_thread_rdwr_wlock( &query->rwlock );
+ if ( query->bind_refcnt-- ) {
+ rem = 0;
+ } else {
+ rem = 1;
+ }
+ ldap_pvt_thread_rdwr_wunlock( &query->rwlock );
+ if ( rem ) free_query(query);
+ ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock);
+ } else if ( !templ->ttr && query->expiry_time > ttl ) {
+ /* We don't need to check for refreshes, and this
+ * query's expiry is too new, and all subsequent queries
+ * will be newer yet. So stop looking.
+ *
+ * If we have refreshes, then we always have to walk the
+ * entire query list.
+ */
+ break;
+ }
+ }
+ }
+
+leave:
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, rtask );
+ }
+ /* If there were no queries, defer processing for a while */
+ if ( cm->cc_paused != pause )
+ cm->cc_paused = pause;
+ ldap_pvt_runqueue_resched( &slapd_rq, rtask, pause );
+
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
+
+#define MAX_ATTR_SETS 500
+
+enum {
+ PC_MAIN = 1,
+ PC_ATTR,
+ PC_TEMP,
+ PC_RESP,
+ PC_QUERIES,
+ PC_OFFLINE,
+ PC_BIND,
+ PC_PRIVATE_DB
+};
+
+static ConfigDriver pc_cf_gen;
+static ConfigLDAPadd pc_ldadd;
+static ConfigCfAdd pc_cfadd;
+
+static ConfigTable pccfg[] = {
+ { "pcache", "backend> <max_entries> <numattrsets> <entry limit> "
+ "<cycle_time",
+ 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
+ "( OLcfgOvAt:2.1 NAME ( 'olcPcache' 'olcProxyCache' ) "
+ "DESC 'Proxy Cache basic parameters' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "pcacheAttrset", "index> <attributes...",
+ 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
+ "( OLcfgOvAt:2.2 NAME ( 'olcPcacheAttrset' 'olcProxyAttrset' ) "
+ "DESC 'A set of attributes to cache' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcacheTemplate", "filter> <attrset-index> <TTL> <negTTL> "
+ "<limitTTL> <TTR",
+ 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+ "( OLcfgOvAt:2.3 NAME ( 'olcPcacheTemplate' 'olcProxyCacheTemplate' ) "
+ "DESC 'Filter template, attrset, cache TTL, "
+ "optional negative TTL, optional sizelimit TTL, "
+ "optional TTR' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcachePosition", "head|tail(default)",
+ 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
+ "( OLcfgOvAt:2.4 NAME 'olcPcachePosition' "
+ "DESC 'Response callback position in overlay stack' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "pcacheMaxQueries", "queries",
+ 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
+ "( OLcfgOvAt:2.5 NAME ( 'olcPcacheMaxQueries' 'olcProxyCacheQueries' ) "
+ "DESC 'Maximum number of queries to cache' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "pcachePersist", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
+ "( OLcfgOvAt:2.6 NAME ( 'olcPcachePersist' 'olcProxySaveQueries' ) "
+ "DESC 'Save cached queries for hot restart' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheValidate", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+ "( OLcfgOvAt:2.7 NAME ( 'olcPcacheValidate' 'olcProxyCheckCacheability' ) "
+ "DESC 'Check whether the results of a query are cacheable, e.g. for schema issues' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheOffline", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|PC_OFFLINE, pc_cf_gen,
+ "( OLcfgOvAt:2.8 NAME 'olcPcacheOffline' "
+ "DESC 'Set cache to offline mode and disable expiration' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "pcacheBind", "filter> <attrset-index> <TTR> <scope> <base",
+ 6, 6, 0, ARG_MAGIC|PC_BIND, pc_cf_gen,
+ "( OLcfgOvAt:2.9 NAME 'olcPcacheBind' "
+ "DESC 'Parameters for caching Binds' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "pcache-", "private database args",
+ 1, 0, STRLENOF("pcache-"), ARG_MAGIC|PC_PRIVATE_DB, pc_cf_gen,
+ NULL, NULL, NULL },
+
+ /* Legacy keywords */
+ { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> "
+ "<cycle_time",
+ 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxyattrset", "index> <attributes...",
+ 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL",
+ 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "response-callback", "head|tail(default)",
+ 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxyCacheQueries", "queries",
+ 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen,
+ NULL, NULL, NULL },
+ { "proxySaveQueries", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries),
+ NULL, NULL, NULL },
+ { "proxyCheckCacheability", "TRUE|FALSE",
+ 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability),
+ NULL, NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs pcocs[] = {
+ { "( OLcfgOvOc:2.1 "
+ "NAME 'olcPcacheConfig' "
+ "DESC 'ProxyCache configuration' "
+ "SUP olcOverlayConfig "
+ "MUST ( olcPcache $ olcPcacheAttrset $ olcPcacheTemplate ) "
+ "MAY ( olcPcachePosition $ olcPcacheMaxQueries $ olcPcachePersist $ "
+ "olcPcacheValidate $ olcPcacheOffline $ olcPcacheBind ) )",
+ Cft_Overlay, pccfg, NULL, pc_cfadd },
+ { "( OLcfgOvOc:2.2 "
+ "NAME 'olcPcacheDatabase' "
+ "DESC 'Cache database configuration' "
+ "AUXILIARY )", Cft_Misc, olcDatabaseDummy, pc_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int pcache_db_open2( slap_overinst *on, ConfigReply *cr );
+
+static int
+pc_ldadd_cleanup( ConfigArgs *c )
+{
+ slap_overinst *on = c->ca_private;
+ return pcache_db_open2( on, &c->reply );
+}
+
+static int
+pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ cache_manager *cm;
+
+ if ( p->ce_type != Cft_Overlay || !p->ce_bi ||
+ p->ce_bi->bi_cf_ocs != pcocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ on = (slap_overinst *)p->ce_bi;
+ cm = on->on_bi.bi_private;
+ ca->be = &cm->db;
+ /* Defer open if this is an LDAPadd */
+ if ( CONFIG_ONLINE_ADD( ca ))
+ ca->cleanup = pc_ldadd_cleanup;
+ else
+ cm->defer_db_open = 0;
+ ca->ca_private = on;
+ return LDAP_SUCCESS;
+}
+
+static int
+pc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ CfEntryInfo *pe = p->e_private;
+ slap_overinst *on = (slap_overinst *)pe->ce_bi;
+ cache_manager *cm = on->on_bi.bi_private;
+ struct berval bv;
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase=" SLAP_X_ORDERED_FMT "%s",
+ 0, cm->db.bd_info->bi_type );
+ if ( bv.bv_len >= sizeof( ca->cr_msg ) ) {
+ return -1;
+ }
+ bv.bv_val = ca->cr_msg;
+ ca->be = &cm->db;
+ cm->defer_db_open = 0;
+
+ /* We can only create this entry if the database is table-driven
+ */
+ if ( cm->db.bd_info->bi_cf_ocs )
+ config_build_entry( op, rs, pe, ca, &bv, cm->db.bd_info->bi_cf_ocs,
+ &pcocs[1] );
+
+ return 0;
+}
+
+static int
+pc_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ cache_manager* cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ QueryTemplate* temp;
+ AttributeName* attr_name;
+ AttributeName* attrarray;
+ const char* text=NULL;
+ int i, num, rc = 0;
+ char *ptr;
+ unsigned long t;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv;
+ switch( c->type ) {
+ case PC_MAIN:
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s %d %d %d %ld",
+ cm->db.bd_info->bi_type, cm->max_entries, cm->numattrsets,
+ cm->num_entries_limit, cm->cc_period );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ case PC_ATTR:
+ for (i=0; i<cm->numattrsets; i++) {
+ if ( !qm->attr_sets[i].count ) continue;
+
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), "%d", i );
+
+ /* count the attr length */
+ for ( attr_name = qm->attr_sets[i].attrs;
+ attr_name->an_name.bv_val; attr_name++ )
+ {
+ bv.bv_len += attr_name->an_name.bv_len + 1;
+ if ( attr_name->an_desc &&
+ ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) {
+ bv.bv_len += STRLENOF("undef:");
+ }
+ }
+
+ bv.bv_val = ch_malloc( bv.bv_len+1 );
+ ptr = lutil_strcopy( bv.bv_val, c->cr_msg );
+ for ( attr_name = qm->attr_sets[i].attrs;
+ attr_name->an_name.bv_val; attr_name++ ) {
+ *ptr++ = ' ';
+ if ( attr_name->an_desc &&
+ ( attr_name->an_desc->ad_flags & SLAP_DESC_TEMPORARY ) ) {
+ ptr = lutil_strcopy( ptr, "undef:" );
+ }
+ ptr = lutil_strcopy( ptr, attr_name->an_name.bv_val );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_TEMP:
+ for (temp=qm->templates; temp; temp=temp->qmnext) {
+ /* HEADS-UP: always print all;
+ * if optional == 0, ignore */
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ " %d %ld %ld %ld %ld",
+ temp->attr_set_index,
+ temp->ttl,
+ temp->negttl,
+ temp->limitttl,
+ temp->ttr );
+ bv.bv_len += temp->querystr.bv_len + 2;
+ bv.bv_val = ch_malloc( bv.bv_len+1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->querystr.bv_val );
+ *ptr++ = '"';
+ strcpy( ptr, c->cr_msg );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_BIND:
+ for (temp=qm->templates; temp; temp=temp->qmnext) {
+ if ( !temp->bindttr ) continue;
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ " %d %ld %s ",
+ temp->attr_set_index,
+ temp->bindttr,
+ ldap_pvt_scope2str( temp->bindscope ));
+ bv.bv_len += temp->bindbase.bv_len + temp->bindftemp.bv_len + 4;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->bindftemp.bv_val );
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, c->cr_msg );
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, temp->bindbase.bv_val );
+ *ptr++ = '"';
+ *ptr = '\0';
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ if ( !c->rvalue_vals )
+ rc = 1;
+ break;
+ case PC_RESP:
+ if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) {
+ BER_BVSTR( &bv, "head" );
+ } else {
+ BER_BVSTR( &bv, "tail" );
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ case PC_QUERIES:
+ c->value_int = cm->max_queries;
+ break;
+ case PC_OFFLINE:
+ c->value_int = (cm->cc_paused & PCACHE_CC_OFFLINE) != 0;
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ rc = 1;
+ switch( c->type ) {
+ case PC_ATTR: /* FIXME */
+ case PC_TEMP:
+ case PC_BIND:
+ break;
+ case PC_OFFLINE:
+ cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+ /* If there were cached queries when we went offline,
+ * restart the checker now.
+ */
+ if ( cm->num_cached_queries ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ cm->cc_paused = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ rc = 0;
+ break;
+ }
+ return rc;
+ }
+
+ switch( c->type ) {
+ case PC_MAIN:
+ if ( cm->numattrsets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->numattrsets, c->argv[3] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse num attrsets=\"%s\" (arg #3)",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( cm->numattrsets <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( cm->numattrsets > MAX_ATTR_SETS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be <= %d", MAX_ATTR_SETS );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( !backend_db_init( c->argv[1], &cm->db, -1, NULL )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown backend type (arg #1)" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->max_entries, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse max entries=\"%s\" (arg #2)",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( cm->max_entries <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "max entries (arg #2) must be positive.\n" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( lutil_atoi( &cm->num_entries_limit, c->argv[4] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse entry limit=\"%s\" (arg #4)",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( cm->num_entries_limit <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( cm->num_entries_limit > cm->max_entries ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be less than max entries %d (arg #2)", cm->max_entries );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( lutil_parse_time( c->argv[5], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse period=\"%s\" (arg #5)",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ cm->cc_period = (time_t)t;
+ Debug( pcache_debug,
+ "Total # of attribute sets to be cached = %d.\n",
+ cm->numattrsets, 0, 0 );
+ qm->attr_sets = ( struct attr_set * )ch_calloc( cm->numattrsets,
+ sizeof( struct attr_set ) );
+ break;
+ case PC_ATTR:
+ if ( cm->numattrsets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( lutil_atoi( &num, c->argv[1] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse attrset #=\"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( num < 0 || num >= cm->numattrsets ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "attrset index %d out of bounds (must be %s%d)",
+ num, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ qm->attr_sets[num].flags |= PC_CONFIGURED;
+ if ( c->argc == 2 ) {
+ /* assume "1.1" */
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need an explicit attr in attrlist; use \"*\" to indicate all attrs" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+
+ } else if ( c->argc == 3 ) {
+ if ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) {
+ qm->attr_sets[num].count = 1;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES );
+ break;
+
+ } else if ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) {
+ qm->attr_sets[num].count = 1;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ break;
+
+ } else if ( strcmp( c->argv[2], LDAP_NO_ATTRS ) == 0 ) {
+ break;
+ }
+ /* else: fallthru */
+
+ } else if ( c->argc == 4 ) {
+ if ( ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 )
+ || ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) )
+ {
+ qm->attr_sets[num].count = 2;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 3,
+ sizeof( AttributeName ) );
+ BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES );
+ BER_BVSTR( &qm->attr_sets[num].attrs[1].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ break;
+ }
+ /* else: fallthru */
+ }
+
+ if ( c->argc > 2 ) {
+ int all_user = 0, all_op = 0;
+
+ qm->attr_sets[num].count = c->argc - 2;
+ qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( c->argc - 1,
+ sizeof( AttributeName ) );
+ attr_name = qm->attr_sets[num].attrs;
+ for ( i = 2; i < c->argc; i++ ) {
+ attr_name->an_desc = NULL;
+ if ( strcmp( c->argv[i], LDAP_NO_ATTRS ) == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid attr #%d \"%s\" in attrlist",
+ i - 2, c->argv[i] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ ch_free( qm->attr_sets[num].attrs );
+ qm->attr_sets[num].attrs = NULL;
+ qm->attr_sets[num].count = 0;
+ return 1;
+ }
+ if ( strcmp( c->argv[i], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) {
+ all_user = 1;
+ BER_BVSTR( &attr_name->an_name, LDAP_ALL_USER_ATTRIBUTES );
+ } else if ( strcmp( c->argv[i], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) {
+ all_op = 1;
+ BER_BVSTR( &attr_name->an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES );
+ } else {
+ if ( strncasecmp( c->argv[i], "undef:", STRLENOF("undef:") ) == 0 ) {
+ struct berval bv;
+ ber_str2bv( c->argv[i] + STRLENOF("undef:"), 0, 0, &bv );
+ attr_name->an_desc = slap_bv2tmp_ad( &bv, NULL );
+
+ } else if ( slap_str2ad( c->argv[i], &attr_name->an_desc, &text ) ) {
+ strcpy( c->cr_msg, text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ ch_free( qm->attr_sets[num].attrs );
+ qm->attr_sets[num].attrs = NULL;
+ qm->attr_sets[num].count = 0;
+ return 1;
+ }
+ attr_name->an_name = attr_name->an_desc->ad_cname;
+ }
+ attr_name->an_oc = NULL;
+ attr_name->an_flags = 0;
+ if ( attr_name->an_desc == slap_schema.si_ad_objectClass )
+ qm->attr_sets[num].flags |= PC_GOT_OC;
+ attr_name++;
+ BER_BVZERO( &attr_name->an_name );
+ }
+
+ /* warn if list contains both "*" and "+" */
+ if ( i > 4 && all_user && all_op ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "warning: attribute list contains \"*\" and \"+\"" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ }
+ }
+ break;
+ case PC_TEMP:
+ if ( cm->numattrsets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( lutil_atoi( &i, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template #=\"%s\"",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( i < 0 || i >= cm->numattrsets ||
+ !(qm->attr_sets[i].flags & PC_CONFIGURED )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "template index %d invalid (%s%d)",
+ i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ {
+ AttributeName *attrs;
+ int cnt;
+ cnt = template_attrs( c->argv[1], &qm->attr_sets[i], &attrs, &text );
+ if ( cnt < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s",
+ text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ temp = ch_calloc( 1, sizeof( QueryTemplate ));
+ temp->qmnext = qm->templates;
+ qm->templates = temp;
+ temp->t_attrs.attrs = attrs;
+ temp->t_attrs.count = cnt;
+ }
+ ldap_pvt_thread_rdwr_init( &temp->t_rwlock );
+ temp->query = temp->query_last = NULL;
+ if ( lutil_parse_time( c->argv[3], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template ttl=\"%s\"",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+pc_temp_fail:
+ ch_free( temp->t_attrs.attrs );
+ ch_free( temp );
+ return( 1 );
+ }
+ temp->ttl = (time_t)t;
+ temp->negttl = (time_t)0;
+ temp->limitttl = (time_t)0;
+ temp->ttr = (time_t)0;
+ switch ( c->argc ) {
+ case 7:
+ if ( lutil_parse_time( c->argv[6], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template ttr=\"%s\"",
+ c->argv[6] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ goto pc_temp_fail;
+ }
+ temp->ttr = (time_t)t;
+ /* fallthru */
+
+ case 6:
+ if ( lutil_parse_time( c->argv[5], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template sizelimit ttl=\"%s\"",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ goto pc_temp_fail;
+ }
+ temp->limitttl = (time_t)t;
+ /* fallthru */
+
+ case 5:
+ if ( lutil_parse_time( c->argv[4], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse template negative ttl=\"%s\"",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ goto pc_temp_fail;
+ }
+ temp->negttl = (time_t)t;
+ break;
+ }
+
+ temp->no_of_queries = 0;
+
+ ber_str2bv( c->argv[1], 0, 1, &temp->querystr );
+ Debug( pcache_debug, "Template:\n", 0, 0, 0 );
+ Debug( pcache_debug, " query template: %s\n",
+ temp->querystr.bv_val, 0, 0 );
+ temp->attr_set_index = i;
+ qm->attr_sets[i].flags |= PC_REFERENCED;
+ temp->qtnext = qm->attr_sets[i].templates;
+ qm->attr_sets[i].templates = temp;
+ Debug( pcache_debug, " attributes: \n", 0, 0, 0 );
+ if ( ( attrarray = qm->attr_sets[i].attrs ) != NULL ) {
+ for ( i=0; attrarray[i].an_name.bv_val; i++ )
+ Debug( pcache_debug, "\t%s\n",
+ attrarray[i].an_name.bv_val, 0, 0 );
+ }
+ break;
+ case PC_BIND:
+ if ( !qm->templates ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcacheTemplate\" directive not provided yet" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ if ( lutil_atoi( &i, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse Bind index #=\"%s\"",
+ c->argv[2] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( i < 0 || i >= cm->numattrsets ||
+ !(qm->attr_sets[i].flags & PC_CONFIGURED )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind index %d invalid (%s%d)",
+ i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ { struct berval bv, tempbv;
+ AttributeDescription **descs;
+ int ndescs;
+ ber_str2bv( c->argv[1], 0, 0, &bv );
+ ndescs = ftemp_attrs( &bv, &tempbv, &descs, &text );
+ if ( ndescs < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s",
+ text );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ for ( temp = qm->templates; temp; temp=temp->qmnext ) {
+ if ( temp->attr_set_index == i && bvmatch( &tempbv,
+ &temp->querystr ))
+ break;
+ }
+ ch_free( tempbv.bv_val );
+ if ( !temp ) {
+ ch_free( descs );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind template %s %d invalid",
+ c->argv[1], i );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ ber_dupbv( &temp->bindftemp, &bv );
+ temp->bindfattrs = descs;
+ temp->bindnattrs = ndescs;
+ }
+ if ( lutil_parse_time( c->argv[3], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bind ttr=\"%s\"",
+ c->argv[3] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+pc_bind_fail:
+ ch_free( temp->bindfattrs );
+ temp->bindfattrs = NULL;
+ ch_free( temp->bindftemp.bv_val );
+ BER_BVZERO( &temp->bindftemp );
+ return( 1 );
+ }
+ num = ldap_pvt_str2scope( c->argv[4] );
+ if ( num < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bind scope=\"%s\"",
+ c->argv[4] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ goto pc_bind_fail;
+ }
+ {
+ struct berval dn, ndn;
+ ber_str2bv( c->argv[5], 0, 0, &dn );
+ rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid bind baseDN=\"%s\"",
+ c->argv[5] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ goto pc_bind_fail;
+ }
+ if ( temp->bindbase.bv_val )
+ ch_free( temp->bindbase.bv_val );
+ temp->bindbase = ndn;
+ }
+ {
+ /* convert the template into dummy filter */
+ struct berval bv;
+ char *eq = temp->bindftemp.bv_val, *e2;
+ Filter *f;
+ i = 0;
+ while ((eq = strchr(eq, '=' ))) {
+ eq++;
+ if ( eq[0] == ')' )
+ i++;
+ }
+ bv.bv_len = temp->bindftemp.bv_len + i;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ for ( e2 = bv.bv_val, eq = temp->bindftemp.bv_val;
+ *eq; eq++ ) {
+ if ( *eq == '=' ) {
+ *e2++ = '=';
+ if ( eq[1] == ')' )
+ *e2++ = '*';
+ } else {
+ *e2++ = *eq;
+ }
+ }
+ *e2 = '\0';
+ f = str2filter( bv.bv_val );
+ if ( !f ) {
+ ch_free( bv.bv_val );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse bindfilter=\"%s\"", bv.bv_val );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ ch_free( temp->bindbase.bv_val );
+ BER_BVZERO( &temp->bindbase );
+ goto pc_bind_fail;
+ }
+ if ( temp->bindfilter )
+ filter_free( temp->bindfilter );
+ if ( temp->bindfilterstr.bv_val )
+ ch_free( temp->bindfilterstr.bv_val );
+ temp->bindfilterstr = bv;
+ temp->bindfilter = f;
+ }
+ temp->bindttr = (time_t)t;
+ temp->bindscope = num;
+ cm->cache_binds = 1;
+ break;
+
+ case PC_RESP:
+ if ( strcasecmp( c->argv[1], "head" ) == 0 ) {
+ cm->response_cb = PCACHE_RESPONSE_CB_HEAD;
+
+ } else if ( strcasecmp( c->argv[1], "tail" ) == 0 ) {
+ cm->response_cb = PCACHE_RESPONSE_CB_TAIL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown specifier" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ break;
+ case PC_QUERIES:
+ if ( c->value_int <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "max queries must be positive" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+ cm->max_queries = c->value_int;
+ break;
+ case PC_OFFLINE:
+ if ( c->value_int )
+ cm->cc_paused |= PCACHE_CC_OFFLINE;
+ else
+ cm->cc_paused &= ~PCACHE_CC_OFFLINE;
+ break;
+ case PC_PRIVATE_DB:
+ if ( cm->db.be_private == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "private database must be defined before setting database specific options" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return( 1 );
+ }
+
+ if ( cm->db.bd_info->bi_cf_ocs ) {
+ ConfigTable *ct;
+ ConfigArgs c2 = *c;
+ char *argv0 = c->argv[ 0 ];
+
+ c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ];
+
+ ct = config_find_keyword( cm->db.bd_info->bi_cf_ocs->co_table, c );
+ if ( ct == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "private database does not recognize specific option '%s'",
+ c->argv[ 0 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ rc = 1;
+
+ } else {
+ c->table = cm->db.bd_info->bi_cf_ocs->co_type;
+ c->be = &cm->db;
+ c->bi = c->be->bd_info;
+
+ rc = config_add_vals( ct, c );
+
+ c->bi = c2.bi;
+ c->be = c2.be;
+ c->table = c2.table;
+ }
+
+ c->argv[ 0 ] = argv0;
+
+ } else if ( cm->db.be_config != NULL ) {
+ char *argv0 = c->argv[ 0 ];
+
+ c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ];
+ rc = cm->db.be_config( &cm->db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[ 0 ] = argv0;
+
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "no means to set private database specific options" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg, 0 );
+ return 1;
+ }
+ break;
+ default:
+ rc = SLAP_CONF_UNKNOWN;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+pcache_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager* cm = on->on_bi.bi_private;
+
+ /* Something for the cache database? */
+ if ( cm->db.bd_info && cm->db.bd_info->bi_db_config )
+ return cm->db.bd_info->bi_db_config( &cm->db, fname, lineno,
+ argc, argv );
+ return SLAP_CONF_UNKNOWN;
+}
+
+static int
+pcache_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm;
+ query_manager *qm;
+
+ cm = (cache_manager *)ch_malloc(sizeof(cache_manager));
+ on->on_bi.bi_private = cm;
+
+ qm = (query_manager*)ch_malloc(sizeof(query_manager));
+
+ cm->db = *be;
+ cm->db.bd_info = NULL;
+ SLAP_DBFLAGS(&cm->db) |= SLAP_DBFLAG_NO_SCHEMA_CHECK;
+ cm->db.be_private = NULL;
+ cm->db.bd_self = &cm->db;
+ cm->qm = qm;
+ cm->numattrsets = 0;
+ cm->num_entries_limit = 5;
+ cm->num_cached_queries = 0;
+ cm->max_entries = 0;
+ cm->cur_entries = 0;
+ cm->max_queries = 10000;
+ cm->save_queries = 0;
+ cm->check_cacheability = 0;
+ cm->response_cb = PCACHE_RESPONSE_CB_TAIL;
+ cm->defer_db_open = 1;
+ cm->cache_binds = 0;
+ cm->cc_period = 1000;
+ cm->cc_paused = 0;
+ cm->cc_arg = NULL;
+#ifdef PCACHE_MONITOR
+ cm->monitor_cb = NULL;
+#endif /* PCACHE_MONITOR */
+
+ qm->attr_sets = NULL;
+ qm->templates = NULL;
+ qm->lru_top = NULL;
+ qm->lru_bottom = NULL;
+
+ qm->qcfunc = query_containment;
+ qm->crfunc = cache_replacement;
+ qm->addfunc = add_query;
+ ldap_pvt_thread_mutex_init(&qm->lru_mutex);
+
+ ldap_pvt_thread_mutex_init(&cm->cache_mutex);
+
+#ifndef PCACHE_MONITOR
+ return 0;
+#else /* PCACHE_MONITOR */
+ return pcache_monitor_db_init( be );
+#endif /* PCACHE_MONITOR */
+}
+
+static int
+pcache_cachedquery_open_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ Attribute *a;
+
+ a = attr_find( rs->sr_entry->e_attrs, ad_cachedQueryURL );
+ if ( a != NULL ) {
+ BerVarray *valsp;
+
+ assert( a->a_nvals != NULL );
+
+ valsp = op->o_callback->sc_private;
+ assert( *valsp == NULL );
+
+ ber_bvarray_dup_x( valsp, a->a_nvals, op->o_tmpmemctx );
+ }
+ }
+
+ return 0;
+}
+
+static int
+pcache_cachedquery_count_cb( Operation *op, SlapReply *rs )
+{
+ assert( op->o_tag == LDAP_REQ_SEARCH );
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ int *countp = (int *)op->o_callback->sc_private;
+
+ (*countp)++;
+ }
+
+ return 0;
+}
+
+static int
+pcache_db_open2(
+ slap_overinst *on,
+ ConfigReply *cr )
+{
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ int rc;
+
+ rc = backend_startup_one( &cm->db, cr );
+ if ( rc == 0 ) {
+ cm->defer_db_open = 0;
+ }
+
+ /* There is no runqueue in TOOL mode */
+ if (( slapMode & SLAP_SERVER_MODE ) && rc == 0 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_insert( &slapd_rq, cm->cc_period,
+ consistency_check, on,
+ "pcache_consistency", cm->db.be_suffix[0].bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ /* Cached database must have the rootdn */
+ if ( BER_BVISNULL( &cm->db.be_rootndn )
+ || BER_BVISEMPTY( &cm->db.be_rootndn ) )
+ {
+ Debug( LDAP_DEBUG_ANY, "pcache_db_open(): "
+ "underlying database of type \"%s\"\n"
+ " serving naming context \"%s\"\n"
+ " has no \"rootdn\", required by \"pcache\".\n",
+ on->on_info->oi_orig->bi_type,
+ cm->db.be_suffix[0].bv_val, 0 );
+ return 1;
+ }
+
+ if ( cm->save_queries ) {
+ void *thrctx = ldap_pvt_thread_pool_context();
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { 0 };
+ SlapReply rs = { REP_RESULT };
+ BerVarray vals = NULL;
+ Filter f = { 0 }, f2 = { 0 };
+ AttributeAssertion ava = ATTRIBUTEASSERTION_INIT;
+ AttributeName attrs[ 2 ] = {{{ 0 }}};
+
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ op->o_bd = &cm->db;
+
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->o_protocol = LDAP_VERSION3;
+ cb.sc_response = pcache_cachedquery_open_cb;
+ cb.sc_private = &vals;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+ op->o_req_dn = cm->db.be_suffix[ 0 ];
+ op->o_req_ndn = cm->db.be_nsuffix[ 0 ];
+
+ op->ors_scope = LDAP_SCOPE_BASE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_slimit = 1;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+ op->ors_limit = NULL;
+ ber_str2bv( "(pcacheQueryURL=*)", 0, 0, &op->ors_filterstr );
+ f.f_choice = LDAP_FILTER_PRESENT;
+ f.f_desc = ad_cachedQueryURL;
+ op->ors_filter = &f;
+ attrs[ 0 ].an_desc = ad_cachedQueryURL;
+ attrs[ 0 ].an_name = ad_cachedQueryURL->ad_cname;
+ op->ors_attrs = attrs;
+ op->ors_attrsonly = 0;
+
+ rc = op->o_bd->be_search( op, &rs );
+ if ( rc == LDAP_SUCCESS && vals != NULL ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) {
+ if ( url2query( vals[ i ].bv_val, op, qm ) == 0 ) {
+ cm->num_cached_queries++;
+ }
+ }
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ /* count cached entries */
+ f.f_choice = LDAP_FILTER_NOT;
+ f.f_not = &f2;
+ f2.f_choice = LDAP_FILTER_EQUALITY;
+ f2.f_ava = &ava;
+ f2.f_av_desc = slap_schema.si_ad_objectClass;
+ BER_BVSTR( &f2.f_av_value, "glue" );
+ ber_str2bv( "(!(objectClass=glue))", 0, 0, &op->ors_filterstr );
+
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ rs_reinit( &rs, REP_RESULT );
+ op->o_callback->sc_response = pcache_cachedquery_count_cb;
+ op->o_callback->sc_private = &rs.sr_nentries;
+
+ rc = op->o_bd->be_search( op, &rs );
+
+ cm->cur_entries = rs.sr_nentries;
+
+ /* ignore errors */
+ rc = 0;
+ }
+ }
+ return rc;
+}
+
+static int
+pcache_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager* qm = cm->qm;
+ int i, ncf = 0, rf = 0, nrf = 0, rc = 0;
+
+ /* check attr sets */
+ for ( i = 0; i < cm->numattrsets; i++) {
+ if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) {
+ if ( qm->attr_sets[i].flags & PC_REFERENCED ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i, 0, 0 );
+ rf++;
+
+ } else {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i, 0, 0 );
+ }
+ ncf++;
+
+ } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i, 0, 0 );
+ nrf++;
+ }
+ }
+
+ if ( ncf || rf || nrf ) {
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf, 0, 0 );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf, 0, 0 );
+ Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf, 0, 0 );
+
+ if ( rf > 0 ) {
+ return 1;
+ }
+ }
+
+ /* need to inherit something from the original database... */
+ cm->db.be_def_limit = be->be_def_limit;
+ cm->db.be_limits = be->be_limits;
+ cm->db.be_acl = be->be_acl;
+ cm->db.be_dfltaccess = be->be_dfltaccess;
+
+ if ( SLAP_DBMONITORING( be ) ) {
+ SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING;
+
+ } else {
+ SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING;
+ }
+
+ if ( !cm->defer_db_open ) {
+ rc = pcache_db_open2( on, cr );
+ }
+
+#ifdef PCACHE_MONITOR
+ if ( rc == LDAP_SUCCESS ) {
+ rc = pcache_monitor_db_open( be );
+ }
+#endif /* PCACHE_MONITOR */
+
+ return rc;
+}
+
+static void
+pcache_free_qbase( void *v )
+{
+ Qbase *qb = v;
+ int i;
+
+ for (i=0; i<3; i++)
+ tavl_free( qb->scopes[i], NULL );
+ ch_free( qb );
+}
+
+static int
+pcache_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ QueryTemplate *tm;
+ int rc = 0;
+
+ /* stop the thread ... */
+ if ( cm->cc_arg ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, cm->cc_arg ) ) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, cm->cc_arg );
+ }
+ ldap_pvt_runqueue_remove( &slapd_rq, cm->cc_arg );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ cm->cc_arg = NULL;
+ }
+
+ if ( cm->save_queries ) {
+ CachedQuery *qc;
+ BerVarray vals = NULL;
+
+ void *thrctx;
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { 0 };
+
+ SlapReply rs = { REP_RESULT };
+ Modifications mod = {{ 0 }};
+
+ thrctx = ldap_pvt_thread_pool_context();
+
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+
+ mod.sml_numvals = 0;
+ if ( qm->templates != NULL ) {
+ for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) {
+ for ( qc = tm->query; qc; qc = qc->next ) {
+ struct berval bv;
+
+ if ( query2url( op, qc, &bv, 0 ) == 0 ) {
+ ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx );
+ mod.sml_numvals++;
+ }
+ }
+ }
+ }
+
+ op->o_bd = &cm->db;
+ op->o_dn = cm->db.be_rootdn;
+ op->o_ndn = cm->db.be_rootndn;
+
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->o_protocol = LDAP_VERSION3;
+ cb.sc_response = slap_null_cb;
+ op->o_callback = &cb;
+ op->o_time = slap_get_time();
+ op->o_do_not_cache = 1;
+ op->o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op->o_req_dn = op->o_bd->be_suffix[0];
+ op->o_req_ndn = op->o_bd->be_nsuffix[0];
+
+ mod.sml_op = LDAP_MOD_REPLACE;
+ mod.sml_flags = 0;
+ mod.sml_desc = ad_cachedQueryURL;
+ mod.sml_type = ad_cachedQueryURL->ad_cname;
+ mod.sml_values = vals;
+ mod.sml_nvalues = NULL;
+ mod.sml_next = NULL;
+ Debug( pcache_debug,
+ "%sSETTING CACHED QUERY URLS\n",
+ vals == NULL ? "RE" : "", 0, 0 );
+
+ op->orm_modlist = &mod;
+
+ op->o_bd->be_modify( op, &rs );
+
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+
+ /* cleanup stuff inherited from the original database... */
+ cm->db.be_limits = NULL;
+ cm->db.be_acl = NULL;
+
+ if ( cm->db.bd_info->bi_db_close ) {
+ rc = cm->db.bd_info->bi_db_close( &cm->db, NULL );
+ }
+
+#ifdef PCACHE_MONITOR
+ if ( rc == LDAP_SUCCESS ) {
+ rc = pcache_monitor_db_close( be );
+ }
+#endif /* PCACHE_MONITOR */
+
+ return rc;
+}
+
+static int
+pcache_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ query_manager *qm = cm->qm;
+ QueryTemplate *tm;
+ int i;
+
+ if ( cm->db.be_private != NULL ) {
+ backend_stopdown_one( &cm->db );
+ }
+
+ while ( (tm = qm->templates) != NULL ) {
+ CachedQuery *qc, *qn;
+ qm->templates = tm->qmnext;
+ for ( qc = tm->query; qc; qc = qn ) {
+ qn = qc->next;
+ free_query( qc );
+ }
+ avl_free( tm->qbase, pcache_free_qbase );
+ free( tm->querystr.bv_val );
+ free( tm->bindfattrs );
+ free( tm->bindftemp.bv_val );
+ free( tm->bindfilterstr.bv_val );
+ free( tm->bindbase.bv_val );
+ filter_free( tm->bindfilter );
+ ldap_pvt_thread_rdwr_destroy( &tm->t_rwlock );
+ free( tm->t_attrs.attrs );
+ free( tm );
+ }
+
+ for ( i = 0; i < cm->numattrsets; i++ ) {
+ int j;
+
+ /* Account of LDAP_NO_ATTRS */
+ if ( !qm->attr_sets[i].count ) continue;
+
+ for ( j = 0; !BER_BVISNULL( &qm->attr_sets[i].attrs[j].an_name ); j++ ) {
+ if ( qm->attr_sets[i].attrs[j].an_desc &&
+ ( qm->attr_sets[i].attrs[j].an_desc->ad_flags &
+ SLAP_DESC_TEMPORARY ) ) {
+ slap_sl_mfuncs.bmf_free( qm->attr_sets[i].attrs[j].an_desc, NULL );
+ }
+ }
+ free( qm->attr_sets[i].attrs );
+ }
+ free( qm->attr_sets );
+ qm->attr_sets = NULL;
+
+ ldap_pvt_thread_mutex_destroy( &qm->lru_mutex );
+ ldap_pvt_thread_mutex_destroy( &cm->cache_mutex );
+ free( qm );
+ free( cm );
+
+#ifdef PCACHE_MONITOR
+ pcache_monitor_db_destroy( be );
+#endif /* PCACHE_MONITOR */
+
+ return 0;
+}
+
+#ifdef PCACHE_CONTROL_PRIVDB
+/*
+ Control ::= SEQUENCE {
+ controlType LDAPOID,
+ criticality BOOLEAN DEFAULT FALSE,
+ controlValue OCTET STRING OPTIONAL }
+
+ controlType ::= 1.3.6.1.4.1.4203.666.11.9.5.1
+
+ * criticality must be TRUE; controlValue must be absent.
+ */
+static int
+parse_privdb_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "privateDB control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "privateDB control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( !ctrl->ldctl_iscritical ) {
+ rs->sr_text = "privateDB control criticality required";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_ctrlflag[ privDB_cid ] = SLAP_CONTROL_CRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static char *extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ NULL
+};
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+static struct berval pcache_exop_MODIFY_PASSWD = BER_BVC( LDAP_EXOP_MODIFY_PASSWD );
+#ifdef PCACHE_EXOP_QUERY_DELETE
+static struct berval pcache_exop_QUERY_DELETE = BER_BVC( PCACHE_EXOP_QUERY_DELETE );
+
+#define LDAP_TAG_EXOP_QUERY_DELETE_BASE ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 0)
+#define LDAP_TAG_EXOP_QUERY_DELETE_DN ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 1)
+#define LDAP_TAG_EXOP_QUERY_DELETE_UUID ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 2)
+
+/*
+ ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
+ requestName [0] LDAPOID,
+ requestValue [1] OCTET STRING OPTIONAL }
+
+ requestName ::= 1.3.6.1.4.1.4203.666.11.9.6.1
+
+ requestValue ::= SEQUENCE { CHOICE {
+ baseDN [0] LDAPDN
+ entryDN [1] LDAPDN },
+ queryID [2] OCTET STRING (SIZE(16))
+ -- constrained to UUID }
+
+ * Either baseDN or entryDN must be present, to allow database selection.
+ *
+ * 1. if baseDN and queryID are present, then the query corresponding
+ * to queryID is deleted;
+ * 2. if baseDN is present and queryID is absent, then all queries
+ * are deleted;
+ * 3. if entryDN is present and queryID is absent, then all queries
+ * corresponding to the queryID values present in entryDN are deleted;
+ * 4. if entryDN and queryID are present, then all queries
+ * corresponding to the queryID values present in entryDN are deleted,
+ * but only if the value of queryID is contained in the entry;
+ *
+ * Currently, only 1, 3 and 4 are implemented. 2 can be obtained by either
+ * recursively deleting the database (ldapdelete -r) with PRIVDB control,
+ * or by removing the database files.
+
+ ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
+ COMPONENTS OF LDAPResult,
+ responseName [10] LDAPOID OPTIONAL,
+ responseValue [11] OCTET STRING OPTIONAL }
+
+ * responseName and responseValue must be absent.
+ */
+
+/*
+ * - on success, *tagp is either LDAP_TAG_EXOP_QUERY_DELETE_BASE
+ * or LDAP_TAG_EXOP_QUERY_DELETE_DN.
+ * - if ndn != NULL, it is set to the normalized DN in the request
+ * corresponding to either the baseDN or the entryDN, according
+ * to *tagp; memory is malloc'ed on the Operation's slab, and must
+ * be freed by the caller.
+ * - if uuid != NULL, it is set to point to the normalized UUID;
+ * memory is malloc'ed on the Operation's slab, and must
+ * be freed by the caller.
+ */
+static int
+pcache_parse_query_delete(
+ struct berval *in,
+ ber_tag_t *tagp,
+ struct berval *ndn,
+ struct berval *uuid,
+ const char **text,
+ void *ctx )
+{
+ int rc = LDAP_SUCCESS;
+ ber_tag_t tag;
+ ber_len_t len = -1;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval reqdata = BER_BVNULL;
+
+ *text = NULL;
+
+ if ( ndn ) {
+ BER_BVZERO( ndn );
+ }
+
+ if ( uuid ) {
+ BER_BVZERO( uuid );
+ }
+
+ if ( in == NULL || in->bv_len == 0 ) {
+ *text = "empty request data field in queryDelete exop";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_dupbv_x( &reqdata, in, ctx );
+
+ /* ber_init2 uses reqdata directly, doesn't allocate new buffers */
+ ber_init2( ber, &reqdata, 0 );
+
+ tag = ber_scanf( ber, "{" /*}*/ );
+
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: decoding error.\n",
+ 0, 0, 0 );
+ goto decoding_error;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE
+ || tag == LDAP_TAG_EXOP_QUERY_DELETE_DN )
+ {
+ *tagp = tag;
+
+ if ( ndn != NULL ) {
+ struct berval dn;
+
+ tag = ber_scanf( ber, "m", &dn );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: DN parse failed.\n",
+ 0, 0, 0 );
+ goto decoding_error;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx );
+ if ( rc != LDAP_SUCCESS ) {
+ *text = "invalid DN in queryDelete exop request data";
+ goto done;
+ }
+
+ } else {
+ tag = ber_scanf( ber, "x" /* "m" */ );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_UUID ) {
+ if ( uuid != NULL ) {
+ struct berval bv;
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+
+ tag = ber_scanf( ber, "m", &bv );
+ if ( tag == LBER_ERROR ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: UUID parse failed.\n",
+ 0, 0, 0 );
+ goto decoding_error;
+ }
+
+ if ( bv.bv_len != 16 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: invalid UUID length %lu.\n",
+ (unsigned long)bv.bv_len, 0, 0 );
+ goto decoding_error;
+ }
+
+ rc = lutil_uuidstr_from_normalized(
+ bv.bv_val, bv.bv_len,
+ uuidbuf, sizeof( uuidbuf ) );
+ if ( rc == -1 ) {
+ goto decoding_error;
+ }
+ ber_str2bv( uuidbuf, rc, 1, uuid );
+ rc = LDAP_SUCCESS;
+
+ } else {
+ tag = ber_skip_tag( ber, &len );
+ if ( tag == LBER_DEFAULT ) {
+ goto decoding_error;
+ }
+
+ if ( len != 16 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: invalid UUID length %lu.\n",
+ (unsigned long)len, 0, 0 );
+ goto decoding_error;
+ }
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag != LBER_DEFAULT || len != 0 ) {
+decoding_error:;
+ Debug( LDAP_DEBUG_TRACE,
+ "pcache_parse_query_delete: decoding error\n",
+ 0, 0, 0 );
+ rc = LDAP_PROTOCOL_ERROR;
+ *text = "queryDelete data decoding error";
+
+done:;
+ if ( ndn && !BER_BVISNULL( ndn ) ) {
+ slap_sl_free( ndn->bv_val, ctx );
+ BER_BVZERO( ndn );
+ }
+
+ if ( uuid && !BER_BVISNULL( uuid ) ) {
+ slap_sl_free( uuid->bv_val, ctx );
+ BER_BVZERO( uuid );
+ }
+ }
+
+ if ( !BER_BVISNULL( &reqdata ) ) {
+ ber_memfree_x( reqdata.bv_val, ctx );
+ }
+
+ return rc;
+}
+
+static int
+pcache_exop_query_delete(
+ Operation *op,
+ SlapReply *rs )
+{
+ BackendDB *bd = op->o_bd;
+
+ struct berval uuid = BER_BVNULL,
+ *uuidp = NULL;
+ char buf[ SLAP_TEXT_BUFLEN ];
+ unsigned len;
+ ber_tag_t tag = LBER_DEFAULT;
+
+ if ( LogTest( LDAP_DEBUG_STATS ) ) {
+ uuidp = &uuid;
+ }
+
+ rs->sr_err = pcache_parse_query_delete( op->ore_reqdata,
+ &tag, &op->o_req_ndn, uuidp,
+ &rs->sr_text, op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ if ( LogTest( LDAP_DEBUG_STATS ) ) {
+ assert( !BER_BVISNULL( &op->o_req_ndn ) );
+ len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val );
+
+ if ( !BER_BVISNULL( &uuid ) && len < sizeof( buf ) ) {
+ snprintf( &buf[ len ], sizeof( buf ) - len, " pcacheQueryId=\"%s\"", uuid.bv_val );
+ }
+
+ Debug( LDAP_DEBUG_STATS, "%s QUERY DELETE%s\n",
+ op->o_log_prefix, buf, 0 );
+ }
+ op->o_req_dn = op->o_req_ndn;
+
+ op->o_bd = select_backend( &op->o_req_ndn, 0 );
+ if ( op->o_bd == NULL ) {
+ send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT,
+ "no global superior knowledge" );
+ }
+ rs->sr_err = backend_check_restrictions( op, rs,
+ (struct berval *)&pcache_exop_QUERY_DELETE );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ if ( op->o_bd->be_extended == NULL ) {
+ send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "backend does not support extended operations" );
+ goto done;
+ }
+
+ op->o_bd->be_extended( op, rs );
+
+done:;
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &op->o_req_ndn );
+ BER_BVZERO( &op->o_req_dn );
+ }
+
+ if ( !BER_BVISNULL( &uuid ) ) {
+ op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
+ }
+
+ op->o_bd = bd;
+
+ return rs->sr_err;
+}
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+static int
+pcache_op_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) {
+ return pcache_op_privdb( op, rs );
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+#ifdef PCACHE_EXOP_QUERY_DELETE
+ if ( bvmatch( &op->ore_reqoid, &pcache_exop_QUERY_DELETE ) ) {
+ struct berval uuid = BER_BVNULL;
+ ber_tag_t tag = LBER_DEFAULT;
+
+ rs->sr_err = pcache_parse_query_delete( op->ore_reqdata,
+ &tag, NULL, &uuid, &rs->sr_text, op->o_tmpmemctx );
+ assert( rs->sr_err == LDAP_SUCCESS );
+
+ if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) {
+ /* remove all queries related to the selected entry */
+ rs->sr_err = pcache_remove_entry_queries_from_cache( op,
+ cm, &op->o_req_ndn, &uuid );
+
+ } else if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE ) {
+ if ( !BER_BVISNULL( &uuid ) ) {
+ /* remove the selected query */
+ rs->sr_err = pcache_remove_query_from_cache( op,
+ cm, &uuid );
+
+ } else {
+ /* TODO: remove all queries */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "deletion of all queries not implemented";
+ }
+ }
+
+ op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx );
+ return rs->sr_err;
+ }
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+ /* We only care if we're configured for Bind caching */
+ if ( bvmatch( &op->ore_reqoid, &pcache_exop_MODIFY_PASSWD ) &&
+ cm->cache_binds ) {
+ /* See if the local entry exists and has a password.
+ * It's too much work to find the matching query, so
+ * we just see if there's a hashed password to update.
+ */
+ Operation op2 = *op;
+ Entry *e = NULL;
+ int rc;
+ int doit = 0;
+
+ op2.o_bd = &cm->db;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+ rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL,
+ slap_schema.si_ad_userPassword, 0, &e );
+ if ( rc == LDAP_SUCCESS && e ) {
+ /* See if a recognized password is hashed here */
+ Attribute *a = attr_find( e->e_attrs,
+ slap_schema.si_ad_userPassword );
+ if ( a && a->a_vals[0].bv_val[0] == '{' &&
+ lutil_passwd_scheme( a->a_vals[0].bv_val )) {
+ doit = 1;
+ }
+ be_entry_release_r( &op2, e );
+ }
+
+ if ( doit ) {
+ rc = overlay_op_walk( op, rs, op_extended, on->on_info,
+ on->on_next );
+ if ( rc == LDAP_SUCCESS ) {
+ req_pwdexop_s *qpw = &op->oq_pwdexop;
+
+ /* We don't care if it succeeds or not */
+ pc_setpw( &op2, &qpw->rs_new, cm );
+ }
+ return rc;
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_entry_release( Operation *op, Entry *e, int rw )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ BackendDB *db = op->o_bd;
+ int rc;
+
+ op->o_bd = &cm->db;
+ rc = be_entry_release_rw( op, e, rw );
+ op->o_bd = db;
+ return rc;
+}
+
+#ifdef PCACHE_MONITOR
+
+static int
+pcache_monitor_update(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ void *priv )
+{
+ cache_manager *cm = (cache_manager *) priv;
+ query_manager *qm = cm->qm;
+
+ CachedQuery *qc;
+ BerVarray vals = NULL;
+
+ attr_delete( &e->e_attrs, ad_cachedQueryURL );
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( ad_cachedQueryURL, rs->sr_attrs ) )
+ && qm->templates != NULL )
+ {
+ QueryTemplate *tm;
+
+ for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) {
+ for ( qc = tm->query; qc; qc = qc->next ) {
+ struct berval bv;
+
+ if ( query2url( op, qc, &bv, 1 ) == 0 ) {
+ ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx );
+ }
+ }
+ }
+
+
+ if ( vals != NULL ) {
+ attr_merge_normalize( e, ad_cachedQueryURL, vals, NULL );
+ ber_bvarray_free_x( vals, op->o_tmpmemctx );
+ }
+ }
+
+ {
+ Attribute *a;
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+
+ /* number of cached queries */
+ a = attr_find( e->e_attrs, ad_numQueries );
+ assert( a != NULL );
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", cm->num_cached_queries );
+
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvreplace( &a->a_nvals[ 0 ], &bv );
+ }
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+
+ /* number of cached entries */
+ a = attr_find( e->e_attrs, ad_numEntries );
+ assert( a != NULL );
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%d", cm->cur_entries );
+
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvreplace( &a->a_nvals[ 0 ], &bv );
+ }
+ ber_bvreplace( &a->a_vals[ 0 ], &bv );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+pcache_monitor_free(
+ Entry *e,
+ void **priv )
+{
+ struct berval values[ 2 ];
+ Modification mod = { 0 };
+
+ const char *text;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+
+ int rc;
+
+ /* NOTE: if slap_shutdown != 0, priv might have already been freed */
+ *priv = NULL;
+
+ /* Remove objectClass */
+ mod.sm_op = LDAP_MOD_DELETE;
+ mod.sm_desc = slap_schema.si_ad_objectClass;
+ mod.sm_values = values;
+ mod.sm_numvals = 1;
+ values[ 0 ] = oc_olmPCache->soc_cname;
+ BER_BVZERO( &values[ 1 ] );
+
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_cachedQueryURL;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_numQueries;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ /* remove attrs */
+ mod.sm_values = NULL;
+ mod.sm_desc = ad_numEntries;
+ mod.sm_numvals = 0;
+ rc = modify_delete_values( e, &mod, 1, &text,
+ textbuf, sizeof( textbuf ) );
+ /* don't care too much about return code... */
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * call from within pcache_initialize()
+ */
+static int
+pcache_monitor_initialize( void )
+{
+ static int pcache_monitor_initialized = 0;
+
+ if ( backend_info( "monitor" ) == NULL ) {
+ return -1;
+ }
+
+ if ( pcache_monitor_initialized++ ) {
+ return 0;
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_init( BackendDB *be )
+{
+ if ( pcache_monitor_initialize() == LDAP_SUCCESS ) {
+ SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING;
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_open( BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+ Attribute *a, *next;
+ monitor_callback_t *cb = NULL;
+ int rc = 0;
+ BackendInfo *mi;
+ monitor_extra_t *mbe;
+
+ if ( !SLAP_DBMONITORING( be ) ) {
+ return 0;
+ }
+
+ mi = backend_info( "monitor" );
+ if ( !mi || !mi->bi_extra ) {
+ SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING;
+ return 0;
+ }
+ mbe = mi->bi_extra;
+
+ /* don't bother if monitor is not configured */
+ if ( !mbe->is_configured() ) {
+ static int warning = 0;
+
+ if ( warning++ == 0 ) {
+ Debug( LDAP_DEBUG_ANY, "pcache_monitor_db_open: "
+ "monitoring disabled; "
+ "configure monitor database to enable\n",
+ 0, 0, 0 );
+ }
+
+ return 0;
+ }
+
+ /* alloc as many as required (plus 1 for objectClass) */
+ a = attrs_alloc( 1 + 2 );
+ if ( a == NULL ) {
+ rc = 1;
+ goto cleanup;
+ }
+
+ a->a_desc = slap_schema.si_ad_objectClass;
+ attr_valadd( a, &oc_olmPCache->soc_cname, NULL, 1 );
+ next = a->a_next;
+
+ {
+ struct berval bv = BER_BVC( "0" );
+
+ next->a_desc = ad_numQueries;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+
+ next->a_desc = ad_numEntries;
+ attr_valadd( next, &bv, NULL, 1 );
+ next = next->a_next;
+ }
+
+ cb = ch_calloc( sizeof( monitor_callback_t ), 1 );
+ cb->mc_update = pcache_monitor_update;
+ cb->mc_free = pcache_monitor_free;
+ cb->mc_private = (void *)cm;
+
+ /* make sure the database is registered; then add monitor attributes */
+ BER_BVZERO( &cm->monitor_ndn );
+ rc = mbe->register_overlay( be, on, &cm->monitor_ndn );
+ if ( rc == 0 ) {
+ rc = mbe->register_entry_attrs( &cm->monitor_ndn, a, cb,
+ NULL, -1, NULL);
+ }
+
+cleanup:;
+ if ( rc != 0 ) {
+ if ( cb != NULL ) {
+ ch_free( cb );
+ cb = NULL;
+ }
+
+ if ( a != NULL ) {
+ attrs_free( a );
+ a = NULL;
+ }
+ }
+
+ /* store for cleanup */
+ cm->monitor_cb = (void *)cb;
+
+ /* we don't need to keep track of the attributes, because
+ * bdb_monitor_free() takes care of everything */
+ if ( a != NULL ) {
+ attrs_free( a );
+ }
+
+ return rc;
+}
+
+static int
+pcache_monitor_db_close( BackendDB *be )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ cache_manager *cm = on->on_bi.bi_private;
+
+ if ( cm->monitor_cb != NULL ) {
+ BackendInfo *mi = backend_info( "monitor" );
+ monitor_extra_t *mbe;
+
+ if ( mi && &mi->bi_extra ) {
+ mbe = mi->bi_extra;
+ mbe->unregister_entry_callback( &cm->monitor_ndn,
+ (monitor_callback_t *)cm->monitor_cb,
+ NULL, 0, NULL );
+ }
+ }
+
+ return 0;
+}
+
+static int
+pcache_monitor_db_destroy( BackendDB *be )
+{
+ return 0;
+}
+
+#endif /* PCACHE_MONITOR */
+
+static slap_overinst pcache;
+
+static char *obsolete_names[] = {
+ "proxycache",
+ NULL
+};
+
+#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC */
+int
+pcache_initialize()
+{
+ int i, code;
+ struct berval debugbv = BER_BVC("pcache");
+ ConfigArgs c;
+ char *argv[ 4 ];
+
+ code = slap_loglevel_get( &debugbv, &pcache_debug );
+ if ( code ) {
+ return code;
+ }
+
+#ifdef PCACHE_CONTROL_PRIVDB
+ code = register_supported_control( PCACHE_CONTROL_PRIVDB,
+ SLAP_CTRL_BIND|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, extops,
+ parse_privdb_ctrl, &privDB_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: failed to register control %s (%d)\n",
+ PCACHE_CONTROL_PRIVDB, code, 0 );
+ return code;
+ }
+#endif /* PCACHE_CONTROL_PRIVDB */
+
+#ifdef PCACHE_EXOP_QUERY_DELETE
+ code = load_extop2( (struct berval *)&pcache_exop_QUERY_DELETE,
+ SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, pcache_exop_query_delete,
+ 0 );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: unable to register queryDelete exop: %d.\n",
+ code, 0, 0 );
+ return code;
+ }
+#endif /* PCACHE_EXOP_QUERY_DELETE */
+
+ argv[ 0 ] = "back-bdb/back-hdb monitor";
+ c.argv = argv;
+ c.argc = 3;
+ c.fname = argv[0];
+
+ for ( i = 0; s_oid[ i ].name; i++ ) {
+ c.lineno = i;
+ argv[ 1 ] = s_oid[ i ].name;
+ argv[ 2 ] = s_oid[ i ].oid;
+
+ if ( parse_oidm( &c, 0, NULL ) != 0 ) {
+ Debug( LDAP_DEBUG_ANY, "pcache_initialize: "
+ "unable to add objectIdentifier \"%s=%s\"\n",
+ s_oid[ i ].name, s_oid[ i ].oid, 0 );
+ return 1;
+ }
+ }
+
+ for ( i = 0; s_ad[i].desc != NULL; i++ ) {
+ code = register_at( s_ad[i].desc, s_ad[i].adp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: register_at #%d failed\n", i, 0, 0 );
+ return code;
+ }
+ (*s_ad[i].adp)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+
+ for ( i = 0; s_oc[i].desc != NULL; i++ ) {
+ code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "pcache_initialize: register_oc #%d failed\n", i, 0, 0 );
+ return code;
+ }
+ (*s_oc[i].ocp)->soc_flags |= SLAP_OC_HIDE;
+ }
+
+ pcache.on_bi.bi_type = "pcache";
+ pcache.on_bi.bi_obsolete_names = obsolete_names;
+ pcache.on_bi.bi_db_init = pcache_db_init;
+ pcache.on_bi.bi_db_config = pcache_db_config;
+ pcache.on_bi.bi_db_open = pcache_db_open;
+ pcache.on_bi.bi_db_close = pcache_db_close;
+ pcache.on_bi.bi_db_destroy = pcache_db_destroy;
+
+ pcache.on_bi.bi_op_search = pcache_op_search;
+ pcache.on_bi.bi_op_bind = pcache_op_bind;
+#ifdef PCACHE_CONTROL_PRIVDB
+ pcache.on_bi.bi_op_compare = pcache_op_privdb;
+ pcache.on_bi.bi_op_modrdn = pcache_op_privdb;
+ pcache.on_bi.bi_op_modify = pcache_op_privdb;
+ pcache.on_bi.bi_op_add = pcache_op_privdb;
+ pcache.on_bi.bi_op_delete = pcache_op_privdb;
+#endif /* PCACHE_CONTROL_PRIVDB */
+ pcache.on_bi.bi_extended = pcache_op_extended;
+
+ pcache.on_bi.bi_entry_release_rw = pcache_entry_release;
+ pcache.on_bi.bi_chk_controls = pcache_chk_controls;
+
+ pcache.on_bi.bi_cf_ocs = pcocs;
+
+ code = config_register_schema( pccfg, pcocs );
+ if ( code ) return code;
+
+ return overlay_register( &pcache );
+}
+
+#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return pcache_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_PROXYCACHE) */
diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c
new file mode 100644
index 0000000..991ed56
--- /dev/null
+++ b/servers/slapd/overlays/ppolicy.c
@@ -0,0 +1,2526 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2004-2005 Howard Chu, Symas Corporation.
+ * Portions Copyright 2004 Hewlett-Packard Company.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Howard Chu for inclusion in
+ * OpenLDAP Software, based on prior work by Neil Dunbar (HP).
+ * This work was sponsored by the Hewlett-Packard Company.
+ */
+
+#include "portable.h"
+
+/* This file implements "Password Policy for LDAP Directories",
+ * based on draft behera-ldap-password-policy-09
+ */
+
+#ifdef SLAPD_OVER_PPOLICY
+
+#include <ldap.h>
+#include "lutil.h"
+#include "slap.h"
+#ifdef SLAPD_MODULES
+#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */
+#include <ltdl.h>
+#endif
+#include <ac/errno.h>
+#include <ac/time.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include "config.h"
+
+#ifndef MODULE_NAME_SZ
+#define MODULE_NAME_SZ 256
+#endif
+
+#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE
+#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5
+#endif
+
+/* Per-instance configuration information */
+typedef struct pp_info {
+ struct berval def_policy; /* DN of default policy subentry */
+ int use_lockout; /* send AccountLocked result? */
+ int hash_passwords; /* transparently hash cleartext pwds */
+ int forward_updates; /* use frontend for policy state updates */
+} pp_info;
+
+/* Our per-connection info - note, it is not per-instance, it is
+ * used by all instances
+ */
+typedef struct pw_conn {
+ struct berval dn; /* DN of restricted user */
+} pw_conn;
+
+static pw_conn *pwcons;
+static int ppolicy_cid;
+static int ov_count;
+
+typedef struct pass_policy {
+ AttributeDescription *ad; /* attribute to which the policy applies */
+ int pwdMinAge; /* minimum time (seconds) until passwd can change */
+ int pwdMaxAge; /* time in seconds until pwd will expire after change */
+ int pwdInHistory; /* number of previous passwords kept */
+ int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible,
+ 2 = check mandatory; fail if not possible */
+ int pwdMinLength; /* minimum number of chars in password */
+ int pwdExpireWarning; /* number of seconds that warning controls are
+ sent before a password expires */
+ int pwdGraceAuthNLimit; /* number of times you can log in with an
+ expired password */
+ int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */
+ int pwdLockoutDuration; /* time in seconds a password is locked out for */
+ int pwdMaxFailure; /* number of failed binds allowed before lockout */
+ int pwdMaxRecordedFailure; /* number of failed binds to store */
+ int pwdFailureCountInterval; /* number of seconds before failure
+ counts are zeroed */
+ int pwdMustChange; /* 0 = users can use admin set password
+ 1 = users must change password after admin set */
+ int pwdAllowUserChange; /* 0 = users cannot change their passwords
+ 1 = users can change them */
+ int pwdSafeModify; /* 0 = old password doesn't need to come
+ with password change request
+ 1 = password change must supply existing pwd */
+ char pwdCheckModule[MODULE_NAME_SZ]; /* name of module to dynamically
+ load to check password */
+} PassPolicy;
+
+typedef struct pw_hist {
+ time_t t; /* timestamp of history entry */
+ struct berval pw; /* old password hash */
+ struct berval bv; /* text of entire entry */
+ struct pw_hist *next;
+} pw_hist;
+
+/* Operational attributes */
+static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime,
+ *ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset,
+ *ad_pwdPolicySubentry;
+
+static struct schema_info {
+ char *def;
+ AttributeDescription **ad;
+} pwd_OpSchema[] = {
+ { "( 1.3.6.1.4.1.42.2.27.8.1.16 "
+ "NAME ( 'pwdChangedTime' ) "
+ "DESC 'The time the password was last changed' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation )",
+ &ad_pwdChangedTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.17 "
+ "NAME ( 'pwdAccountLockedTime' ) "
+ "DESC 'The time an user account was locked' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "SINGLE-VALUE "
+#if 0
+ /* Not until Relax control is released */
+ "NO-USER-MODIFICATION "
+#endif
+ "USAGE directoryOperation )",
+ &ad_pwdAccountLockedTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.19 "
+ "NAME ( 'pwdFailureTime' ) "
+ "DESC 'The timestamps of the last consecutive authentication failures' "
+ "EQUALITY generalizedTimeMatch "
+ "ORDERING generalizedTimeOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "NO-USER-MODIFICATION USAGE directoryOperation )",
+ &ad_pwdFailureTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.20 "
+ "NAME ( 'pwdHistory' ) "
+ "DESC 'The history of users passwords' "
+ "EQUALITY octetStringMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "NO-USER-MODIFICATION USAGE directoryOperation )",
+ &ad_pwdHistory },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.21 "
+ "NAME ( 'pwdGraceUseTime' ) "
+ "DESC 'The timestamps of the grace login once the password has expired' "
+ "EQUALITY generalizedTimeMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 "
+ "NO-USER-MODIFICATION USAGE directoryOperation )",
+ &ad_pwdGraceUseTime },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.22 "
+ "NAME ( 'pwdReset' ) "
+ "DESC 'The indication that the password has been reset' "
+ "EQUALITY booleanMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE USAGE directoryOperation )",
+ &ad_pwdReset },
+ { "( 1.3.6.1.4.1.42.2.27.8.1.23 "
+ "NAME ( 'pwdPolicySubentry' ) "
+ "DESC 'The pwdPolicy subentry in effect for this object' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE "
+#if 0
+ /* Not until Relax control is released */
+ "NO-USER-MODIFICATION "
+#endif
+ "USAGE directoryOperation )",
+ &ad_pwdPolicySubentry },
+ { NULL, NULL }
+};
+
+/* User attributes */
+static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdInHistory,
+ *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxFailure,
+ *ad_pwdGraceAuthNLimit, *ad_pwdExpireWarning, *ad_pwdLockoutDuration,
+ *ad_pwdFailureCountInterval, *ad_pwdCheckModule, *ad_pwdLockout,
+ *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify,
+ *ad_pwdAttribute, *ad_pwdMaxRecordedFailure;
+
+#define TAB(name) { #name, &ad_##name }
+
+static struct schema_info pwd_UsSchema[] = {
+ TAB(pwdAttribute),
+ TAB(pwdMinAge),
+ TAB(pwdMaxAge),
+ TAB(pwdInHistory),
+ TAB(pwdCheckQuality),
+ TAB(pwdMinLength),
+ TAB(pwdMaxFailure),
+ TAB(pwdMaxRecordedFailure),
+ TAB(pwdGraceAuthNLimit),
+ TAB(pwdExpireWarning),
+ TAB(pwdLockout),
+ TAB(pwdLockoutDuration),
+ TAB(pwdFailureCountInterval),
+ TAB(pwdCheckModule),
+ TAB(pwdMustChange),
+ TAB(pwdAllowUserChange),
+ TAB(pwdSafeModify),
+ { NULL, NULL }
+};
+
+static ldap_pvt_thread_mutex_t chk_syntax_mutex;
+
+enum {
+ PPOLICY_DEFAULT = 1,
+ PPOLICY_HASH_CLEARTEXT,
+ PPOLICY_USE_LOCKOUT
+};
+
+static ConfigDriver ppolicy_cf_default;
+
+static ConfigTable ppolicycfg[] = {
+ { "ppolicy_default", "policyDN", 2, 2, 0,
+ ARG_DN|ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT, ppolicy_cf_default,
+ "( OLcfgOvAt:12.1 NAME 'olcPPolicyDefault' "
+ "DESC 'DN of a pwdPolicy object for uncustomized objects' "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_hash_cleartext", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT,
+ (void *)offsetof(pp_info,hash_passwords),
+ "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' "
+ "DESC 'Hash passwords on add or modify' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_forward_updates", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(pp_info,forward_updates),
+ "( OLcfgOvAt:12.4 NAME 'olcPPolicyForwardUpdates' "
+ "DESC 'Allow policy state updates to be forwarded via updateref' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "ppolicy_use_lockout", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT,
+ (void *)offsetof(pp_info,use_lockout),
+ "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' "
+ "DESC 'Warn clients with AccountLocked' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { 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 ) )",
+ Cft_Overlay, ppolicycfg },
+ { NULL, 0, NULL }
+};
+
+static int
+ppolicy_cf_default( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ pp_info *pi = (pp_info *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ assert ( c->type == PPOLICY_DEFAULT );
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n", 0, 0, 0);
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n", 0, 0, 0);
+ rc = 0;
+ if ( !BER_BVISEMPTY( &pi->def_policy )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &pi->def_policy );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &pi->def_policy );
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n", 0, 0, 0);
+ if ( pi->def_policy.bv_val ) {
+ ber_memfree ( pi->def_policy.bv_val );
+ pi->def_policy.bv_val = NULL;
+ }
+ pi->def_policy.bv_len = 0;
+ rc = 0;
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthrough to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n", 0, 0, 0);
+ if ( pi->def_policy.bv_val ) {
+ ber_memfree ( pi->def_policy.bv_val );
+ }
+ pi->def_policy = c->value_ndn;
+ ber_memfree( c->value_dn.bv_val );
+ BER_BVZERO( &c->value_dn );
+ BER_BVZERO( &c->value_ndn );
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+static time_t
+parse_time( char *atm )
+{
+ struct lutil_tm tm;
+ struct lutil_timet tt;
+ time_t ret = (time_t)-1;
+
+ if ( lutil_parsetime( atm, &tm ) == 0) {
+ lutil_tm2time( &tm, &tt );
+ ret = tt.tt_sec;
+ }
+ return ret;
+}
+
+static int
+account_locked( Operation *op, Entry *e,
+ PassPolicy *pp, Modifications **mod )
+{
+ Attribute *la;
+
+ assert(mod != NULL);
+
+ if ( !pp->pwdLockout )
+ return 0;
+
+ if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) {
+ BerVarray vals = la->a_nvals;
+
+ /*
+ * there is a lockout stamp - we now need to know if it's
+ * a valid one.
+ */
+ if (vals[0].bv_val != NULL) {
+ time_t then, now;
+ Modifications *m;
+
+ if (!pp->pwdLockoutDuration)
+ return 1;
+
+ if ((then = parse_time( vals[0].bv_val )) == (time_t)0)
+ return 1;
+
+ now = slap_get_time();
+
+ if (now < then + pp->pwdLockoutDuration)
+ return 1;
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdAccountLockedTime->ad_cname;
+ m->sml_desc = ad_pwdAccountLockedTime;
+ m->sml_next = *mod;
+ *mod = m;
+ }
+ }
+
+ return 0;
+}
+
+/* IMPLICIT TAGS, all context-specific */
+#define PPOLICY_WARNING 0xa0L /* constructed + 0 */
+#define PPOLICY_ERROR 0x81L /* primitive + 1 */
+
+#define PPOLICY_EXPIRE 0x80L /* primitive + 0 */
+#define PPOLICY_GRACE 0x81L /* primitive + 1 */
+
+static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE;
+
+static 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 **
+add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl )
+{
+ LDAPControl **ctrls, **oldctrls = rs->sr_ctrls;
+ int n;
+
+ n = 0;
+ if ( oldctrls ) {
+ for ( ; oldctrls[n]; n++ )
+ ;
+ }
+ n += 2;
+
+ ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx );
+
+ n = 0;
+ if ( oldctrls ) {
+ for ( ; oldctrls[n]; n++ ) {
+ ctrls[n] = oldctrls[n];
+ }
+ }
+ ctrls[n] = ctrl;
+ ctrls[n+1] = NULL;
+
+ rs->sr_ctrls = ctrls;
+
+ return oldctrls;
+}
+
+static void
+ppolicy_get_default( PassPolicy *pp )
+{
+ memset( pp, 0, sizeof(PassPolicy) );
+
+ pp->ad = slap_schema.si_ad_userPassword;
+
+ /* Users can change their own password by default */
+ pp->pwdAllowUserChange = 1;
+ if ( !pp->pwdMaxRecordedFailure )
+ pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
+}
+
+
+static void
+ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ Attribute *a;
+ BerVarray vals;
+ int rc;
+ Entry *pe = NULL;
+#if 0
+ const char *text;
+#endif
+
+ ppolicy_get_default( pp );
+
+ if ((a = attr_find( e->e_attrs, ad_pwdPolicySubentry )) == NULL) {
+ /*
+ * entry has no password policy assigned - use default
+ */
+ vals = &pi->def_policy;
+ if ( !vals->bv_val )
+ goto defaultpol;
+ } else {
+ vals = a->a_nvals;
+ if (vals[0].bv_val == NULL) {
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_get: NULL value for policySubEntry\n", 0, 0, 0 );
+ goto defaultpol;
+ }
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc ) goto defaultpol;
+
+#if 0 /* Only worry about userPassword for now */
+ if ((a = attr_find( pe->e_attrs, ad_pwdAttribute )))
+ slap_bv2ad( &a->a_vals[0], &pp->ad, &text );
+#endif
+
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdMinAge ) )
+ && lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxAge ) )
+ && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdInHistory ) )
+ && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckQuality ) )
+ && lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdMinLength ) )
+ && lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxFailure ) )
+ && lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdMaxRecordedFailure ) )
+ && lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdGraceAuthNLimit ) )
+ && lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdExpireWarning ) )
+ && lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdFailureCountInterval ) )
+ && lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdLockoutDuration ) )
+ && lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 )
+ goto defaultpol;
+
+ if ( ( a = attr_find( pe->e_attrs, ad_pwdCheckModule ) ) ) {
+ strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val,
+ sizeof(pp->pwdCheckModule) );
+ pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0';
+ }
+
+ if ((a = attr_find( pe->e_attrs, ad_pwdLockout )))
+ pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv );
+ if ((a = attr_find( pe->e_attrs, ad_pwdMustChange )))
+ pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+ if ((a = attr_find( pe->e_attrs, ad_pwdAllowUserChange )))
+ pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv );
+ if ((a = attr_find( pe->e_attrs, ad_pwdSafeModify )))
+ pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv );
+
+ if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure )
+ pp->pwdMaxRecordedFailure = pp->pwdMaxFailure;
+ if ( !pp->pwdMaxRecordedFailure )
+ pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, pe );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ return;
+
+defaultpol:
+ if ( pe ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, pe );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_get: using default policy\n", 0, 0, 0 );
+
+ ppolicy_get_default( pp );
+
+ return;
+}
+
+static int
+password_scheme( struct berval *cred, struct berval *sch )
+{
+ int e;
+
+ assert( cred != NULL );
+
+ if (sch) {
+ sch->bv_val = NULL;
+ sch->bv_len = 0;
+ }
+
+ if ((cred->bv_len == 0) || (cred->bv_val == NULL) ||
+ (cred->bv_val[0] != '{')) return LDAP_OTHER;
+
+ for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++);
+ if (cred->bv_val[e]) {
+ int rc;
+ rc = lutil_passwd_scheme( cred->bv_val );
+ if (rc) {
+ if (sch) {
+ sch->bv_val = cred->bv_val;
+ sch->bv_len = e;
+ }
+ return LDAP_SUCCESS;
+ }
+ }
+ return LDAP_OTHER;
+}
+
+static int
+check_password_quality( struct berval *cred, PassPolicy *pp, LDAPPasswordPolicyError *err, Entry *e, char **txt )
+{
+ int rc = LDAP_SUCCESS, ok = LDAP_SUCCESS;
+ char *ptr;
+ struct berval sch;
+
+ assert( cred != NULL );
+ assert( pp != NULL );
+ assert( txt != NULL );
+
+ ptr = cred->bv_val;
+
+ *txt = NULL;
+
+ if ((cred->bv_len == 0) || (pp->pwdMinLength > cred->bv_len)) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if ( err ) *err = PP_passwordTooShort;
+ return rc;
+ }
+
+ /*
+ * We need to know if the password is already hashed - if so
+ * what scheme is it. The reason being that the "hash" of
+ * {cleartext} still allows us to check the password.
+ */
+ rc = password_scheme( cred, &sch );
+ if (rc == LDAP_SUCCESS) {
+ if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}",
+ sch.bv_len ) == 0)) {
+ /*
+ * We can check the cleartext "hash"
+ */
+ ptr = cred->bv_val + sch.bv_len;
+ } else {
+ /* everything else, we can't check */
+ if (pp->pwdCheckQuality == 2) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if (err) *err = PP_insufficientPasswordQuality;
+ return rc;
+ }
+ /*
+ * We can't check the syntax of the password, but it's not
+ * mandatory (according to the policy), so we return success.
+ */
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ rc = LDAP_SUCCESS;
+
+ if (pp->pwdCheckModule[0]) {
+#ifdef SLAPD_MODULES
+ lt_dlhandle mod;
+ const char *err;
+
+ if ((mod = lt_dlopen( pp->pwdCheckModule )) == NULL) {
+ err = lt_dlerror();
+
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: lt_dlopen failed: (%s) %s.\n",
+ pp->pwdCheckModule, err, 0 );
+ ok = LDAP_OTHER; /* internal error */
+ } else {
+ /* FIXME: the error message ought to be passed thru a
+ * struct berval, with preallocated buffer and size
+ * passed in. Module can still allocate a buffer for
+ * it if the provided one is too small.
+ */
+ int (*prog)( char *passwd, char **text, Entry *ent );
+
+ if ((prog = lt_dlsym( mod, "check_password" )) == NULL) {
+ err = lt_dlerror();
+
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: lt_dlsym failed: (%s) %s.\n",
+ pp->pwdCheckModule, err, 0 );
+ ok = LDAP_OTHER;
+ } else {
+ ldap_pvt_thread_mutex_lock( &chk_syntax_mutex );
+ ok = prog( ptr, txt, e );
+ ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex );
+ if (ok != LDAP_SUCCESS) {
+ Debug(LDAP_DEBUG_ANY,
+ "check_password_quality: module error: (%s) %s.[%d]\n",
+ pp->pwdCheckModule, *txt ? *txt : "", ok );
+ }
+ }
+
+ lt_dlclose( mod );
+ }
+#else
+ Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not "
+ "supported. pwdCheckModule ignored.\n", 0, 0, 0);
+#endif /* SLAPD_MODULES */
+ }
+
+
+ if (ok != LDAP_SUCCESS) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ if (err) *err = PP_insufficientPasswordQuality;
+ }
+
+ return rc;
+}
+
+static int
+parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw )
+{
+ char *ptr;
+ struct berval nv, npw;
+ ber_len_t i, j;
+
+ assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw );
+
+ if ( oid ) {
+ *oid = 0;
+ }
+ *oldtime = (time_t)-1;
+ BER_BVZERO( oldpw );
+
+ ber_dupbv( &nv, bv );
+
+ /* first get the time field */
+ for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ ptr = nv.bv_val;
+ *oldtime = parse_time( ptr );
+ if (*oldtime == (time_t)-1) {
+ goto exit_failure;
+ }
+
+ /* get the OID field */
+ for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ if ( oid ) {
+ *oid = ber_strdup( ptr );
+ }
+
+ /* get the length field */
+ for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ )
+ ;
+ if ( i == nv.bv_len ) {
+ goto exit_failure; /* couldn't locate the '#' separator */
+ }
+ nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */
+ oldpw->bv_len = strtol( ptr, NULL, 10 );
+ if (errno == ERANGE) {
+ goto exit_failure;
+ }
+
+ /* lastly, get the octets of the string */
+ for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ )
+ ;
+ if ( i - j != oldpw->bv_len) {
+ goto exit_failure; /* length is wrong */
+ }
+
+ npw.bv_val = ptr;
+ npw.bv_len = oldpw->bv_len;
+ ber_dupbv( oldpw, &npw );
+ ber_memfree( nv.bv_val );
+
+ return LDAP_SUCCESS;
+
+exit_failure:;
+ if ( oid && *oid ) {
+ ber_memfree(*oid);
+ *oid = NULL;
+ }
+ if ( oldpw->bv_val ) {
+ ber_memfree( oldpw->bv_val);
+ BER_BVZERO( oldpw );
+ }
+ ber_memfree( nv.bv_val );
+
+ return LDAP_OTHER;
+}
+
+static void
+add_to_pwd_history( pw_hist **l, time_t t,
+ struct berval *oldpw, struct berval *bv )
+{
+ pw_hist *p, *p1, *p2;
+
+ if (!l) return;
+
+ p = ch_malloc( sizeof( pw_hist ));
+ p->pw = *oldpw;
+ ber_dupbv( &p->bv, bv );
+ p->t = t;
+ p->next = NULL;
+
+ if (*l == NULL) {
+ /* degenerate case */
+ *l = p;
+ return;
+ }
+ /*
+ * advance p1 and p2 such that p1 is the node before the
+ * new one, and p2 is the node after it
+ */
+ for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next );
+ p->next = p2;
+ if (p1 == NULL) { *l = p; return; }
+ p1->next = p;
+}
+
+#ifndef MAX_PWD_HISTORY_SZ
+#define MAX_PWD_HISTORY_SZ 1024
+#endif /* MAX_PWD_HISTORY_SZ */
+
+static void
+make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa )
+{
+ char str[ MAX_PWD_HISTORY_SZ ];
+ int nlen;
+
+ snprintf( str, MAX_PWD_HISTORY_SZ,
+ "%s#%s#%lu#", timebuf,
+ pa->a_desc->ad_type->sat_syntax->ssyn_oid,
+ (unsigned long) pa->a_nvals[0].bv_len );
+ str[MAX_PWD_HISTORY_SZ-1] = 0;
+ nlen = strlen(str);
+
+ /*
+ * We have to assume that the string is a string of octets,
+ * not readable characters. In reality, yes, it probably is
+ * a readable (ie, base64) string, but we can't count on that
+ * Hence, while the first 3 fields of the password history
+ * are definitely readable (a timestamp, an OID and an integer
+ * length), the remaining octets of the actual password
+ * are deemed to be binary data.
+ */
+ AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len );
+ nlen += pa->a_nvals[0].bv_len;
+ bv->bv_val = ch_malloc( nlen + 1 );
+ AC_MEMCPY( bv->bv_val, str, nlen );
+ bv->bv_val[nlen] = '\0';
+ bv->bv_len = nlen;
+}
+
+static void
+free_pwd_history_list( pw_hist **l )
+{
+ pw_hist *p;
+
+ if (!l) return;
+ p = *l;
+ while (p) {
+ pw_hist *pp = p->next;
+
+ free(p->pw.bv_val);
+ free(p->bv.bv_val);
+ free(p);
+ p = pp;
+ }
+ *l = NULL;
+}
+
+typedef struct ppbind {
+ slap_overinst *on;
+ int send_ctrl;
+ int set_restrict;
+ LDAPControl **oldctrls;
+ Modifications *mod;
+ LDAPPasswordPolicyError pErr;
+ PassPolicy pp;
+} ppbind;
+
+static void
+ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls )
+{
+ int n;
+
+ assert( rs->sr_ctrls != NULL );
+ assert( rs->sr_ctrls[0] != NULL );
+
+ for ( n = 0; rs->sr_ctrls[n]; n++ ) {
+ if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid ) {
+ op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx );
+ rs->sr_ctrls[n] = (LDAPControl *)(-1);
+ break;
+ }
+ }
+
+ if ( rs->sr_ctrls[n] == NULL ) {
+ /* missed? */
+ }
+
+ op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx );
+
+ rs->sr_ctrls = oldctrls;
+}
+
+static int
+ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs )
+{
+ ppbind *ppb = op->o_callback->sc_private;
+ if ( ppb->send_ctrl ) {
+ ctrls_cleanup( op, rs, ppb->oldctrls );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind_response( Operation *op, SlapReply *rs )
+{
+ ppbind *ppb = op->o_callback->sc_private;
+ slap_overinst *on = ppb->on;
+ Modifications *mod = ppb->mod, *m;
+ int pwExpired = 0;
+ int ngut = -1, warn = -1, age, rc;
+ Attribute *a;
+ time_t now, pwtime = (time_t)-1;
+ struct lutil_tm now_tm;
+ struct lutil_timet now_usec;
+ char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ];
+ struct berval timestamp, timestamp_usec;
+ BackendInfo *bi = op->o_bd->bd_info;
+ Entry *e;
+
+ /* If we already know it's locked, just get on with it */
+ if ( ppb->pErr != PP_noError ) {
+ goto locked;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ op->o_bd->bd_info = bi;
+
+ if ( rc != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ ldap_pvt_gettime(&now_tm); /* stored for later consideration */
+ lutil_tm2time(&now_tm, &now_usec);
+ now = now_usec.tt_sec;
+ timestamp.bv_val = nowstr;
+ timestamp.bv_len = sizeof(nowstr);
+ slap_timestamp( &now, &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_usec );
+ timestamp_usec.bv_len += STRLENOF(".123456");
+
+ if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) {
+ int i = 0, fc = 0;
+
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdFailureTime->ad_cname;
+ m->sml_desc = ad_pwdFailureTime;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+
+ ber_dupbv( &m->sml_values[0], &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 ( rs->sr_err == LDAP_SUCCESS ) {
+ if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+ pwtime = parse_time( a->a_nvals[0].bv_val );
+
+ /* delete all pwdFailureTimes */
+ if ( attr_find( e->e_attrs, ad_pwdFailureTime )) {
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdFailureTime->ad_cname;
+ m->sml_desc = ad_pwdFailureTime;
+ m->sml_next = mod;
+ mod = m;
+ }
+
+ /*
+ * check to see if the password must be changed
+ */
+ if ( ppb->pp.pwdMustChange &&
+ (a = attr_find( e->e_attrs, ad_pwdReset )) &&
+ bvmatch( &a->a_nvals[0], &slap_true_bv ) )
+ {
+ /*
+ * need to inject client controls here to give
+ * more information. For the moment, we ensure
+ * that we are disallowed from doing anything
+ * other than change password.
+ */
+ if ( ppb->set_restrict ) {
+ ber_dupbv( &pwcons[op->o_conn->c_conn_idx].dn,
+ &op->o_conn->c_ndn );
+ }
+
+ ppb->pErr = PP_changeAfterReset;
+
+ } else {
+ /*
+ * the password does not need to be changed, so
+ * we now check whether the password has expired.
+ *
+ * We can skip this bit if passwords don't age in
+ * the policy. Also, if there was no pwdChangedTime
+ * attribute in the entry, the password never expires.
+ */
+ if (ppb->pp.pwdMaxAge == 0) goto grace;
+
+ if (pwtime != (time_t)-1) {
+ /*
+ * Check: was the last change time of
+ * the password older than the maximum age
+ * allowed. (Ignore case 2 from I-D, it's just silly.)
+ */
+ if (now - pwtime > ppb->pp.pwdMaxAge ) pwExpired = 1;
+ }
+ }
+
+grace:
+ if (!pwExpired) goto check_expiring_password;
+
+ if ((a = attr_find( e->e_attrs, ad_pwdGraceUseTime )) == NULL)
+ ngut = ppb->pp.pwdGraceAuthNLimit;
+ else {
+ for(ngut=0; a->a_nvals[ngut].bv_val; ngut++);
+ ngut = ppb->pp.pwdGraceAuthNLimit - ngut;
+ }
+
+ /*
+ * ngut is the number of remaining grace logins
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_bind: Entry %s has an expired password: %d grace logins\n",
+ e->e_name.bv_val, ngut, 0);
+
+ if (ngut < 1) {
+ ppb->pErr = PP_passwordExpired;
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto done;
+ }
+
+ /*
+ * Add a grace user time to the entry
+ */
+ m = ch_calloc( sizeof(Modifications), 1 );
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_type = ad_pwdGraceUseTime->ad_cname;
+ m->sml_desc = ad_pwdGraceUseTime;
+ m->sml_numvals = 1;
+ m->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 );
+ ber_dupbv( &m->sml_values[0], &timestamp );
+ ber_dupbv( &m->sml_nvalues[0], &timestamp );
+ m->sml_next = mod;
+ mod = m;
+
+check_expiring_password:
+ /*
+ * Now we need to check to see
+ * if it is about to expire, and if so, should the user
+ * be warned about it in the password policy control.
+ *
+ * If the password has expired, and we're in the grace period, then
+ * we don't need to do this bit. Similarly, if we don't have password
+ * aging, then there's no need to do this bit either.
+ */
+ if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1))
+ goto done;
+
+ age = (int)(now - pwtime);
+
+ /*
+ * We know that there is a password Change Time attribute - if
+ * there wasn't, then the pwdExpired value would be true, unless
+ * there is no password aging - and if there is no password aging,
+ * then this section isn't called anyway - you can't have an
+ * expiring password if there's no limit to expire.
+ */
+ if (ppb->pp.pwdMaxAge - age < ppb->pp.pwdExpireWarning ) {
+ /*
+ * Set the warning value.
+ */
+ warn = ppb->pp.pwdMaxAge - age; /* seconds left until expiry */
+ if (warn < 0) warn = 0; /* something weird here - why is pwExpired not set? */
+
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n",
+ op->o_req_dn.bv_val, warn, 0 );
+ }
+ }
+
+done:
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+
+locked:
+ if ( mod ) {
+ Operation op2 = *op;
+ SlapReply r2 = { REP_RESULT };
+ slap_callback cb = { NULL, slap_null_cb, NULL, NULL };
+ pp_info *pi = on->on_bi.bi_private;
+ LDAPControl c, *ca[2];
+
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.o_callback = &cb;
+ op2.orm_modlist = mod;
+ op2.orm_no_opattrs = 0;
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ /* If this server is a shadow and forward_updates is true,
+ * use the frontend to perform this modify. That will trigger
+ * the update referral, which can then be forwarded by the
+ * chain overlay. Obviously the updateref and chain overlay
+ * must be configured appropriately for this to be useful.
+ */
+ if ( SLAP_SHADOW( op->o_bd ) && pi->forward_updates ) {
+ op2.o_bd = frontendDB;
+
+ /* Must use Relax control since these are no-user-mod */
+ op2.o_relax = SLAP_CONTROL_CRITICAL;
+ op2.o_ctrls = ca;
+ ca[0] = &c;
+ ca[1] = NULL;
+ BER_BVZERO( &c.ldctl_value );
+ c.ldctl_iscritical = 1;
+ c.ldctl_oid = LDAP_CONTROL_RELAX;
+ } else {
+ /* If not forwarding, don't update opattrs and don't replicate */
+ if ( SLAP_SINGLE_SHADOW( op->o_bd )) {
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+ }
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+ }
+ rc = op2.o_bd->be_modify( &op2, &r2 );
+ slap_mods_free( mod, 1 );
+ }
+
+ if ( ppb->send_ctrl ) {
+ LDAPControl *ctrl = NULL;
+ pp_info *pi = on->on_bi.bi_private;
+
+ /* 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 );
+ ppb->oldctrls = add_passcontrol( op, rs, ctrl );
+ op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup;
+ }
+ op->o_bd->bd_info = bi;
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ /* Reset lockout status on all Bind requests */
+ if ( !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ }
+
+ /* Root bypasses policy */
+ if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn )) {
+ Entry *e;
+ int rc;
+ ppbind *ppb;
+ slap_callback *cb;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+ ppb = (ppbind *)(cb+1);
+ ppb->on = on;
+ ppb->pErr = PP_noError;
+ ppb->set_restrict = 1;
+
+ /* Setup a callback so we can munge the result */
+
+ cb->sc_response = ppolicy_bind_response;
+ cb->sc_next = op->o_callback->sc_next;
+ cb->sc_private = ppb;
+ op->o_callback->sc_next = cb;
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ ppb->send_ctrl = 1;
+ }
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ ppolicy_get( op, e, &ppb->pp );
+
+ rc = account_locked( op, e, &ppb->pp, &ppb->mod );
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+
+ if ( rc ) {
+ ppb->pErr = PP_accountLocked;
+ send_ldap_error( op, rs, LDAP_INVALID_CREDENTIALS, NULL );
+ return rs->sr_err;
+ }
+
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* Reset the restricted info for the next session on this connection */
+static int
+ppolicy_connection_destroy( BackendDB *bd, Connection *conn )
+{
+ if ( pwcons && !BER_BVISEMPTY( &pwcons[conn->c_conn_idx].dn )) {
+ ch_free( pwcons[conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[conn->c_conn_idx].dn );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* Check if this connection is restricted */
+static int
+ppolicy_restrict(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ int send_ctrl = 0;
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+
+ if ( op->o_conn && !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) {
+ LDAPControl **oldctrls;
+ /* if the current authcDN doesn't match the one we recorded,
+ * then an intervening Bind has succeeded and the restriction
+ * no longer applies. (ITS#4516)
+ */
+ if ( !dn_match( &op->o_conn->c_ndn,
+ &pwcons[op->o_conn->c_conn_idx].dn )) {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "connection restricted to password changing only\n", 0, 0, 0);
+ if ( send_ctrl ) {
+ LDAPControl *ctrl = NULL;
+ ctrl = create_passcontrol( op, -1, -1, PP_changeAfterReset );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "Operations are restricted to bind/unbind/abandon/StartTLS/modify password" );
+ if ( send_ctrl ) {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_compare_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ /* map compare responses to bind responses */
+ if ( rs->sr_err == LDAP_COMPARE_TRUE )
+ rs->sr_err = LDAP_SUCCESS;
+ else if ( rs->sr_err == LDAP_COMPARE_FALSE )
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+
+ ppolicy_bind_response( op, rs );
+
+ /* map back to compare */
+ if ( rs->sr_err == LDAP_SUCCESS )
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ else if ( rs->sr_err == LDAP_INVALID_CREDENTIALS )
+ rs->sr_err = LDAP_COMPARE_FALSE;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_compare(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+ return rs->sr_err;
+
+ /* Did we receive a password policy request control?
+ * Are we testing the userPassword?
+ */
+ if ( op->o_ctrlflag[ppolicy_cid] &&
+ op->orc_ava->aa_desc == slap_schema.si_ad_userPassword ) {
+ Entry *e;
+ int rc;
+ ppbind *ppb;
+ slap_callback *cb;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback),
+ 1, op->o_tmpmemctx );
+ ppb = (ppbind *)(cb+1);
+ ppb->on = on;
+ ppb->pErr = PP_noError;
+ ppb->send_ctrl = 1;
+ /* failures here don't lockout the connection */
+ ppb->set_restrict = 0;
+
+ /* Setup a callback so we can munge the result */
+
+ cb->sc_response = ppolicy_compare_response;
+ cb->sc_next = op->o_callback->sc_next;
+ cb->sc_private = ppb;
+ op->o_callback->sc_next = cb;
+
+ op->o_bd->bd_info = (BackendInfo *)on;
+ ppolicy_get( op, e, &ppb->pp );
+
+ rc = account_locked( op, e, &ppb->pp, &ppb->mod );
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+
+ if ( rc ) {
+ ppb->pErr = PP_accountLocked;
+ send_ldap_error( op, rs, LDAP_COMPARE_FALSE, NULL );
+ return rs->sr_err;
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+ppolicy_add(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ PassPolicy pp;
+ Attribute *pa;
+ const char *txt;
+
+ if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE )
+ return rs->sr_err;
+
+ /* If this is a replica, assume the master checked everything */
+ if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) )
+ return SLAP_CB_CONTINUE;
+
+ /* Check for password in entry */
+ if ((pa = attr_find( op->oq_add.rs_e->e_attrs,
+ slap_schema.si_ad_userPassword )))
+ {
+ assert( pa->a_vals != NULL );
+ assert( !BER_BVISNULL( &pa->a_vals[ 0 ] ) );
+
+ if ( !BER_BVISNULL( &pa->a_vals[ 1 ] ) ) {
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "Password policy only allows one password value" );
+ return rs->sr_err;
+ }
+
+ /*
+ * new entry contains a password - if we're not the root user
+ * then we need to check that the password fits in with the
+ * security policy for the new entry.
+ */
+ ppolicy_get( op, op->ora_e, &pp );
+ if (pp.pwdCheckQuality > 0 && !be_isroot( op )) {
+ struct berval *bv = &(pa->a_vals[0]);
+ int rc, send_ctrl = 0;
+ LDAPPasswordPolicyError pErr = PP_noError;
+ char *txt;
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+ rc = check_password_quality( bv, &pp, &pErr, op->ora_e, &txt );
+ if (rc != LDAP_SUCCESS) {
+ LDAPControl **oldctrls = NULL;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ if ( send_ctrl ) {
+ LDAPControl *ctrl = NULL;
+ ctrl = create_passcontrol( op, -1, -1, pErr );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ send_ldap_error( op, rs, rc, txt ? txt : "Password fails quality checking policy" );
+ if ( txt ) {
+ free( txt );
+ }
+ if ( send_ctrl ) {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ return rs->sr_err;
+ }
+ }
+ /*
+ * A controversial bit. We hash cleartext
+ * passwords provided via add and modify operations
+ * You're not really supposed to do this, since
+ * the X.500 model says "store attributes" as they
+ * get provided. By default, this is what we do
+ *
+ * But if the hash_passwords flag is set, we hash
+ * any cleartext password attribute values via the
+ * default password hashing scheme.
+ */
+ if ((pi->hash_passwords) &&
+ (password_scheme( &(pa->a_vals[0]), NULL ) != LDAP_SUCCESS)) {
+ struct berval hpw;
+
+ slap_passwd_hash( &(pa->a_vals[0]), &hpw, &txt );
+ if (hpw.bv_val == NULL) {
+ /*
+ * hashing didn't work. Emit an error.
+ */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = txt;
+ send_ldap_error( op, rs, LDAP_OTHER, "Password hashing failed" );
+ return rs->sr_err;
+ }
+
+ memset( pa->a_vals[0].bv_val, 0, pa->a_vals[0].bv_len);
+ ber_memfree( pa->a_vals[0].bv_val );
+ pa->a_vals[0].bv_val = hpw.bv_val;
+ pa->a_vals[0].bv_len = hpw.bv_len;
+ }
+
+ /* If password aging is in effect, set the pwdChangedTime */
+ if ( pp.pwdMaxAge || pp.pwdMinAge ) {
+ struct berval timestamp;
+ char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ];
+ time_t now = slap_get_time();
+
+ timestamp.bv_val = timebuf;
+ timestamp.bv_len = sizeof(timebuf);
+ slap_timestamp( &now, &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_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+ int i, rc, mod_pw_only, pwmod, pwmop = -1, deladd,
+ hsize = 0;
+ PassPolicy pp;
+ Modifications *mods = NULL, *modtail = NULL,
+ *ml, *delmod, *addmod;
+ Attribute *pa, *ha, at;
+ const char *txt;
+ pw_hist *tl = NULL, *p;
+ int zapReset, send_ctrl = 0, free_txt = 0;
+ Entry *e;
+ struct berval newpw = BER_BVNULL, oldpw = BER_BVNULL,
+ *bv, cr[2];
+ LDAPPasswordPolicyError pErr = PP_noError;
+ LDAPControl *ctrl = NULL;
+ LDAPControl **oldctrls = NULL;
+ int is_pwdexop = 0;
+ int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0;
+ int got_changed = 0, got_history = 0;
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e );
+ op->o_bd->bd_info = (BackendInfo *)on;
+
+ if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE;
+
+ /* If this is a replica, we may need to tweak some of the
+ * master's modifications. Otherwise, just pass it through.
+ */
+ if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) {
+ Modifications **prev;
+ Attribute *a_grace, *a_lock, *a_fail;
+
+ a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime );
+ a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime );
+ a_fail = attr_find( e->e_attrs, ad_pwdFailureTime );
+
+ for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) {
+
+ if ( ml->sml_desc == slap_schema.si_ad_userPassword )
+ got_pw = 1;
+
+ /* If we're deleting an attr that didn't exist,
+ * drop this delete op
+ */
+ if ( ml->sml_op == LDAP_MOD_DELETE ||
+ ml->sml_op == SLAP_MOD_SOFTDEL ) {
+ int drop = 0;
+
+ if ( ml->sml_desc == ad_pwdGraceUseTime ) {
+ if ( !a_grace || got_del_grace ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_grace = 1;
+ }
+ } else
+ if ( ml->sml_desc == ad_pwdAccountLockedTime ) {
+ if ( !a_lock || got_del_lock ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_lock = 1;
+ }
+ } else
+ if ( ml->sml_desc == ad_pwdFailureTime ) {
+ if ( !a_fail || got_del_fail ) {
+ drop = ml->sml_op == LDAP_MOD_DELETE;
+ } else {
+ got_del_fail = 1;
+ }
+ }
+ if ( drop ) {
+ *prev = ml->sml_next;
+ ml->sml_next = NULL;
+ slap_mods_free( ml, 1 );
+ continue;
+ }
+ }
+ prev = &ml->sml_next;
+ }
+
+ /* If we're resetting the password, make sure grace, accountlock,
+ * and failure also get removed.
+ */
+ if ( got_pw ) {
+ if ( a_grace && !got_del_grace ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdGraceUseTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ prev = &ml->sml_next;
+ }
+ if ( a_lock && !got_del_lock ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdAccountLockedTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ }
+ if ( a_fail && !got_del_fail ) {
+ ml = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_type.bv_val = NULL;
+ ml->sml_desc = ad_pwdFailureTime;
+ ml->sml_numvals = 0;
+ ml->sml_values = NULL;
+ ml->sml_nvalues = NULL;
+ ml->sml_next = NULL;
+ *prev = ml;
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* Did we receive a password policy request control? */
+ if ( op->o_ctrlflag[ppolicy_cid] ) {
+ send_ctrl = 1;
+ }
+
+ /* See if this is a pwdModify exop. If so, we can
+ * access the plaintext passwords from that request.
+ */
+ {
+ slap_callback *sc;
+
+ for ( sc = op->o_callback; sc; sc=sc->sc_next ) {
+ if ( sc->sc_response == slap_null_cb &&
+ sc->sc_private ) {
+ req_pwdexop_s *qpw = sc->sc_private;
+ newpw = qpw->rs_new;
+ oldpw = qpw->rs_old;
+ is_pwdexop = 1;
+ break;
+ }
+ }
+ }
+
+ ppolicy_get( op, e, &pp );
+
+ for ( ml = op->orm_modlist,
+ pwmod = 0, mod_pw_only = 1,
+ deladd = 0, delmod = NULL,
+ addmod = NULL,
+ zapReset = 1;
+ ml != NULL; modtail = ml, ml = ml->sml_next )
+ {
+ if ( ml->sml_desc == pp.ad ) {
+ pwmod = 1;
+ pwmop = ml->sml_op;
+ if ((deladd == 0) && (ml->sml_op == LDAP_MOD_DELETE) &&
+ (ml->sml_values) && !BER_BVISNULL( &ml->sml_values[0] ))
+ {
+ deladd = 1;
+ delmod = ml;
+ }
+
+ if ((ml->sml_op == LDAP_MOD_ADD) ||
+ (ml->sml_op == LDAP_MOD_REPLACE))
+ {
+ if ( ml->sml_values && !BER_BVISNULL( &ml->sml_values[0] )) {
+ if ( deladd == 1 )
+ deladd = 2;
+
+ /* FIXME: there's no easy way to ensure
+ * that add does not cause multiple
+ * userPassword values; one way (that
+ * would be consistent with the single
+ * password constraint) would be to turn
+ * add into replace); another would be
+ * to disallow add.
+ *
+ * Let's check at least that a single value
+ * is being added
+ */
+ if ( addmod || !BER_BVISNULL( &ml->sml_values[ 1 ] ) ) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password policy only allows one password value";
+ goto return_results;
+ }
+
+ addmod = ml;
+ } else {
+ /* replace can have no values, add cannot */
+ assert( ml->sml_op == LDAP_MOD_REPLACE );
+ }
+ }
+
+ } else if ( !(ml->sml_flags & SLAP_MOD_INTERNAL) && !is_at_operational( ml->sml_desc->ad_type ) ) {
+ mod_pw_only = 0;
+ /* modifying something other than password */
+ }
+
+ /*
+ * If there is a request to explicitly add a pwdReset
+ * attribute, then we suppress the normal behaviour on
+ * password change, which is to remove the pwdReset
+ * attribute.
+ *
+ * This enables an administrator to assign a new password
+ * and place a "must reset" flag on the entry, which will
+ * stay until the user explicitly changes his/her password.
+ */
+ if (ml->sml_desc == ad_pwdReset ) {
+ if ((ml->sml_op == LDAP_MOD_ADD) ||
+ (ml->sml_op == LDAP_MOD_REPLACE))
+ zapReset = 0;
+ }
+ if ( ml->sml_op == LDAP_MOD_DELETE ) {
+ if ( ml->sml_desc == ad_pwdGraceUseTime ) {
+ got_del_grace = 1;
+ } else if ( ml->sml_desc == ad_pwdAccountLockedTime ) {
+ got_del_lock = 1;
+ } else if ( ml->sml_desc == ad_pwdFailureTime ) {
+ got_del_fail = 1;
+ }
+ }
+ if ( ml->sml_desc == ad_pwdChangedTime ) {
+ got_changed = 1;
+ } else if (ml->sml_desc == ad_pwdHistory ) {
+ got_history = 1;
+ }
+ }
+
+ if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn ) && !mod_pw_only ) {
+ if ( dn_match( &op->o_conn->c_ndn,
+ &pwcons[op->o_conn->c_conn_idx].dn )) {
+ Debug( LDAP_DEBUG_TRACE,
+ "connection restricted to password changing only\n", 0, 0, 0 );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "Operations are restricted to bind/unbind/abandon/StartTLS/modify password";
+ pErr = PP_changeAfterReset;
+ goto return_results;
+ } else {
+ ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val );
+ BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn );
+ }
+ }
+
+ /*
+ * if we have a "safe password modify policy", then we need to check if we're doing
+ * a delete (with the old password), followed by an add (with the new password).
+ *
+ * If we got just a delete with nothing else, just let it go. We also skip all the checks if
+ * the root user is bound. Root can do anything, including avoid the policies.
+ */
+
+ if (!pwmod) goto do_modify;
+
+ /*
+ * Build the password history list in ascending time order
+ * We need this, even if the user is root, in order to maintain
+ * the pwdHistory operational attributes properly.
+ */
+ if (addmod && pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) {
+ struct berval oldpw;
+ time_t oldtime;
+
+ for(i=0; ha->a_nvals[i].bv_val; i++) {
+ rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL,
+ &oldtime, &oldpw );
+
+ if (rc != LDAP_SUCCESS) continue; /* invalid history entry */
+
+ if (oldpw.bv_val) {
+ add_to_pwd_history( &tl, oldtime, &oldpw,
+ &(ha->a_nvals[i]) );
+ oldpw.bv_val = NULL;
+ oldpw.bv_len = 0;
+ }
+ }
+ for(p=tl; p; p=p->next, hsize++); /* count history size */
+ }
+
+ if (be_isroot( op )) goto do_modify;
+
+ /* NOTE: according to draft-behera-ldap-password-policy
+ * pwdAllowUserChange == FALSE must only prevent pwd changes
+ * by the user the pwd belongs to (ITS#7021) */
+ if (!pp.pwdAllowUserChange && dn_match(&op->o_req_ndn, &op->o_ndn)) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "User alteration of password is not allowed";
+ pErr = PP_passwordModNotAllowed;
+ goto return_results;
+ }
+
+ /* Just deleting? */
+ if (!addmod) {
+ /* skip everything else */
+ pwmod = 0;
+ goto do_modify;
+ }
+
+ /* This is a pwdModify exop that provided the old pw.
+ * We need to create a Delete mod for this old pw and
+ * let the matching value get found later
+ */
+ if (pp.pwdSafeModify && oldpw.bv_val ) {
+ ml = (Modifications *)ch_calloc( sizeof( Modifications ), 1 );
+ ml->sml_op = LDAP_MOD_DELETE;
+ ml->sml_flags = SLAP_MOD_INTERNAL;
+ ml->sml_desc = pp.ad;
+ ml->sml_type = pp.ad->ad_cname;
+ ml->sml_numvals = 1;
+ ml->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &ml->sml_values[0], &oldpw );
+ BER_BVZERO( &ml->sml_values[1] );
+ ml->sml_next = op->orm_modlist;
+ op->orm_modlist = ml;
+ delmod = ml;
+ deladd = 2;
+ }
+
+ if (pp.pwdSafeModify && deladd != 2) {
+ Debug( LDAP_DEBUG_TRACE,
+ "change password must use DELETE followed by ADD/REPLACE\n",
+ 0, 0, 0 );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ rs->sr_text = "Must supply old password to be changed as well as new one";
+ pErr = PP_mustSupplyOldPassword;
+ goto return_results;
+ }
+
+ /* Check age, but only if pwdReset is not TRUE */
+ pa = attr_find( e->e_attrs, ad_pwdReset );
+ if ((!pa || !bvmatch( &pa->a_nvals[0], &slap_true_bv )) &&
+ pp.pwdMinAge > 0) {
+ time_t pwtime = (time_t)-1, now;
+ int age;
+
+ if ((pa = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL)
+ pwtime = parse_time( pa->a_nvals[0].bv_val );
+ now = slap_get_time();
+ age = (int)(now - pwtime);
+ if ((pwtime != (time_t)-1) && (age < pp.pwdMinAge)) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password is too young to change";
+ pErr = PP_passwordTooYoung;
+ goto return_results;
+ }
+ }
+
+ /* pa is used in password history check below, be sure it's set */
+ if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL && delmod) {
+ /*
+ * we have a password to check
+ */
+ bv = oldpw.bv_val ? &oldpw : delmod->sml_values;
+ /* FIXME: no access checking? */
+ rc = slap_passwd_check( op, NULL, pa, bv, &txt );
+ if (rc != LDAP_SUCCESS) {
+ Debug( LDAP_DEBUG_TRACE,
+ "old password check failed: %s\n", txt, 0, 0 );
+
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Must supply correct old password to change to new one";
+ pErr = PP_mustSupplyOldPassword;
+ goto return_results;
+
+ } else {
+ int i;
+
+ /*
+ * replace the delete value with the (possibly hashed)
+ * value which is currently in the password.
+ */
+ for ( i = 0; !BER_BVISNULL( &delmod->sml_values[i] ); i++ ) {
+ free( delmod->sml_values[i].bv_val );
+ BER_BVZERO( &delmod->sml_values[i] );
+ }
+ free( delmod->sml_values );
+ delmod->sml_values = ch_calloc( sizeof(struct berval), 2 );
+ BER_BVZERO( &delmod->sml_values[1] );
+ ber_dupbv( &(delmod->sml_values[0]), &(pa->a_nvals[0]) );
+ }
+ }
+
+ bv = newpw.bv_val ? &newpw : &addmod->sml_values[0];
+ if (pp.pwdCheckQuality > 0) {
+
+ rc = check_password_quality( bv, &pp, &pErr, e, (char **)&txt );
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = rc;
+ if ( txt ) {
+ rs->sr_text = txt;
+ free_txt = 1;
+ } else {
+ rs->sr_text = "Password fails quality checking policy";
+ }
+ goto return_results;
+ }
+ }
+
+ /* If pwdInHistory is zero, passwords may be reused */
+ if (pa && pp.pwdInHistory > 0) {
+ /*
+ * Last check - the password history.
+ */
+ /* FIXME: no access checking? */
+ if (slap_passwd_check( op, NULL, pa, bv, &txt ) == LDAP_SUCCESS) {
+ /*
+ * This is bad - it means that the user is attempting
+ * to set the password to the same as the old one.
+ */
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION;
+ rs->sr_text = "Password is not being changed from existing value";
+ pErr = PP_passwordInHistory;
+ goto return_results;
+ }
+
+ /*
+ * 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) {
+ 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_malloc( 2 * sizeof( struct berval ) );
+ ber_dupbv( &mods->sml_values[0], &timestamp );
+ BER_BVZERO( &mods->sml_values[1] );
+ assert( !BER_BVISNULL( &mods->sml_values[0] ) );
+ } else if (attr_find(e->e_attrs, ad_pwdChangedTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ }
+ if (mods) {
+ mods->sml_desc = ad_pwdChangedTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ }
+
+ if (!got_del_grace && attr_find(e->e_attrs, ad_pwdGraceUseTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdGraceUseTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_del_lock && attr_find(e->e_attrs, ad_pwdAccountLockedTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdAccountLockedTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdFailureTime;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ /* Delete the pwdReset attribute, since it's being reset */
+ if ((zapReset) && (attr_find(e->e_attrs, ad_pwdReset ))) {
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_desc = ad_pwdReset;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+
+ if (!got_history && pp.pwdInHistory > 0) {
+ if (hsize >= pp.pwdInHistory) {
+ /*
+ * We use the >= operator, since we are going to add
+ * the existing password attribute value into the
+ * history - thus the cardinality of history values is
+ * about to rise by one.
+ *
+ * If this would push it over the limit of history
+ * values (remembering - the password policy could have
+ * changed since the password was last altered), we must
+ * delete at least 1 value from the pwdHistory list.
+ *
+ * In fact, we delete '(#pwdHistory attrs - max pwd
+ * history length) + 1' values, starting with the oldest.
+ * This is easily evaluated, since the linked list is
+ * created in ascending time order.
+ */
+ mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 );
+ mods->sml_op = LDAP_MOD_DELETE;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_desc = ad_pwdHistory;
+ mods->sml_numvals = hsize - pp.pwdInHistory + 1;
+ mods->sml_values = ch_calloc( sizeof( struct berval ),
+ hsize - pp.pwdInHistory + 2 );
+ BER_BVZERO( &mods->sml_values[ hsize - pp.pwdInHistory + 1 ] );
+ for(i=0,p=tl; i < (hsize - pp.pwdInHistory + 1); i++, p=p->next) {
+ BER_BVZERO( &mods->sml_values[i] );
+ ber_dupbv( &(mods->sml_values[i]), &p->bv );
+ }
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+ }
+ free_pwd_history_list( &tl );
+
+ /*
+ * Now add the existing password into the history list.
+ * This will be executed even if the operation is to delete
+ * the password entirely.
+ *
+ * This isn't in the spec explicitly, but it seems to make
+ * sense that the password history list is the list of all
+ * previous passwords - even if they were deleted. Thus, if
+ * someone tries to add a historical password at some future
+ * point, it will fail.
+ */
+ if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL) {
+ mods = (Modifications *) ch_malloc( sizeof( Modifications ) );
+ mods->sml_op = LDAP_MOD_ADD;
+ mods->sml_flags = SLAP_MOD_INTERNAL;
+ mods->sml_type.bv_val = NULL;
+ mods->sml_desc = ad_pwdHistory;
+ mods->sml_nvalues = NULL;
+ mods->sml_numvals = 1;
+ mods->sml_values = ch_calloc( sizeof( struct berval ), 2 );
+ mods->sml_values[ 1 ].bv_val = NULL;
+ mods->sml_values[ 1 ].bv_len = 0;
+ make_pwd_history_value( timebuf, &mods->sml_values[0], pa );
+ mods->sml_next = NULL;
+ modtail->sml_next = mods;
+ modtail = mods;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ "ppolicy_modify: password attr lookup failed\n", 0, 0, 0 );
+ }
+ }
+
+ /*
+ * Controversial bit here. If the new password isn't hashed
+ * (ie, is cleartext), we probably should hash it according
+ * to the default hash. The reason for this is that we want
+ * to use the policy if possible, but if we hash the password
+ * before, then we're going to run into trouble when it
+ * comes time to check the password.
+ *
+ * Now, the right thing to do is to use the extended password
+ * modify operation, but not all software can do this,
+ * therefore it makes sense to hash the new password, now
+ * we know it passes the policy requirements.
+ *
+ * Of course, if the password is already hashed, then we
+ * leave it alone.
+ */
+
+ if ((pi->hash_passwords) && (addmod) && !newpw.bv_val &&
+ (password_scheme( &(addmod->sml_values[0]), NULL ) != LDAP_SUCCESS))
+ {
+ struct berval hpw, bv;
+
+ slap_passwd_hash( &(addmod->sml_values[0]), &hpw, &txt );
+ if (hpw.bv_val == NULL) {
+ /*
+ * hashing didn't work. Emit an error.
+ */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = txt;
+ goto return_results;
+ }
+ bv = addmod->sml_values[0];
+ /* clear and discard the clear password */
+ memset(bv.bv_val, 0, bv.bv_len);
+ ber_memfree(bv.bv_val);
+ addmod->sml_values[0] = hpw;
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ return SLAP_CB_CONTINUE;
+
+return_results:
+ free_pwd_history_list( &tl );
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, e );
+ if ( send_ctrl ) {
+ ctrl = create_passcontrol( op, -1, -1, pErr );
+ oldctrls = add_passcontrol( op, rs, ctrl );
+ }
+ send_ldap_result( op, rs );
+ if ( free_txt ) {
+ free( (char *)txt );
+ rs->sr_text = NULL;
+ }
+ if ( send_ctrl ) {
+ if ( is_pwdexop ) {
+ if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) {
+ op->o_tmpfree( oldctrls, op->o_tmpmemctx );
+ }
+ oldctrls = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+
+ } else {
+ ctrls_cleanup( op, rs, oldctrls );
+ }
+ }
+ return rs->sr_err;
+}
+
+static int
+ppolicy_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "passwordPolicyRequest control value not absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ op->o_ctrlflag[ppolicy_cid] = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+attrPretty(
+ Syntax *syntax,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ AttributeDescription *ad = NULL;
+ const char *err;
+ int code;
+
+ code = slap_bv2ad( val, &ad, &err );
+ if ( !code ) {
+ ber_dupbv_x( out, &ad->ad_type->sat_cname, ctx );
+ }
+ return code;
+}
+
+static int
+attrNormalize(
+ slap_mask_t use,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *val,
+ struct berval *out,
+ void *ctx )
+{
+ AttributeDescription *ad = NULL;
+ const char *err;
+ int code;
+
+ code = slap_bv2ad( val, &ad, &err );
+ if ( !code ) {
+ ber_str2bv_x( ad->ad_type->sat_oid, 0, 1, out, ctx );
+ }
+ return code;
+}
+
+static int
+ppolicy_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ /* do not allow slapo-ppolicy to be global by now (ITS#5858) */
+ if ( cr ){
+ snprintf( cr->msg, sizeof(cr->msg),
+ "slapo-ppolicy cannot be global" );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 );
+ }
+ return 1;
+ }
+
+ /* Has User Schema been initialized yet? */
+ if ( !pwd_UsSchema[0].ad[0] ) {
+ const char *err;
+ int i, code;
+
+ for (i=0; pwd_UsSchema[i].def; i++) {
+ code = slap_str2ad( pwd_UsSchema[i].def, pwd_UsSchema[i].ad, &err );
+ if ( code ) {
+ if ( cr ){
+ snprintf( cr->msg, sizeof(cr->msg),
+ "User Schema load failed for attribute \"%s\". Error code %d: %s",
+ pwd_UsSchema[i].def, code, err );
+ Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg, 0, 0 );
+ }
+ return code;
+ }
+ }
+ {
+ Syntax *syn;
+ MatchingRule *mr;
+
+ syn = ch_malloc( sizeof( Syntax ));
+ *syn = *ad_pwdAttribute->ad_type->sat_syntax;
+ syn->ssyn_pretty = attrPretty;
+ ad_pwdAttribute->ad_type->sat_syntax = syn;
+
+ mr = ch_malloc( sizeof( MatchingRule ));
+ *mr = *ad_pwdAttribute->ad_type->sat_equality;
+ mr->smr_normalize = attrNormalize;
+ ad_pwdAttribute->ad_type->sat_equality = mr;
+ }
+ }
+
+ on->on_bi.bi_private = ch_calloc( sizeof(pp_info), 1 );
+
+ if ( dtblsize && !pwcons ) {
+ /* accommodate for c_conn_idx == -1 */
+ pwcons = ch_calloc( sizeof(pw_conn), dtblsize + 1 );
+ pwcons++;
+ }
+
+ ov_count++;
+
+ return 0;
+}
+
+static int
+ppolicy_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
+}
+
+static int
+ppolicy_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST );
+#endif /* SLAP_CONFIG_DELETE */
+
+ return 0;
+}
+
+static int
+ppolicy_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ pp_info *pi = on->on_bi.bi_private;
+
+ on->on_bi.bi_private = NULL;
+ free( pi->def_policy.bv_val );
+ free( pi );
+
+ ov_count--;
+ if ( ov_count <=0 && pwcons ) {
+ pw_conn *pwc = pwcons;
+ pwcons = NULL;
+ pwc--;
+ ch_free( pwc );
+ }
+ return 0;
+}
+
+static char *extops[] = {
+ LDAP_EXOP_MODIFY_PASSWD,
+ NULL
+};
+
+static slap_overinst ppolicy;
+
+int ppolicy_initialize()
+{
+ int i, code;
+
+ for (i=0; pwd_OpSchema[i].def; i++) {
+ code = register_at( pwd_OpSchema[i].def, pwd_OpSchema[i].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "ppolicy_initialize: register_at failed\n", 0, 0, 0 );
+ return code;
+ }
+ /* Allow Manager to set these as needed */
+ if ( is_at_no_user_mod( (*pwd_OpSchema[i].ad)->ad_type )) {
+ (*pwd_OpSchema[i].ad)->ad_type->sat_flags |=
+ SLAP_AT_MANAGEABLE;
+ }
+ }
+
+ code = register_supported_control( LDAP_CONTROL_PASSWORDPOLICYREQUEST,
+ SLAP_CTRL_ADD|SLAP_CTRL_BIND|SLAP_CTRL_MODIFY|SLAP_CTRL_HIDE, extops,
+ ppolicy_parseCtrl, &ppolicy_cid );
+ if ( code != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code, 0, 0 );
+ return code;
+ }
+
+ ldap_pvt_thread_mutex_init( &chk_syntax_mutex );
+
+ ppolicy.on_bi.bi_type = "ppolicy";
+ ppolicy.on_bi.bi_db_init = ppolicy_db_init;
+ ppolicy.on_bi.bi_db_open = ppolicy_db_open;
+ ppolicy.on_bi.bi_db_close = ppolicy_db_close;
+ ppolicy.on_bi.bi_db_destroy = ppolicy_db_destroy;
+
+ ppolicy.on_bi.bi_op_add = ppolicy_add;
+ ppolicy.on_bi.bi_op_bind = ppolicy_bind;
+ ppolicy.on_bi.bi_op_compare = ppolicy_compare;
+ ppolicy.on_bi.bi_op_delete = ppolicy_restrict;
+ ppolicy.on_bi.bi_op_modify = ppolicy_modify;
+ ppolicy.on_bi.bi_op_search = ppolicy_restrict;
+ ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy;
+
+ ppolicy.on_bi.bi_cf_ocs = ppolicyocs;
+ code = config_register_schema( ppolicycfg, ppolicyocs );
+ if ( code ) return code;
+
+ return overlay_register( &ppolicy );
+}
+
+#if SLAPD_OVER_PPOLICY == SLAPD_MOD_DYNAMIC
+int init_module(int argc, char *argv[]) {
+ return ppolicy_initialize();
+}
+#endif
+
+#endif /* defined(SLAPD_OVER_PPOLICY) */
diff --git a/servers/slapd/overlays/refint.c b/servers/slapd/overlays/refint.c
new file mode 100644
index 0000000..4657c8c
--- /dev/null
+++ b/servers/slapd/overlays/refint.c
@@ -0,0 +1,1087 @@
+/* refint.c - referential integrity module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2004 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+/* This module maintains referential integrity for a set of
+ * DN-valued attributes by searching for all references to a given
+ * DN whenever the DN is changed or its entry is deleted, and making
+ * the appropriate update.
+ *
+ * Updates are performed using the database rootdn in a separate task
+ * to allow the original operation to complete immediately.
+ */
+
+#ifdef SLAPD_OVER_REFINT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+#include "ldap_rq.h"
+
+static slap_overinst refint;
+
+/* The DN to use in the ModifiersName for all refint updates */
+static BerValue refint_dn = BER_BVC("cn=Referential Integrity Overlay");
+static BerValue refint_ndn = BER_BVC("cn=referential integrity overlay");
+
+typedef struct refint_attrs_s {
+ struct refint_attrs_s *next;
+ AttributeDescription *attr;
+ BerVarray old_vals;
+ BerVarray old_nvals;
+ BerVarray new_vals;
+ BerVarray new_nvals;
+ int ra_numvals;
+ int dont_empty;
+} refint_attrs;
+
+typedef struct dependents_s {
+ struct dependents_s *next;
+ BerValue dn; /* target dn */
+ BerValue ndn;
+ refint_attrs *attrs;
+} dependent_data;
+
+typedef struct refint_q {
+ struct refint_q *next;
+ struct refint_data_s *rdata;
+ dependent_data *attrs; /* entries and attrs returned from callback */
+ BackendDB *db;
+ BerValue olddn;
+ BerValue oldndn;
+ BerValue newdn;
+ BerValue newndn;
+ int do_sub;
+} refint_q;
+
+typedef struct refint_data_s {
+ struct refint_attrs_s *attrs; /* list of known attrs */
+ BerValue dn; /* basedn in parent, */
+ BerValue nothing; /* the nothing value, if needed */
+ BerValue nnothing; /* normalized nothingness */
+ BerValue refint_dn; /* modifier's name */
+ BerValue refint_ndn; /* normalized modifier's name */
+ struct re_s *qtask;
+ refint_q *qhead;
+ refint_q *qtail;
+ BackendDB *db;
+ ldap_pvt_thread_mutex_t qmutex;
+} refint_data;
+
+typedef struct refint_pre_s {
+ slap_overinst *on;
+ int do_sub;
+} refint_pre;
+
+#define RUNQ_INTERVAL 36000 /* a long time */
+
+static MatchingRule *mr_dnSubtreeMatch;
+
+enum {
+ REFINT_ATTRS = 1,
+ REFINT_NOTHING,
+ REFINT_MODIFIERSNAME
+};
+
+static ConfigDriver refint_cf_gen;
+
+static ConfigTable refintcfg[] = {
+ { "refint_attributes", "attribute...", 2, 0, 0,
+ ARG_MAGIC|REFINT_ATTRS, refint_cf_gen,
+ "( OLcfgOvAt:11.1 NAME 'olcRefintAttribute' "
+ "DESC 'Attributes for referential integrity' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "refint_nothing", "string", 2, 2, 0,
+ ARG_DN|ARG_MAGIC|REFINT_NOTHING, refint_cf_gen,
+ "( OLcfgOvAt:11.2 NAME 'olcRefintNothing' "
+ "DESC 'Replacement DN to supply when needed' "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "refint_modifiersName", "DN", 2, 2, 0,
+ ARG_DN|ARG_MAGIC|REFINT_MODIFIERSNAME, refint_cf_gen,
+ "( OLcfgOvAt:11.3 NAME 'olcRefintModifiersName' "
+ "DESC 'The DN to use as modifiersName' "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs refintocs[] = {
+ { "( OLcfgOvOc:11.1 "
+ "NAME 'olcRefintConfig' "
+ "DESC 'Referential integrity configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcRefintAttribute "
+ "$ olcRefintNothing "
+ "$ olcRefintModifiersName "
+ ") )",
+ Cft_Overlay, refintcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+refint_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ refint_data *dd = (refint_data *)on->on_bi.bi_private;
+ refint_attrs *ip, *pip, **pipp = NULL;
+ AttributeDescription *ad;
+ const char *text;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ ip = dd->attrs;
+ while ( ip ) {
+ value_add_one( &c->rvalue_vals,
+ &ip->attr->ad_cname );
+ ip = ip->next;
+ }
+ rc = 0;
+ break;
+ case REFINT_NOTHING:
+ if ( !BER_BVISEMPTY( &dd->nothing )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &dd->nothing );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &dd->nnothing );
+ return rc;
+ }
+ rc = 0;
+ break;
+ case REFINT_MODIFIERSNAME:
+ if ( !BER_BVISEMPTY( &dd->refint_dn )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &dd->refint_dn );
+ if ( rc ) return rc;
+ rc = value_add_one( &c->rvalue_nvals,
+ &dd->refint_ndn );
+ return rc;
+ }
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ pipp = &dd->attrs;
+ if ( c->valx < 0 ) {
+ ip = *pipp;
+ *pipp = NULL;
+ while ( ip ) {
+ pip = ip;
+ ip = ip->next;
+ ch_free ( pip );
+ }
+ } else {
+ /* delete from linked list */
+ for ( i=0; i < c->valx; ++i ) {
+ pipp = &(*pipp)->next;
+ }
+ ip = *pipp;
+ *pipp = (*pipp)->next;
+
+ /* AttributeDescriptions are global so
+ * shouldn't be freed here... */
+ ch_free ( ip );
+ }
+ rc = 0;
+ break;
+ case REFINT_NOTHING:
+ ch_free( dd->nothing.bv_val );
+ ch_free( dd->nnothing.bv_val );
+ BER_BVZERO( &dd->nothing );
+ BER_BVZERO( &dd->nnothing );
+ rc = 0;
+ break;
+ case REFINT_MODIFIERSNAME:
+ ch_free( dd->refint_dn.bv_val );
+ ch_free( dd->refint_ndn.bv_val );
+ BER_BVZERO( &dd->refint_dn );
+ BER_BVZERO( &dd->refint_ndn );
+ rc = 0;
+ break;
+ default:
+ abort ();
+ }
+ break;
+ case SLAP_CONFIG_ADD:
+ /* fallthrough to LDAP_MOD_ADD */
+ case LDAP_MOD_ADD:
+ switch ( c->type ) {
+ case REFINT_ATTRS:
+ rc = 0;
+ for ( i=1; i < c->argc; ++i ) {
+ ad = NULL;
+ if ( slap_str2ad ( c->argv[i], &ad, &text )
+ == LDAP_SUCCESS) {
+ ip = ch_malloc (
+ sizeof ( refint_attrs ) );
+ ip->attr = ad;
+ ip->next = dd->attrs;
+ dd->attrs = ip;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s <%s>: %s", c->argv[0], c->argv[i], text );
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ rc = ARG_BAD_CONF;
+ }
+ }
+ break;
+ case REFINT_NOTHING:
+ if ( !BER_BVISNULL( &c->value_ndn )) {
+ ch_free ( dd->nothing.bv_val );
+ ch_free ( dd->nnothing.bv_val );
+ dd->nothing = c->value_dn;
+ dd->nnothing = c->value_ndn;
+ rc = 0;
+ } else {
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ case REFINT_MODIFIERSNAME:
+ if ( !BER_BVISNULL( &c->value_ndn )) {
+ ch_free( dd->refint_dn.bv_val );
+ ch_free( dd->refint_ndn.bv_val );
+ dd->refint_dn = c->value_dn;
+ dd->refint_ndn = c->value_ndn;
+ rc = 0;
+ } else {
+ rc = ARG_BAD_CONF;
+ }
+ break;
+ default:
+ abort ();
+ }
+ break;
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+/*
+** allocate new refint_data;
+** store in on_bi.bi_private;
+**
+*/
+
+static int
+refint_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ refint_data *id = ch_calloc(1,sizeof(refint_data));
+
+ on->on_bi.bi_private = id;
+ ldap_pvt_thread_mutex_init( &id->qmutex );
+ return(0);
+}
+
+static int
+refint_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+
+ if ( on->on_bi.bi_private ) {
+ refint_data *id = on->on_bi.bi_private;
+ refint_attrs *ii, *ij;
+
+ on->on_bi.bi_private = NULL;
+ ldap_pvt_thread_mutex_destroy( &id->qmutex );
+
+ for(ii = id->attrs; ii; ii = ij) {
+ ij = ii->next;
+ ch_free(ii);
+ }
+
+ ch_free( id->nothing.bv_val );
+ BER_BVZERO( &id->nothing );
+ ch_free( id->nnothing.bv_val );
+ BER_BVZERO( &id->nnothing );
+
+ ch_free( id );
+ }
+ return(0);
+}
+
+/*
+** initialize, copy basedn if not already set
+**
+*/
+
+static int
+refint_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+
+ if ( BER_BVISNULL( &id->dn )) {
+ if ( BER_BVISNULL( &be->be_nsuffix[0] ))
+ return -1;
+ ber_dupbv( &id->dn, &be->be_nsuffix[0] );
+ }
+ if ( BER_BVISNULL( &id->refint_dn ) ) {
+ ber_dupbv( &id->refint_dn, &refint_dn );
+ ber_dupbv( &id->refint_ndn, &refint_ndn );
+ }
+
+ /*
+ ** find the backend that matches our configured basedn;
+ ** make sure it exists and has search and modify methods;
+ **
+ */
+
+ if ( on->on_info->oi_origdb != frontendDB ) {
+ BackendDB *db = select_backend(&id->dn, 1);
+
+ if ( db ) {
+ BackendInfo *bi;
+ if ( db == be )
+ bi = on->on_info->oi_orig;
+ else
+ bi = db->bd_info;
+ if ( !bi->bi_op_search || !bi->bi_op_modify ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "refint_response: backend missing search and/or modify\n",
+ 0, 0, 0 );
+ return -1;
+ }
+ id->db = db;
+ } else {
+ Debug( LDAP_DEBUG_CONFIG,
+ "refint_response: no backend for our baseDN %s??\n",
+ id->dn.bv_val, 0, 0 );
+ return -1;
+ }
+ }
+ return(0);
+}
+
+
+/*
+** free our basedn;
+** free our refintdn
+**
+*/
+
+static int
+refint_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+
+ ch_free( id->dn.bv_val );
+ BER_BVZERO( &id->dn );
+ ch_free( id->refint_dn.bv_val );
+ BER_BVZERO( &id->refint_dn );
+ ch_free( id->refint_ndn.bv_val );
+ BER_BVZERO( &id->refint_ndn );
+
+ return(0);
+}
+
+/*
+** search callback
+** generates a list of Attributes from search results
+*/
+
+static int
+refint_search_cb(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ Attribute *a;
+ BerVarray b = NULL;
+ refint_q *rq = op->o_callback->sc_private;
+ refint_data *dd = rq->rdata;
+ refint_attrs *ia, *da = dd->attrs, *na;
+ dependent_data *ip;
+ int i;
+
+ Debug(LDAP_DEBUG_TRACE, "refint_search_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING", 0, 0);
+
+ if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0);
+
+ /*
+ ** foreach configured attribute type:
+ ** if this attr exists in the search result,
+ ** and it has a value matching the target:
+ ** allocate an attr;
+ ** save/build DNs of any subordinate matches;
+ ** handle special case: found exact + subordinate match;
+ ** handle olcRefintNothing;
+ **
+ */
+
+ ip = op->o_tmpalloc(sizeof(dependent_data), op->o_tmpmemctx );
+ ber_dupbv_x( &ip->dn, &rs->sr_entry->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &ip->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx );
+ ip->next = rq->attrs;
+ rq->attrs = ip;
+ ip->attrs = NULL;
+ for(ia = da; ia; ia = ia->next) {
+ if ( (a = attr_find(rs->sr_entry->e_attrs, ia->attr) ) ) {
+ int exact = -1, is_exact;
+
+ na = NULL;
+
+ /* Are we doing subtree matching or simple equality? */
+ if ( rq->do_sub ) {
+ for(i = 0, b = a->a_nvals; b[i].bv_val; i++) {
+ if(dnIsSuffix(&b[i], &rq->oldndn)) {
+ is_exact = b[i].bv_len == rq->oldndn.bv_len;
+
+ /* Paranoia: skip buggy duplicate exact match,
+ * it would break ra_numvals
+ */
+ if ( is_exact && exact >= 0 )
+ continue;
+
+ /* first match? create structure */
+ if ( na == NULL ) {
+ na = op->o_tmpcalloc( 1,
+ sizeof( refint_attrs ),
+ op->o_tmpmemctx );
+ na->next = ip->attrs;
+ ip->attrs = na;
+ na->attr = ia->attr;
+ }
+
+ na->ra_numvals++;
+
+ if ( is_exact ) {
+ /* Exact match: refint_repair will deduce the DNs */
+ exact = i;
+
+ } else {
+ /* Subordinate match */
+ struct berval newsub, newdn, olddn, oldndn;
+
+ /* Save old DN */
+ ber_dupbv_x( &olddn, &a->a_vals[i], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_vals, &olddn, op->o_tmpmemctx );
+
+ ber_dupbv_x( &oldndn, &a->a_nvals[i], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_nvals, &oldndn, op->o_tmpmemctx );
+
+ if ( BER_BVISEMPTY( &rq->newdn ) )
+ continue;
+
+ /* Rename subordinate match: Build new DN */
+ newsub = a->a_vals[i];
+ newsub.bv_len -= rq->olddn.bv_len + 1;
+ build_new_dn( &newdn, &rq->newdn, &newsub, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_vals, &newdn, op->o_tmpmemctx );
+
+ newsub = a->a_nvals[i];
+ newsub.bv_len -= rq->oldndn.bv_len + 1;
+ build_new_dn( &newdn, &rq->newndn, &newsub, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_nvals, &newdn, op->o_tmpmemctx );
+ }
+ }
+ }
+
+ /* If we got both subordinate and exact match,
+ * refint_repair won't special-case the exact match */
+ if ( exact >= 0 && na->old_vals ) {
+ struct berval dn;
+
+ ber_dupbv_x( &dn, &a->a_vals[exact], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_vals, &dn, op->o_tmpmemctx );
+ ber_dupbv_x( &dn, &a->a_nvals[exact], op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->old_nvals, &dn, op->o_tmpmemctx );
+
+ if ( !BER_BVISEMPTY( &rq->newdn ) ) {
+ ber_dupbv_x( &dn, &rq->newdn, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_vals, &dn, op->o_tmpmemctx );
+ ber_dupbv_x( &dn, &rq->newndn, op->o_tmpmemctx );
+ ber_bvarray_add_x( &na->new_nvals, &dn, op->o_tmpmemctx );
+ }
+ }
+ } else {
+ /* entry has no children, just equality matching */
+ is_exact = attr_valfind( a,
+ SLAP_MR_EQUALITY|SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH|
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, &rq->oldndn, &i, NULL );
+ if ( is_exact == LDAP_SUCCESS ) {
+ na = op->o_tmpcalloc( 1,
+ sizeof( refint_attrs ),
+ op->o_tmpmemctx );
+ na->next = ip->attrs;
+ ip->attrs = na;
+ na->attr = ia->attr;
+ na->ra_numvals = 1;
+ }
+ }
+
+ /* Deleting/replacing all values and a nothing DN is configured? */
+ if ( na && na->ra_numvals == a->a_numvals && !BER_BVISNULL(&dd->nothing) )
+ na->dont_empty = 1;
+
+ Debug( LDAP_DEBUG_TRACE, "refint_search_cb: %s: %s (#%d)\n",
+ a->a_desc->ad_cname.bv_val, rq->olddn.bv_val, i );
+ }
+ }
+
+ return(0);
+}
+
+static int
+refint_repair(
+ Operation *op,
+ refint_data *id,
+ refint_q *rq )
+{
+ dependent_data *dp;
+ SlapReply rs = {REP_RESULT};
+ Operation op2;
+ unsigned long opid;
+ int rc;
+ int cache;
+
+ op->o_callback->sc_response = refint_search_cb;
+ op->o_req_dn = op->o_bd->be_suffix[ 0 ];
+ op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ];
+ op->o_dn = op->o_bd->be_rootdn;
+ op->o_ndn = op->o_bd->be_rootndn;
+ cache = op->o_do_not_cache;
+ op->o_do_not_cache = 1;
+
+ /* search */
+ rc = op->o_bd->be_search( op, &rs );
+ op->o_do_not_cache = cache;
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: search failed: %d\n",
+ rc, 0, 0 );
+ return rc;
+ }
+
+ /* safety? paranoid just in case */
+ if ( op->o_callback->sc_private == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: callback wiped out sc_private?!\n",
+ 0, 0, 0 );
+ return 0;
+ }
+
+ /* Set up the Modify requests */
+ op->o_callback->sc_response = &slap_null_cb;
+
+ /*
+ * [our search callback builds a list of attrs]
+ * foreach attr:
+ * make sure its dn has a backend;
+ * build Modification* chain;
+ * call the backend modify function;
+ *
+ */
+
+ opid = op->o_opid;
+ op2 = *op;
+ for ( dp = rq->attrs; dp; dp = dp->next ) {
+ SlapReply rs2 = {REP_RESULT};
+ refint_attrs *ra;
+ Modifications *m;
+
+ if ( dp->attrs == NULL ) continue; /* TODO: Is this needed? */
+
+ op2.o_bd = select_backend( &dp->ndn, 1 );
+ if ( !op2.o_bd ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: no backend for DN %s!\n",
+ dp->dn.bv_val, 0, 0 );
+ continue;
+ }
+ op2.o_tag = LDAP_REQ_MODIFY;
+ op2.orm_modlist = NULL;
+ op2.o_req_dn = dp->dn;
+ op2.o_req_ndn = dp->ndn;
+ /* Internal ops, never replicate these */
+ op2.orm_no_opattrs = 1;
+ op2.o_dont_replicate = 1;
+ op2.o_opid = 0;
+
+ /* Set our ModifiersName */
+ if ( SLAP_LASTMOD( op->o_bd ) ) {
+ m = op2.o_tmpalloc( sizeof(Modifications) +
+ 4*sizeof(BerValue), op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_REPLACE;
+ m->sml_flags = SLAP_MOD_INTERNAL;
+ m->sml_desc = slap_schema.si_ad_modifiersName;
+ m->sml_type = m->sml_desc->ad_cname;
+ m->sml_numvals = 1;
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ m->sml_values[0] = id->refint_dn;
+ m->sml_nvalues[0] = id->refint_ndn;
+ }
+
+ for ( ra = dp->attrs; ra; ra = ra->next ) {
+ size_t len;
+
+ /* Add values */
+ if ( ra->dont_empty || !BER_BVISEMPTY( &rq->newdn ) ) {
+ len = sizeof(Modifications);
+
+ if ( ra->new_vals == NULL ) {
+ len += 4*sizeof(BerValue);
+ }
+
+ m = op2.o_tmpalloc( len, op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_ADD;
+ m->sml_flags = 0;
+ m->sml_desc = ra->attr;
+ m->sml_type = ra->attr->ad_cname;
+ if ( ra->new_vals == NULL ) {
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ m->sml_numvals = 1;
+ if ( BER_BVISEMPTY( &rq->newdn ) ) {
+ m->sml_values[0] = id->nothing;
+ m->sml_nvalues[0] = id->nnothing;
+ } else {
+ m->sml_values[0] = rq->newdn;
+ m->sml_nvalues[0] = rq->newndn;
+ }
+ } else {
+ m->sml_values = ra->new_vals;
+ m->sml_nvalues = ra->new_nvals;
+ m->sml_numvals = ra->ra_numvals;
+ }
+ }
+
+ /* Delete values */
+ len = sizeof(Modifications);
+ if ( ra->old_vals == NULL ) {
+ len += 4*sizeof(BerValue);
+ }
+ m = op2.o_tmpalloc( len, op2.o_tmpmemctx );
+ m->sml_next = op2.orm_modlist;
+ op2.orm_modlist = m;
+ m->sml_op = LDAP_MOD_DELETE;
+ m->sml_flags = 0;
+ m->sml_desc = ra->attr;
+ m->sml_type = ra->attr->ad_cname;
+ if ( ra->old_vals == NULL ) {
+ m->sml_numvals = 1;
+ m->sml_values = (BerVarray)(m+1);
+ m->sml_nvalues = m->sml_values+2;
+ m->sml_values[0] = rq->olddn;
+ m->sml_nvalues[0] = rq->oldndn;
+ BER_BVZERO( &m->sml_values[1] );
+ BER_BVZERO( &m->sml_nvalues[1] );
+ } else {
+ m->sml_values = ra->old_vals;
+ m->sml_nvalues = ra->old_nvals;
+ m->sml_numvals = ra->ra_numvals;
+ }
+ }
+
+ op2.o_dn = op2.o_bd->be_rootdn;
+ op2.o_ndn = op2.o_bd->be_rootndn;
+ rc = op2.o_bd->be_modify( &op2, &rs2 );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "refint_repair: dependent modify failed: %d\n",
+ rs2.sr_err, 0, 0 );
+ }
+
+ while ( ( m = op2.orm_modlist ) ) {
+ op2.orm_modlist = m->sml_next;
+ op2.o_tmpfree( m, op2.o_tmpmemctx );
+ }
+ }
+ op2.o_opid = opid;
+
+ return 0;
+}
+
+static void *
+refint_qtask( void *ctx, void *arg )
+{
+ struct re_s *rtask = arg;
+ refint_data *id = rtask->arg;
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ Filter ftop, *fptr;
+ refint_q *rq;
+ refint_attrs *ip;
+ int pausing = 0, rc = 0;
+
+ connection_fake_init( &conn, &opbuf, ctx );
+ op = &opbuf.ob_op;
+
+ /*
+ ** build a search filter for all configured attributes;
+ ** populate our Operation;
+ ** pass our data (attr list, dn) to backend via sc_private;
+ ** call the backend search function;
+ ** nb: (|(one=thing)) is valid, but do smart formatting anyway;
+ ** nb: 16 is arbitrarily a dozen or so extra bytes;
+ **
+ */
+
+ ftop.f_choice = LDAP_FILTER_OR;
+ ftop.f_next = NULL;
+ ftop.f_or = NULL;
+ op->ors_filter = &ftop;
+ for(ip = id->attrs; ip; ip = ip->next) {
+ /* this filter can be either EQUALITY or EXT */
+ fptr = op->o_tmpcalloc( sizeof(Filter) + sizeof(MatchingRuleAssertion),
+ 1, op->o_tmpmemctx );
+ fptr->f_mra = (MatchingRuleAssertion *)(fptr+1);
+ fptr->f_mr_rule = mr_dnSubtreeMatch;
+ fptr->f_mr_rule_text = mr_dnSubtreeMatch->smr_bvoid;
+ fptr->f_mr_desc = ip->attr;
+ fptr->f_mr_dnattrs = 0;
+ fptr->f_next = ftop.f_or;
+ ftop.f_or = fptr;
+ }
+
+ for (;;) {
+ dependent_data *dp, *dp_next;
+ refint_attrs *ra, *ra_next;
+
+ if ( ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) {
+ pausing = 1;
+ break;
+ }
+
+ /* Dequeue an op */
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ rq = id->qhead;
+ if ( rq ) {
+ id->qhead = rq->next;
+ if ( !id->qhead )
+ id->qtail = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+ if ( !rq )
+ break;
+
+ for (fptr = ftop.f_or; fptr; fptr = fptr->f_next ) {
+ fptr->f_mr_value = rq->oldndn;
+ /* Use (attr:dnSubtreeMatch:=value) to catch subtree rename
+ * and subtree delete where supported */
+ if (rq->do_sub)
+ fptr->f_choice = LDAP_FILTER_EXT;
+ else
+ fptr->f_choice = LDAP_FILTER_EQUALITY;
+ }
+
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+
+ /* callback gets the searched dn instead */
+ cb.sc_private = rq;
+ cb.sc_response = refint_search_cb;
+ op->o_callback = &cb;
+ op->o_tag = LDAP_REQ_SEARCH;
+ op->ors_scope = LDAP_SCOPE_SUBTREE;
+ op->ors_deref = LDAP_DEREF_NEVER;
+ op->ors_limit = NULL;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_tlimit = SLAP_NO_LIMIT;
+
+ /* no attrs! */
+ op->ors_attrs = slap_anlist_no_attrs;
+
+ slap_op_time( &op->o_time, &op->o_tincr );
+
+ if ( rq->db != NULL ) {
+ op->o_bd = rq->db;
+ rc = refint_repair( op, id, rq );
+
+ } else {
+ BackendDB *be;
+
+ LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) {
+ /* we may want to skip cn=config */
+ if ( be == LDAP_STAILQ_FIRST(&backendDB) ) {
+ continue;
+ }
+
+ if ( be->be_search && be->be_modify ) {
+ op->o_bd = be;
+ rc = refint_repair( op, id, rq );
+ }
+ }
+ }
+
+ for ( dp = rq->attrs; dp; dp = dp_next ) {
+ dp_next = dp->next;
+ for ( ra = dp->attrs; ra; ra = ra_next ) {
+ ra_next = ra->next;
+ ber_bvarray_free_x( ra->new_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->new_vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->old_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( ra->old_vals, op->o_tmpmemctx );
+ op->o_tmpfree( ra, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( dp->ndn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( dp->dn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( dp, op->o_tmpmemctx );
+ }
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ if ( rc == LDAP_BUSY ) {
+ pausing = 1;
+ /* re-queue this op */
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ rq->next = id->qhead;
+ id->qhead = rq;
+ if ( !id->qtail )
+ id->qtail = rq;
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+ break;
+ }
+
+ if ( !BER_BVISNULL( &rq->newndn )) {
+ ch_free( rq->newndn.bv_val );
+ ch_free( rq->newdn.bv_val );
+ }
+ ch_free( rq->oldndn.bv_val );
+ ch_free( rq->olddn.bv_val );
+ ch_free( rq );
+ }
+
+ /* free filter */
+ for ( fptr = ftop.f_or; fptr; ) {
+ Filter *f_next = fptr->f_next;
+ op->o_tmpfree( fptr, op->o_tmpmemctx );
+ fptr = f_next;
+ }
+
+ /* wait until we get explicitly scheduled again */
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ ldap_pvt_runqueue_stoptask( &slapd_rq, id->qtask );
+ if ( pausing ) {
+ /* try to run again as soon as the pause is done */
+ id->qtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 );
+ id->qtask->interval.tv_sec = RUNQ_INTERVAL;
+ } else {
+ ldap_pvt_runqueue_resched( &slapd_rq,id->qtask, 1 );
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+ return NULL;
+}
+
+/*
+** refint_response
+** search for matching records and modify them
+*/
+
+static int
+refint_response(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ refint_pre *rp;
+ slap_overinst *on;
+ refint_data *id;
+ BerValue pdn;
+ refint_q *rq;
+ refint_attrs *ip;
+ int ac;
+
+ /* If the main op failed or is not a Delete or ModRdn, ignore it */
+ if (( op->o_tag != LDAP_REQ_DELETE && op->o_tag != LDAP_REQ_MODRDN ) ||
+ rs->sr_err != LDAP_SUCCESS )
+ return SLAP_CB_CONTINUE;
+
+ rp = op->o_callback->sc_private;
+ on = rp->on;
+ id = on->on_bi.bi_private;
+
+ rq = ch_calloc( 1, sizeof( refint_q ));
+ ber_dupbv( &rq->olddn, &op->o_req_dn );
+ ber_dupbv( &rq->oldndn, &op->o_req_ndn );
+ rq->db = id->db;
+ rq->rdata = id;
+ rq->do_sub = rp->do_sub;
+
+ if ( op->o_tag == LDAP_REQ_MODRDN ) {
+ if ( op->oq_modrdn.rs_newSup ) {
+ pdn = *op->oq_modrdn.rs_newSup;
+ } else {
+ dnParent( &op->o_req_dn, &pdn );
+ }
+ build_new_dn( &rq->newdn, &pdn, &op->orr_newrdn, NULL );
+ if ( op->oq_modrdn.rs_nnewSup ) {
+ pdn = *op->oq_modrdn.rs_nnewSup;
+ } else {
+ dnParent( &op->o_req_ndn, &pdn );
+ }
+ build_new_dn( &rq->newndn, &pdn, &op->orr_nnewrdn, NULL );
+ }
+
+ ldap_pvt_thread_mutex_lock( &id->qmutex );
+ if ( id->qtail ) {
+ id->qtail->next = rq;
+ } else {
+ id->qhead = rq;
+ }
+ id->qtail = rq;
+ ldap_pvt_thread_mutex_unlock( &id->qmutex );
+
+ ac = 0;
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( !id->qtask ) {
+ id->qtask = ldap_pvt_runqueue_insert( &slapd_rq, RUNQ_INTERVAL,
+ refint_qtask, id, "refint_qtask",
+ op->o_bd->be_suffix[0].bv_val );
+ ac = 1;
+ } else {
+ if ( !ldap_pvt_runqueue_isrunning( &slapd_rq, id->qtask ) &&
+ !id->qtask->next_sched.tv_sec ) {
+ id->qtask->interval.tv_sec = 0;
+ ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 );
+ id->qtask->interval.tv_sec = RUNQ_INTERVAL;
+ ac = 1;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ if ( ac )
+ slap_wake_listener();
+
+ return SLAP_CB_CONTINUE;
+}
+
+/* Check if the target entry exists and has children.
+ * Do nothing if target doesn't exist.
+ */
+static int
+refint_preop(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ refint_data *id = on->on_bi.bi_private;
+ Entry *e;
+ int rc;
+
+ /* are any attrs configured? */
+ if ( !id->attrs )
+ return SLAP_CB_CONTINUE;
+
+ rc = overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on );
+ if ( rc == LDAP_SUCCESS ) {
+ slap_callback *sc = op->o_tmpcalloc( 1,
+ sizeof(slap_callback)+sizeof(refint_pre), op->o_tmpmemctx );
+ refint_pre *rp = (refint_pre *)(sc+1);
+ rp->on = on;
+ rp->do_sub = 1; /* assume there are children */
+ if ( op->o_bd->be_has_subordinates ) {
+ int has = 0;
+ rc = op->o_bd->be_has_subordinates( op, e, &has );
+ /* there definitely are not children */
+ if ( rc == LDAP_SUCCESS && has == LDAP_COMPARE_FALSE )
+ rp->do_sub = 0;
+ }
+ overlay_entry_release_ov( op, e, 0, on );
+ sc->sc_response = refint_response;
+ sc->sc_private = rp;
+ sc->sc_next = op->o_callback;
+ op->o_callback = sc;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int refint_initialize() {
+ int rc;
+
+ mr_dnSubtreeMatch = mr_find( "dnSubtreeMatch" );
+ if ( mr_dnSubtreeMatch == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "refint_initialize: "
+ "unable to find MatchingRule 'dnSubtreeMatch'.\n",
+ 0, 0, 0 );
+ return 1;
+ }
+
+ /* statically declared just after the #includes at top */
+ refint.on_bi.bi_type = "refint";
+ refint.on_bi.bi_db_init = refint_db_init;
+ refint.on_bi.bi_db_destroy = refint_db_destroy;
+ refint.on_bi.bi_db_open = refint_open;
+ refint.on_bi.bi_db_close = refint_close;
+ refint.on_bi.bi_op_delete = refint_preop;
+ refint.on_bi.bi_op_modrdn = refint_preop;
+
+ refint.on_bi.bi_cf_ocs = refintocs;
+ rc = config_register_schema ( refintcfg, refintocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&refint));
+}
+
+#if SLAPD_OVER_REFINT == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return refint_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_REFINT */
diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c
new file mode 100644
index 0000000..a626a80
--- /dev/null
+++ b/servers/slapd/overlays/retcode.c
@@ -0,0 +1,1571 @@
+/* retcode.c - customizable response for client testing purposes */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Pierangelo Masarati for inclusion
+ * in OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RETCODE
+
+#include <stdio.h>
+
+#include <ac/unistd.h>
+#include <ac/string.h>
+#include <ac/ctype.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+#include "ldif.h"
+
+static slap_overinst retcode;
+
+static AttributeDescription *ad_errCode;
+static AttributeDescription *ad_errText;
+static AttributeDescription *ad_errOp;
+static AttributeDescription *ad_errSleepTime;
+static AttributeDescription *ad_errMatchedDN;
+static AttributeDescription *ad_errUnsolicitedOID;
+static AttributeDescription *ad_errUnsolicitedData;
+static AttributeDescription *ad_errDisconnect;
+
+static ObjectClass *oc_errAbsObject;
+static ObjectClass *oc_errObject;
+static ObjectClass *oc_errAuxObject;
+
+typedef enum retcode_op_e {
+ SN_DG_OP_NONE = 0x0000,
+ SN_DG_OP_ADD = 0x0001,
+ SN_DG_OP_BIND = 0x0002,
+ SN_DG_OP_COMPARE = 0x0004,
+ SN_DG_OP_DELETE = 0x0008,
+ SN_DG_OP_MODIFY = 0x0010,
+ SN_DG_OP_RENAME = 0x0020,
+ SN_DG_OP_SEARCH = 0x0040,
+ SN_DG_EXTENDED = 0x0080,
+ SN_DG_OP_AUTH = SN_DG_OP_BIND,
+ SN_DG_OP_READ = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH),
+ SN_DG_OP_WRITE = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME),
+ SN_DG_OP_ALL = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED)
+} retcode_op_e;
+
+typedef struct retcode_item_t {
+ struct berval rdi_line;
+ struct berval rdi_dn;
+ struct berval rdi_ndn;
+ struct berval rdi_text;
+ struct berval rdi_matched;
+ int rdi_err;
+ BerVarray rdi_ref;
+ int rdi_sleeptime;
+ Entry rdi_e;
+ slap_mask_t rdi_mask;
+ struct berval rdi_unsolicited_oid;
+ struct berval rdi_unsolicited_data;
+
+ unsigned rdi_flags;
+#define RDI_PRE_DISCONNECT (0x1U)
+#define RDI_POST_DISCONNECT (0x2U)
+
+ struct retcode_item_t *rdi_next;
+} retcode_item_t;
+
+typedef struct retcode_t {
+ struct berval rd_pdn;
+ struct berval rd_npdn;
+
+ int rd_sleep;
+
+ retcode_item_t *rd_item;
+
+ int rd_indir;
+#define RETCODE_FINDIR 0x01
+#define RETCODE_INDIR( rd ) ( (rd)->rd_indir )
+} retcode_t;
+
+static int
+retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e );
+
+static unsigned int
+retcode_sleep( int s )
+{
+ unsigned int r = 0;
+
+ /* sleep as required */
+ if ( s < 0 ) {
+#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */
+ r = rand() % (-s);
+#endif
+ r = ((double)(-s))*rand()/(RAND_MAX + 1.0);
+ } else if ( s > 0 ) {
+ r = (unsigned int)s;
+ }
+ if ( r ) {
+ sleep( r );
+ }
+
+ return r;
+}
+
+static int
+retcode_cleanup_cb( Operation *op, SlapReply *rs )
+{
+ rs->sr_matched = NULL;
+ rs->sr_text = NULL;
+
+ if ( rs->sr_ref != NULL ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ ch_free( op->o_callback );
+ op->o_callback = NULL;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_send_onelevel( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ retcode_item_t *rdi;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) {
+ if ( op->o_abandon ) {
+ return rs->sr_err = SLAPD_ABANDON;
+ }
+
+ rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter );
+ if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
+ /* safe default */
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_ctrls = NULL;
+ rs->sr_flags = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_entry = &rdi->rdi_e;
+
+ rs->sr_err = send_search_entry( op, rs );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+
+ switch ( rs->sr_err ) {
+ case LDAP_UNAVAILABLE: /* connection closed */
+ rs->sr_err = LDAP_OTHER;
+ /* fallthru */
+ case LDAP_SIZELIMIT_EXCEEDED:
+ goto done;
+ }
+ }
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+done:;
+
+ send_ldap_result( op, rs );
+
+ return rs->sr_err;
+}
+
+static int
+retcode_op_add( Operation *op, SlapReply *rs )
+{
+ return retcode_entry_response( op, rs, NULL, op->ora_e );
+}
+
+typedef struct retcode_cb_t {
+ BackendInfo *rdc_info;
+ unsigned rdc_flags;
+ ber_tag_t rdc_tag;
+ AttributeName *rdc_attrs;
+} retcode_cb_t;
+
+static int
+retcode_cb_response( Operation *op, SlapReply *rs )
+{
+ retcode_cb_t *rdc = (retcode_cb_t *)op->o_callback->sc_private;
+
+ op->o_tag = rdc->rdc_tag;
+ if ( rs->sr_type == REP_SEARCH ) {
+ ber_tag_t o_tag = op->o_tag;
+ int rc;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ rs->sr_attrs = rdc->rdc_attrs;
+ }
+ rc = retcode_entry_response( op, rs, rdc->rdc_info, rs->sr_entry );
+ op->o_tag = o_tag;
+
+ return rc;
+ }
+
+ switch ( rs->sr_err ) {
+ case LDAP_SUCCESS:
+ case LDAP_NO_SUCH_OBJECT:
+ /* in case of noSuchObject, stop the internal search
+ * for in-directory error stuff */
+ if ( !op->o_abandon ) {
+ rdc->rdc_flags = SLAP_CB_CONTINUE;
+ }
+ return 0;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_op_internal( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+
+ Operation op2 = *op;
+ BackendDB db = *op->o_bd;
+ slap_callback sc = { 0 };
+ retcode_cb_t rdc;
+
+ int rc;
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.ors_scope = LDAP_SCOPE_BASE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_limit = NULL;
+ op2.ors_attrsonly = 0;
+ op2.ors_attrs = slap_anlist_all_attributes;
+
+ ber_str2bv_x( "(objectClass=errAbsObject)",
+ STRLENOF( "(objectClass=errAbsObject)" ),
+ 1, &op2.ors_filterstr, op2.o_tmpmemctx );
+ op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val );
+
+ /* errAbsObject is defined by this overlay! */
+ assert( op2.ors_filter != NULL );
+
+ db.bd_info = on->on_info->oi_orig;
+ op2.o_bd = &db;
+
+ rdc.rdc_info = on->on_info->oi_orig;
+ rdc.rdc_flags = RETCODE_FINDIR;
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ rdc.rdc_attrs = op->ors_attrs;
+ }
+ rdc.rdc_tag = op->o_tag;
+ sc.sc_response = retcode_cb_response;
+ sc.sc_private = &rdc;
+ op2.o_callback = &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;
+ }
+
+ op->o_abandon = 1;
+ return rs->sr_err;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+retcode_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ if ( rs->sr_type != REP_SEARCH || !RETCODE_INDIR( rd ) ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ return retcode_entry_response( op, rs, NULL, rs->sr_entry );
+}
+
+static int
+retcode_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd;
+
+ srand( getpid() );
+
+ rd = (retcode_t *)ch_malloc( sizeof( retcode_t ) );
+ memset( rd, 0, sizeof( retcode_t ) );
+
+ on->on_bi.bi_private = (void *)rd;
+
+ return 0;
+}
+
+static void
+retcode_item_destroy( retcode_item_t *rdi )
+{
+ ber_memfree( rdi->rdi_line.bv_val );
+
+ ber_memfree( rdi->rdi_dn.bv_val );
+ ber_memfree( rdi->rdi_ndn.bv_val );
+
+ if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+ ber_memfree( rdi->rdi_text.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
+ ber_memfree( rdi->rdi_matched.bv_val );
+ }
+
+ if ( rdi->rdi_ref ) {
+ ber_bvarray_free( rdi->rdi_ref );
+ }
+
+ BER_BVZERO( &rdi->rdi_e.e_name );
+ BER_BVZERO( &rdi->rdi_e.e_nname );
+
+ entry_clean( &rdi->rdi_e );
+
+ if ( !BER_BVISNULL( &rdi->rdi_unsolicited_oid ) ) {
+ ber_memfree( rdi->rdi_unsolicited_oid.bv_val );
+ if ( !BER_BVISNULL( &rdi->rdi_unsolicited_data ) )
+ ber_memfree( rdi->rdi_unsolicited_data.bv_val );
+ }
+
+ ch_free( rdi );
+}
+
+enum {
+ RC_PARENT = 1,
+ RC_ITEM
+};
+
+static ConfigDriver rc_cf_gen;
+
+static ConfigTable rccfg[] = {
+ { "retcode-parent", "dn",
+ 2, 2, 0, ARG_MAGIC|ARG_DN|RC_PARENT, rc_cf_gen,
+ "( OLcfgOvAt:20.1 NAME 'olcRetcodeParent' "
+ "DESC '' "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "retcode-item", "rdn> <retcode> <...",
+ 3, 0, 0, ARG_MAGIC|RC_ITEM, rc_cf_gen,
+ "( OLcfgOvAt:20.2 NAME 'olcRetcodeItem' "
+ "DESC '' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )", NULL, NULL },
+ { "retcode-indir", "on|off",
+ 1, 2, 0, ARG_OFFSET|ARG_ON_OFF,
+ (void *)offsetof(retcode_t, rd_indir),
+ "( OLcfgOvAt:20.3 NAME 'olcRetcodeInDir' "
+ "DESC '' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+
+ { "retcode-sleep", "sleeptime",
+ 2, 2, 0, ARG_OFFSET|ARG_INT,
+ (void *)offsetof(retcode_t, rd_sleep),
+ "( OLcfgOvAt:20.4 NAME 'olcRetcodeSleep' "
+ "DESC '' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs rcocs[] = {
+ { "( OLcfgOvOc:20.1 "
+ "NAME 'olcRetcodeConfig' "
+ "DESC 'Retcode configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcRetcodeParent "
+ "$ olcRetcodeItem "
+ "$ olcRetcodeInDir "
+ "$ olcRetcodeSleep "
+ ") )",
+ Cft_Overlay, rccfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int
+rc_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+ int rc = ARG_BAD_CONF;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( !BER_BVISEMPTY( &rd->rd_pdn )) {
+ rc = value_add_one( &c->rvalue_vals,
+ &rd->rd_pdn );
+ if ( rc == 0 ) {
+ rc = value_add_one( &c->rvalue_nvals,
+ &rd->rd_npdn );
+ }
+ return rc;
+ }
+ rc = 0;
+ break;
+
+ case RC_ITEM: {
+ retcode_item_t *rdi;
+ int i;
+
+ for ( rdi = rd->rd_item, i = 0; rdi; rdi = rdi->rdi_next, i++ ) {
+ char buf[4096];
+ struct berval bv;
+ char *ptr;
+
+ bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len += rdi->rdi_line.bv_len;
+ ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, rdi->rdi_line.bv_val, rdi->rdi_line.bv_len );
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ } break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( rd->rd_pdn.bv_val ) {
+ ber_memfree ( rd->rd_pdn.bv_val );
+ rc = 0;
+ }
+ if ( rd->rd_npdn.bv_val ) {
+ ber_memfree ( rd->rd_npdn.bv_val );
+ }
+ break;
+
+ case RC_ITEM:
+ if ( c->valx == -1 ) {
+ retcode_item_t *rdi, *next;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
+ next = rdi->rdi_next;
+ retcode_item_destroy( rdi );
+ }
+
+ } else {
+ retcode_item_t **rdip, *rdi;
+ int i;
+
+ for ( rdip = &rd->rd_item, i = 0; i <= c->valx && *rdip; i++, rdip = &(*rdip)->rdi_next )
+ ;
+ if ( *rdip == NULL ) {
+ return 1;
+ }
+ rdi = *rdip;
+ *rdip = rdi->rdi_next;
+
+ retcode_item_destroy( rdi );
+ }
+ rc = 0;
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ return rc; /* FIXME */
+ }
+
+ switch( c->type ) {
+ case RC_PARENT:
+ if ( rd->rd_pdn.bv_val ) {
+ ber_memfree ( rd->rd_pdn.bv_val );
+ }
+ if ( rd->rd_npdn.bv_val ) {
+ ber_memfree ( rd->rd_npdn.bv_val );
+ }
+ rd->rd_pdn = c->value_dn;
+ rd->rd_npdn = c->value_ndn;
+ rc = 0;
+ break;
+
+ case RC_ITEM: {
+ retcode_item_t rdi = { BER_BVNULL }, **rdip;
+ struct berval bv, rdn, nrdn;
+ char *next = NULL;
+ int i;
+
+ if ( c->argc < 3 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"retcode-item <RDN> <retcode> [<text>]\": "
+ "missing args" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ ber_str2bv( c->argv[ 1 ], 0, 0, &bv );
+
+ rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to normalize RDN \"%s\": %d",
+ c->argv[ 1 ], rc );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ if ( !dnIsOneLevelRDN( &nrdn ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "value \"%s\" is not a RDN",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ if ( BER_BVISNULL( &rd->rd_npdn ) ) {
+ /* FIXME: we use the database suffix */
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "either \"retcode-parent\" "
+ "or \"suffix\" must be defined" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ ber_dupbv( &rd->rd_pdn, &c->be->be_suffix[ 0 ] );
+ ber_dupbv( &rd->rd_npdn, &c->be->be_nsuffix[ 0 ] );
+ }
+
+ build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL );
+ build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL );
+
+ ch_free( rdn.bv_val );
+ ch_free( nrdn.bv_val );
+
+ rdi.rdi_err = strtol( c->argv[ 2 ], &next, 0 );
+ if ( next == c->argv[ 2 ] || next[ 0 ] != '\0' ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse return code \"%s\"",
+ c->argv[ 2 ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ rdi.rdi_mask = SN_DG_OP_ALL;
+
+ if ( c->argc > 3 ) {
+ for ( i = 3; i < c->argc; i++ ) {
+ if ( strncasecmp( c->argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 )
+ {
+ char **ops;
+ int j;
+
+ ops = ldap_str2charray( &c->argv[ i ][ STRLENOF( "op=" ) ], "," );
+ assert( ops != NULL );
+
+ rdi.rdi_mask = SN_DG_OP_NONE;
+
+ for ( j = 0; ops[ j ] != NULL; j++ ) {
+ if ( strcasecmp( ops[ j ], "add" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_ADD;
+
+ } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_BIND;
+
+ } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_COMPARE;
+
+ } else if ( strcasecmp( ops[ j ], "delete" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_DELETE;
+
+ } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_MODIFY;
+
+ } else if ( strcasecmp( ops[ j ], "rename" ) == 0
+ || strcasecmp( ops[ j ], "modrdn" ) == 0 )
+ {
+ rdi.rdi_mask |= SN_DG_OP_RENAME;
+
+ } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_SEARCH;
+
+ } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_EXTENDED;
+
+ } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_AUTH;
+
+ } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_READ;
+
+ } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_WRITE;
+
+ } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) {
+ rdi.rdi_mask |= SN_DG_OP_ALL;
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown op \"%s\"",
+ ops[ j ] );
+ ldap_charray_free( ops );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ }
+
+ ldap_charray_free( ops );
+
+ } else if ( strncasecmp( c->argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 )
+ {
+ if ( !BER_BVISNULL( &rdi.rdi_text ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"text\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text );
+
+ } else if ( strncasecmp( c->argv[ i ], "matched=", STRLENOF( "matched=" ) ) == 0 )
+ {
+ struct berval dn;
+
+ if ( !BER_BVISNULL( &rdi.rdi_matched ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"matched\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "matched=" ) ], 0, 0, &dn );
+ if ( dnPretty( NULL, &dn, &rdi.rdi_matched, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to prettify matched DN \"%s\"",
+ &c->argv[ i ][ STRLENOF( "matched=" ) ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 )
+ {
+ char **refs;
+ int j;
+
+ if ( rdi.rdi_ref != NULL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"ref\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ if ( rdi.rdi_err != LDAP_REFERRAL ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "providing \"ref\" "
+ "along with a non-referral "
+ "resultCode may cause slapd failures "
+ "related to internal checks" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ }
+
+ refs = ldap_str2charray( &c->argv[ i ][ STRLENOF( "ref=" ) ], " " );
+ assert( refs != NULL );
+
+ for ( j = 0; refs[ j ] != NULL; j++ ) {
+ struct berval bv;
+
+ ber_str2bv( refs[ j ], 0, 1, &bv );
+ ber_bvarray_add( &rdi.rdi_ref, &bv );
+ }
+
+ ldap_charray_free( refs );
+
+ } else if ( strncasecmp( c->argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 )
+ {
+ if ( rdi.rdi_sleeptime != 0 ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"sleeptime\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ if ( lutil_atoi( &rdi.rdi_sleeptime, &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse \"sleeptime=%s\"",
+ &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "unsolicited=", STRLENOF( "unsolicited=" ) ) == 0 )
+ {
+ char *data;
+
+ if ( !BER_BVISNULL( &rdi.rdi_unsolicited_oid ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"unsolicited\" already provided" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ data = strchr( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], ':' );
+ if ( data != NULL ) {
+ struct berval oid;
+
+ if ( ldif_parse_line2( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ],
+ &oid, &rdi.rdi_unsolicited_data, NULL ) )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unable to parse \"unsolicited\"" );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ ber_dupbv( &rdi.rdi_unsolicited_oid, &oid );
+
+ } else {
+ ber_str2bv( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], 0, 1,
+ &rdi.rdi_unsolicited_oid );
+ }
+
+ } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 )
+ {
+ char *arg = &c->argv[ i ][ STRLENOF( "flags=" ) ];
+ if ( strcasecmp( arg, "disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_PRE_DISCONNECT;
+
+ } else if ( strcasecmp( arg, "pre-disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_PRE_DISCONNECT;
+
+ } else if ( strcasecmp( arg, "post-disconnect" ) == 0 ) {
+ rdi.rdi_flags |= RDI_POST_DISCONNECT;
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown flag \"%s\"", arg );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "unknown option \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n",
+ c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ }
+ }
+
+ rdi.rdi_line.bv_len = 2*(c->argc - 1) + c->argc - 2;
+ for ( i = 1; i < c->argc; i++ ) {
+ rdi.rdi_line.bv_len += strlen( c->argv[ i ] );
+ }
+ next = rdi.rdi_line.bv_val = ch_malloc( rdi.rdi_line.bv_len + 1 );
+
+ for ( i = 1; i < c->argc; i++ ) {
+ *next++ = '"';
+ next = lutil_strcopy( next, c->argv[ i ] );
+ *next++ = '"';
+ *next++ = ' ';
+ }
+ *--next = '\0';
+
+ for ( rdip = &rd->rd_item; *rdip; rdip = &(*rdip)->rdi_next )
+ /* go to last */ ;
+
+
+ *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) );
+ *(*rdip) = rdi;
+
+ rc = 0;
+ } break;
+
+ default:
+ rc = SLAP_CONF_UNKNOWN;
+ break;
+ }
+
+ return rc;
+}
+
+static int
+retcode_db_open( BackendDB *be, ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ retcode_item_t *rdi;
+
+ for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) {
+ LDAPRDN rdn = NULL;
+ int rc, j;
+ char* p;
+ struct berval val[ 3 ];
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ /* DN */
+ rdi->rdi_e.e_name = rdi->rdi_dn;
+ rdi->rdi_e.e_nname = rdi->rdi_ndn;
+
+ /* objectClass */
+ val[ 0 ] = oc_errObject->soc_cname;
+ val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname;
+ BER_BVZERO( &val[ 2 ] );
+
+ attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL );
+
+ /* RDN avas */
+ rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p,
+ LDAP_DN_FORMAT_LDAP );
+
+ assert( rc == LDAP_SUCCESS );
+
+ for ( j = 0; rdn[ j ]; j++ ) {
+ LDAPAVA *ava = rdn[ j ];
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ assert( rc == LDAP_SUCCESS );
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad,
+ &ava->la_value, NULL );
+ }
+
+ ldap_rdnfree( rdn );
+
+ /* error code */
+ snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err );
+ ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+ attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL );
+
+ if ( rdi->rdi_ref != NULL ) {
+ attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref,
+ rdi->rdi_ref, NULL );
+ }
+
+ /* text */
+ if ( !BER_BVISNULL( &rdi->rdi_text ) ) {
+ val[ 0 ] = rdi->rdi_text;
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL );
+ }
+
+ /* matched */
+ if ( !BER_BVISNULL( &rdi->rdi_matched ) ) {
+ val[ 0 ] = rdi->rdi_matched;
+
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errMatchedDN, &val[ 0 ], NULL );
+ }
+
+ /* sleep time */
+ if ( rdi->rdi_sleeptime ) {
+ snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime );
+ ber_str2bv( buf, 0, 0, &val[ 0 ] );
+
+ attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL );
+ }
+
+ /* operations */
+ if ( rdi->rdi_mask & SN_DG_OP_ADD ) {
+ BER_BVSTR( &val[ 0 ], "add" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_BIND ) {
+ BER_BVSTR( &val[ 0 ], "bind" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) {
+ BER_BVSTR( &val[ 0 ], "compare" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_DELETE ) {
+ BER_BVSTR( &val[ 0 ], "delete" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_EXTENDED ) {
+ BER_BVSTR( &val[ 0 ], "extended" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) {
+ BER_BVSTR( &val[ 0 ], "modify" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_RENAME ) {
+ BER_BVSTR( &val[ 0 ], "rename" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+
+ if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) {
+ BER_BVSTR( &val[ 0 ], "search" );
+ attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL );
+ }
+ }
+
+ return 0;
+}
+
+static int
+retcode_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ retcode_t *rd = (retcode_t *)on->on_bi.bi_private;
+
+ if ( rd ) {
+ retcode_item_t *rdi, *next;
+
+ for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) {
+ next = rdi->rdi_next;
+ retcode_item_destroy( rdi );
+ }
+
+ if ( !BER_BVISNULL( &rd->rd_pdn ) ) {
+ ber_memfree( rd->rd_pdn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &rd->rd_npdn ) ) {
+ ber_memfree( rd->rd_npdn.bv_val );
+ }
+
+ ber_memfree( rd );
+ }
+
+ return 0;
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+int
+retcode_initialize( void )
+{
+ int i, code;
+
+ static struct {
+ char *desc;
+ AttributeDescription **ad;
+ } retcode_at[] = {
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.1 "
+ "NAME ( 'errCode' ) "
+ "DESC 'LDAP error code' "
+ "EQUALITY integerMatch "
+ "ORDERING integerOrderingMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_errCode },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.2 "
+ "NAME ( 'errOp' ) "
+ "DESC 'Operations the errObject applies to' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )",
+ &ad_errOp},
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.3 "
+ "NAME ( 'errText' ) "
+ "DESC 'LDAP error textual description' "
+ "EQUALITY caseIgnoreMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 "
+ "SINGLE-VALUE )",
+ &ad_errText },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.4 "
+ "NAME ( 'errSleepTime' ) "
+ "DESC 'Time to wait before returning the error' "
+ "EQUALITY integerMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 "
+ "SINGLE-VALUE )",
+ &ad_errSleepTime },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.5 "
+ "NAME ( 'errMatchedDN' ) "
+ "DESC 'Value to be returned as matched DN' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 "
+ "SINGLE-VALUE )",
+ &ad_errMatchedDN },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.6 "
+ "NAME ( 'errUnsolicitedOID' ) "
+ "DESC 'OID to be returned within unsolicited response' "
+ "EQUALITY objectIdentifierMatch "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 "
+ "SINGLE-VALUE )",
+ &ad_errUnsolicitedOID },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.7 "
+ "NAME ( 'errUnsolicitedData' ) "
+ "DESC 'Data to be returned within unsolicited response' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 "
+ "SINGLE-VALUE )",
+ &ad_errUnsolicitedData },
+ { "( 1.3.6.1.4.1.4203.666.11.4.1.8 "
+ "NAME ( 'errDisconnect' ) "
+ "DESC 'Disconnect without notice' "
+ "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 "
+ "SINGLE-VALUE )",
+ &ad_errDisconnect },
+ { NULL }
+ };
+
+ static struct {
+ char *desc;
+ ObjectClass **oc;
+ } retcode_oc[] = {
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.0 "
+ "NAME ( 'errAbsObject' ) "
+ "SUP top ABSTRACT "
+ "MUST ( errCode ) "
+ "MAY ( "
+ "cn "
+ "$ description "
+ "$ errOp "
+ "$ errText "
+ "$ errSleepTime "
+ "$ errMatchedDN "
+ "$ errUnsolicitedOID "
+ "$ errUnsolicitedData "
+ "$ errDisconnect "
+ ") )",
+ &oc_errAbsObject },
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.1 "
+ "NAME ( 'errObject' ) "
+ "SUP errAbsObject STRUCTURAL "
+ ")",
+ &oc_errObject },
+ { "( 1.3.6.1.4.1.4203.666.11.4.3.2 "
+ "NAME ( 'errAuxObject' ) "
+ "SUP errAbsObject AUXILIARY "
+ ")",
+ &oc_errAuxObject },
+ { NULL }
+ };
+
+
+ for ( i = 0; retcode_at[ i ].desc != NULL; i++ ) {
+ code = register_at( retcode_at[ i ].desc, retcode_at[ i ].ad, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "retcode: register_at failed\n", 0, 0, 0 );
+ return code;
+ }
+
+ (*retcode_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE;
+ }
+
+ for ( i = 0; retcode_oc[ i ].desc != NULL; i++ ) {
+ code = register_oc( retcode_oc[ i ].desc, retcode_oc[ i ].oc, 0 );
+ if ( code ) {
+ Debug( LDAP_DEBUG_ANY,
+ "retcode: register_oc failed\n", 0, 0, 0 );
+ return code;
+ }
+
+ (*retcode_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE;
+ }
+
+ retcode.on_bi.bi_type = "retcode";
+
+ retcode.on_bi.bi_db_init = retcode_db_init;
+ retcode.on_bi.bi_db_open = retcode_db_open;
+ retcode.on_bi.bi_db_destroy = retcode_db_destroy;
+
+ retcode.on_bi.bi_op_add = retcode_op_func;
+ retcode.on_bi.bi_op_bind = retcode_op_func;
+ retcode.on_bi.bi_op_compare = retcode_op_func;
+ retcode.on_bi.bi_op_delete = retcode_op_func;
+ retcode.on_bi.bi_op_modify = retcode_op_func;
+ retcode.on_bi.bi_op_modrdn = retcode_op_func;
+ retcode.on_bi.bi_op_search = retcode_op_func;
+
+ retcode.on_bi.bi_extended = retcode_op_func;
+
+ retcode.on_response = retcode_response;
+
+ retcode.on_bi.bi_cf_ocs = rcocs;
+
+ code = config_register_schema( rccfg, rcocs );
+ if ( code ) {
+ return code;
+ }
+
+ return overlay_register( &retcode );
+}
+
+#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return retcode_initialize();
+}
+#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RETCODE */
diff --git a/servers/slapd/overlays/rwm.c b/servers/slapd/overlays/rwm.c
new file mode 100644
index 0000000..3102d2d
--- /dev/null
+++ b/servers/slapd/overlays/rwm.c
@@ -0,0 +1,2759 @@
+/* rwm.c - rewrite/remap operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2003-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+#include "rwm.h"
+
+typedef struct rwm_op_state {
+ ber_tag_t r_tag;
+ struct berval ro_dn;
+ struct berval ro_ndn;
+ struct berval r_dn;
+ struct berval r_ndn;
+ struct berval rx_dn;
+ struct berval rx_ndn;
+ AttributeName *mapped_attrs;
+ OpRequest o_request;
+} rwm_op_state;
+
+typedef struct rwm_op_cb {
+ slap_callback cb;
+ rwm_op_state ros;
+} rwm_op_cb;
+
+static int
+rwm_db_destroy( BackendDB *be, ConfigReply *cr );
+
+static int
+rwm_send_entry( Operation *op, SlapReply *rs );
+
+static void
+rwm_op_rollback( Operation *op, SlapReply *rs, rwm_op_state *ros )
+{
+ /* in case of successful extended operation cleanup
+ * gets called *after* (ITS#6632); this hack counts
+ * on others to cleanup our o_req_dn/o_req_ndn,
+ * while we cleanup theirs. */
+ if ( ros->r_tag == LDAP_REQ_EXTENDED && rs->sr_err == LDAP_SUCCESS ) {
+ if ( !BER_BVISNULL( &ros->rx_dn ) ) {
+ ch_free( ros->rx_dn.bv_val );
+ }
+ if ( !BER_BVISNULL( &ros->rx_ndn ) ) {
+ ch_free( ros->rx_ndn.bv_val );
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &ros->ro_dn ) ) {
+ op->o_req_dn = ros->ro_dn;
+ }
+ if ( !BER_BVISNULL( &ros->ro_ndn ) ) {
+ op->o_req_ndn = ros->ro_ndn;
+ }
+
+ if ( !BER_BVISNULL( &ros->r_dn )
+ && ros->r_dn.bv_val != ros->ro_dn.bv_val )
+ {
+ assert( ros->r_dn.bv_val != ros->r_ndn.bv_val );
+ ch_free( ros->r_dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &ros->r_ndn )
+ && ros->r_ndn.bv_val != ros->ro_ndn.bv_val )
+ {
+ ch_free( ros->r_ndn.bv_val );
+ }
+ }
+
+ BER_BVZERO( &ros->r_dn );
+ BER_BVZERO( &ros->r_ndn );
+ BER_BVZERO( &ros->ro_dn );
+ BER_BVZERO( &ros->ro_ndn );
+ BER_BVZERO( &ros->rx_dn );
+ BER_BVZERO( &ros->rx_ndn );
+
+ switch( ros->r_tag ) {
+ case LDAP_REQ_COMPARE:
+ if ( op->orc_ava->aa_value.bv_val != ros->orc_ava->aa_value.bv_val )
+ op->o_tmpfree( op->orc_ava->aa_value.bv_val, op->o_tmpmemctx );
+ op->orc_ava = ros->orc_ava;
+ break;
+ case LDAP_REQ_MODIFY:
+ slap_mods_free( op->orm_modlist, 1 );
+ op->orm_modlist = ros->orm_modlist;
+ break;
+ case LDAP_REQ_MODRDN:
+ if ( op->orr_newSup != ros->orr_newSup ) {
+ if ( op->orr_newSup ) {
+ ch_free( op->orr_newSup->bv_val );
+ ch_free( op->orr_nnewSup->bv_val );
+ op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx );
+ }
+ op->orr_newSup = ros->orr_newSup;
+ op->orr_nnewSup = ros->orr_nnewSup;
+ }
+ if ( op->orr_newrdn.bv_val != ros->orr_newrdn.bv_val ) {
+ ch_free( op->orr_newrdn.bv_val );
+ ch_free( op->orr_nnewrdn.bv_val );
+ op->orr_newrdn = ros->orr_newrdn;
+ op->orr_nnewrdn = ros->orr_nnewrdn;
+ }
+ break;
+ case LDAP_REQ_SEARCH:
+ op->o_tmpfree( ros->mapped_attrs, op->o_tmpmemctx );
+ filter_free_x( op, op->ors_filter, 1 );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ op->ors_attrs = ros->ors_attrs;
+ op->ors_filter = ros->ors_filter;
+ op->ors_filterstr = ros->ors_filterstr;
+ break;
+ case LDAP_REQ_EXTENDED:
+ if ( op->ore_reqdata != ros->ore_reqdata ) {
+ ber_bvfree( op->ore_reqdata );
+ op->ore_reqdata = ros->ore_reqdata;
+ }
+ break;
+ case LDAP_REQ_BIND:
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+#if 0
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+ /* too late, c_mutex released */
+ Debug( LDAP_DEBUG_ANY, "*** DN: \"%s\" => \"%s\"\n",
+ op->o_conn->c_ndn.bv_val,
+ op->o_req_ndn.bv_val );
+ ber_bvreplace( &op->o_conn->c_ndn,
+ &op->o_req_ndn );
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+#endif
+ }
+ break;
+ default: break;
+ }
+}
+
+static int
+rwm_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ rwm_op_state *ros = cb->sc_private;
+
+ if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_EXTENDED ||
+ op->o_abandon || rs->sr_err == SLAPD_ABANDON )
+ {
+ rwm_op_rollback( op, rs, ros );
+
+ op->o_callback = op->o_callback->sc_next;
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static rwm_op_cb *
+rwm_callback_get( Operation *op )
+{
+ rwm_op_cb *roc;
+
+ roc = op->o_tmpcalloc( 1, sizeof( struct rwm_op_cb ), op->o_tmpmemctx );
+ roc->cb.sc_cleanup = rwm_op_cleanup;
+ roc->cb.sc_response = NULL;
+ roc->cb.sc_next = op->o_callback;
+ roc->cb.sc_private = &roc->ros;
+ roc->ros.r_tag = op->o_tag;
+ roc->ros.ro_dn = op->o_req_dn;
+ roc->ros.ro_ndn = op->o_req_ndn;
+ BER_BVZERO( &roc->ros.r_dn );
+ BER_BVZERO( &roc->ros.r_ndn );
+ BER_BVZERO( &roc->ros.rx_dn );
+ BER_BVZERO( &roc->ros.rx_ndn );
+ roc->ros.mapped_attrs = NULL;
+ roc->ros.o_request = op->o_request;
+
+ return roc;
+}
+
+
+static int
+rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie,
+ rwm_op_state *ros )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ int rc = 0;
+ dncookie dc;
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ /* NOTE: in those cases where only the ndn is available,
+ * and the caller sets op->o_req_dn = op->o_req_ndn,
+ * only rewrite the op->o_req_ndn and use it as
+ * op->o_req_dn as well */
+ ndn = op->o_req_ndn;
+ if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) {
+ dn = op->o_req_dn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &op->o_req_dn, &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_normalize( &dc, &op->o_req_ndn, &ndn );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val && dn.bv_val == op->o_req_dn.bv_val )
+ || ndn.bv_val == op->o_req_ndn.bv_val )
+ {
+ return LDAP_SUCCESS;
+ }
+
+ if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) {
+ op->o_req_dn = dn;
+ assert( BER_BVISNULL( &ros->r_dn ) );
+ ros->r_dn = dn;
+ } else {
+ op->o_req_dn = ndn;
+ }
+ op->o_req_ndn = ndn;
+ assert( BER_BVISNULL( &ros->r_ndn ) );
+ ros->r_ndn = ndn;
+
+ if ( ros->r_tag == LDAP_REQ_EXTENDED ) {
+ ros->rx_dn = ros->r_dn;
+ ros->rx_ndn = ros->r_ndn;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+rwm_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc,
+ i;
+ Attribute **ap = NULL;
+ char *olddn = op->o_req_dn.bv_val;
+ int isupdate;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "addDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "addDN massage error" );
+ return -1;
+ }
+
+ if ( olddn != op->o_req_dn.bv_val ) {
+ ber_bvreplace( &op->ora_e->e_name, &op->o_req_dn );
+ ber_bvreplace( &op->ora_e->e_nname, &op->o_req_ndn );
+ }
+
+ /* Count number of attributes in entry */
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ap = &op->oq_add.rs_e->e_attrs; *ap; ) {
+ Attribute *a;
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_objectClass ||
+ (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ int j, last;
+
+ last = (*ap)->a_numvals - 1;
+ for ( j = 0; !BER_BVISNULL( &(*ap)->a_vals[ j ] ); j++ ) {
+ struct ldapmapping *mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_oc, &(*ap)->a_vals[ j ],
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ /* FIXME: we allow to remove objectClasses as well;
+ * if the resulting entry is inconsistent, that's
+ * the relayed database's business...
+ */
+ ch_free( (*ap)->a_vals[ j ].bv_val );
+ if ( last > j ) {
+ (*ap)->a_vals[ j ] = (*ap)->a_vals[ last ];
+ }
+ BER_BVZERO( &(*ap)->a_vals[ last ] );
+ (*ap)->a_numvals--;
+ last--;
+ j--;
+ }
+
+ } else {
+ ch_free( (*ap)->a_vals[ j ].bv_val );
+ ber_dupbv( &(*ap)->a_vals[ j ], &mapping->m_dst );
+ }
+ }
+
+ } else if ( !isupdate && !get_relax( op ) && (*ap)->a_desc->ad_type->sat_no_user_mod )
+ {
+ goto next_attr;
+
+ } else {
+ struct ldapmapping *mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_at, &(*ap)->a_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ goto cleanup_attr;
+ }
+ }
+
+ if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ /*
+ * FIXME: rewrite could fail; in this case
+ * the operation should give up, right?
+ */
+ rc = rwm_dnattr_rewrite( op, rs, "addAttrDN",
+ (*ap)->a_vals,
+ (*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+ if ( rc ) {
+ goto cleanup_attr;
+ }
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+ rc = rwm_referral_rewrite( op, rs, "referralAttrDN",
+ (*ap)->a_vals,
+ (*ap)->a_nvals ? &(*ap)->a_nvals : NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+ }
+
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+ (*ap)->a_desc = mapping->m_dst_ad;
+ }
+ }
+
+next_attr:;
+ ap = &(*ap)->a_next;
+ continue;
+
+cleanup_attr:;
+ /* FIXME: leaking attribute/values? */
+ a = *ap;
+
+ *ap = (*ap)->a_next;
+ attr_free( a );
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_conn_init( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ ( void )rewrite_session_init( rwmap->rwm_rw, conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_conn_destroy( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ ( void )rewrite_session_delete( rwmap->rwm_rw, conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_bind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "bindDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "bindDN massage error" );
+ return -1;
+ }
+
+ overlay_callback_after_backover( op, &roc->cb, 1 );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_unbind( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ rewrite_session_delete( rwmap->rwm_rw, op->o_conn );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+ struct berval mapped_vals[2] = { BER_BVNULL, BER_BVNULL };
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "compareDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "compareDN massage error" );
+ return -1;
+ }
+
+ /* if the attribute is an objectClass, try to remap its value */
+ if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass
+ || op->orc_ava->aa_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ rwm_map( &rwmap->rwm_oc, &op->orc_ava->aa_value,
+ &mapped_vals[0], RWM_MAP );
+ if ( BER_BVISNULL( &mapped_vals[0] ) || BER_BVISEMPTY( &mapped_vals[0] ) )
+ {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_OTHER, "compare objectClass map error" );
+ return -1;
+
+ } else if ( mapped_vals[0].bv_val != op->orc_ava->aa_value.bv_val ) {
+ ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0],
+ op->o_tmpmemctx );
+ }
+
+ } else {
+ struct ldapmapping *mapping = NULL;
+ AttributeDescription *ad = op->orc_ava->aa_desc;
+
+ ( void )rwm_mapping( &rwmap->rwm_at, &op->orc_ava->aa_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, LDAP_OTHER, "compare attributeType map error" );
+ return -1;
+ }
+
+ } else {
+ assert( mapping->m_dst_ad != NULL );
+ ad = mapping->m_dst_ad;
+ }
+
+ if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ struct berval *mapped_valsp[2];
+
+ mapped_valsp[0] = &mapped_vals[0];
+ mapped_valsp[1] = &mapped_vals[1];
+
+ mapped_vals[0] = op->orc_ava->aa_value;
+
+ rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", NULL, mapped_valsp );
+
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "compareAttrDN massage error" );
+ return -1;
+ }
+
+ if ( mapped_vals[ 0 ].bv_val != op->orc_ava->aa_value.bv_val ) {
+ /* NOTE: if we get here, rwm_dnattr_rewrite()
+ * already freed the old value, so now
+ * it's invalid */
+ ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0],
+ op->o_tmpmemctx );
+ ber_memfree_x( mapped_vals[ 0 ].bv_val, NULL );
+ }
+ }
+ op->orc_ava->aa_desc = ad;
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_delete( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "deleteDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "deleteDN massage error" );
+ return -1;
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int isupdate;
+ Modifications **mlp;
+ int rc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "modifyDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "modifyDN massage error" );
+ return -1;
+ }
+
+ isupdate = be_shadow_update( op );
+ for ( mlp = &op->orm_modlist; *mlp; ) {
+ int is_oc = 0;
+ Modifications *ml = *mlp;
+ struct ldapmapping *mapping = NULL;
+
+ /* ml points to a temporary mod until needs duplication */
+ if ( ml->sml_desc == slap_schema.si_ad_objectClass
+ || ml->sml_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ is_oc = 1;
+
+ } else if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ ml = ch_malloc( sizeof( Modifications ) );
+ *ml = **mlp;
+ if ( (*mlp)->sml_values ) {
+ ber_bvarray_dup_x( &ml->sml_values, (*mlp)->sml_values, NULL );
+ if ( (*mlp)->sml_nvalues ) {
+ ber_bvarray_dup_x( &ml->sml_nvalues, (*mlp)->sml_nvalues, NULL );
+ }
+ }
+ *mlp = ml;
+ goto next_mod;
+
+ } else {
+ int drop_missing;
+
+ drop_missing = rwm_mapping( &rwmap->rwm_at,
+ &ml->sml_desc->ad_cname,
+ &mapping, RWM_MAP );
+ if ( drop_missing || ( mapping != NULL && BER_BVISNULL( &mapping->m_dst ) ) )
+ {
+ goto skip_mod;
+ }
+ }
+
+ /* duplicate the modlist */
+ ml = ch_malloc( sizeof( Modifications ));
+ *ml = **mlp;
+ *mlp = ml;
+
+ if ( ml->sml_values != NULL ) {
+ int i, num;
+ struct berval *bva;
+
+ for ( num = 0; !BER_BVISNULL( &ml->sml_values[ num ] ); num++ )
+ /* count values */ ;
+
+ bva = ch_malloc( (num+1) * sizeof( struct berval ));
+ for (i=0; i<num; i++)
+ ber_dupbv( &bva[i], &ml->sml_values[i] );
+ BER_BVZERO( &bva[i] );
+ ml->sml_values = bva;
+
+ if ( ml->sml_nvalues ) {
+ bva = ch_malloc( (num+1) * sizeof( struct berval ));
+ for (i=0; i<num; i++)
+ ber_dupbv( &bva[i], &ml->sml_nvalues[i] );
+ BER_BVZERO( &bva[i] );
+ ml->sml_nvalues = bva;
+ }
+
+ if ( is_oc ) {
+ int last, j;
+
+ last = num-1;
+
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+ struct ldapmapping *oc_mapping = NULL;
+
+ ( void )rwm_mapping( &rwmap->rwm_oc, &ml->sml_values[ j ],
+ &oc_mapping, RWM_MAP );
+ if ( oc_mapping == NULL ) {
+ if ( rwmap->rwm_at.drop_missing ) {
+ /* FIXME: we allow to remove objectClasses as well;
+ * if the resulting entry is inconsistent, that's
+ * the relayed database's business...
+ */
+ if ( last > j ) {
+ ch_free( ml->sml_values[ j ].bv_val );
+ ml->sml_values[ j ] = ml->sml_values[ last ];
+ }
+ BER_BVZERO( &ml->sml_values[ last ] );
+ last--;
+ j--;
+ }
+
+ } else {
+ ch_free( ml->sml_values[ j ].bv_val );
+ ber_dupbv( &ml->sml_values[ j ], &oc_mapping->m_dst );
+ }
+ }
+
+ } else {
+ if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ rc = rwm_dnattr_rewrite( op, rs, "modifyAttrDN",
+ ml->sml_values,
+ ml->sml_nvalues ? &ml->sml_nvalues : NULL );
+
+ } else if ( ml->sml_desc == slap_schema.si_ad_ref ) {
+ rc = rwm_referral_rewrite( op, rs,
+ "referralAttrDN",
+ ml->sml_values,
+ ml->sml_nvalues ? &ml->sml_nvalues : NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_mod;
+ }
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_mod;
+ }
+ }
+ }
+
+next_mod:;
+ if ( mapping != NULL ) {
+ /* use new attribute description */
+ assert( mapping->m_dst_ad != NULL );
+ ml->sml_desc = mapping->m_dst_ad;
+ }
+
+ mlp = &ml->sml_next;
+ continue;
+
+skip_mod:;
+ *mlp = (*mlp)->sml_next;
+ continue;
+
+cleanup_mod:;
+ ml = *mlp;
+ *mlp = (*mlp)->sml_next;
+ slap_mod_free( &ml->sml_mod, 0 );
+ free( ml );
+ }
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+ dncookie dc;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ if ( op->orr_newSup ) {
+ struct berval nnewSup = BER_BVNULL;
+ struct berval newSup = BER_BVNULL;
+
+ /*
+ * Rewrite the new superior, if defined and required
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "newSuperiorDN";
+ newSup = *op->orr_newSup;
+ nnewSup = *op->orr_nnewSup;
+ rc = rwm_dn_massage_pretty_normalize( &dc, op->orr_newSup, &newSup, &nnewSup );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "newSuperiorDN massage error" );
+ return -1;
+ }
+
+ if ( op->orr_newSup->bv_val != newSup.bv_val ) {
+ op->orr_newSup = op->o_tmpalloc( sizeof( struct berval ),
+ op->o_tmpmemctx );
+ op->orr_nnewSup = op->o_tmpalloc( sizeof( struct berval ),
+ op->o_tmpmemctx );
+ *op->orr_newSup = newSup;
+ *op->orr_nnewSup = nnewSup;
+ }
+ }
+
+ /*
+ * Rewrite the newRDN, if needed
+ */
+ {
+ struct berval newrdn = BER_BVNULL;
+ struct berval nnewrdn = BER_BVNULL;
+
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "newRDN";
+ newrdn = op->orr_newrdn;
+ nnewrdn = op->orr_nnewrdn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &op->orr_newrdn, &newrdn, &nnewrdn );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "newRDN massage error" );
+ goto err;
+ }
+
+ if ( op->orr_newrdn.bv_val != newrdn.bv_val ) {
+ op->orr_newrdn = newrdn;
+ op->orr_nnewrdn = nnewrdn;
+ }
+ }
+
+ /*
+ * Rewrite the dn, if needed
+ */
+ rc = rwm_op_dn_massage( op, rs, "renameDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "renameDN massage error" );
+ goto err;
+ }
+
+ op->o_callback = &roc->cb;
+
+ rc = SLAP_CB_CONTINUE;
+
+ if ( 0 ) {
+err:;
+ if ( op->orr_newSup != roc->ros.orr_newSup ) {
+ ch_free( op->orr_newSup->bv_val );
+ ch_free( op->orr_nnewSup->bv_val );
+ op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx );
+ op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx );
+ op->orr_newSup = roc->ros.orr_newSup;
+ op->orr_nnewSup = roc->ros.orr_nnewSup;
+ }
+
+ if ( op->orr_newrdn.bv_val != roc->ros.orr_newrdn.bv_val ) {
+ ch_free( op->orr_newrdn.bv_val );
+ ch_free( op->orr_nnewrdn.bv_val );
+ op->orr_newrdn = roc->ros.orr_newrdn;
+ op->orr_nnewrdn = roc->ros.orr_nnewrdn;
+ }
+ }
+
+ return rc;
+}
+
+
+static int
+rwm_swap_attrs( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ rwm_op_state *ros = cb->sc_private;
+
+ rs->sr_attrs = ros->ors_attrs;
+
+ /* other overlays might have touched op->ors_attrs,
+ * so we restore the original version here, otherwise
+ * attribute-mapping might fail */
+ op->ors_attrs = ros->mapped_attrs;
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+ * NOTE: this implementation of get/release entry is probably far from
+ * optimal. The rationale consists in intercepting the request directed
+ * to the underlying database, in order to rewrite/remap the request,
+ * perform it using the modified data, duplicate the resulting entry
+ * and finally free it when release is called.
+ * This implies that subsequent overlays are not called, as the request
+ * is directly shunted to the underlying database.
+ */
+static int
+rwm_entry_release_rw( Operation *op, Entry *e, int rw )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+
+ /* can't be ours */
+ if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* just free entry if (probably) ours */
+ if ( e->e_private == NULL && BER_BVISNULL( &e->e_bv ) ) {
+ entry_free( e );
+ return LDAP_SUCCESS;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_entry_get_rw( Operation *op, struct berval *ndn,
+ ObjectClass *oc, AttributeDescription *at, int rw, Entry **ep )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ BackendDB db;
+ Operation op2;
+ SlapReply rs = { REP_SEARCH };
+
+ rwm_op_state ros = { 0 };
+ struct berval mndn = BER_BVNULL;
+
+ if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* massage DN */
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2 = *op;
+ op2.o_req_dn = *ndn;
+ op2.o_req_ndn = *ndn;
+ rc = rwm_op_dn_massage( &op2, &rs, "searchDN", &ros );
+ if ( rc != LDAP_SUCCESS ) {
+ return LDAP_OTHER;
+ }
+
+ mndn = BER_BVISNULL( &ros.r_ndn ) ? *ndn : ros.r_ndn;
+
+ /* map attribute & objectClass */
+ if ( at != NULL ) {
+ }
+
+ if ( oc != NULL ) {
+ }
+
+ /* fetch entry */
+ db = *op->o_bd;
+ op2.o_bd = &db;
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info->oi_orig;
+ op2.ors_attrs = slap_anlist_all_attributes;
+ rc = op2.o_bd->bd_info->bi_entry_get_rw( &op2, &mndn, oc, at, rw, ep );
+ if ( rc == LDAP_SUCCESS && *ep != NULL ) {
+ /* we assume be_entry_release() needs to be called */
+ rs.sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs.sr_entry = *ep;
+
+ /* duplicate & release */
+ op2.o_bd->bd_info = (BackendInfo *)on;
+ rc = rwm_send_entry( &op2, &rs );
+ RS_ASSERT( rs.sr_flags & REP_ENTRY_MUSTFLUSH );
+ if ( rc == SLAP_CB_CONTINUE ) {
+ *ep = rs.sr_entry;
+ rc = LDAP_SUCCESS;
+ } else {
+ assert( rc != LDAP_SUCCESS && rs.sr_entry == *ep );
+ *ep = NULL;
+ op2.o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( &op2, rs.sr_entry );
+ op2.o_bd->bd_info = (BackendInfo *)on;
+ }
+ }
+
+ if ( !BER_BVISNULL( &ros.r_ndn) && ros.r_ndn.bv_val != ndn->bv_val ) {
+ op->o_tmpfree( ros.r_ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ return rc;
+}
+
+static int
+rwm_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+ dncookie dc;
+
+ struct berval fstr = BER_BVNULL;
+ Filter *f = NULL;
+
+ AttributeName *an = NULL;
+
+ char *text = NULL;
+
+ rwm_op_cb *roc = rwm_callback_get( op );
+
+ rc = rewrite_session_var_set( rwmap->rwm_rw, op->o_conn,
+ "searchFilter", op->ors_filterstr.bv_val );
+ if ( rc == LDAP_SUCCESS )
+ rc = rwm_op_dn_massage( op, rs, "searchDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "searchDN massage error";
+ goto error_return;
+ }
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "searchFilterAttrDN";
+
+ rc = rwm_filter_map_rewrite( op, &dc, op->ors_filter, &fstr );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "searchFilter/searchFilterAttrDN massage error";
+ goto error_return;
+ }
+
+ f = str2filter_x( op, fstr.bv_val );
+
+ if ( f == NULL ) {
+ text = "massaged filter parse error";
+ goto error_return;
+ }
+
+ op->ors_filter = f;
+ op->ors_filterstr = fstr;
+
+ rc = rwm_map_attrnames( op, &rwmap->rwm_at, &rwmap->rwm_oc,
+ op->ors_attrs, &an, RWM_MAP );
+ if ( rc != LDAP_SUCCESS ) {
+ text = "attribute list mapping error";
+ goto error_return;
+ }
+
+ op->ors_attrs = an;
+ /* store the mapped Attributes for later usage, in
+ * the case that other overlays change op->ors_attrs */
+ roc->ros.mapped_attrs = an;
+ roc->cb.sc_response = rwm_swap_attrs;
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+
+error_return:;
+ if ( an != NULL ) {
+ ch_free( an );
+ }
+
+ if ( f != NULL ) {
+ filter_free_x( op, f, 1 );
+ }
+
+ if ( !BER_BVISNULL( &fstr ) ) {
+ op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx );
+ }
+
+ rwm_op_rollback( op, rs, &roc->ros );
+ op->oq_search = roc->ros.oq_search;
+ op->o_tmpfree( roc, op->o_tmpmemctx );
+
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, text );
+
+ return -1;
+
+}
+
+static int
+rwm_exop_passwd( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ rwm_op_cb *roc;
+
+ struct berval id = BER_BVNULL,
+ pwold = BER_BVNULL,
+ pwnew = BER_BVNULL;
+ BerElement *ber = NULL;
+
+ if ( !BER_BVISNULL( &op->o_req_ndn ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( !SLAP_ISGLOBALOVERLAY( op->o_bd ) ) {
+ rs->sr_err = LDAP_OTHER;
+ return rs->sr_err;
+ }
+
+ rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id,
+ &pwold, &pwnew, &rs->sr_text );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ if ( !BER_BVISNULL( &id ) ) {
+ char idNul = id.bv_val[id.bv_len];
+ id.bv_val[id.bv_len] = '\0';
+ rs->sr_err = dnPrettyNormal( NULL, &id, &op->o_req_dn,
+ &op->o_req_ndn, op->o_tmpmemctx );
+ id.bv_val[id.bv_len] = idNul;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_text = "Invalid DN";
+ return rs->sr_err;
+ }
+
+ } else {
+ ber_dupbv_x( &op->o_req_dn, &op->o_dn, op->o_tmpmemctx );
+ ber_dupbv_x( &op->o_req_ndn, &op->o_ndn, op->o_tmpmemctx );
+ }
+
+ roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "extendedDN massage error" );
+ return -1;
+ }
+
+ ber = ber_alloc_t( LBER_USE_DER );
+ if ( !ber ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "No memory";
+ return rs->sr_err;
+ }
+ ber_printf( ber, "{" );
+ if ( !BER_BVISNULL( &id )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+ &op->o_req_dn );
+ }
+ if ( !BER_BVISNULL( &pwold )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, &pwold );
+ }
+ if ( !BER_BVISNULL( &pwnew )) {
+ ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &pwnew );
+ }
+ ber_printf( ber, "N}" );
+ ber_flatten( ber, &op->ore_reqdata );
+ ber_free( ber, 1 );
+
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static struct exop {
+ struct berval oid;
+ BI_op_extended *extended;
+} exop_table[] = {
+ { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), rwm_exop_passwd },
+ { BER_BVNULL, NULL }
+};
+
+static int
+rwm_extended( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+ rwm_op_cb *roc;
+
+ int i;
+
+ for ( i = 0; exop_table[i].extended != NULL; i++ ) {
+ if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) )
+ {
+ rc = exop_table[i].extended( op, rs );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+
+ case SLAP_CB_CONTINUE:
+ case SLAPD_ABANDON:
+ return rc;
+
+ default:
+ send_ldap_result( op, rs );
+ return rc;
+ }
+ break;
+ }
+ }
+
+ roc = rwm_callback_get( op );
+
+ rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "extendedDN massage error" );
+ return -1;
+ }
+
+ /* TODO: rewrite/map extended data ? ... */
+ op->o_callback = &roc->cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static void
+rwm_matched( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval dn, mdn;
+ dncookie dc;
+ int rc;
+
+ if ( rs->sr_matched == NULL ) {
+ return;
+ }
+
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = "matchedDN";
+ ber_str2bv( rs->sr_matched, 0, 0, &dn );
+ mdn = dn;
+ rc = rwm_dn_massage_pretty( &dc, &dn, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ rs->sr_text = "Rewrite error";
+
+ } else if ( mdn.bv_val != dn.bv_val ) {
+ if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) {
+ ch_free( (void *)rs->sr_matched );
+
+ } else {
+ rs->sr_flags |= REP_MATCHED_MUSTBEFREED;
+ }
+ rs->sr_matched = mdn.bv_val;
+ }
+}
+
+static int
+rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first, int stripEntryDN )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ dncookie dc;
+ int rc;
+ Attribute **ap;
+ int isupdate;
+ int check_duplicate_attrs = 0;
+
+ /*
+ * Rewrite the dn attrs, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+
+ /* FIXME: in principle, one could map an attribute
+ * on top of another, which already exists.
+ * As such, in the end there might exist more than
+ * one instance of an attribute.
+ * We should at least check if this occurs, and issue
+ * an error (because multiple instances of attrs in
+ * response are not valid), or merge the values (what
+ * about duplicate values?) */
+ isupdate = be_shadow_update( op );
+ for ( ap = a_first; *ap; ) {
+ struct ldapmapping *mapping = NULL;
+ int drop_missing;
+ int last = -1;
+ Attribute *a;
+
+ if ( ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ) &&
+ op->ors_attrs != NULL &&
+ !SLAP_USERATTRS( rs->sr_attr_flags ) &&
+ !ad_inlist( (*ap)->a_desc, op->ors_attrs ) )
+ {
+ goto cleanup_attr;
+ }
+
+ drop_missing = rwm_mapping( &rwmap->rwm_at,
+ &(*ap)->a_desc->ad_cname, &mapping, RWM_REMAP );
+ if ( drop_missing || ( mapping != NULL && BER_BVISEMPTY( &mapping->m_dst ) ) )
+ {
+ goto cleanup_attr;
+ }
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+
+ /* try to normalize mapped Attributes if the original
+ * AttributeType was not normalized */
+ if ( (!(*ap)->a_desc->ad_type->sat_equality ||
+ !(*ap)->a_desc->ad_type->sat_equality->smr_normalize) &&
+ mapping->m_dst_ad->ad_type->sat_equality &&
+ mapping->m_dst_ad->ad_type->sat_equality->smr_normalize )
+ {
+ if ((rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS))
+ {
+ int i = 0;
+
+ last = (*ap)->a_numvals;
+ if ( last )
+ {
+ (*ap)->a_nvals = ch_malloc( (last+1) * sizeof(struct berval) );
+
+ for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[i]); i++ ) {
+ int rc;
+ /*
+ * check that each value is valid per syntax
+ * and pretty if appropriate
+ */
+ rc = mapping->m_dst_ad->ad_type->sat_equality->smr_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ mapping->m_dst_ad->ad_type->sat_syntax,
+ mapping->m_dst_ad->ad_type->sat_equality,
+ &(*ap)->a_vals[i], &(*ap)->a_nvals[i],
+ NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ /* FIXME: this is wrong, putting a non-normalized value
+ * into nvals. But when a proxy sends us bogus data,
+ * we still need to give it to the client, even if it
+ * violates the syntax. I.e., we don't want to silently
+ * drop things and trigger an apparent data loss.
+ */
+ ber_dupbv( &(*ap)->a_nvals[i], &(*ap)->a_vals[i] );
+ }
+ }
+ BER_BVZERO( &(*ap)->a_nvals[i] );
+ }
+
+ } else {
+ assert( (*ap)->a_nvals == (*ap)->a_vals );
+ (*ap)->a_nvals = NULL;
+ ber_bvarray_dup_x( &(*ap)->a_nvals, (*ap)->a_vals, NULL );
+ }
+ }
+
+ /* rewrite the attribute description */
+ (*ap)->a_desc = mapping->m_dst_ad;
+
+ /* will need to check for duplicate attrs */
+ check_duplicate_attrs++;
+ }
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_entryDN ) {
+ if ( stripEntryDN ) {
+ /* will be generated by frontend */
+ goto cleanup_attr;
+ }
+
+ } else if ( !isupdate
+ && !get_relax( op )
+ && (*ap)->a_desc->ad_type->sat_no_user_mod
+ && (*ap)->a_desc->ad_type != slap_schema.si_at_undefined )
+ {
+ goto next_attr;
+ }
+
+ if ( last == -1 ) { /* not yet counted */
+ last = (*ap)->a_numvals;
+ }
+
+ if ( last == 0 ) {
+ /* empty? leave it in place because of attrsonly and vlv */
+ goto next_attr;
+ }
+ last--;
+
+ if ( (*ap)->a_desc == slap_schema.si_ad_objectClass
+ || (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass )
+ {
+ struct berval *bv;
+
+ for ( bv = (*ap)->a_vals; !BER_BVISNULL( bv ); bv++ ) {
+ struct berval mapped;
+
+ rwm_map( &rwmap->rwm_oc, &bv[0], &mapped, RWM_REMAP );
+ if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) {
+remove_oc:;
+ ch_free( bv[0].bv_val );
+ BER_BVZERO( &bv[0] );
+ if ( &(*ap)->a_vals[last] > &bv[0] ) {
+ bv[0] = (*ap)->a_vals[last];
+ BER_BVZERO( &(*ap)->a_vals[last] );
+ }
+ last--;
+ bv--;
+
+ } else if ( mapped.bv_val != bv[0].bv_val
+ && ber_bvstrcasecmp( &mapped, &bv[0] ) != 0 )
+ {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[ i ] ); i++ ) {
+ if ( &(*ap)->a_vals[ i ] == bv ) {
+ continue;
+ }
+
+ if ( ber_bvstrcasecmp( &mapped, &(*ap)->a_vals[ i ] ) == 0 ) {
+ break;
+ }
+ }
+
+ if ( !BER_BVISNULL( &(*ap)->a_vals[ i ] ) ) {
+ goto remove_oc;
+ }
+
+ /*
+ * FIXME: after LBER_FREEing
+ * the value is replaced by
+ * ch_alloc'ed memory
+ */
+ ber_bvreplace( &bv[0], &mapped );
+
+ /* FIXME: will need to check
+ * if the structuralObjectClass
+ * changed */
+ }
+ }
+
+ /*
+ * It is necessary to try to rewrite attributes with
+ * dn syntax because they might be used in ACLs as
+ * members of groups; since ACLs are applied to the
+ * rewritten stuff, no dn-based subject clause could
+ * be used at the ldap backend side (see
+ * http://www.OpenLDAP.org/faq/data/cache/452.html)
+ * The problem can be overcome by moving the dn-based
+ * ACLs to the target directory server, and letting
+ * everything pass thru the ldap backend. */
+ /* FIXME: handle distinguishedName-like syntaxes, like
+ * nameAndOptionalUID */
+ } else if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_src_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ dc.ctx = "searchAttrDN";
+ rc = rwm_dnattr_result_rewrite( &dc, (*ap)->a_vals, (*ap)->a_nvals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) {
+ dc.ctx = "searchAttrDN";
+ rc = rwm_referral_result_rewrite( &dc, (*ap)->a_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto cleanup_attr;
+ }
+ }
+
+
+next_attr:;
+ ap = &(*ap)->a_next;
+ continue;
+
+cleanup_attr:;
+ a = *ap;
+ *ap = (*ap)->a_next;
+
+ attr_free( a );
+ }
+
+ /* only check if some mapping occurred */
+ if ( check_duplicate_attrs ) {
+ for ( ap = a_first; *ap != NULL; ap = &(*ap)->a_next ) {
+ Attribute **tap;
+
+ for ( tap = &(*ap)->a_next; *tap != NULL; ) {
+ if ( (*tap)->a_desc == (*ap)->a_desc ) {
+ Entry e = { 0 };
+ Modification mod = { 0 };
+ const char *text = NULL;
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ Attribute *next = (*tap)->a_next;
+
+ BER_BVSTR( &e.e_name, "" );
+ BER_BVSTR( &e.e_nname, "" );
+ e.e_attrs = *ap;
+ mod.sm_op = LDAP_MOD_ADD;
+ mod.sm_desc = (*ap)->a_desc;
+ mod.sm_type = mod.sm_desc->ad_cname;
+ mod.sm_numvals = (*tap)->a_numvals;
+ mod.sm_values = (*tap)->a_vals;
+ if ( (*tap)->a_nvals != (*tap)->a_vals ) {
+ mod.sm_nvalues = (*tap)->a_nvals;
+ }
+
+ (void)modify_add_values( &e, &mod,
+ /* permissive */ 1,
+ &text, textbuf, sizeof( textbuf ) );
+
+ /* should not insert new attrs! */
+ assert( e.e_attrs == *ap );
+
+ attr_free( *tap );
+ *tap = next;
+
+ } else {
+ tap = &(*tap)->a_next;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Should return SLAP_CB_CONTINUE or failure, never LDAP_SUCCESS. */
+static int
+rwm_send_entry( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ Entry *e = NULL;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ dncookie dc;
+ int rc;
+
+ assert( rs->sr_entry != NULL );
+
+ /*
+ * Rewrite the dn of the result, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+ dc.ctx = "searchEntryDN";
+
+ e = rs->sr_entry;
+ if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+ /* FIXME: all we need to duplicate are:
+ * - dn
+ * - ndn
+ * - attributes that are requested
+ * - no values if attrsonly is set
+ */
+ e = entry_dup( e );
+ if ( e == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto fail;
+ }
+ } else if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) {
+ /* ITS#6423: REP_ENTRY_MUSTRELEASE incompatible
+ * with REP_ENTRY_MODIFIABLE */
+ RS_ASSERT( 0 );
+ rc = 1;
+ goto fail;
+ }
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ */
+ dn = e->e_name;
+ ndn = e->e_nname;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &e->e_name, &dn, &ndn );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = 1;
+ goto fail;
+ }
+
+ if ( e->e_name.bv_val != dn.bv_val ) {
+ ch_free( e->e_name.bv_val );
+ ch_free( e->e_nname.bv_val );
+
+ e->e_name = dn;
+ e->e_nname = ndn;
+ }
+
+ /* TODO: map entry attribute types, objectclasses
+ * and dn-valued attribute values */
+
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+ (void)rwm_attrs( op, rs, &e->e_attrs, 1 );
+
+ if ( e != rs->sr_entry ) {
+ /* Reimplementing rs_replace_entry(), I suppose to
+ * bypass our own dubious rwm_entry_release_rw() */
+ if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) {
+ rs->sr_flags ^= REP_ENTRY_MUSTRELEASE;
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ be_entry_release_r( op, rs->sr_entry );
+ op->o_bd->bd_info = (BackendInfo *)on;
+ } else if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) {
+ entry_free( rs->sr_entry );
+ }
+ rs->sr_entry = e;
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+
+ return SLAP_CB_CONTINUE;
+
+fail:;
+ if ( e != NULL && e != rs->sr_entry ) {
+ if ( e->e_name.bv_val == dn.bv_val ) {
+ BER_BVZERO( &e->e_name );
+ }
+
+ if ( e->e_nname.bv_val == ndn.bv_val ) {
+ BER_BVZERO( &e->e_nname );
+ }
+
+ entry_free( e );
+ }
+
+ if ( !BER_BVISNULL( &dn ) ) {
+ ch_free( dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &ndn ) ) {
+ ch_free( ndn.bv_val );
+ }
+
+ return rc;
+}
+
+static int
+rwm_operational( Operation *op, SlapReply *rs )
+{
+ /* FIXME: the entries are in the remote mapping form;
+ * so we need to select those attributes we are willing
+ * to return, and remap them accordingly */
+ if ( rs->sr_operational_attrs ) {
+ rwm_attrs( op, rs, &rs->sr_operational_attrs, 1 );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+#if 0
+/* don't use this; it cannot be reverted, and leaves op->o_req_dn
+ * rewritten for subsequent operations; fine for plain suffixmassage,
+ * but destroys everything else */
+static int
+rwm_chk_referrals( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ int rc;
+
+ rc = rwm_op_dn_massage( op, rs, "referralCheckDN" );
+ if ( rc != LDAP_SUCCESS ) {
+ op->o_bd->bd_info = (BackendInfo *)on->on_info;
+ send_ldap_error( op, rs, rc, "referralCheckDN massage error" );
+ return -1;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+#endif
+
+static int
+rwm_rw_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ return rewrite_parse( rwmap->rwm_rw,
+ fname, lineno, argc, argv );
+
+ return 0;
+}
+
+static int
+rwm_suffixmassage_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ struct berval bvnc, nvnc, pvnc, brnc, nrnc, prnc;
+ int massaged;
+ int rc;
+
+ /*
+ * syntax:
+ *
+ * suffixmassage [<suffix>] <massaged suffix>
+ *
+ * the [<suffix>] field must be defined as a valid suffix
+ * for the current database;
+ * the <massaged suffix> shouldn't have already been
+ * defined as a valid suffix for the current server
+ */
+ if ( argc == 2 ) {
+ if ( be->be_suffix == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: "
+ " \"suffixMassage [<suffix>]"
+ " <massaged suffix>\" without "
+ "<suffix> part requires database "
+ "suffix be defined first.\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+ bvnc = be->be_suffix[ 0 ];
+ massaged = 1;
+
+ } else if ( argc == 3 ) {
+ ber_str2bv( argv[ 1 ], 0, 0, &bvnc );
+ massaged = 2;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is"
+ " \"suffixMassage [<suffix>]"
+ " <massaged suffix>\"\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+
+ if ( dnPrettyNormal( NULL, &bvnc, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n",
+ fname, lineno, bvnc.bv_val );
+ return 1;
+ }
+
+ ber_str2bv( argv[ massaged ], 0, 0, &brnc );
+ if ( dnPrettyNormal( NULL, &brnc, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n",
+ fname, lineno, brnc.bv_val );
+ free( nvnc.bv_val );
+ free( pvnc.bv_val );
+ return 1;
+ }
+
+ /*
+ * The suffix massaging is emulated
+ * by means of the rewrite capabilities
+ */
+ rc = rwm_suffix_massage_config( rwmap->rwm_rw,
+ &pvnc, &nvnc, &prnc, &nrnc );
+ free( nvnc.bv_val );
+ free( pvnc.bv_val );
+ free( nrnc.bv_val );
+ free( prnc.bv_val );
+
+ return rc;
+}
+
+static int
+rwm_m_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ /* objectclass/attribute mapping */
+ return rwm_map_config( &rwmap->rwm_oc,
+ &rwmap->rwm_at,
+ fname, lineno, argc, argv );
+}
+
+static int
+rwm_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc;
+
+ if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) {
+ return rwm_send_entry( op, rs );
+ }
+
+ switch( op->o_tag ) {
+ case LDAP_REQ_SEARCH:
+ case LDAP_REQ_BIND:
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_EXTENDED:
+ if ( rs->sr_ref ) {
+ dncookie dc;
+
+ /*
+ * Rewrite the dn of the referrals, if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = NULL;
+ dc.ctx = "referralDN";
+ rc = rwm_referral_result_rewrite( &dc, rs->sr_ref );
+ /* FIXME: impossible, so far */
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ break;
+ }
+ }
+
+ rwm_matched( op, rs );
+ break;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+rwm_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int rc = 0;
+ char *argv0 = NULL;
+
+ if ( strncasecmp( argv[ 0 ], "rwm-", STRLENOF( "rwm-" ) ) == 0 ) {
+ argv0 = argv[ 0 ];
+ argv[ 0 ] = &argv0[ STRLENOF( "rwm-" ) ];
+ }
+
+ if ( strncasecmp( argv[0], "rewrite", STRLENOF("rewrite") ) == 0 ) {
+ rc = rwm_rw_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "map" ) == 0 ) {
+ rc = rwm_m_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( be, fname, lineno, argc, argv );
+
+ } else if ( strcasecmp( argv[0], "t-f-support" ) == 0 ) {
+ if ( argc != 2 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"t-f-support {no|yes|discover}\" needs 1 argument.\n",
+ fname, lineno, 0 );
+ return( 1 );
+ }
+
+ if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) {
+ rwmap->rwm_flags &= ~(RWM_F_SUPPORT_T_F_MASK2);
+
+ } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) {
+ rwmap->rwm_flags |= RWM_F_SUPPORT_T_F;
+
+ /* TODO: not implemented yet */
+ } else if ( strcasecmp( argv[ 1 ], "discover" ) == 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"discover\" not supported yet "
+ "in \"t-f-support {no|yes|discover}\".\n",
+ fname, lineno, 0 );
+ return( 1 );
+#if 0
+ rwmap->rwm_flags |= RWM_F_SUPPORT_T_F_DISCOVER;
+#endif
+
+ } else {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: unknown value \"%s\" for \"t-f-support {no|yes|discover}\".\n",
+ fname, lineno, argv[ 1 ] );
+ return 1;
+ }
+
+ } else if ( strcasecmp( argv[0], "normalize-mapped-attrs" ) == 0 ) {
+ if ( argc !=2 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: \"normalize-mapped-attrs {no|yes}\" needs 1 argument.\n",
+ fname, lineno, 0 );
+ return( 1 );
+ }
+
+ if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) {
+ rwmap->rwm_flags &= ~(RWM_F_NORMALIZE_MAPPED_ATTRS);
+
+ } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) {
+ rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS;
+ }
+
+ } else {
+ rc = SLAP_CONF_UNKNOWN;
+ }
+
+ if ( argv0 ) {
+ argv[ 0 ] = argv0;
+ }
+
+ return rc;
+}
+
+/*
+ * dynamic configuration...
+ */
+
+enum {
+ /* rewrite */
+ RWM_CF_REWRITE = 1,
+
+ /* map */
+ RWM_CF_MAP,
+ RWM_CF_T_F_SUPPORT,
+ RWM_CF_NORMALIZE_MAPPED,
+ RWM_CF_DROP_UNREQUESTED,
+
+ RWM_CF_LAST
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "true" ), RWM_F_SUPPORT_T_F },
+ { BER_BVC( "yes" ), RWM_F_SUPPORT_T_F },
+ { BER_BVC( "discover" ), RWM_F_SUPPORT_T_F_DISCOVER },
+ { BER_BVC( "false" ), RWM_F_NONE },
+ { BER_BVC( "no" ), RWM_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static ConfigDriver rwm_cf_gen;
+
+static ConfigTable rwmcfg[] = {
+ { "rwm-rewrite", "rewrite",
+ 2, 0, STRLENOF("rwm-rewrite"),
+ ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen,
+ "( OLcfgOvAt:16.1 NAME 'olcRwmRewrite' "
+ "DESC 'Rewrites strings' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+
+ { "rwm-suffixmassage", "[virtual]> <real",
+ 2, 3, 0, ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen,
+ NULL, NULL, NULL },
+
+ { "rwm-t-f-support", "true|false|discover",
+ 2, 2, 0, ARG_MAGIC|RWM_CF_T_F_SUPPORT, rwm_cf_gen,
+ "( OLcfgOvAt:16.2 NAME 'olcRwmTFSupport' "
+ "DESC 'Absolute filters support' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "rwm-map", "{objectClass|attribute}",
+ 2, 4, 0, ARG_MAGIC|RWM_CF_MAP, rwm_cf_gen,
+ "( OLcfgOvAt:16.3 NAME 'olcRwmMap' "
+ "DESC 'maps attributes/objectClasses' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+
+ { "rwm-normalize-mapped-attrs", "true|false",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_NORMALIZE_MAPPED, rwm_cf_gen,
+ "( OLcfgOvAt:16.4 NAME 'olcRwmNormalizeMapped' "
+ "DESC 'Normalize mapped attributes/objectClasses' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "rwm-drop-unrequested-attrs", "true|false",
+ 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_DROP_UNREQUESTED, rwm_cf_gen,
+ "( OLcfgOvAt:16.5 NAME 'olcRwmDropUnrequested' "
+ "DESC 'Drop unrequested attributes' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs rwmocs[] = {
+ { "( OLcfgOvOc:16.1 "
+ "NAME 'olcRwmConfig' "
+ "DESC 'Rewrite/remap configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( "
+ "olcRwmRewrite $ "
+ "olcRwmTFSupport $ "
+ "olcRwmMap $ "
+ "olcRwmNormalizeMapped $ "
+ "olcRwmDropUnrequested"
+ ") )",
+ Cft_Overlay, rwmcfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static void
+slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out )
+{
+ int i;
+ BerVarray bva = NULL;
+ char ibuf[32], *ptr;
+ struct berval idx;
+
+ assert( in != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ )
+ /* count'em */ ;
+
+ if ( i == 0 ) {
+ return;
+ }
+
+ idx.bv_val = ibuf;
+
+ bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) );
+ BER_BVZERO( &bva[ 0 ] );
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+ idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), "{%d}", i );
+ if ( idx.bv_len >= sizeof( ibuf ) ) {
+ ber_bvarray_free( bva );
+ return;
+ }
+
+ bva[i].bv_len = idx.bv_len + in[i].bv_len;
+ bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 );
+ ptr = lutil_strcopy( bva[i].bv_val, ibuf );
+ ptr = lutil_strcopy( ptr, in[i].bv_val );
+ *ptr = '\0';
+ BER_BVZERO( &bva[ i + 1 ] );
+ }
+
+ *out = bva;
+}
+
+static int
+rwm_bva_add(
+ BerVarray *bva,
+ int idx,
+ char **argv )
+{
+ char *line;
+ struct berval bv;
+
+ line = ldap_charray2str( argv, "\" \"" );
+ if ( line != NULL ) {
+ int len = strlen( argv[ 0 ] );
+
+ ber_str2bv( line, 0, 0, &bv );
+ AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ],
+ bv.bv_len - ( len + 1 ) );
+ bv.bv_val[ bv.bv_len - 1 ] = '"';
+
+ if ( idx == -1 ) {
+ ber_bvarray_add( bva, &bv );
+
+ } else {
+ (*bva)[ idx ] = bv;
+ }
+
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+rwm_bva_rewrite_add(
+ struct ldaprwmap *rwmap,
+ int idx,
+ char **argv )
+{
+ return rwm_bva_add( &rwmap->rwm_bva_rewrite, idx, argv );
+}
+
+#ifdef unused
+static int
+rwm_bva_map_add(
+ struct ldaprwmap *rwmap,
+ int idx,
+ char **argv )
+{
+ return rwm_bva_add( &rwmap->rwm_bva_map, idx, argv );
+}
+#endif /* unused */
+
+static int
+rwm_info_init( struct rewrite_info ** rwm_rw )
+{
+ char *rargv[ 3 ];
+
+ *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT );
+ if ( *rwm_rw == NULL ) {
+ return -1;
+ }
+
+ /* this rewriteContext by default must be null;
+ * rules can be added if required */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchFilter";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( *rwm_rw, "<suffix massage>", 2, 2, rargv );
+
+ return 0;
+}
+
+static int
+rwm_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ BackendDB db;
+ char *argv0;
+ int idx0 = 0;
+ int rc = 0;
+
+ db = *c->be;
+ db.bd_info = c->bi;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( rwmap->rwm_bva_rewrite == NULL ) {
+ rc = 1;
+
+ } else {
+ slap_bv_x_ordered_unparse( rwmap->rwm_bva_rewrite, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) {
+ rc = 1;
+ }
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ enum_to_verb( t_f_mode, (rwmap->rwm_flags & RWM_F_SUPPORT_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case RWM_CF_MAP:
+ if ( rwmap->rwm_bva_map == NULL ) {
+ rc = 1;
+
+ } else {
+ slap_bv_x_ordered_unparse( rwmap->rwm_bva_map, &c->rvalue_vals );
+ if ( !c->rvalue_vals ) {
+ rc = 1;
+ }
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ c->value_int = ( rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS );
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ c->value_int = ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS );
+ break;
+
+ default:
+ assert( 0 );
+ rc = 1;
+ }
+
+ return rc;
+
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( c->valx >= 0 ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ /* count'em */ ;
+
+ if ( c->valx >= i ) {
+ rc = 1;
+ break;
+ }
+
+ ber_memfree( rwmap->rwm_bva_rewrite[ c->valx ].bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i + 1 ] ); i++ )
+ {
+ rwmap->rwm_bva_rewrite[ i ] = rwmap->rwm_bva_rewrite[ i + 1 ];
+ }
+ BER_BVZERO( &rwmap->rwm_bva_rewrite[ i ] );
+
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ } else if ( rwmap->rwm_rw != NULL ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ ber_bvarray_free( rwmap->rwm_bva_rewrite );
+ rwmap->rwm_bva_rewrite = NULL;
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2;
+ break;
+
+ case RWM_CF_MAP:
+ if ( c->valx >= 0 ) {
+ struct ldapmap rwm_oc = rwmap->rwm_oc;
+ struct ldapmap rwm_at = rwmap->rwm_at;
+ char *argv[5];
+ int cnt = 0;
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( c->valx >= cnt ) {
+ rc = 1;
+ break;
+ }
+
+ memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) );
+ memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) );
+
+ /* re-parse all mappings except the one
+ * that needs to be eliminated */
+ argv[0] = "map";
+ for ( cnt = 0; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ if ( cnt == c->valx ) {
+ continue;
+ }
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ rwmap->rwm_oc = rwm_oc;
+ rwmap->rwm_at = rwm_at;
+ break;
+ }
+ }
+
+ /* in case of success, destroy the old mapping
+ * and eliminate the deleted one */
+ if ( rc == 0 ) {
+ avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwm_oc.map, rwm_mapping_free );
+ avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwm_at.map, rwm_mapping_free );
+
+ ber_memfree( rwmap->rwm_bva_map[ c->valx ].bv_val );
+ for ( cnt = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ rwmap->rwm_bva_map[ cnt ] = rwmap->rwm_bva_map[ cnt + 1 ];
+ }
+ }
+
+ } else {
+ avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+
+ rwmap->rwm_oc.remap = NULL;
+ rwmap->rwm_oc.map = NULL;
+ rwmap->rwm_at.remap = NULL;
+ rwmap->rwm_at.map = NULL;
+
+ ber_bvarray_free( rwmap->rwm_bva_map );
+ rwmap->rwm_bva_map = NULL;
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS;
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS;
+ break;
+
+ default:
+ return 1;
+ }
+ return rc;
+ }
+
+ if ( strncasecmp( c->argv[ 0 ], "olcRwm", STRLENOF( "olcRwm" ) ) == 0 ) {
+ idx0 = 1;
+ }
+
+ switch ( c->type ) {
+ case RWM_CF_REWRITE:
+ if ( c->valx >= 0 ) {
+ struct rewrite_info *rwm_rw = rwmap->rwm_rw;
+ int i, last;
+
+ for ( last = 0; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ last ] ); last++ )
+ /* count'em */ ;
+
+ if ( c->valx > last ) {
+ c->valx = last;
+ }
+
+ rwmap->rwm_rw = NULL;
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ for ( i = 0; i < c->valx; i++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ argv0 = c->argv[ idx0 ];
+ if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) {
+ return 1;
+ }
+ c->argv[ idx0 ] += STRLENOF( "rwm-" );
+ if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+ }
+ c->argv[ idx0 ] = argv0;
+ if ( rc != 0 ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ assert( rwmap->rwm_rw == NULL );
+
+ rwmap->rwm_rw = rwm_rw;
+ return 1;
+ }
+
+ for ( i = c->valx; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ )
+ {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv0 = ca.argv[ 0 ];
+ ca.argv[ 0 ] += STRLENOF( "rwm-" );
+
+ if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ ca.argc, ca.argv );
+ }
+
+ ca.argv[ 0 ] = argv0;
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ assert( rc == 0 );
+ }
+
+ rwmap->rwm_bva_rewrite = ch_realloc( rwmap->rwm_bva_rewrite,
+ ( last + 2 )*sizeof( struct berval ) );
+ BER_BVZERO( &rwmap->rwm_bva_rewrite[last+1] );
+
+ for ( i = last - 1; i >= c->valx; i-- )
+ {
+ rwmap->rwm_bva_rewrite[ i + 1 ] = rwmap->rwm_bva_rewrite[ i ];
+ }
+
+ rwm_bva_rewrite_add( rwmap, c->valx, &c->argv[ idx0 ] );
+
+ rewrite_info_delete( &rwm_rw );
+ assert( rwm_rw == NULL );
+
+ break;
+ }
+
+ argv0 = c->argv[ idx0 ];
+ if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) {
+ return 1;
+ }
+ c->argv[ idx0 ] += STRLENOF( "rwm-" );
+ if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) {
+ rc = rwm_suffixmassage_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+
+ } else {
+ rc = rwm_rw_config( &db, c->fname, c->lineno,
+ c->argc - idx0, &c->argv[ idx0 ] );
+ }
+ c->argv[ idx0 ] = argv0;
+ if ( rc ) {
+ return 1;
+
+ } else {
+ rwm_bva_rewrite_add( rwmap, -1, &c->argv[ idx0 ] );
+ }
+ break;
+
+ case RWM_CF_T_F_SUPPORT:
+ rc = verb_to_mask( c->argv[ 1 ], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[ rc ].word ) ) {
+ return 1;
+ }
+
+ rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2;
+ rwmap->rwm_flags |= t_f_mode[ rc ].mask;
+ rc = 0;
+ break;
+
+ case RWM_CF_MAP:
+ if ( c->valx >= 0 ) {
+ struct ldapmap rwm_oc = rwmap->rwm_oc;
+ struct ldapmap rwm_at = rwmap->rwm_at;
+ char *argv[5];
+ int cnt = 0;
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ )
+ /* count */ ;
+ }
+
+ if ( c->valx >= cnt ) {
+ c->valx = cnt;
+ }
+
+ memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) );
+ memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) );
+
+ /* re-parse all mappings, including the one
+ * that needs to be added */
+ argv[0] = "map";
+ for ( cnt = 0; cnt < c->valx; cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+ }
+
+ argv0 = c->argv[0];
+ c->argv[0] = "map";
+ rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[0] = argv0;
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+
+ if ( rwmap->rwm_bva_map ) {
+ for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) {
+ ConfigArgs ca = { 0 };
+
+ ca.line = rwmap->rwm_bva_map[ cnt ].bv_val;
+ ca.argc = 0;
+ init_config_argv( &ca );
+ config_parse_ldif( &ca );
+
+ argv[1] = ca.argv[0];
+ argv[2] = ca.argv[1];
+ argv[3] = ca.argv[2];
+ argv[4] = ca.argv[3];
+
+ rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv );
+
+ ch_free( ca.tline );
+ ch_free( ca.argv );
+
+ /* in case of failure, restore
+ * the existing mapping */
+ if ( rc ) {
+ goto rwmmap_fail;
+ }
+ }
+ }
+
+ /* in case of success, destroy the old mapping
+ * and add the new one */
+ if ( rc == 0 ) {
+ BerVarray tmp;
+ struct berval bv, *bvp = &bv;
+
+ if ( rwm_bva_add( &bvp, 0, &c->argv[ idx0 ] ) ) {
+ rc = 1;
+ goto rwmmap_fail;
+ }
+
+ tmp = ber_memrealloc( rwmap->rwm_bva_map,
+ sizeof( struct berval )*( cnt + 2 ) );
+ if ( tmp == NULL ) {
+ ber_memfree( bv.bv_val );
+ rc = 1;
+ goto rwmmap_fail;
+ }
+ rwmap->rwm_bva_map = tmp;
+ BER_BVZERO( &rwmap->rwm_bva_map[ cnt + 1 ] );
+
+ avl_free( rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwm_oc.map, rwm_mapping_free );
+ avl_free( rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwm_at.map, rwm_mapping_free );
+
+ for ( ; cnt-- > c->valx; ) {
+ rwmap->rwm_bva_map[ cnt + 1 ] = rwmap->rwm_bva_map[ cnt ];
+ }
+ rwmap->rwm_bva_map[ c->valx ] = bv;
+
+ } else {
+rwmmap_fail:;
+ avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ rwmap->rwm_oc = rwm_oc;
+ rwmap->rwm_at = rwm_at;
+ }
+
+ break;
+ }
+
+ argv0 = c->argv[ 0 ];
+ c->argv[ 0 ] += STRLENOF( "rwm-" );
+ rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv );
+ c->argv[ 0 ] = argv0;
+ if ( rc ) {
+ return 1;
+
+ } else {
+ char *line;
+ struct berval bv;
+
+ line = ldap_charray2str( &c->argv[ 1 ], " " );
+ if ( line != NULL ) {
+ ber_str2bv( line, 0, 0, &bv );
+ ber_bvarray_add( &rwmap->rwm_bva_map, &bv );
+ }
+ }
+ break;
+
+ case RWM_CF_NORMALIZE_MAPPED:
+ if ( c->value_int ) {
+ rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS;
+ } else {
+ rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS;
+ }
+ break;
+
+ case RWM_CF_DROP_UNREQUESTED:
+ if ( c->value_int ) {
+ rwmap->rwm_flags |= RWM_F_DROP_UNREQUESTED_ATTRS;
+ } else {
+ rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ return 1;
+ }
+
+ return rc;
+}
+
+static int
+rwm_db_init(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ struct ldaprwmap *rwmap;
+ int rc = 0;
+
+ rwmap = (struct ldaprwmap *)ch_calloc( 1, sizeof( struct ldaprwmap ) );
+
+ /* default */
+ rwmap->rwm_flags = RWM_F_DROP_UNREQUESTED_ATTRS;
+
+ rc = rwm_info_init( &rwmap->rwm_rw );
+
+ on->on_bi.bi_private = (void *)rwmap;
+
+ if ( rc ) {
+ (void)rwm_db_destroy( be, NULL );
+ }
+
+ return rc;
+}
+
+static int
+rwm_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ int rc = 0;
+
+ if ( on->on_bi.bi_private ) {
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ if ( rwmap->rwm_rw ) {
+ rewrite_info_delete( &rwmap->rwm_rw );
+ if ( rwmap->rwm_bva_rewrite )
+ ber_bvarray_free( rwmap->rwm_bva_rewrite );
+ }
+
+ avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_oc.map, rwm_mapping_free );
+ avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free );
+ avl_free( rwmap->rwm_at.map, rwm_mapping_free );
+ ber_bvarray_free( rwmap->rwm_bva_map );
+
+ ch_free( rwmap );
+ }
+
+ return rc;
+}
+
+static slap_overinst rwm = { { NULL } };
+
+#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC
+static
+#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */
+int
+rwm_initialize( void )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( RWM_CF_LAST );
+
+ memset( &rwm, 0, sizeof( slap_overinst ) );
+
+ rwm.on_bi.bi_type = "rwm";
+ rwm.on_bi.bi_flags =
+ SLAPO_BFLAG_SINGLE |
+ 0;
+
+ rwm.on_bi.bi_db_init = rwm_db_init;
+ rwm.on_bi.bi_db_config = rwm_db_config;
+ rwm.on_bi.bi_db_destroy = rwm_db_destroy;
+
+ rwm.on_bi.bi_op_bind = rwm_op_bind;
+ rwm.on_bi.bi_op_search = rwm_op_search;
+ rwm.on_bi.bi_op_compare = rwm_op_compare;
+ rwm.on_bi.bi_op_modify = rwm_op_modify;
+ rwm.on_bi.bi_op_modrdn = rwm_op_modrdn;
+ rwm.on_bi.bi_op_add = rwm_op_add;
+ rwm.on_bi.bi_op_delete = rwm_op_delete;
+ rwm.on_bi.bi_op_unbind = rwm_op_unbind;
+ rwm.on_bi.bi_extended = rwm_extended;
+#if 1 /* TODO */
+ rwm.on_bi.bi_entry_release_rw = rwm_entry_release_rw;
+ rwm.on_bi.bi_entry_get_rw = rwm_entry_get_rw;
+#endif
+
+ rwm.on_bi.bi_operational = rwm_operational;
+ rwm.on_bi.bi_chk_referrals = 0 /* rwm_chk_referrals */ ;
+
+ rwm.on_bi.bi_connection_init = rwm_conn_init;
+ rwm.on_bi.bi_connection_destroy = rwm_conn_destroy;
+
+ rwm.on_response = rwm_response;
+
+ rwm.on_bi.bi_cf_ocs = rwmocs;
+
+ rc = config_register_schema( rwmcfg, rwmocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return overlay_register( &rwm );
+}
+
+#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return rwm_initialize();
+}
+#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwm.h b/servers/slapd/overlays/rwm.h
new file mode 100644
index 0000000..8d16a66
--- /dev/null
+++ b/servers/slapd/overlays/rwm.h
@@ -0,0 +1,187 @@
+/* rwm.h - dn rewrite/attribute mapping header file */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2018 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#ifndef RWM_H
+#define RWM_H
+
+#ifndef ENABLE_REWRITE
+#error "librewrite must be enabled!"
+#endif /* ENABLE_REWRITE */
+
+/* String rewrite library */
+#include "rewrite.h"
+
+LDAP_BEGIN_DECL
+
+/* define to enable referral DN massage by default */
+#undef RWM_REFERRAL_REWRITE
+
+struct ldapmap {
+ int drop_missing;
+
+ Avlnode *map;
+ Avlnode *remap;
+};
+
+struct ldapmapping {
+ int m_flags;
+#define RWMMAP_F_NONE 0x00
+#define RWMMAP_F_IS_OC 0x01
+#define RWMMAP_F_FREE_SRC 0x10
+#define RWMMAP_F_FREE_DST 0x20
+ struct berval m_src;
+ union {
+ AttributeDescription *m_s_ad;
+ ObjectClass *m_s_oc;
+ } m_src_ref;
+#define m_src_ad m_src_ref.m_s_ad
+#define m_src_oc m_src_ref.m_s_oc
+ struct berval m_dst;
+ union {
+ AttributeDescription *m_d_ad;
+ ObjectClass *m_d_oc;
+ } m_dst_ref;
+#define m_dst_ad m_dst_ref.m_d_ad
+#define m_dst_oc m_dst_ref.m_d_oc
+};
+
+struct ldaprwmap {
+ /*
+ * DN rewriting
+ */
+ struct rewrite_info *rwm_rw;
+ BerVarray rwm_bva_rewrite;
+
+ /*
+ * Attribute/objectClass mapping
+ */
+ struct ldapmap rwm_oc;
+ struct ldapmap rwm_at;
+ BerVarray rwm_bva_map;
+
+#define RWM_F_NONE (0x0000U)
+#define RWM_F_NORMALIZE_MAPPED_ATTRS (0x0001U)
+#define RWM_F_DROP_UNREQUESTED_ATTRS (0x0002U)
+#define RWM_F_SUPPORT_T_F (0x4000U)
+#define RWM_F_SUPPORT_T_F_DISCOVER (0x8000U)
+#define RWM_F_SUPPORT_T_F_MASK (RWM_F_SUPPORT_T_F)
+#define RWM_F_SUPPORT_T_F_MASK2 (RWM_F_SUPPORT_T_F|RWM_F_SUPPORT_T_F_DISCOVER)
+ unsigned rwm_flags;
+};
+
+/* Whatever context ldap_back_dn_massage needs... */
+typedef struct dncookie {
+ struct ldaprwmap *rwmap;
+
+ Connection *conn;
+ char *ctx;
+ SlapReply *rs;
+} dncookie;
+
+int rwm_dn_massage( dncookie *dc, struct berval *in, struct berval *dn );
+int rwm_dn_massage_pretty( dncookie *dc, struct berval *in, struct berval *pdn );
+int rwm_dn_massage_normalize( dncookie *dc, struct berval *in, struct berval *ndn );
+int rwm_dn_massage_pretty_normalize( dncookie *dc, struct berval *in, struct berval *pdn, struct berval *ndn );
+
+/* attributeType/objectClass mapping */
+int rwm_mapping_cmp (const void *, const void *);
+int rwm_mapping_dup (void *, void *);
+
+int rwm_map_init ( struct ldapmap *lm, struct ldapmapping ** );
+void rwm_map ( struct ldapmap *map, struct berval *s, struct berval *m,
+ int remap );
+int rwm_mapping ( struct ldapmap *map, struct berval *s,
+ struct ldapmapping **m, int remap );
+#define RWM_MAP 0
+#define RWM_REMAP 1
+char *
+rwm_map_filter(
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ struct berval *f );
+
+#if 0 /* unused! */
+int
+rwm_map_attrs(
+ struct ldapmap *at_map,
+ AttributeName *a,
+ int remap,
+ char ***mapped_attrs );
+#endif
+
+int
+rwm_map_attrnames(
+ Operation *op,
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ AttributeName *an,
+ AttributeName **anp,
+ int remap );
+
+extern void rwm_mapping_dst_free ( void *mapping );
+
+extern void rwm_mapping_free ( void *mapping );
+
+extern int rwm_map_config(
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv );
+
+extern int
+rwm_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr );
+
+/* suffix massaging by means of librewrite */
+extern int
+rwm_suffix_massage_config(
+ struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc);
+extern int
+rwm_dnattr_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals );
+extern int
+rwm_referral_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals );
+extern int rwm_dnattr_result_rewrite( dncookie *dc, BerVarray a_vals, BerVarray a_nvals );
+extern int rwm_referral_result_rewrite( dncookie *dc, BerVarray a_vals );
+
+LDAP_END_DECL
+
+#endif /* RWM_H */
diff --git a/servers/slapd/overlays/rwmconf.c b/servers/slapd/overlays/rwmconf.c
new file mode 100644
index 0000000..170f8e1
--- /dev/null
+++ b/servers/slapd/overlays/rwmconf.c
@@ -0,0 +1,417 @@
+/* rwmconf.c - rewrite/map configuration file routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2018 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+#include "lutil.h"
+
+int
+rwm_map_config(
+ struct ldapmap *oc_map,
+ struct ldapmap *at_map,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv )
+{
+ struct ldapmap *map;
+ struct ldapmapping *mapping;
+ char *src, *dst;
+ int is_oc = 0;
+ int rc = 0;
+
+ if ( argc < 3 || argc > 4 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: syntax is \"map {objectclass | attribute} [<local> | *] {<foreign> | *}\"\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+
+ if ( strcasecmp( argv[1], "objectclass" ) == 0 ) {
+ map = oc_map;
+ is_oc = 1;
+
+ } else if ( strcasecmp( argv[1], "attribute" ) == 0 ) {
+ map = at_map;
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is "
+ "\"map {objectclass | attribute} [<local> | *] "
+ "{<foreign> | *}\"\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+
+ if ( !is_oc && map->map == NULL ) {
+ /* only init if required */
+ if ( rwm_map_init( map, &mapping ) != LDAP_SUCCESS ) {
+ return 1;
+ }
+ }
+
+ if ( strcmp( argv[2], "*" ) == 0 ) {
+ if ( argc < 4 || strcmp( argv[3], "*" ) == 0 ) {
+ map->drop_missing = ( argc < 4 );
+ goto success_return;
+ }
+ src = dst = argv[3];
+
+ } else if ( argc < 4 ) {
+ src = "";
+ dst = argv[2];
+
+ } else {
+ src = argv[2];
+ dst = ( strcmp( argv[3], "*" ) == 0 ? src : argv[3] );
+ }
+
+ if ( ( map == at_map )
+ && ( strcasecmp( src, "objectclass" ) == 0
+ || strcasecmp( dst, "objectclass" ) == 0 ) )
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: objectclass attribute cannot be mapped\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof(struct ldapmapping) );
+ if ( mapping == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: out of memory\n",
+ fname, lineno, 0 );
+ return 1;
+ }
+ ber_str2bv( src, 0, 1, &mapping[0].m_src );
+ ber_str2bv( dst, 0, 1, &mapping[0].m_dst );
+ mapping[1].m_src = mapping[0].m_dst;
+ mapping[1].m_dst = mapping[0].m_src;
+
+ mapping[0].m_flags = RWMMAP_F_NONE;
+ mapping[1].m_flags = RWMMAP_F_NONE;
+
+ /*
+ * schema check
+ */
+ if ( is_oc ) {
+ if ( src[0] != '\0' ) {
+ mapping[0].m_src_oc = oc_bvfind( &mapping[0].m_src );
+ if ( mapping[0].m_src_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, source objectClass '%s' "
+ "should be defined in schema\n",
+ fname, lineno, src );
+
+ /*
+ * FIXME: this should become an err
+ */
+ mapping[0].m_src_oc = ch_malloc( sizeof( ObjectClass ) );
+ memset( mapping[0].m_src_oc, 0, sizeof( ObjectClass ) );
+ mapping[0].m_src_oc->soc_cname = mapping[0].m_src;
+ mapping[0].m_flags |= RWMMAP_F_FREE_SRC;
+ }
+ mapping[1].m_dst_oc = mapping[0].m_src_oc;
+ }
+
+ mapping[0].m_dst_oc = oc_bvfind( &mapping[0].m_dst );
+ if ( mapping[0].m_dst_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, destination objectClass '%s' "
+ "is not defined in schema\n",
+ fname, lineno, dst );
+
+ mapping[0].m_dst_oc = oc_bvfind_undef( &mapping[0].m_dst );
+ if ( mapping[0].m_dst_oc == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "%s: line %d: unable to mimic destination objectClass '%s'\n",
+ fname, lineno, dst );
+ goto error_return;
+ }
+ }
+ mapping[1].m_src_oc = mapping[0].m_dst_oc;
+
+ mapping[0].m_flags |= RWMMAP_F_IS_OC;
+ mapping[1].m_flags |= RWMMAP_F_IS_OC;
+
+ } else {
+ int rc;
+ const char *text = NULL;
+
+ if ( src[0] != '\0' ) {
+ rc = slap_bv2ad( &mapping[0].m_src,
+ &mapping[0].m_src_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, source attributeType '%s' "
+ "should be defined in schema\n",
+ fname, lineno, src );
+
+ /*
+ * we create a fake "proxied" ad
+ * and add it here.
+ */
+
+ rc = slap_bv2undef_ad( &mapping[0].m_src,
+ &mapping[0].m_src_ad, &text,
+ SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ char prefix[1024];
+ snprintf( prefix, sizeof(prefix),
+ "%s: line %d: source attributeType '%s': %d",
+ fname, lineno, src, rc );
+ Debug( LDAP_DEBUG_ANY, "%s (%s)\n",
+ prefix, text ? text : "null", 0 );
+ goto error_return;
+ }
+
+ }
+ mapping[1].m_dst_ad = mapping[0].m_src_ad;
+ }
+
+ rc = slap_bv2ad( &mapping[0].m_dst, &mapping[0].m_dst_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: warning, destination attributeType '%s' "
+ "is not defined in schema\n",
+ fname, lineno, dst );
+
+ rc = slap_bv2undef_ad( &mapping[0].m_dst,
+ &mapping[0].m_dst_ad, &text,
+ SLAP_AD_PROXIED );
+ if ( rc != LDAP_SUCCESS ) {
+ char prefix[1024];
+ snprintf( prefix, sizeof(prefix),
+ "%s: line %d: destination attributeType '%s': %d",
+ fname, lineno, dst, rc );
+ Debug( LDAP_DEBUG_ANY, "%s (%s)\n",
+ prefix, text ? text : "null", 0 );
+ goto error_return;
+ }
+ }
+ mapping[1].m_src_ad = mapping[0].m_dst_ad;
+ }
+
+ if ( ( src[0] != '\0' && avl_find( map->map, (caddr_t)mapping, rwm_mapping_cmp ) != NULL)
+ || avl_find( map->remap, (caddr_t)&mapping[1], rwm_mapping_cmp ) != NULL)
+ {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: line %d: duplicate mapping found.\n",
+ fname, lineno, 0 );
+ /* FIXME: free stuff */
+ goto error_return;
+ }
+
+ if ( src[0] != '\0' ) {
+ avl_insert( &map->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ }
+ avl_insert( &map->remap, (caddr_t)&mapping[1],
+ rwm_mapping_cmp, rwm_mapping_dup );
+
+success_return:;
+ return rc;
+
+error_return:;
+ if ( mapping ) {
+ rwm_mapping_free( mapping );
+ }
+
+ return 1;
+}
+
+static char *
+rwm_suffix_massage_regexize( const char *s )
+{
+ char *res, *ptr;
+ const char *p, *r;
+ int i;
+
+ if ( s[0] == '\0' ) {
+ return ch_strdup( "^(.+)$" );
+ }
+
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1, i++ )
+ ;
+
+ res = ch_calloc( sizeof( char ), strlen( s )
+ + STRLENOF( "((.+),)?" )
+ + STRLENOF( "[ ]?" ) * i
+ + STRLENOF( "$" ) + 1 );
+
+ ptr = lutil_strcopy( res, "((.+),)?" );
+ for ( i = 0, p = s;
+ ( r = strchr( p, ',' ) ) != NULL;
+ p = r + 1 , i++ ) {
+ ptr = lutil_strncopy( ptr, p, r - p + 1 );
+ ptr = lutil_strcopy( ptr, "[ ]?" );
+
+ if ( r[ 1 ] == ' ' ) {
+ r++;
+ }
+ }
+ ptr = lutil_strcopy( ptr, p );
+ ptr[0] = '$';
+ ptr[1] = '\0';
+
+ return res;
+}
+
+static char *
+rwm_suffix_massage_patternize( const char *s, const char *p )
+{
+ ber_len_t len;
+ char *res, *ptr;
+
+ len = strlen( p );
+
+ if ( s[ 0 ] == '\0' ) {
+ len++;
+ }
+
+ res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 );
+ if ( res == NULL ) {
+ return NULL;
+ }
+
+ ptr = lutil_strcopy( res, ( p[0] == '\0' ? "%2" : "%1" ) );
+ if ( s[ 0 ] == '\0' ) {
+ ptr[ 0 ] = ',';
+ ptr++;
+ }
+ lutil_strcopy( ptr, p );
+
+ return res;
+}
+
+int
+rwm_suffix_massage_config(
+ struct rewrite_info *info,
+ struct berval *pvnc,
+ struct berval *nvnc,
+ struct berval *prnc,
+ struct berval *nrnc
+)
+{
+ char *rargv[ 5 ];
+ int line = 0;
+
+ rargv[ 0 ] = "rewriteEngine";
+ rargv[ 1 ] = "on";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "default";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = rwm_suffix_massage_regexize( pvnc->bv_val );
+ rargv[ 2 ] = rwm_suffix_massage_patternize( pvnc->bv_val, prnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( pvnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = prnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchEntryDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = rwm_suffix_massage_regexize( prnc->bv_val );
+ rargv[ 2 ] = rwm_suffix_massage_patternize( prnc->bv_val, pvnc->bv_val );
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ ch_free( rargv[ 1 ] );
+ ch_free( rargv[ 2 ] );
+
+ if ( BER_BVISEMPTY( prnc ) ) {
+ rargv[ 0 ] = "rewriteRule";
+ rargv[ 1 ] = "^$";
+ rargv[ 2 ] = pvnc->bv_val;
+ rargv[ 3 ] = ":";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+ }
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "matchedDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+#ifdef RWM_REFERRAL_REWRITE
+ /* FIXME: we don't want this on by default, do we? */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+#else /* ! RWM_REFERRAL_REWRITE */
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralAttrDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "referralDN";
+ rargv[ 2 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 2, rargv );
+#endif /* ! RWM_REFERRAL_REWRITE */
+
+ rargv[ 0 ] = "rewriteContext";
+ rargv[ 1 ] = "searchAttrDN";
+ rargv[ 2 ] = "alias";
+ rargv[ 3 ] = "searchEntryDN";
+ rargv[ 4 ] = NULL;
+ rewrite_parse( info, "<suffix massage>", ++line, 4, rargv );
+
+ return 0;
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwmdn.c b/servers/slapd/overlays/rwmdn.c
new file mode 100644
index 0000000..5afc048
--- /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-2018 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+
+/* FIXME: after rewriting, we should also remap attributes ... */
+
+/*
+ * massages "in" and normalizes it into "ndn"
+ *
+ * "ndn" may be untouched if no massaging occurred and its value was not null
+ */
+int
+rwm_dn_massage_normalize(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *ndn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage and normalize a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( ndn ) ) {
+ return rc;
+ }
+
+ rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" and prettifies it into "pdn"
+ *
+ * "pdn" may be untouched if no massaging occurred and its value was not null
+ */
+int
+rwm_dn_massage_pretty(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *pdn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage and pretty a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) {
+ return rc;
+ }
+
+ rc = dnPretty( NULL, &mdn, pdn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" and prettifies and normalizes it into "pdn" and "ndn"
+ *
+ * "pdn" may be untouched if no massaging occurred and its value was not null;
+ * "ndn" may be untouched if no massaging occurred and its value was not null;
+ * if no massage occurred and "ndn" value was not null, it is filled
+ * with the normaized value of "pdn", much like ndn = dnNormalize( pdn )
+ */
+int
+rwm_dn_massage_pretty_normalize(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *pdn,
+ struct berval *ndn )
+{
+ int rc;
+ struct berval mdn = BER_BVNULL;
+
+ /* massage, pretty and normalize a DN */
+ rc = rwm_dn_massage( dc, in, &mdn );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) {
+ if ( BER_BVISNULL( ndn ) ) {
+ rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL );
+ }
+ return rc;
+ }
+
+ rc = dnPrettyNormal( NULL, &mdn, pdn, ndn, NULL );
+
+ if ( mdn.bv_val != in->bv_val ) {
+ ch_free( mdn.bv_val );
+ }
+
+ return rc;
+}
+
+/*
+ * massages "in" into "dn"
+ *
+ * "dn" may contain the value of "in" if no massage occurred
+ */
+int
+rwm_dn_massage(
+ dncookie *dc,
+ struct berval *in,
+ struct berval *dn
+)
+{
+ int rc = 0;
+ struct berval mdn;
+ static char *dmy = "";
+ char *in_val;
+
+ assert( dc != NULL );
+ assert( in != NULL );
+ assert( dn != NULL );
+
+ /* protect from NULL berval */
+ in_val = in->bv_val ? in->bv_val : dmy;
+
+ rc = rewrite_session( dc->rwmap->rwm_rw, dc->ctx,
+ in_val, dc->conn, &mdn.bv_val );
+ switch ( rc ) {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( &mdn ) && mdn.bv_val != in_val ) {
+ mdn.bv_len = strlen( mdn.bv_val );
+ *dn = mdn;
+ } else {
+ dn->bv_len = in->bv_len;
+ dn->bv_val = in_val;
+ }
+ rc = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ dc->ctx, in_val, dn->bv_val );
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ dc->rs->sr_text = "Operation not allowed";
+ }
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( dc->rs ) {
+ dc->rs->sr_err = LDAP_OTHER;
+ dc->rs->sr_text = "Rewrite error";
+ }
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ if ( mdn.bv_val == dmy ) {
+ BER_BVZERO( &mdn );
+ }
+
+ if ( dn->bv_val == dmy ) {
+ BER_BVZERO( dn );
+ }
+
+ return rc;
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/rwmmap.c b/servers/slapd/overlays/rwmmap.c
new file mode 100644
index 0000000..12aab08
--- /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-2018 The OpenLDAP Foundation.
+ * Portions Copyright 1999-2003 Howard Chu.
+ * Portions Copyright 2000-2003 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by the Howard Chu for inclusion
+ * in OpenLDAP Software and subsequently enhanced by Pierangelo
+ * Masarati.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_RWM
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "rwm.h"
+
+#undef ldap_debug /* silence a warning in ldap-int.h */
+#include "../../../libraries/libldap/ldap-int.h"
+
+int
+rwm_mapping_cmp( const void *c1, const void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+ int rc = map1->m_src.bv_len - map2->m_src.bv_len;
+
+ if ( rc ) {
+ return rc;
+ }
+
+ return strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val );
+}
+
+int
+rwm_mapping_dup( void *c1, void *c2 )
+{
+ struct ldapmapping *map1 = (struct ldapmapping *)c1;
+ struct ldapmapping *map2 = (struct ldapmapping *)c2;
+ int rc = map1->m_src.bv_len - map2->m_src.bv_len;
+
+ if ( rc ) {
+ return 0;
+ }
+
+ return ( ( strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ) == 0 ) ? -1 : 0 );
+}
+
+int
+rwm_map_init( struct ldapmap *lm, struct ldapmapping **m )
+{
+ struct ldapmapping *mapping;
+ const char *text;
+ int rc;
+
+ assert( m != NULL );
+
+ *m = NULL;
+
+ mapping = (struct ldapmapping *)ch_calloc( 2,
+ sizeof( struct ldapmapping ) );
+ if ( mapping == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ /* NOTE: this is needed to make sure that
+ * rwm-map attribute *
+ * does not filter out all attributes including objectClass */
+ rc = slap_str2ad( "objectClass", &mapping[0].m_src_ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ ch_free( mapping );
+ return rc;
+ }
+
+ mapping[0].m_dst_ad = mapping[0].m_src_ad;
+ ber_dupbv( &mapping[0].m_src, &mapping[0].m_src_ad->ad_cname );
+ ber_dupbv( &mapping[0].m_dst, &mapping[0].m_src );
+
+ mapping[1].m_src = mapping[0].m_src;
+ mapping[1].m_dst = mapping[0].m_dst;
+ mapping[1].m_src_ad = mapping[0].m_src_ad;
+ mapping[1].m_dst_ad = mapping[1].m_src_ad;
+
+ avl_insert( &lm->map, (caddr_t)&mapping[0],
+ rwm_mapping_cmp, rwm_mapping_dup );
+ avl_insert( &lm->remap, (caddr_t)&mapping[1],
+ rwm_mapping_cmp, rwm_mapping_dup );
+
+ *m = mapping;
+
+ return rc;
+}
+
+int
+rwm_mapping( struct ldapmap *map, struct berval *s, struct ldapmapping **m, int remap )
+{
+ Avlnode *tree;
+ struct ldapmapping fmapping;
+
+ if ( map == NULL ) {
+ return 0;
+ }
+
+ assert( m != NULL );
+
+ /* let special attrnames slip through (ITS#5760) */
+ if ( bvmatch( s, slap_bv_no_attrs )
+ || bvmatch( s, slap_bv_all_user_attrs )
+ || bvmatch( s, slap_bv_all_operational_attrs ) )
+ {
+ *m = NULL;
+ return 0;
+ }
+
+ if ( remap == RWM_REMAP ) {
+ tree = map->remap;
+
+ } else {
+ tree = map->map;
+ }
+
+ fmapping.m_src = *s;
+ *m = (struct ldapmapping *)avl_find( tree, (caddr_t)&fmapping,
+ rwm_mapping_cmp );
+
+ if ( *m == NULL ) {
+ return map->drop_missing;
+ }
+
+ return 0;
+}
+
+void
+rwm_map( struct ldapmap *map, struct berval *s, struct berval *bv, int remap )
+{
+ struct ldapmapping *mapping;
+
+ /* map->map may be NULL when mapping is configured,
+ * but map->remap can't */
+ if ( map->remap == NULL ) {
+ *bv = *s;
+ return;
+ }
+
+ BER_BVZERO( bv );
+ ( void )rwm_mapping( map, s, &mapping, remap );
+ if ( mapping != NULL ) {
+ if ( !BER_BVISNULL( &mapping->m_dst ) ) {
+ *bv = mapping->m_dst;
+ }
+ return;
+ }
+
+ if ( !map->drop_missing ) {
+ *bv = *s;
+ }
+}
+
+/*
+ * Map attribute names in place
+ */
+int
+rwm_map_attrnames(
+ Operation *op,
+ struct ldapmap *at_map,
+ struct ldapmap *oc_map,
+ AttributeName *an,
+ AttributeName **anp,
+ int remap )
+{
+ int i, j, x;
+
+ assert( anp != NULL );
+
+ *anp = NULL;
+
+ if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) {
+ return LDAP_SUCCESS;
+ }
+
+ i = 0;
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ )
+ /* just count */ ;
+ }
+
+ x = 0;
+ if ( op->o_bd->be_extra_anlist ) {
+ for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ )
+ /* just count */ ;
+ }
+
+ assert( i > 0 || x > 0 );
+ *anp = op->o_tmpcalloc( ( i + x + 1 ), sizeof( AttributeName ),
+ op->o_tmpmemctx );
+ if ( *anp == NULL ) {
+ return LDAP_NO_MEMORY;
+ }
+
+ j = 0;
+ if ( an != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ struct ldapmapping *m;
+ int at_drop_missing = 0,
+ oc_drop_missing = 0;
+
+ if ( an[i].an_desc ) {
+ if ( !at_map ) {
+ /* FIXME: better leave as is? */
+ continue;
+ }
+
+ at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap );
+ if ( at_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) {
+ continue;
+ }
+
+ if ( !m ) {
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_desc = m->m_dst_ad;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_desc = m->m_src_ad;
+
+ }
+
+ j++;
+ continue;
+
+ } else if ( an[i].an_oc ) {
+ if ( !oc_map ) {
+ /* FIXME: better leave as is? */
+ continue;
+ }
+
+ oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap );
+
+ if ( oc_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) {
+ continue;
+ }
+
+ if ( !m ) {
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_oc = m->m_dst_oc;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_oc = m->m_src_oc;
+ }
+
+ } else {
+ at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap );
+
+ if ( at_drop_missing || !m ) {
+ oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap );
+
+ /* if both at_map and oc_map required to drop missing,
+ * then do it */
+ if ( oc_drop_missing && at_drop_missing ) {
+ continue;
+ }
+
+ /* if no oc_map mapping was found and at_map required
+ * to drop missing, then do it; otherwise, at_map wins
+ * and an is considered an attr and is left unchanged */
+ if ( !m ) {
+ if ( at_drop_missing ) {
+ continue;
+ }
+ (*anp)[j] = an[i];
+ j++;
+ continue;
+ }
+
+ if ( BER_BVISNULL( &m->m_dst ) ) {
+ continue;
+ }
+
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_oc = m->m_dst_oc;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_oc = m->m_src_oc;
+ }
+ j++;
+ continue;
+ }
+
+ if ( !BER_BVISNULL( &m->m_dst ) ) {
+ (*anp)[j] = an[i];
+ if ( remap == RWM_MAP ) {
+ (*anp)[j].an_name = m->m_dst;
+ (*anp)[j].an_desc = m->m_dst_ad;
+ } else {
+ (*anp)[j].an_name = m->m_src;
+ (*anp)[j].an_desc = m->m_src_ad;
+ }
+ j++;
+ continue;
+ }
+ }
+ }
+ }
+
+ if ( op->o_bd->be_extra_anlist != NULL ) {
+ /* we assume be_extra_anlist are already mapped */
+ for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) {
+ BER_BVZERO( &(*anp)[j].an_name );
+ if ( op->o_bd->be_extra_anlist[x].an_desc &&
+ ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, *anp ) )
+ {
+ continue;
+ }
+
+ (*anp)[j] = op->o_bd->be_extra_anlist[x];
+ j++;
+ }
+ }
+
+ if ( j == 0 && ( i != 0 || x != 0 ) ) {
+ memset( &(*anp)[0], 0, sizeof( AttributeName ) );
+ (*anp)[0].an_name = *slap_bv_no_attrs;
+ j = 1;
+ }
+ memset( &(*anp)[j], 0, sizeof( AttributeName ) );
+
+ return LDAP_SUCCESS;
+}
+
+#if 0 /* unused! */
+int
+rwm_map_attrs(
+ struct ldapmap *at_map,
+ AttributeName *an,
+ int remap,
+ char ***mapped_attrs )
+{
+ int i, j;
+ char **na;
+
+ if ( an == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ); i++ )
+ /* count'em */ ;
+
+ na = (char **)ch_calloc( i + 1, sizeof( char * ) );
+ if ( na == NULL ) {
+ *mapped_attrs = NULL;
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = j = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) {
+ struct ldapmapping *mapping;
+
+ if ( rwm_mapping( at_map, &an[i].an_name, &mapping, remap ) ) {
+ continue;
+ }
+
+ if ( !mapping ) {
+ na[ j++ ] = an[ i ].an_name.bv_val;
+
+ } else if ( !BER_BVISNULL( &mapping->m_dst ) ) {
+ na[ j++ ] = mapping->m_dst.bv_val;
+ }
+ }
+
+ if ( j == 0 && i != 0 ) {
+ na[ j++ ] = LDAP_NO_ATTRS;
+ }
+
+ na[ j ] = NULL;
+
+ *mapped_attrs = na;
+
+ return LDAP_SUCCESS;
+}
+#endif
+
+static int
+map_attr_value(
+ dncookie *dc,
+ AttributeDescription **adp,
+ struct berval *mapped_attr,
+ struct berval *value,
+ struct berval *mapped_value,
+ int remap,
+ void *memctx )
+{
+ struct berval vtmp = BER_BVNULL;
+ int freeval = 0;
+ AttributeDescription *ad = *adp;
+ struct ldapmapping *mapping = NULL;
+
+ rwm_mapping( &dc->rwmap->rwm_at, &ad->ad_cname, &mapping, remap );
+ if ( mapping == NULL ) {
+ if ( dc->rwmap->rwm_at.drop_missing ) {
+ return -1;
+ }
+
+ *mapped_attr = ad->ad_cname;
+
+ } else {
+ *mapped_attr = mapping->m_dst;
+ }
+
+ if ( value != NULL ) {
+ assert( mapped_value != NULL );
+
+ if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName
+ || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) )
+ {
+ dncookie fdc = *dc;
+ int rc;
+
+ fdc.ctx = "searchFilterAttrDN";
+
+ vtmp = *value;
+ rc = rwm_dn_massage_normalize( &fdc, value, &vtmp );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ if ( vtmp.bv_val != value->bv_val ) {
+ freeval = 1;
+ }
+ break;
+
+ case LDAP_UNWILLING_TO_PERFORM:
+ case LDAP_OTHER:
+ default:
+ return -1;
+ }
+
+ } else if ( ad->ad_type->sat_equality &&
+ ( ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) )
+ {
+ if ( ad->ad_type->sat_equality->smr_normalize(
+ (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX),
+ NULL, NULL, value, &vtmp, memctx ) )
+ {
+ return -1;
+ }
+ freeval = 2;
+
+ } else if ( ad == slap_schema.si_ad_objectClass
+ || ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ rwm_map( &dc->rwmap->rwm_oc, value, &vtmp, remap );
+ if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) {
+ vtmp = *value;
+ }
+
+ } else {
+ vtmp = *value;
+ }
+
+ filter_escape_value_x( &vtmp, mapped_value, memctx );
+
+ switch ( freeval ) {
+ case 1:
+ ch_free( vtmp.bv_val );
+ break;
+
+ case 2:
+ ber_memfree_x( vtmp.bv_val, memctx );
+ break;
+ }
+ }
+
+ if ( mapping != NULL ) {
+ assert( mapping->m_dst_ad != NULL );
+ *adp = mapping->m_dst_ad;
+ }
+
+ return 0;
+}
+
+static int
+rwm_int_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int i;
+ Filter *p;
+ AttributeDescription *ad;
+ struct berval atmp,
+ vtmp,
+ *tmp;
+ static struct berval
+ /* better than nothing... */
+ ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+ ber_bvtf_false = BER_BVC( "(|)" ),
+ /* better than nothing... */
+ ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+ ber_bvtf_true = BER_BVC( "(&)" ),
+#if 0
+ /* no longer needed; preserved for completeness */
+ ber_bvundefined = BER_BVC( "(?=undefined)" ),
+#endif
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" );
+ ber_len_t len;
+
+ assert( fstr != NULL );
+ BER_BVZERO( fstr );
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx );
+ return LDAP_OTHER;
+ }
+
+#if 0
+ /* ITS#6814: give the caller a chance to use undefined filters */
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ goto computed;
+ }
+#endif
+
+ switch ( f->f_choice & SLAPD_FILTER_MASK ) {
+ case LDAP_FILTER_EQUALITY:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(>=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(<=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ ad = f->f_av_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(~=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ ad = f->f_sub_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ NULL, NULL, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ /* cannot be a DN ... */
+
+ fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &vtmp, op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3,
+ /* "(attr=" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i], &vtmp,
+ op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len + 1;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &vtmp, op->o_tmpmemctx );
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ NULL, NULL, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ atmp.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF( "(%)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ int rc;
+
+ len = fstr->bv_len;
+
+ rc = rwm_int_filter_map_rewrite( op, dc, p, &vtmp );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1,
+ op->o_tmpmemctx );
+
+ snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+ /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT: {
+ if ( f->f_mr_desc ) {
+ ad = f->f_mr_desc;
+ if ( map_attr_value( dc, &ad, &atmp,
+ &f->f_mr_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) )
+ {
+ goto computed;
+ }
+
+ } else {
+ BER_BVSTR( &atmp, "" );
+ filter_escape_value_x( &f->f_mr_value, &vtmp, op->o_tmpmemctx );
+ }
+
+
+ fstr->bv_len = atmp.bv_len +
+ ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+ ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+ vtmp.bv_len + STRLENOF( "(:=)" );
+ fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ atmp.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+ vtmp.bv_len ? vtmp.bv_val : "" );
+ op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx );
+ break;
+ }
+
+ case -1:
+computed:;
+ filter_free_x( op, f, 0 );
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = SLAPD_COMPARE_UNDEFINED;
+ /* fallthru */
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ case LDAP_COMPARE_FALSE:
+ /* FIXME: treat UNDEFINED as FALSE */
+ case SLAPD_COMPARE_UNDEFINED:
+ if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) {
+ tmp = &ber_bvtf_false;
+ break;
+ }
+ tmp = &ber_bvfalse;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) {
+ tmp = &ber_bvtf_true;
+ break;
+ }
+ tmp = &ber_bvtrue;
+ break;
+
+ default:
+ tmp = &ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, tmp, op->o_tmpmemctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx );
+ break;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+rwm_filter_map_rewrite(
+ Operation *op,
+ dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int rc;
+ dncookie fdc;
+ struct berval ftmp;
+
+ rc = rwm_int_filter_map_rewrite( op, dc, f, fstr );
+
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ fdc = *dc;
+ ftmp = *fstr;
+
+ fdc.ctx = "searchFilter";
+
+ switch ( rewrite_session( fdc.rwmap->rwm_rw, fdc.ctx,
+ ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : "" ),
+ fdc.conn, &fstr->bv_val ) )
+ {
+ case REWRITE_REGEXEC_OK:
+ if ( !BER_BVISNULL( fstr ) ) {
+ fstr->bv_len = strlen( fstr->bv_val );
+
+ } else {
+ *fstr = ftmp;
+ }
+
+ Debug( LDAP_DEBUG_ARGS,
+ "[rw] %s: \"%s\" -> \"%s\"\n",
+ fdc.ctx, ftmp.bv_val, fstr->bv_val );
+ if ( fstr->bv_val != ftmp.bv_val ) {
+ ber_bvreplace_x( &ftmp, fstr, op->o_tmpmemctx );
+ ch_free( fstr->bv_val );
+ *fstr = ftmp;
+ }
+ rc = LDAP_SUCCESS;
+ break;
+
+ case REWRITE_REGEXEC_UNWILLING:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ fdc.rs->sr_text = "Operation not allowed";
+ }
+ op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx );
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ break;
+
+ case REWRITE_REGEXEC_ERR:
+ if ( fdc.rs ) {
+ fdc.rs->sr_err = LDAP_OTHER;
+ fdc.rs->sr_text = "Rewrite error";
+ }
+ op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+rwm_referral_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int i, last;
+
+ dncookie dc;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+
+ assert( a_vals != NULL );
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ )
+ ;
+ last--;
+
+ if ( pa_nvals != NULL ) {
+ if ( *pa_nvals == NULL ) {
+ *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) );
+ memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) );
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval olddn = BER_BVNULL,
+ oldval;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ oldval = a_vals[i];
+ rc = ldap_url_parse( oldval.bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ber_dupbv( &(*pa_nvals)[i], &oldval );
+ }
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ dn = olddn;
+ if ( pa_nvals ) {
+ ndn = olddn;
+ rc = rwm_dn_massage_pretty_normalize( &dc, &olddn,
+ &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_pretty( &dc, &olddn, &dn );
+ }
+
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( a_vals[i].bv_val );
+ if (last > i ) {
+ a_vals[i] = a_vals[last];
+ if ( pa_nvals ) {
+ (*pa_nvals)[i] = (*pa_nvals)[last];
+ }
+ }
+ BER_BVZERO( &a_vals[last] );
+ if ( pa_nvals ) {
+ BER_BVZERO( &(*pa_nvals)[last] );
+ }
+ last--;
+ break;
+
+ case LDAP_SUCCESS:
+ if ( !BER_BVISNULL( &dn ) && dn.bv_val != olddn.bv_val ) {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ ludp->lud_dn = olddn.bv_val;
+ ch_free( dn.bv_val );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ break;
+ }
+
+ ber_str2bv( newurl, 0, 1, &a_vals[i] );
+ ber_memfree( newurl );
+
+ if ( pa_nvals ) {
+ ludp->lud_dn = ndn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ ludp->lud_dn = olddn.bv_val;
+ ch_free( ndn.bv_val );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = oldval;
+ break;
+ }
+
+ if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ }
+ ber_str2bv( newurl, 0, 1, &(*pa_nvals)[i] );
+ ber_memfree( newurl );
+ }
+
+ ch_free( oldval.bv_val );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ber_dupbv( &(*pa_nvals)[i], &a_vals[i] );
+ }
+ break;
+ }
+ ldap_free_urldesc( ludp );
+ }
+
+ return 0;
+}
+
+/*
+ * I don't like this much, but we need two different
+ * functions because different heap managers may be
+ * in use in back-ldap/meta to reduce the amount of
+ * calls to malloc routines, and some of the free()
+ * routines may be macros with args
+ */
+int
+rwm_dnattr_rewrite(
+ Operation *op,
+ SlapReply *rs,
+ void *cookie,
+ BerVarray a_vals,
+ BerVarray *pa_nvals )
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct ldaprwmap *rwmap =
+ (struct ldaprwmap *)on->on_bi.bi_private;
+
+ int i, last;
+
+ dncookie dc;
+ struct berval dn = BER_BVNULL,
+ ndn = BER_BVNULL;
+ BerVarray in;
+
+ if ( a_vals ) {
+ in = a_vals;
+
+ } else {
+ if ( pa_nvals == NULL || *pa_nvals == NULL ) {
+ return LDAP_OTHER;
+ }
+ in = *pa_nvals;
+ }
+
+ /*
+ * Rewrite the dn if needed
+ */
+ dc.rwmap = rwmap;
+ dc.conn = op->o_conn;
+ dc.rs = rs;
+ dc.ctx = (char *)cookie;
+
+ for ( last = 0; !BER_BVISNULL( &in[last] ); last++ );
+ last--;
+ if ( pa_nvals != NULL ) {
+ if ( *pa_nvals == NULL ) {
+ *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) );
+ memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) );
+ }
+ }
+
+ for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) {
+ int rc;
+
+ if ( a_vals ) {
+ dn = in[i];
+ if ( pa_nvals ) {
+ ndn = (*pa_nvals)[i];
+ rc = rwm_dn_massage_pretty_normalize( &dc, &in[i], &dn, &ndn );
+ } else {
+ rc = rwm_dn_massage_pretty( &dc, &in[i], &dn );
+ }
+ } else {
+ ndn = in[i];
+ rc = rwm_dn_massage_normalize( &dc, &in[i], &ndn );
+ }
+
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( in[i].bv_val );
+ if (last > i ) {
+ in[i] = in[last];
+ if ( a_vals && pa_nvals ) {
+ (*pa_nvals)[i] = (*pa_nvals)[last];
+ }
+ }
+ BER_BVZERO( &in[last] );
+ if ( a_vals && pa_nvals ) {
+ BER_BVZERO( &(*pa_nvals)[last] );
+ }
+ last--;
+ break;
+
+ case LDAP_SUCCESS:
+ if ( a_vals ) {
+ if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) {
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = dn;
+
+ if ( pa_nvals ) {
+ if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ }
+ (*pa_nvals)[i] = ndn;
+ }
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &ndn ) && ndn.bv_val != (*pa_nvals)[i].bv_val ) {
+ ch_free( (*pa_nvals)[i].bv_val );
+ (*pa_nvals)[i] = ndn;
+ }
+ }
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( a_vals && pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) {
+ dnNormalize( 0, NULL, NULL, &a_vals[i], &(*pa_nvals)[i], NULL );
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+int
+rwm_referral_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals )
+{
+ int i, last;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval dn,
+ olddn = BER_BVNULL;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ rc = ldap_url_parse( a_vals[i].bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ dn = olddn;
+ rc = rwm_dn_massage_pretty( dc, &olddn, &dn );
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ ch_free( a_vals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ last--;
+ i--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ if ( newurl == NULL ) {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+ break;
+ }
+
+ ch_free( a_vals[i].bv_val );
+ ber_str2bv( newurl, 0, 1, &a_vals[i] );
+ ber_memfree( newurl );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ break;
+ }
+
+ ldap_free_urldesc( ludp );
+ }
+
+ return 0;
+}
+
+int
+rwm_dnattr_result_rewrite(
+ dncookie *dc,
+ BerVarray a_vals,
+ BerVarray a_nvals )
+{
+ int i, last;
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ );
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ struct berval pdn, ndn = BER_BVNULL;
+ int rc;
+
+ pdn = a_vals[i];
+ rc = rwm_dn_massage_pretty_normalize( dc, &a_vals[i], &pdn, &ndn );
+ switch ( rc ) {
+ case LDAP_UNWILLING_TO_PERFORM:
+ /*
+ * FIXME: need to check if it may be considered
+ * legal to trim values when adding/modifying;
+ * it should be when searching (e.g. ACLs).
+ */
+ assert( a_vals[i].bv_val != a_nvals[i].bv_val );
+ ch_free( a_vals[i].bv_val );
+ ch_free( a_nvals[i].bv_val );
+ if ( last > i ) {
+ a_vals[i] = a_vals[last];
+ a_nvals[i] = a_nvals[last];
+ }
+ BER_BVZERO( &a_vals[last] );
+ BER_BVZERO( &a_nvals[last] );
+ last--;
+ break;
+
+ default:
+ /* leave attr untouched if massage failed */
+ if ( !BER_BVISNULL( &pdn ) && a_vals[i].bv_val != pdn.bv_val ) {
+ ch_free( a_vals[i].bv_val );
+ a_vals[i] = pdn;
+ }
+ if ( !BER_BVISNULL( &ndn ) && a_nvals[i].bv_val != ndn.bv_val ) {
+ ch_free( a_nvals[i].bv_val );
+ a_nvals[i] = ndn;
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+void
+rwm_mapping_dst_free( void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+
+ if ( BER_BVISEMPTY( &mapping[0].m_dst ) ) {
+ rwm_mapping_free( &mapping[ -1 ] );
+ }
+}
+
+void
+rwm_mapping_free( void *v_mapping )
+{
+ struct ldapmapping *mapping = v_mapping;
+
+ if ( !BER_BVISNULL( &mapping[0].m_src ) ) {
+ ch_free( mapping[0].m_src.bv_val );
+ }
+
+ if ( mapping[0].m_flags & RWMMAP_F_FREE_SRC ) {
+ if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) {
+ if ( mapping[0].m_src_oc ) {
+ ch_free( mapping[0].m_src_oc );
+ }
+
+ } else {
+ if ( mapping[0].m_src_ad ) {
+ ch_free( mapping[0].m_src_ad );
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &mapping[0].m_dst ) ) {
+ ch_free( mapping[0].m_dst.bv_val );
+ }
+
+ if ( mapping[0].m_flags & RWMMAP_F_FREE_DST ) {
+ if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) {
+ if ( mapping[0].m_dst_oc ) {
+ ch_free( mapping[0].m_dst_oc );
+ }
+
+ } else {
+ if ( mapping[0].m_dst_ad ) {
+ ch_free( mapping[0].m_dst_ad );
+ }
+ }
+ }
+
+ ch_free( mapping );
+
+}
+
+#endif /* SLAPD_OVER_RWM */
diff --git a/servers/slapd/overlays/seqmod.c b/servers/slapd/overlays/seqmod.c
new file mode 100644
index 0000000..66e3dc1
--- /dev/null
+++ b/servers/slapd/overlays/seqmod.c
@@ -0,0 +1,206 @@
+/* seqmod.c - sequenced modifies */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SEQMOD
+
+#include "slap.h"
+#include "config.h"
+
+/* This overlay serializes concurrent attempts to modify a single entry */
+
+typedef struct modtarget {
+ struct modtarget *mt_next;
+ struct modtarget *mt_tail;
+ Operation *mt_op;
+} modtarget;
+
+typedef struct seqmod_info {
+ Avlnode *sm_mods; /* entries being modified */
+ ldap_pvt_thread_mutex_t sm_mutex;
+} seqmod_info;
+
+static int
+sm_avl_cmp( const void *c1, const void *c2 )
+{
+ const modtarget *m1, *m2;
+ int rc;
+
+ m1 = c1; m2 = c2;
+ rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len;
+
+ if ( rc ) return rc;
+ return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn );
+}
+
+static int
+seqmod_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ seqmod_info *sm = sc->sc_private;
+ modtarget *mt, mtdummy;
+ Avlnode *av;
+
+ mtdummy.mt_op = op;
+ /* This op is done, remove it */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ av = avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp );
+ assert(av != NULL);
+
+ mt = av->avl_data;
+
+ /* If there are more, promote the next one */
+ if ( mt->mt_next ) {
+ av->avl_data = mt->mt_next;
+ mt->mt_next->mt_tail = mt->mt_tail;
+ } else {
+ avl_delete( &sm->sm_mods, mt, sm_avl_cmp );
+ }
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+ op->o_callback = sc->sc_next;
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+
+ return 0;
+}
+
+static int
+seqmod_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ seqmod_info *sm = on->on_bi.bi_private;
+ modtarget *mt;
+ Avlnode *av;
+ slap_callback *cb;
+
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget),
+ op->o_tmpmemctx );
+ mt = (modtarget *)(cb+1);
+ mt->mt_next = NULL;
+ mt->mt_tail = mt;
+ mt->mt_op = op;
+
+ /* See if we're already modifying this entry - don't allow
+ * near-simultaneous mods of the same entry
+ */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ av = avl_find2( sm->sm_mods, mt, sm_avl_cmp );
+ if ( av ) {
+ modtarget *mtp = av->avl_data;
+ mtp->mt_tail->mt_next = mt;
+ mtp->mt_tail = mt;
+ /* Wait for this op to get to head of list */
+ while ( mtp != mt ) {
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+ ldap_pvt_thread_yield();
+ /* Let it finish - should use a condition
+ * variable here... */
+ ldap_pvt_thread_mutex_lock( &sm->sm_mutex );
+ mtp = av->avl_data;
+ }
+ } else {
+ /* Record that we're modifying this now */
+ avl_insert( &sm->sm_mods, mt, sm_avl_cmp, avl_dup_error );
+ }
+ ldap_pvt_thread_mutex_unlock( &sm->sm_mutex );
+
+ cb->sc_cleanup = seqmod_op_cleanup;
+ cb->sc_private = sm;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+seqmod_op_extended(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ if ( exop_is_write( op )) return seqmod_op_mod( op, rs );
+ else return SLAP_CB_CONTINUE;
+}
+
+static int
+seqmod_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ seqmod_info *sm;
+
+ sm = ch_calloc(1, sizeof(seqmod_info));
+ on->on_bi.bi_private = sm;
+
+ ldap_pvt_thread_mutex_init( &sm->sm_mutex );
+
+ return 0;
+}
+
+static int
+seqmod_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ seqmod_info *sm = (seqmod_info *)on->on_bi.bi_private;
+
+ if ( sm ) {
+ ldap_pvt_thread_mutex_destroy( &sm->sm_mutex );
+
+ ch_free( sm );
+ on->on_bi.bi_private = NULL;
+ }
+
+ return 0;
+}
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+static slap_overinst seqmod;
+
+int
+seqmod_initialize()
+{
+ seqmod.on_bi.bi_type = "seqmod";
+ seqmod.on_bi.bi_db_open = seqmod_db_open;
+ seqmod.on_bi.bi_db_close = seqmod_db_close;
+
+ seqmod.on_bi.bi_op_modify = seqmod_op_mod;
+ seqmod.on_bi.bi_op_modrdn = seqmod_op_mod;
+ seqmod.on_bi.bi_extended = seqmod_op_extended;
+
+ return overlay_register( &seqmod );
+}
+
+#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return seqmod_initialize();
+}
+#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_SEQMOD) */
diff --git a/servers/slapd/overlays/slapover.txt b/servers/slapd/overlays/slapover.txt
new file mode 100644
index 0000000..2015d8d
--- /dev/null
+++ b/servers/slapd/overlays/slapover.txt
@@ -0,0 +1,158 @@
+slapd internal APIs
+
+Introduction
+
+Frontend, backend, database, callback, overlay - what does it all mean?
+
+The "frontend" refers to all the code that deals with the actual interaction
+with an LDAP client. This includes the code to read requests from the network
+and parse them into C data structures, all of the session management, and the
+formatting of responses for transmission onto the network. It also includes the
+access control engine and other features that are generic to LDAP processing,
+features which are not dependent on a particular database implementation.
+Because the frontend serves as the framework that ties everything together,
+it should not change much over time.
+
+The terms "backend" and "database" have historically been used interchangeably
+and/or in combination as if they are the same thing, but the code has a clear
+distinction between the two. A "backend" is a type of module, and a "database"
+is an instance of a backend type. Together they work with the frontend to
+manage the actual data that are operated on by LDAP requests. Originally the
+backend interface was relatively compact, with individual functions
+corresponding to each LDAP operation type, plus functions for init, config, and
+shutdown. The number of entry points has grown to allow greater flexibility,
+but the concept is much the same as before.
+
+The language here can get a bit confusing. A backend in slapd is embodied in a
+BackendInfo data structure, and a database is held in a BackendDB structure.
+Originally it was all just a single Backend data structure, but things have
+grown and the concept was split into these two parts. The idea behind the
+distinct BackendInfo is to allow for any initialization and configuration that
+may be needed by every instance of a type of database, as opposed to items that
+are specific to just one instance. For example, you might have a database
+library that requires an initialization routine to be called exactly once at
+program startup. Then there may be a "open" function that must be called once
+for each database instance. The BackendInfo.bi_open function provides the
+one-time startup, while the BackendInfo.bi_db_open function provides the
+per-database startup. The main feature of the BackendInfo structure is its
+table of entry points for all of the database functions that it implements.
+There's also a bi_private pointer that can be used to carry any configuration
+state needed by the backend. (Note that this is state that applies to the
+backend type, and thus to all database instances of the backend as well.) The
+BackendDB structure carries all of the per-instance state for a backend
+database. This includes the database suffix, ACLs, flags, various DNs, etc. It
+also has a pointer to its BackendInfo, and a be_private pointer for use by the
+particular backend instance. In practice, the per-type features are seldom
+used, and all of the work is done in the per-instance data structures.
+
+Ordinarily an LDAP request is received by the slapd frontend, parsed into a
+request structure, and then passed to the backend for processing. The backend
+may call various utility functions in the frontend to assist in processing, and
+then it eventually calls some send_ldap_result function in the frontend to send
+results back to the client. The processing flow is pretty rigidly defined; even
+though slapd is capable of dynamically loading new code modules, it was
+difficult to add extensions that changed the basic protocol operations. If you
+wanted to extend the server with special behaviors you would need to modify the
+frontend or the backend or both, and generally you would need to write an
+entire new backend to get some set of special features working. With OpenLDAP
+2.1 we added the notion of a callback, which can intercept the results sent
+from a backend before they are sent to a client. Using callbacks makes it
+possible to modify the results if desired, or to simply discard the results
+instead of sending them to any client. This callback feature is used
+extensively in the SASL support to perform internal searches of slapd databases
+when mapping authentication IDs into regular DNs. The callback mechanism is
+also the basis of backglue, which allows separate databases to be searched as
+if they were a single naming context.
+
+Very often, one needs to add just a tiny feature onto an otherwise "normal"
+database. The usual way to achieve this was to use a programmable backend (like
+back-perl) to preprocess various requests and then forward them back into slapd
+to be handled by the real database. While this technique works, it is fairly
+inefficient because it involves many transitions from network to slapd and back
+again. The overlay concept introduced in OpenLDAP 2.2 allows code to be
+inserted between the slapd frontend and any backend, so that incoming requests
+can be intercepted before reaching the backend database. (There is also a SLAPI
+plugin framework in OpenLDAP 2.2; it offers a lot of flexibility as well but is
+not discussed here.) The overlay framework also uses the callback mechanism, so
+outgoing results can also be intercepted by external code. All of this could
+get unwieldy if a lot of overlays were being used, but there was also another
+significant API change in OpenLDAP 2.2 to streamline internal processing. (See
+the document "Refactoring the slapd ABI"...)
+
+OK, enough generalities... You should probably have a copy of slap.h in front
+of you to continue here.
+
+What is an overlay? The structure defining it includes a BackendInfo structure
+plus a few additional fields. It gets inserted into the usual frontend->backend
+call chain by replacing the BackendDB's BackendInfo pointer with its own. The
+framework to accomplish this is in backover.c. For a given backend, the
+BackendInfo will point to a slap_overinfo structure. The slap_overinfo has a
+BackendInfo that points to all of the overlay framework's entry points. It also
+holds a copy of the original BackendInfo pointer, and a linked list of
+slap_overinst structures. There is one slap_overinst per configured overlay,
+and the set of overlays configured on a backend are treated like a stack; i.e.,
+the last one configured is at the top of the stack, and it executes first.
+
+Continuing with the stack notion - a request enters the frontend, is directed
+to a backend by select_backend, and then intercepted by the top of the overlay
+stack. This first overlay may do something with the request, and then return
+SLAP_CB_CONTINUE, which will then cause processing to fall into the next
+overlay, and so on down the stack until finally the request is handed to the
+actual backend database. Likewise, when the database finishes processing and
+sends a result, the overlay callback intercepts this and the topmost overlay
+gets to process the result. If it returns SLAP_CB_CONTINUE then processing will
+continue in the next overlay, and then any other callbacks, then finally the
+result reaches the frontend for sending back to the client. At any step along
+the way, a module may choose to fully process the request or result and not
+allow it to propagate any further down the stack. Whenever a module returns
+anything other than SLAP_CB_CONTINUE the processing stops.
+
+An overlay can call most frontend functions without any special consideration.
+However, if a call is going to result in any backend code being invoked, then
+the backend environment must be correct. During a normal backend invocation,
+op->o_bd points to the BackendDB structure for the backend, and
+op->o_bd->bd_info points to the BackendInfo for the backend. All of the
+information a specific backend instance needs is in op->o_bd->be_private and
+all of its entry points are in the BackendInfo structure. When overlays are in
+use on a backend, op->o_bd->bd_info points to the BackendInfo (actually a
+slap_overinfo) that contains the overlay framework. When a particular overlay
+instance is executing, op->o_bd points to a copy of the original op->o_bd, and
+op->o_bd->bd_info points to a slap_overinst which carries the information about
+the current overlay. The slap_overinst contains an on_private pointer which can
+be used to carry any configuration or state information the overlay needs. The
+normal way to invoke a backend function is through the op->o_bd->bd_info table
+of entry points, but obviously this must be set to the backend's original
+BackendInfo in order to get to the right function.
+
+There are two approaches here. The slap_overinst also contains a on_info field
+that points to the top slap_overinfo that wraps the current backend. The
+simplest thing is for the overlay to set op->o_bd->bd_info to this on_info
+value before invoking a backend function. This will cause processing of that
+particular operation to begin at the top of the overlay stack, so all the other
+overlays on the backend will also get a chance to handle this internal request.
+The other possibility is to invoke the underlying backend directly, bypassing
+the rest of the overlays, by calling through on_info->oi_orig. You should be
+careful in choosing this approach, since it precludes other overlays from doing
+their jobs.
+
+One of the more interesting uses for an overlay is to attach two (or more)
+different database backends into a single execution stack. Assuming that the
+basic frontend-managed information (suffix, rootdn, ACLs, etc.) will be the
+same for all of the backends, the only thing the overlay needs to maintain is a
+be_private and bd_info pointer for the added backends. The chain and proxycache
+overlays are two complementary examples of this usage. The chain overlay
+attaches a back-ldap backend to a local database backend, and allows referrals
+to remote servers generated by the database to be processed by slapd instead of
+being returned to the client. The proxycache overlay attaches a local database
+to a back-ldap (or back-meta) backend and allows search results from remote
+servers to be cached locally. In both cases the overlays must provide a bit of
+glue to swap in the appropriate be_private and bd_info pointers before invoking
+the attached backend, which can then be invoked as usual.
+
+Note on overlay initialization/destruction: you should allocate storage for
+config info in the _db_init handler, and free this storage in the _db_destroy
+handler. You must not free it in the _db_close handler because a module may
+be opened/closed multiple times in a running slapd when using dynamic
+configuration and the config info must remain intact.
+
+---
diff --git a/servers/slapd/overlays/sssvlv.c b/servers/slapd/overlays/sssvlv.c
new file mode 100644
index 0000000..dff2929
--- /dev/null
+++ b/servers/slapd/overlays/sssvlv.c
@@ -0,0 +1,1434 @@
+/* sssvlv.c - server side sort / virtual list view */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2009-2018 The OpenLDAP Foundation.
+ * Portions copyright 2009 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software. Support for multiple sorts per connection added
+ * by Raphael Ouazana.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SSSVLV
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include <avl.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "config.h"
+
+#include "../../../libraries/liblber/lber-int.h" /* ber_rewind */
+
+/* RFC2891: Server Side Sorting
+ * RFC2696: Paged Results
+ */
+#ifndef LDAP_MATCHRULE_IDENTIFIER
+#define LDAP_MATCHRULE_IDENTIFIER 0x80L
+#define LDAP_REVERSEORDER_IDENTIFIER 0x81L
+#define LDAP_ATTRTYPES_IDENTIFIER 0x80L
+#endif
+
+/* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views
+ */
+#ifndef LDAP_VLVBYINDEX_IDENTIFIER
+#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L
+#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L
+#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L
+
+#define LDAP_VLV_SSS_MISSING 0x4C
+#define LDAP_VLV_RANGE_ERROR 0x4D
+#endif
+
+#define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def))
+
+#define SSSVLV_DEFAULT_MAX_KEYS 5
+#define SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN 5
+
+#define NO_PS_COOKIE (PagedResultsCookie) -1
+#define NO_VC_CONTEXT (unsigned long) -1
+
+typedef struct vlv_ctrl {
+ int vc_before;
+ int vc_after;
+ int vc_offset;
+ int vc_count;
+ struct berval vc_value;
+ unsigned long vc_context;
+} vlv_ctrl;
+
+typedef struct sort_key
+{
+ AttributeDescription *sk_ad;
+ MatchingRule *sk_ordering;
+ int sk_direction; /* 1=normal, -1=reverse */
+} sort_key;
+
+typedef struct sort_ctrl {
+ int sc_nkeys;
+ sort_key sc_keys[1];
+} sort_ctrl;
+
+
+typedef struct sort_node
+{
+ int sn_conn;
+ int sn_session;
+ struct berval sn_dn;
+ struct berval *sn_vals;
+} sort_node;
+
+typedef struct sssvlv_info
+{
+ int svi_max; /* max concurrent sorts */
+ int svi_num; /* current # sorts */
+ int svi_max_keys; /* max sort keys per request */
+ int svi_max_percon; /* max concurrent sorts per con */
+} sssvlv_info;
+
+typedef struct sort_op
+{
+ Avlnode *so_tree;
+ sort_ctrl *so_ctrl;
+ sssvlv_info *so_info;
+ int so_paged;
+ int so_page_size;
+ int so_nentries;
+ int so_vlv;
+ int so_vlv_rc;
+ int so_vlv_target;
+ int so_session;
+ unsigned long so_vcontext;
+ int so_running;
+} sort_op;
+
+/* There is only one conn table for all overlay instances */
+/* Each conn can handle one session by context */
+static sort_op ***sort_conns;
+static ldap_pvt_thread_mutex_t sort_conns_mutex;
+static int ov_count;
+static const char *debug_header = "sssvlv";
+
+static int sss_cid;
+static int vlv_cid;
+
+/* RFC 2981 Section 2.2
+ * If a sort key is a multi-valued attribute, and an entry happens to
+ * have multiple values for that attribute and no other controls are
+ * present that affect the sorting order, then the server SHOULD use the
+ * least value (according to the ORDERING rule for that attribute).
+ */
+static struct berval* select_value(
+ Attribute *attr,
+ sort_key *key )
+{
+ struct berval* ber1, *ber2;
+ MatchingRule *mr = key->sk_ordering;
+ unsigned i;
+ int cmp;
+
+ ber1 = &(attr->a_nvals[0]);
+ ber2 = ber1+1;
+ for ( i = 1; i < attr->a_numvals; i++,ber2++ ) {
+ mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 );
+ if ( cmp > 0 ) {
+ ber1 = ber2;
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n",
+ debug_header,
+ SAFESTR(ber1->bv_val, "<Empty>"),
+ 0);
+
+ return ber1;
+}
+
+static int node_cmp( const void* val1, const void* val2 )
+{
+ sort_node *sn1 = (sort_node *)val1;
+ sort_node *sn2 = (sort_node *)val2;
+ sort_ctrl *sc;
+ MatchingRule *mr;
+ int i, cmp = 0;
+ assert( sort_conns[sn1->sn_conn]
+ && sort_conns[sn1->sn_conn][sn1->sn_session]
+ && sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl );
+ sc = sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl;
+
+ for ( i=0; cmp == 0 && i<sc->sc_nkeys; i++ ) {
+ if ( BER_BVISNULL( &sn1->sn_vals[i] )) {
+ if ( BER_BVISNULL( &sn2->sn_vals[i] ))
+ cmp = 0;
+ else
+ cmp = sc->sc_keys[i].sk_direction;
+ } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) {
+ cmp = sc->sc_keys[i].sk_direction * -1;
+ } else {
+ mr = sc->sc_keys[i].sk_ordering;
+ mr->smr_match( &cmp, 0, mr->smr_syntax, mr,
+ &sn1->sn_vals[i], &sn2->sn_vals[i] );
+ if ( cmp )
+ cmp *= sc->sc_keys[i].sk_direction;
+ }
+ }
+ return cmp;
+}
+
+static int node_insert( const void *val1, const void *val2 )
+{
+ /* Never return equal so that new entries are always inserted */
+ return node_cmp( val1, val2 ) < 0 ? -1 : 1;
+}
+
+static int pack_vlv_response_control(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval cookie, bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ rc = ber_printf( ber, "{iie", so->so_vlv_target, so->so_nentries,
+ so->so_vlv_rc );
+
+ if ( rc != -1 && so->so_vcontext ) {
+ cookie.bv_val = (char *)&so->so_vcontext;
+ cookie.bv_len = sizeof(so->so_vcontext);
+ rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie );
+ }
+
+ if ( rc != -1 ) {
+ rc = ber_printf( ber, "}" );
+ }
+
+ if ( rc != -1 ) {
+ rc = ber_flatten2( ber, &bv, 0 );
+ }
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+static int pack_pagedresult_response_control(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie resp_cookie;
+ struct berval cookie, bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( so->so_nentries > 0 ) {
+ resp_cookie = ( PagedResultsCookie )so->so_tree;
+ cookie.bv_len = sizeof( PagedResultsCookie );
+ cookie.bv_val = (char *)&resp_cookie;
+ } else {
+ resp_cookie = ( PagedResultsCookie )0;
+ BER_BVZERO( &cookie );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie;
+ op->o_conn->c_pagedresults_state.ps_count
+ = ((PagedResultsState *)op->o_pagedresults_state)->ps_count
+ + rs->sr_nentries;
+
+ rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie );
+ if ( rc != -1 ) {
+ rc = ber_flatten2( ber, &bv, 0 );
+ }
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+static int pack_sss_response_control(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrlsp )
+{
+ LDAPControl *ctrl;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval bv;
+ int rc;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* Pack error code */
+ rc = ber_printf(ber, "{e}", rs->sr_err);
+
+ if ( rc != -1)
+ rc = ber_flatten2( ber, &bv, 0 );
+
+ if ( rc != -1 ) {
+ ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+
+ bv.bv_len, op->o_tmpmemctx );
+ ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE;
+ ctrl->ldctl_iscritical = 0;
+ ctrl->ldctl_value.bv_val = (char *)(ctrl+1);
+ ctrl->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrlsp[0] = ctrl;
+ } else {
+ ctrlsp[0] = NULL;
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ ber_free_buf( ber );
+
+ return rs->sr_err;
+}
+
+/* Return the session id or -1 if unknown */
+static int find_session_by_so(
+ int svi_max_percon,
+ int conn_id,
+ sort_op *so )
+{
+ int sess_id;
+ if (so == NULL) {
+ return -1;
+ }
+ for (sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if ( sort_conns[conn_id] && sort_conns[conn_id][sess_id] == so )
+ return sess_id;
+ }
+ return -1;
+}
+
+/* Return the session id or -1 if unknown */
+static int find_session_by_context(
+ int svi_max_percon,
+ int conn_id,
+ unsigned long vc_context,
+ PagedResultsCookie ps_cookie )
+{
+ int sess_id;
+ for(sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if( sort_conns[conn_id] && sort_conns[conn_id][sess_id] &&
+ ( sort_conns[conn_id][sess_id]->so_vcontext == vc_context ||
+ (PagedResultsCookie) sort_conns[conn_id][sess_id]->so_tree == ps_cookie ) )
+ return sess_id;
+ }
+ return -1;
+}
+
+static int find_next_session(
+ int svi_max_percon,
+ int conn_id )
+{
+ int sess_id;
+ assert(sort_conns[conn_id] != NULL);
+ for(sess_id = 0; sess_id < svi_max_percon; sess_id++) {
+ if(!sort_conns[conn_id][sess_id]) {
+ return sess_id;
+ }
+ }
+ if (sess_id >= svi_max_percon) {
+ return -1;
+ } else {
+ return sess_id;
+ }
+}
+
+static void free_sort_op( Connection *conn, sort_op *so )
+{
+ int sess_id;
+
+ ldap_pvt_thread_mutex_lock( &sort_conns_mutex );
+ sess_id = find_session_by_so( so->so_info->svi_max_percon, conn->c_conn_idx, so );
+ if ( sess_id > -1 ) {
+ sort_conns[conn->c_conn_idx][sess_id] = NULL;
+ so->so_info->svi_num--;
+ }
+ ldap_pvt_thread_mutex_unlock( &sort_conns_mutex );
+
+ if ( sess_id > -1 ){
+ if ( so->so_tree ) {
+ if ( so->so_paged > SLAP_CONTROL_IGNORED ) {
+ Avlnode *cur_node, *next_node;
+ cur_node = so->so_tree;
+ while ( cur_node ) {
+ next_node = tavl_next( cur_node, TAVL_DIR_RIGHT );
+ ch_free( cur_node->avl_data );
+ ber_memfree( cur_node );
+
+ cur_node = next_node;
+ }
+ } else {
+ tavl_free( so->so_tree, ch_free );
+ }
+ so->so_tree = NULL;
+ }
+
+ ch_free( so );
+ }
+}
+
+static void free_sort_ops( Connection *conn, sort_op **sos, int svi_max_percon )
+{
+ int sess_id;
+ sort_op *so;
+
+ for( sess_id = 0; sess_id < svi_max_percon ; sess_id++ ) {
+ so = sort_conns[conn->c_conn_idx][sess_id];
+ if ( so ) {
+ free_sort_op( conn, so );
+ sort_conns[conn->c_conn_idx][sess_id] = NULL;
+ }
+ }
+}
+
+static void send_list(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ Avlnode *cur_node, *tmp_node;
+ vlv_ctrl *vc = op->o_controls[vlv_cid];
+ int i, j, dir, rc;
+ BackendDB *be;
+ Entry *e;
+ LDAPControl *ctrls[2];
+
+ rs->sr_attrs = op->ors_attrs;
+
+ /* FIXME: it may be better to just flatten the tree into
+ * an array before doing all of this...
+ */
+
+ /* Are we just counting an offset? */
+ if ( BER_BVISNULL( &vc->vc_value )) {
+ if ( vc->vc_offset == vc->vc_count ) {
+ /* wants the last entry in the list */
+ cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ so->so_vlv_target = so->so_nentries;
+ } else if ( vc->vc_offset == 1 ) {
+ /* wants the first entry in the list */
+ cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ so->so_vlv_target = 1;
+ } else {
+ int target;
+ /* Just iterate to the right spot */
+ if ( vc->vc_count && vc->vc_count != so->so_nentries ) {
+ if ( vc->vc_offset > vc->vc_count )
+ goto range_err;
+ target = so->so_nentries * vc->vc_offset / vc->vc_count;
+ } else {
+ if ( vc->vc_offset > so->so_nentries ) {
+range_err:
+ so->so_vlv_rc = LDAP_VLV_RANGE_ERROR;
+ pack_vlv_response_control( op, rs, so, ctrls );
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_VLV_ERROR;
+ return;
+ }
+ target = vc->vc_offset;
+ }
+ so->so_vlv_target = target;
+ /* Start at left and go right, or start at right and go left? */
+ if ( target < so->so_nentries / 2 ) {
+ cur_node = tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ } else {
+ cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ dir = TAVL_DIR_LEFT;
+ target = so->so_nentries - target + 1;
+ }
+ for ( i=1; i<target; i++ )
+ cur_node = tavl_next( cur_node, dir );
+ }
+ } else {
+ /* we're looking for a specific value */
+ sort_ctrl *sc = so->so_ctrl;
+ MatchingRule *mr = sc->sc_keys[0].sk_ordering;
+ sort_node *sn;
+ struct berval bv;
+
+ if ( mr->smr_normalize ) {
+ rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX,
+ mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx );
+ if ( rc ) {
+ so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING;
+ pack_vlv_response_control( op, rs, so, ctrls );
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ rs->sr_err = LDAP_VLV_ERROR;
+ return;
+ }
+ } else {
+ bv = vc->vc_value;
+ }
+
+ sn = op->o_tmpalloc( sizeof(sort_node) +
+ sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx );
+ sn->sn_vals = (struct berval *)(sn+1);
+ sn->sn_conn = op->o_conn->c_conn_idx;
+ sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so );
+ sn->sn_vals[0] = bv;
+ for (i=1; i<sc->sc_nkeys; i++) {
+ BER_BVZERO( &sn->sn_vals[i] );
+ }
+ cur_node = tavl_find3( so->so_tree, sn, node_cmp, &j );
+ /* didn't find >= match */
+ if ( j > 0 ) {
+ if ( cur_node )
+ cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT );
+ }
+ op->o_tmpfree( sn, op->o_tmpmemctx );
+
+ if ( !cur_node ) {
+ so->so_vlv_target = so->so_nentries + 1;
+ } else {
+ sort_node *sn = so->so_tree->avl_data;
+ /* start from the left or the right side? */
+ mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] );
+ if ( i > 0 ) {
+ tmp_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ dir = TAVL_DIR_LEFT;
+ } else {
+ tmp_node = tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ dir = TAVL_DIR_RIGHT;
+ }
+ for (i=0; tmp_node != cur_node;
+ tmp_node = tavl_next( tmp_node, dir ), i++);
+ so->so_vlv_target = (dir == TAVL_DIR_RIGHT) ? i+1 : so->so_nentries - i;
+ }
+ if ( bv.bv_val != vc->vc_value.bv_val )
+ op->o_tmpfree( bv.bv_val, op->o_tmpmemctx );
+ }
+ if ( !cur_node ) {
+ i = 1;
+ cur_node = tavl_end(so->so_tree, TAVL_DIR_RIGHT);
+ } else {
+ i = 0;
+ }
+ for ( ; i<vc->vc_before; i++ ) {
+ tmp_node = tavl_next( cur_node, TAVL_DIR_LEFT );
+ if ( !tmp_node ) break;
+ cur_node = tmp_node;
+ }
+ j = i + vc->vc_after + 1;
+ be = op->o_bd;
+ for ( i=0; i<j; i++ ) {
+ sort_node *sn = cur_node->avl_data;
+
+ if ( slapd_shutdown ) break;
+
+ op->o_bd = select_backend( &sn->sn_dn, 0 );
+ e = NULL;
+ rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e );
+
+ if ( e && rc == LDAP_SUCCESS ) {
+ rs->sr_entry = e;
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs->sr_err = send_search_entry( op, rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE )
+ break;
+ }
+ cur_node = tavl_next( cur_node, TAVL_DIR_RIGHT );
+ if ( !cur_node ) break;
+ }
+ so->so_vlv_rc = LDAP_SUCCESS;
+
+ op->o_bd = be;
+}
+
+static void send_page( Operation *op, SlapReply *rs, sort_op *so )
+{
+ Avlnode *cur_node = so->so_tree;
+ Avlnode *next_node = NULL;
+ BackendDB *be = op->o_bd;
+ Entry *e;
+ int rc;
+
+ rs->sr_attrs = op->ors_attrs;
+
+ while ( cur_node && rs->sr_nentries < so->so_page_size ) {
+ sort_node *sn = cur_node->avl_data;
+
+ if ( slapd_shutdown ) break;
+
+ next_node = tavl_next( cur_node, TAVL_DIR_RIGHT );
+
+ op->o_bd = select_backend( &sn->sn_dn, 0 );
+ e = NULL;
+ rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e );
+
+ ch_free( cur_node->avl_data );
+ ber_memfree( cur_node );
+
+ cur_node = next_node;
+ so->so_nentries--;
+
+ if ( e && rc == LDAP_SUCCESS ) {
+ rs->sr_entry = e;
+ rs->sr_flags = REP_ENTRY_MUSTRELEASE;
+ rs->sr_err = send_search_entry( op, rs );
+ if ( rs->sr_err == LDAP_UNAVAILABLE )
+ break;
+ }
+ }
+
+ /* Set the first entry to send for the next page */
+ so->so_tree = next_node;
+ if ( next_node )
+ next_node->avl_left = NULL;
+
+ op->o_bd = be;
+}
+
+static void send_entry(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: response control: status=%d, text=%s\n",
+ debug_header, rs->sr_err, SAFESTR(rs->sr_text, "<None>"));
+
+ if ( !so->so_tree )
+ return;
+
+ /* RFC 2891: If critical then send the entries iff they were
+ * succesfully sorted. If non-critical send all entries
+ * whether they were sorted or not.
+ */
+ if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) ||
+ (rs->sr_err == LDAP_SUCCESS) )
+ {
+ if ( so->so_vlv > SLAP_CONTROL_IGNORED ) {
+ send_list( op, rs, so );
+ } else {
+ /* Get the first node to send */
+ Avlnode *start_node = tavl_end(so->so_tree, TAVL_DIR_LEFT);
+ so->so_tree = start_node;
+
+ if ( so->so_paged <= SLAP_CONTROL_IGNORED ) {
+ /* Not paged result search. Send all entries.
+ * Set the page size to the number of entries
+ * so that send_page() will send all entries.
+ */
+ so->so_page_size = so->so_nentries;
+ }
+
+ send_page( op, rs, so );
+ }
+ }
+}
+
+static void send_result(
+ Operation *op,
+ SlapReply *rs,
+ sort_op *so)
+{
+ LDAPControl *ctrls[3];
+ int rc, i = 0;
+
+ rc = pack_sss_response_control( op, rs, ctrls );
+ if ( rc == LDAP_SUCCESS ) {
+ i++;
+ rc = -1;
+ if ( so->so_paged > SLAP_CONTROL_IGNORED ) {
+ rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 );
+ } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) {
+ rc = pack_vlv_response_control( op, rs, so, ctrls+1 );
+ }
+ if ( rc == LDAP_SUCCESS )
+ i++;
+ }
+ ctrls[i] = NULL;
+
+ if ( ctrls[0] != NULL )
+ slap_add_ctrls( op, rs, ctrls );
+ send_ldap_result( op, rs );
+
+ if ( so->so_tree == NULL ) {
+ /* Search finished, so clean up */
+ free_sort_op( op->o_conn, so );
+ } else {
+ so->so_running = 0;
+ }
+}
+
+static int sssvlv_op_response(
+ Operation *op,
+ SlapReply *rs )
+{
+ sort_ctrl *sc = op->o_controls[sss_cid];
+ sort_op *so = op->o_callback->sc_private;
+
+ if ( rs->sr_type == REP_SEARCH ) {
+ int i;
+ size_t len;
+ sort_node *sn, *sn2;
+ struct berval *bv;
+ char *ptr;
+
+ len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) +
+ rs->sr_entry->e_nname.bv_len + 1;
+ sn = op->o_tmpalloc( len, op->o_tmpmemctx );
+ sn->sn_vals = (struct berval *)(sn+1);
+
+ /* Build tmp list of key values */
+ for ( i=0; i<sc->sc_nkeys; i++ ) {
+ Attribute *a = attr_find( rs->sr_entry->e_attrs,
+ sc->sc_keys[i].sk_ad );
+ if ( a ) {
+ if ( a->a_numvals > 1 ) {
+ bv = select_value( a, &sc->sc_keys[i] );
+ } else {
+ bv = a->a_nvals;
+ }
+ sn->sn_vals[i] = *bv;
+ len += bv->bv_len + 1;
+ } else {
+ BER_BVZERO( &sn->sn_vals[i] );
+ }
+ }
+
+ /* Now dup into regular memory */
+ sn2 = ch_malloc( len );
+ sn2->sn_vals = (struct berval *)(sn2+1);
+ AC_MEMCPY( sn2->sn_vals, sn->sn_vals,
+ sc->sc_nkeys * sizeof(struct berval));
+
+ ptr = (char *)(sn2->sn_vals + sc->sc_nkeys);
+ sn2->sn_dn.bv_val = ptr;
+ sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len;
+ AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val,
+ rs->sr_entry->e_nname.bv_len );
+ ptr += rs->sr_entry->e_nname.bv_len;
+ *ptr++ = '\0';
+ for ( i=0; i<sc->sc_nkeys; i++ ) {
+ if ( !BER_BVISNULL( &sn2->sn_vals[i] )) {
+ AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len);
+ sn2->sn_vals[i].bv_val = ptr;
+ ptr += sn2->sn_vals[i].bv_len;
+ *ptr++ = '\0';
+ }
+ }
+ op->o_tmpfree( sn, op->o_tmpmemctx );
+ sn = sn2;
+ sn->sn_conn = op->o_conn->c_conn_idx;
+ sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so );
+
+ /* Insert into the AVL tree */
+ tavl_insert(&(so->so_tree), sn, node_insert, avl_dup_error);
+
+ so->so_nentries++;
+
+ /* Collected the keys so that they can be sorted. Thus, stop
+ * the entry from propagating.
+ */
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ else if ( rs->sr_type == REP_RESULT ) {
+ /* Remove serversort response callback.
+ * We don't want the entries that we are about to send to be
+ * processed by serversort response again.
+ */
+ if ( op->o_callback->sc_response == sssvlv_op_response ) {
+ op->o_callback = op->o_callback->sc_next;
+ }
+
+ send_entry( op, rs, so );
+ send_result( op, rs, so );
+ }
+
+ return rs->sr_err;
+}
+
+static int sssvlv_op_search(
+ Operation *op,
+ SlapReply *rs)
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+ int rc = SLAP_CB_CONTINUE;
+ int ok;
+ sort_op *so = NULL, so2;
+ sort_ctrl *sc;
+ PagedResultsState *ps;
+ vlv_ctrl *vc;
+ int sess_id;
+
+ if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) {
+ if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) {
+ LDAPControl *ctrls[2];
+ so2.so_vcontext = 0;
+ so2.so_vlv_target = 0;
+ so2.so_nentries = 0;
+ so2.so_vlv_rc = LDAP_VLV_SSS_MISSING;
+ so2.so_vlv = op->o_ctrlflag[vlv_cid];
+ rc = pack_vlv_response_control( op, rs, &so2, ctrls );
+ if ( rc == LDAP_SUCCESS ) {
+ ctrls[1] = NULL;
+ slap_add_ctrls( op, rs, ctrls );
+ }
+ rs->sr_err = LDAP_VLV_ERROR;
+ rs->sr_text = "Sort control is required with VLV";
+ goto leave;
+ }
+ /* Not server side sort so just continue */
+ return SLAP_CB_CONTINUE;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "==> sssvlv_search: <%s> %s, control flag: %d\n",
+ op->o_req_dn.bv_val, op->ors_filterstr.bv_val,
+ op->o_ctrlflag[sss_cid]);
+
+ sc = op->o_controls[sss_cid];
+ if ( sc->sc_nkeys > si->svi_max_keys ) {
+ rs->sr_text = "Too many sort keys";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto leave;
+ }
+
+ ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ?
+ (PagedResultsState*)(op->o_pagedresults_state) : NULL;
+ vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ?
+ op->o_controls[vlv_cid] : NULL;
+
+ if ( ps && vc ) {
+ rs->sr_text = "VLV incompatible with PagedResults";
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ goto leave;
+ }
+
+ ok = 1;
+ ldap_pvt_thread_mutex_lock( &sort_conns_mutex );
+ /* Is there already a sort running on this conn? */
+ sess_id = find_session_by_context( si->svi_max_percon, op->o_conn->c_conn_idx, vc ? vc->vc_context : NO_VC_CONTEXT, ps ? ps->ps_cookie : NO_PS_COOKIE );
+ if ( sess_id >= 0 ) {
+ so = sort_conns[op->o_conn->c_conn_idx][sess_id];
+
+ if( so->so_running > 0 ){
+ /* another thread is handling, response busy to client */
+ so = NULL;
+ ok = 0;
+ } else {
+
+ /* Is it a continuation of a VLV search? */
+ if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED ||
+ vc->vc_context != so->so_vcontext ) {
+ /* Is it a continuation of a paged search? */
+ if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED ||
+ op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) {
+ ok = 0;
+ } else if ( !ps->ps_size ) {
+ /* Abandoning current request */
+ ok = 0;
+ so->so_nentries = 0;
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ }
+ if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) ||
+ ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) {
+ /* changed from paged to vlv or vice versa, abandon */
+ ok = 0;
+ so->so_nentries = 0;
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ if ( ok ) {
+ /* occupy before mutex unlock */
+ so->so_running = 1;
+ }
+
+ }
+ /* Are there too many running overall? */
+ } else if ( si->svi_num >= si->svi_max ) {
+ ok = 0;
+ } else if ( ( sess_id = find_next_session(si->svi_max_percon, op->o_conn->c_conn_idx ) ) < 0 ) {
+ ok = 0;
+ } else {
+ /* OK, this connection now has a sort running */
+ si->svi_num++;
+ sort_conns[op->o_conn->c_conn_idx][sess_id] = &so2;
+ sort_conns[op->o_conn->c_conn_idx][sess_id]->so_session = sess_id;
+ }
+ ldap_pvt_thread_mutex_unlock( &sort_conns_mutex );
+ if ( ok ) {
+ /* If we're a global overlay, this check got bypassed */
+ if ( !op->ors_limit && limits_check( op, rs ))
+ return rs->sr_err;
+ /* are we continuing a VLV search? */
+ if ( so && vc && vc->vc_context ) {
+ so->so_ctrl = sc;
+ send_list( op, rs, so );
+ send_result( op, rs, so );
+ rc = LDAP_SUCCESS;
+ /* are we continuing a paged search? */
+ } else if ( so && ps && ps->ps_cookie ) {
+ so->so_ctrl = sc;
+ send_page( op, rs, so );
+ send_result( op, rs, so );
+ rc = LDAP_SUCCESS;
+ } else {
+ slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback),
+ op->o_tmpmemctx );
+ /* Install serversort response callback to handle a new search */
+ if ( ps || vc ) {
+ so = ch_calloc( 1, sizeof(sort_op));
+ } else {
+ so = op->o_tmpcalloc( 1, sizeof(sort_op), op->o_tmpmemctx );
+ }
+ sort_conns[op->o_conn->c_conn_idx][sess_id] = so;
+
+ cb->sc_cleanup = NULL;
+ cb->sc_response = sssvlv_op_response;
+ cb->sc_next = op->o_callback;
+ cb->sc_private = so;
+ cb->sc_writewait = NULL;
+
+ so->so_tree = NULL;
+ so->so_ctrl = sc;
+ so->so_info = si;
+ if ( ps ) {
+ so->so_paged = op->o_pagedresults;
+ so->so_page_size = ps->ps_size;
+ op->o_pagedresults = SLAP_CONTROL_IGNORED;
+ } else {
+ so->so_paged = 0;
+ so->so_page_size = 0;
+ if ( vc ) {
+ so->so_vlv = op->o_ctrlflag[vlv_cid];
+ so->so_vlv_target = 0;
+ so->so_vlv_rc = 0;
+ } else {
+ so->so_vlv = SLAP_CONTROL_NONE;
+ }
+ }
+ so->so_session = sess_id;
+ so->so_vlv = op->o_ctrlflag[vlv_cid];
+ so->so_vcontext = (unsigned long)so;
+ so->so_nentries = 0;
+ so->so_running = 1;
+
+ op->o_callback = cb;
+ }
+ } else {
+ if ( so && !so->so_nentries ) {
+ free_sort_op( op->o_conn, so );
+ } else {
+ rs->sr_text = "Other sort requests already in progress";
+ rs->sr_err = LDAP_BUSY;
+ }
+leave:
+ rc = rs->sr_err;
+ send_ldap_result( op, rs );
+ }
+
+ return rc;
+}
+
+static int get_ordering_rule(
+ AttributeDescription *ad,
+ struct berval *matchrule,
+ SlapReply *rs,
+ MatchingRule **ordering )
+{
+ MatchingRule* mr;
+
+ if ( matchrule && matchrule->bv_val ) {
+ mr = mr_find( matchrule->bv_val );
+ if ( mr == NULL ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_MATCHING;
+ rs->sr_text = "serverSort control: No ordering rule";
+ Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n",
+ debug_header, matchrule->bv_val, 0);
+ }
+ }
+ else {
+ mr = ad->ad_type->sat_ordering;
+ if ( mr == NULL ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_MATCHING;
+ rs->sr_text = "serverSort control: No ordering rule";
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: no ordering rule specified and no default ordering rule for attribute %s\n",
+ debug_header, ad->ad_cname.bv_val, 0);
+ }
+ }
+
+ *ordering = mr;
+ return rs->sr_err;
+}
+
+static int count_key(BerElement *ber)
+{
+ char *end;
+ ber_len_t len;
+ ber_tag_t tag;
+ int count = 0;
+
+ /* Server Side Sort Control is a SEQUENCE of SEQUENCE */
+ for ( tag = ber_first_element( ber, &len, &end );
+ tag == LBER_SEQUENCE;
+ tag = ber_next_element( ber, &len, end ))
+ {
+ tag = ber_skip_tag( ber, &len );
+ ber_skip_data( ber, len );
+ ++count;
+ }
+ ber_rewind( ber );
+
+ return count;
+}
+
+static int build_key(
+ BerElement *ber,
+ SlapReply *rs,
+ sort_key *key )
+{
+ struct berval attr;
+ struct berval matchrule = BER_BVNULL;
+ ber_int_t reverse = 0;
+ ber_tag_t tag;
+ ber_len_t len;
+ MatchingRule *ordering = NULL;
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: attribute decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_MATCHRULE_IDENTIFIER ) {
+ if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: matchrule decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+
+ if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) {
+ if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: reverse decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+ }
+
+ if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) {
+ rs->sr_text = "serverSort control: decoding error";
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ return rs->sr_err;
+ }
+
+ if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) {
+ rs->sr_text =
+ "serverSort control: Unrecognized attribute type in sort key";
+ Debug(LDAP_DEBUG_TRACE,
+ "%s: Unrecognized attribute type in sort key: %s\n",
+ debug_header, SAFESTR(attr.bv_val, "<None>"), 0);
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ return rs->sr_err;
+ }
+
+ /* get_ordering_rule will set sr_err and sr_text */
+ get_ordering_rule( ad, &matchrule, rs, &ordering );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ return rs->sr_err;
+ }
+
+ key->sk_ad = ad;
+ key->sk_ordering = ordering;
+ key->sk_direction = reverse ? -1 : 1;
+
+ return rs->sr_err;
+}
+
+/* Conforms to RFC4510 re: Criticality, original RFC2891 spec is broken
+ * Also see ITS#7253 for discussion
+ */
+static int sss_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ int i;
+ sort_ctrl *sc;
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+
+ if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) {
+ rs->sr_text = "sorted results control specified multiple times";
+ } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is absent";
+ } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "sorted results control value is empty";
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ if ( rs->sr_err != LDAP_SUCCESS )
+ return rs->sr_err;
+
+ op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+
+ ber = (BerElement *)&berbuf;
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+ i = count_key( ber );
+
+ sc = op->o_tmpalloc( sizeof(sort_ctrl) +
+ (i-1) * sizeof(sort_key), op->o_tmpmemctx );
+ sc->sc_nkeys = i;
+ op->o_controls[sss_cid] = sc;
+
+ /* peel off initial sequence */
+ ber_scanf( ber, "{" );
+
+ i = 0;
+ do {
+ if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS )
+ break;
+ i++;
+ tag = ber_peek_tag( ber, &len );
+ } while ( tag != LBER_DEFAULT );
+
+ return rs->sr_err;
+}
+
+static int vlv_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber;
+ ber_tag_t tag;
+ ber_len_t len;
+ vlv_ctrl *vc, vc2;
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ rs->sr_text = NULL;
+
+ if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) {
+ rs->sr_text = "vlv control specified multiple times";
+ } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "vlv control value is absent";
+ } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "vlv control value is empty";
+ }
+ if ( rs->sr_text != NULL )
+ return rs->sr_err;
+
+ op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+
+ ber = (BerElement *)&berbuf;
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+
+ tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after );
+ if ( tag == LBER_ERROR ) {
+ return rs->sr_err;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) {
+ tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count );
+ if ( tag == LBER_ERROR )
+ return rs->sr_err;
+ BER_BVZERO( &vc2.vc_value );
+ } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) {
+ tag = ber_scanf( ber, "m", &vc2.vc_value );
+ if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value ))
+ return rs->sr_err;
+ } else {
+ return rs->sr_err;
+ }
+ tag = ber_peek_tag( ber, &len );
+ if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) {
+ struct berval bv;
+ tag = ber_scanf( ber, "m", &bv );
+ if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context))
+ return rs->sr_err;
+ AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len );
+ } else {
+ vc2.vc_context = 0;
+ }
+
+ vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx );
+ *vc = vc2;
+ op->o_controls[vlv_cid] = vc;
+ rs->sr_err = LDAP_SUCCESS;
+
+ return rs->sr_err;
+}
+
+static int sssvlv_connection_destroy( BackendDB *be, Connection *conn )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+
+ if ( sort_conns[conn->c_conn_idx] ) {
+ free_sort_ops( conn, sort_conns[conn->c_conn_idx], si->svi_max_percon );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int sssvlv_db_open(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = on->on_bi.bi_private;
+ int rc;
+ int conn_index;
+
+ /* If not set, default to 1/2 of available threads */
+ if ( !si->svi_max )
+ si->svi_max = connection_pool_max / 2;
+
+ if ( dtblsize && !sort_conns ) {
+ ldap_pvt_thread_mutex_init( &sort_conns_mutex );
+ /* accommodate for c_conn_idx == -1 */
+ sort_conns = ch_calloc( dtblsize + 1, sizeof(sort_op **) );
+ for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) {
+ sort_conns[conn_index] = ch_calloc( si->svi_max_percon, sizeof(sort_op *) );
+ }
+ sort_conns++;
+ }
+
+ rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST );
+ if ( rc == LDAP_SUCCESS )
+ rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST );
+ return rc;
+}
+
+static ConfigTable sssvlv_cfg[] = {
+ { "sssvlv-max", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max),
+ "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' "
+ "DESC 'Maximum number of concurrent Sort requests' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "sssvlv-maxkeys", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max_keys),
+ "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' "
+ "DESC 'Maximum number of Keys in a Sort request' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "sssvlv-maxperconn", "num",
+ 2, 2, 0, ARG_INT|ARG_OFFSET,
+ (void *)offsetof(sssvlv_info, svi_max_percon),
+ "( OLcfgOvAt:21.3 NAME 'olcSssVlvMaxPerConn' "
+ "DESC 'Maximum number of concurrent paged search requests per connection' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs sssvlv_ocs[] = {
+ { "( OLcfgOvOc:21.1 "
+ "NAME 'olcSssVlvConfig' "
+ "DESC 'SSS VLV configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys $ olcSssVlvMaxPerConn ) )",
+ Cft_Overlay, sssvlv_cfg, NULL, NULL },
+ { NULL, 0, NULL }
+};
+
+static int sssvlv_db_init(
+ BackendDB *be,
+ ConfigReply *cr)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si;
+
+ if ( ov_count == 0 ) {
+ int rc;
+
+ rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ sss_parseCtrl,
+ 1 /* replace */,
+ &sss_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register Sort Request control '%s' (%d)\n",
+ LDAP_CONTROL_SORTREQUEST, rc, 0 );
+ return rc;
+ }
+
+ rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST,
+ SLAP_CTRL_SEARCH,
+ NULL,
+ vlv_parseCtrl,
+ 1 /* replace */,
+ &vlv_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register VLV Request control '%s' (%d)\n",
+ LDAP_CONTROL_VLVREQUEST, rc, 0 );
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST );
+ unregister_supported_control( LDAP_CONTROL_SORTREQUEST );
+#endif /* SLAP_CONFIG_DELETE */
+ return rc;
+ }
+ }
+
+ si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info));
+ on->on_bi.bi_private = si;
+
+ si->svi_max = 0;
+ si->svi_num = 0;
+ si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS;
+ si->svi_max_percon = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN;
+
+ ov_count++;
+
+ return LDAP_SUCCESS;
+}
+
+static int sssvlv_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private;
+ int conn_index;
+
+ ov_count--;
+ if ( !ov_count && sort_conns) {
+ sort_conns--;
+ for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) {
+ ch_free(sort_conns[conn_index]);
+ }
+ ch_free(sort_conns);
+ ldap_pvt_thread_mutex_destroy( &sort_conns_mutex );
+ }
+
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST );
+ overlay_unregister_control( be, LDAP_CONTROL_VLVREQUEST );
+ if ( ov_count == 0 ) {
+ unregister_supported_control( LDAP_CONTROL_SORTREQUEST );
+ unregister_supported_control( LDAP_CONTROL_VLVREQUEST );
+ }
+#endif /* SLAP_CONFIG_DELETE */
+
+ if ( si ) {
+ ch_free( si );
+ on->on_bi.bi_private = NULL;
+ }
+ return LDAP_SUCCESS;
+}
+
+static slap_overinst sssvlv;
+
+int sssvlv_initialize()
+{
+ int rc;
+
+ sssvlv.on_bi.bi_type = "sssvlv";
+ sssvlv.on_bi.bi_db_init = sssvlv_db_init;
+ sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy;
+ sssvlv.on_bi.bi_db_open = sssvlv_db_open;
+ sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy;
+ sssvlv.on_bi.bi_op_search = sssvlv_op_search;
+
+ sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs;
+
+ rc = config_register_schema( sssvlv_cfg, sssvlv_ocs );
+ if ( rc )
+ return rc;
+
+ rc = overlay_register( &sssvlv );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register server side sort overlay\n", 0, 0, 0 );
+ }
+
+ return rc;
+}
+
+#if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC
+int init_module( int argc, char *argv[])
+{
+ return sssvlv_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_SSSVLV */
diff --git a/servers/slapd/overlays/syncprov.c b/servers/slapd/overlays/syncprov.c
new file mode 100644
index 0000000..d2cb9f1
--- /dev/null
+++ b/servers/slapd/overlays/syncprov.c
@@ -0,0 +1,3523 @@
+/* $OpenLDAP$ */
+/* syncprov.c - syncrepl provider */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_SYNCPROV
+
+#include <ac/string.h>
+#include "lutil.h"
+#include "slap.h"
+#include "config.h"
+#include "ldap_rq.h"
+
+#ifdef LDAP_DEVEL
+#define CHECK_CSN 1
+#endif
+
+/* A modify request on a particular entry */
+typedef struct modinst {
+ struct modinst *mi_next;
+ Operation *mi_op;
+} modinst;
+
+typedef struct modtarget {
+ struct modinst *mt_mods;
+ struct modinst *mt_tail;
+ struct berval mt_dn;
+ ldap_pvt_thread_mutex_t mt_mutex;
+} modtarget;
+
+/* All the info of a psearch result that's shared between
+ * multiple queues
+ */
+typedef struct resinfo {
+ struct syncres *ri_list;
+ Entry *ri_e;
+ struct berval ri_dn;
+ struct berval ri_ndn;
+ struct berval ri_uuid;
+ struct berval ri_csn;
+ struct berval ri_cookie;
+ char ri_isref;
+ ldap_pvt_thread_mutex_t ri_mutex;
+} resinfo;
+
+/* A queued result of a persistent search */
+typedef struct syncres {
+ struct syncres *s_next; /* list of results on this psearch queue */
+ struct syncres *s_rilist; /* list of psearches using this result */
+ resinfo *s_info;
+ char s_mode;
+} syncres;
+
+/* Record of a persistent search */
+typedef struct syncops {
+ struct syncops *s_next;
+ struct syncprov_info_t *s_si;
+ struct berval s_base; /* ndn of search base */
+ ID s_eid; /* entryID of search base */
+ Operation *s_op; /* search op */
+ int s_rid;
+ int s_sid;
+ struct berval s_filterstr;
+ int s_flags; /* search status */
+#define PS_IS_REFRESHING 0x01
+#define PS_IS_DETACHED 0x02
+#define PS_WROTE_BASE 0x04
+#define PS_FIND_BASE 0x08
+#define PS_FIX_FILTER 0x10
+#define PS_TASK_QUEUED 0x20
+
+ int s_inuse; /* reference count */
+ struct syncres *s_res;
+ struct syncres *s_restail;
+ ldap_pvt_thread_mutex_t s_mutex;
+} syncops;
+
+/* A received sync control */
+typedef struct sync_control {
+ struct sync_cookie sr_state;
+ int sr_rhint;
+} sync_control;
+
+#if 0 /* moved back to slap.h */
+#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync]
+#endif
+/* o_sync_mode uses data bits of o_sync */
+#define o_sync_mode o_ctrlflag[slap_cids.sc_LDAPsync]
+
+#define SLAP_SYNC_NONE (LDAP_SYNC_NONE<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_REFRESH (LDAP_SYNC_REFRESH_ONLY<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_PERSIST (LDAP_SYNC_RESERVED<<SLAP_CONTROL_SHIFT)
+#define SLAP_SYNC_REFRESH_AND_PERSIST (LDAP_SYNC_REFRESH_AND_PERSIST<<SLAP_CONTROL_SHIFT)
+
+/* Record of which searches matched at premodify step */
+typedef struct syncmatches {
+ struct syncmatches *sm_next;
+ syncops *sm_op;
+} syncmatches;
+
+/* Session log data */
+typedef struct slog_entry {
+ struct slog_entry *se_next;
+ 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;
+ slog_entry *sl_head;
+ slog_entry *sl_tail;
+ ldap_pvt_thread_mutex_t sl_mutex;
+} sessionlog;
+
+/* The main state for this overlay */
+typedef struct syncprov_info_t {
+ syncops *si_ops;
+ struct berval si_contextdn;
+ BerVarray si_ctxcsn; /* ldapsync context */
+ int *si_sids;
+ int si_numcsns;
+ int si_chkops; /* checkpointing info */
+ int si_chktime;
+ int si_numops; /* number of ops since last checkpoint */
+ int si_nopres; /* Skip present phase */
+ int si_usehint; /* use reload hint */
+ int si_active; /* True if there are active mods */
+ int si_dirty; /* True if the context is dirty, i.e changes
+ * have been made without updating the csn. */
+ time_t si_chklast; /* time of last checkpoint */
+ Avlnode *si_mods; /* entries being modified */
+ sessionlog *si_logs;
+ ldap_pvt_thread_rdwr_t si_csn_rwlock;
+ ldap_pvt_thread_mutex_t si_ops_mutex;
+ ldap_pvt_thread_mutex_t si_mods_mutex;
+ ldap_pvt_thread_mutex_t si_resp_mutex;
+} syncprov_info_t;
+
+typedef struct opcookie {
+ slap_overinst *son;
+ syncmatches *smatches;
+ modtarget *smt;
+ Entry *se;
+ struct berval sdn; /* DN of entry, for deletes */
+ struct berval sndn;
+ struct berval suuid; /* UUID of entry */
+ struct berval sctxcsn;
+ short osid; /* sid of op csn */
+ short rsid; /* sid of relay */
+ short sreference; /* Is the entry a reference? */
+ syncres ssres;
+} opcookie;
+
+typedef struct fbase_cookie {
+ struct berval *fdn; /* DN of a modified entry, for scope testing */
+ syncops *fss; /* persistent search we're testing against */
+ int fbase; /* if TRUE we found the search base and it's still valid */
+ int fscope; /* if TRUE then fdn is within the psearch scope */
+} fbase_cookie;
+
+static AttributeName csn_anlist[3];
+static AttributeName uuid_anlist[2];
+
+/* Build a LDAPsync intermediate state control */
+static int
+syncprov_state_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ Entry *e,
+ int entry_sync_state,
+ LDAPControl **ctrls,
+ int num_ctrls,
+ int send_cookie,
+ struct berval *cookie )
+{
+ Attribute* a;
+ int ret;
+
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPControl *cp;
+ struct berval bv;
+ struct berval entryuuid_bv = BER_BVNULL;
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ AttributeDescription *desc = a->a_desc;
+ if ( desc == slap_schema.si_ad_entryUUID ) {
+ entryuuid_bv = a->a_nvals[0];
+ break;
+ }
+ }
+
+ /* FIXME: what if entryuuid is NULL or empty ? */
+
+ if ( send_cookie && cookie ) {
+ ber_printf( ber, "{eOON}",
+ entry_sync_state, &entryuuid_bv, cookie );
+ } else {
+ ber_printf( ber, "{eON}",
+ entry_sync_state, &entryuuid_bv );
+ }
+
+ ret = ber_flatten2( ber, &bv, 0 );
+ if ( ret == 0 ) {
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = LDAP_CONTROL_SYNC_STATE;
+ cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrls[num_ctrls] = cp;
+ }
+ ber_free_buf( ber );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "slap_build_sync_ctrl: ber_flatten2 failed (%d)\n",
+ ret, 0, 0 );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/* Build a LDAPsync final state control */
+static int
+syncprov_done_ctrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl **ctrls,
+ int num_ctrls,
+ int send_cookie,
+ struct berval *cookie,
+ int refreshDeletes )
+{
+ int ret;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ LDAPControl *cp;
+ struct berval bv;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ ber_printf( ber, "{" );
+ if ( send_cookie && cookie ) {
+ ber_printf( ber, "O", cookie );
+ }
+ if ( refreshDeletes == LDAP_SYNC_REFRESH_DELETES ) {
+ ber_printf( ber, "b", refreshDeletes );
+ }
+ ber_printf( ber, "N}" );
+
+ ret = ber_flatten2( ber, &bv, 0 );
+ if ( ret == 0 ) {
+ cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx );
+ cp->ldctl_oid = LDAP_CONTROL_SYNC_DONE;
+ cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL);
+ cp->ldctl_value.bv_val = (char *)&cp[1];
+ cp->ldctl_value.bv_len = bv.bv_len;
+ AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len );
+ ctrls[num_ctrls] = cp;
+ }
+
+ ber_free_buf( ber );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "syncprov_done_ctrl: ber_flatten2 failed (%d)\n",
+ ret, 0, 0 );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_sendinfo(
+ Operation *op,
+ SlapReply *rs,
+ int type,
+ struct berval *cookie,
+ int refreshDone,
+ BerVarray syncUUIDs,
+ int refreshDeletes )
+{
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval rspdata;
+
+ int ret;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( type ) {
+ switch ( type ) {
+ case LDAP_TAG_SYNC_NEW_COOKIE:
+ ber_printf( ber, "tO", type, cookie );
+ break;
+ case LDAP_TAG_SYNC_REFRESH_DELETE:
+ case LDAP_TAG_SYNC_REFRESH_PRESENT:
+ 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:
+ 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,
+ "syncprov_sendinfo: invalid syncinfo type (%d)\n",
+ type, 0, 0 );
+ return LDAP_OTHER;
+ }
+ }
+
+ ret = ber_flatten2( ber, &rspdata, 0 );
+
+ if ( ret < 0 ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "syncprov_sendinfo: ber_flatten2 failed (%d)\n",
+ ret, 0, 0 );
+ send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
+ return LDAP_OTHER;
+ }
+
+ rs->sr_rspoid = LDAP_SYNC_INFO;
+ rs->sr_rspdata = &rspdata;
+ send_ldap_intermediate( op, rs );
+ rs->sr_rspdata = NULL;
+ ber_free_buf( ber );
+
+ return LDAP_SUCCESS;
+}
+
+/* Find a modtarget in an AVL tree */
+static int
+sp_avl_cmp( const void *c1, const void *c2 )
+{
+ const modtarget *m1, *m2;
+ int rc;
+
+ m1 = c1; m2 = c2;
+ rc = m1->mt_dn.bv_len - m2->mt_dn.bv_len;
+
+ if ( rc ) return rc;
+ return ber_bvcmp( &m1->mt_dn, &m2->mt_dn );
+}
+
+/* syncprov_findbase:
+ * finds the true DN of the base of a search (with alias dereferencing) and
+ * checks to make sure the base entry doesn't get replaced with a different
+ * entry (e.g., swapping trees via ModDN, or retargeting an alias). If a
+ * change is detected, any persistent search on this base must be terminated /
+ * reloaded.
+ * On the first call, we just save the DN and entryID. On subsequent calls
+ * we compare the DN and entryID with the saved values.
+ */
+static int
+findbase_cb( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+
+ if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) {
+ fbase_cookie *fc = sc->sc_private;
+
+ /* If no entryID, we're looking for the first time.
+ * Just store whatever we got.
+ */
+ if ( fc->fss->s_eid == NOID ) {
+ fc->fbase = 2;
+ fc->fss->s_eid = rs->sr_entry->e_id;
+ ber_dupbv( &fc->fss->s_base, &rs->sr_entry->e_nname );
+
+ } else if ( rs->sr_entry->e_id == fc->fss->s_eid &&
+ dn_match( &rs->sr_entry->e_nname, &fc->fss->s_base )) {
+
+ /* OK, the DN is the same and the entryID is the same. */
+ fc->fbase = 1;
+ }
+ }
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "findbase failed! %d\n", rs->sr_err,0,0 );
+ }
+ return LDAP_SUCCESS;
+}
+
+static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL };
+static struct berval generic_filterstr = BER_BVC("(objectclass=*)");
+
+static int
+syncprov_findbase( Operation *op, fbase_cookie *fc )
+{
+ /* Use basic parameters from syncrepl search, but use
+ * current op's threadctx / tmpmemctx
+ */
+ ldap_pvt_thread_mutex_lock( &fc->fss->s_mutex );
+ if ( fc->fss->s_flags & PS_FIND_BASE ) {
+ slap_callback cb = {0};
+ Operation fop;
+ SlapReply frs = { REP_RESULT };
+ int rc;
+
+ fc->fss->s_flags ^= PS_FIND_BASE;
+ ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex );
+
+ fop = *fc->fss->s_op;
+
+ fop.o_bd = fop.o_bd->bd_self;
+ fop.o_hdr = op->o_hdr;
+ fop.o_time = op->o_time;
+ fop.o_tincr = op->o_tincr;
+ fop.o_extra = op->o_extra;
+
+ cb.sc_response = findbase_cb;
+ cb.sc_private = fc;
+
+ fop.o_sync_mode = 0; /* turn off sync mode */
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+ fop.o_callback = &cb;
+ fop.o_tag = LDAP_REQ_SEARCH;
+ fop.ors_scope = LDAP_SCOPE_BASE;
+ fop.ors_limit = NULL;
+ fop.ors_slimit = 1;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_attrs = slap_anlist_no_attrs;
+ fop.ors_attrsonly = 1;
+ fop.ors_filter = &generic_filter;
+ fop.ors_filterstr = generic_filterstr;
+
+ 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];
+ }
+
+ fop = *op;
+ fop.o_sync_mode &= SLAP_CONTROL_MASK; /* turn off sync_mode */
+ /* We want pure entries, not referrals */
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ cf.f_ava = &eq;
+ cf.f_av_desc = slap_schema.si_ad_entryCSN;
+ BER_BVZERO( &cf.f_av_value );
+ cf.f_next = NULL;
+
+ fop.o_callback = &cb;
+ fop.ors_limit = NULL;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_filter = &cf;
+ fop.ors_filterstr.bv_val = buf;
+
+again:
+ switch( mode ) {
+ case FIND_MAXCSN:
+ cf.f_choice = LDAP_FILTER_GE;
+ /* If there are multiple CSNs, use the one with our serverID */
+ for ( i=0; i<si->si_numcsns; i++) {
+ if ( slap_serverID == si->si_sids[i] ) {
+ maxid = i;
+ break;
+ }
+ }
+ if ( i == si->si_numcsns ) {
+ /* No match: this is multimaster, and none of the content in the DB
+ * originated locally. Treat like no CSN.
+ */
+ return LDAP_NO_SUCH_OBJECT;
+ }
+ cf.f_av_value = si->si_ctxcsn[maxid];
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN>=%s)", cf.f_av_value.bv_val );
+ if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
+ return LDAP_OTHER;
+ }
+ fop.ors_attrsonly = 0;
+ fop.ors_attrs = csn_anlist;
+ fop.ors_slimit = SLAP_NO_LIMIT;
+ cb.sc_private = &maxcsn;
+ cb.sc_response = findmax_cb;
+ strcpy( cbuf, cf.f_av_value.bv_val );
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = cf.f_av_value.bv_len;
+ break;
+ case FIND_CSN:
+ if ( BER_BVISEMPTY( &cf.f_av_value )) {
+ cf.f_av_value = *csn;
+ }
+ fop.o_dn = op->o_bd->be_rootdn;
+ fop.o_ndn = op->o_bd->be_rootndn;
+ fop.o_req_dn = op->o_bd->be_suffix[0];
+ fop.o_req_ndn = op->o_bd->be_nsuffix[0];
+ /* Look for exact match the first time */
+ if ( findcsn_retry ) {
+ cf.f_choice = LDAP_FILTER_EQUALITY;
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN=%s)", cf.f_av_value.bv_val );
+ /* On retry, look for <= */
+ } else {
+ cf.f_choice = LDAP_FILTER_LE;
+ fop.ors_limit = &fc_limits;
+ memset( &fc_limits, 0, sizeof( fc_limits ));
+ fc_limits.lms_s_unchecked = 1;
+ fop.ors_filterstr.bv_len = snprintf( buf, sizeof( buf ),
+ "(entryCSN<=%s)", cf.f_av_value.bv_val );
+ }
+ if ( fop.ors_filterstr.bv_len >= sizeof( buf ) ) {
+ return LDAP_OTHER;
+ }
+ fop.ors_attrsonly = 1;
+ fop.ors_attrs = slap_anlist_no_attrs;
+ fop.ors_slimit = 1;
+ cb.sc_private = NULL;
+ cb.sc_response = findcsn_cb;
+ break;
+ case FIND_PRESENT:
+ fop.ors_filter = op->ors_filter;
+ fop.ors_filterstr = op->ors_filterstr;
+ fop.ors_attrsonly = 0;
+ fop.ors_attrs = uuid_anlist;
+ fop.ors_slimit = SLAP_NO_LIMIT;
+ cb.sc_private = &pcookie;
+ cb.sc_response = findpres_cb;
+ pcookie.num = 0;
+
+ /* preallocate storage for a full set */
+ pcookie.uuids = op->o_tmpalloc( (SLAP_SYNCUUID_SET_SIZE+1) *
+ sizeof(struct berval) + SLAP_SYNCUUID_SET_SIZE * UUID_LEN,
+ op->o_tmpmemctx );
+ pcookie.last = (char *)(pcookie.uuids + SLAP_SYNCUUID_SET_SIZE+1);
+ pcookie.uuids[0].bv_val = pcookie.last;
+ pcookie.uuids[0].bv_len = UUID_LEN;
+ for (i=1; i<SLAP_SYNCUUID_SET_SIZE; i++) {
+ pcookie.uuids[i].bv_val = pcookie.uuids[i-1].bv_val + UUID_LEN;
+ pcookie.uuids[i].bv_len = UUID_LEN;
+ }
+ break;
+ }
+
+ fop.o_bd->bd_info = (BackendInfo *)on->on_info;
+ fop.o_bd->be_search( &fop, &frs );
+ fop.o_bd->bd_info = (BackendInfo *)on;
+
+ switch( mode ) {
+ case FIND_MAXCSN:
+ if ( ber_bvcmp( &si->si_ctxcsn[maxid], &maxcsn )) {
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+ assert( !syn->ssyn_validate( syn, &maxcsn ));
+#endif
+ ber_bvreplace( &si->si_ctxcsn[maxid], &maxcsn );
+ si->si_numops++; /* ensure a checkpoint */
+ }
+ break;
+ case FIND_CSN:
+ /* If matching CSN was not found, invalidate the context. */
+ if ( !cb.sc_private ) {
+ /* If we didn't find an exact match, then try for <= */
+ if ( findcsn_retry ) {
+ findcsn_retry = 0;
+ rs_reinit( &frs, REP_RESULT );
+ goto again;
+ }
+ rc = LDAP_NO_SUCH_OBJECT;
+ }
+ break;
+ case FIND_PRESENT:
+ op->o_tmpfree( pcookie.uuids, op->o_tmpmemctx );
+ break;
+ }
+
+ return rc;
+}
+
+static void free_resinfo( syncres *sr )
+{
+ syncres **st;
+ int freeit = 0;
+ ldap_pvt_thread_mutex_lock( &sr->s_info->ri_mutex );
+ for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) {
+ if (*st == sr) {
+ *st = sr->s_rilist;
+ break;
+ }
+ }
+ if ( !sr->s_info->ri_list )
+ freeit = 1;
+ ldap_pvt_thread_mutex_unlock( &sr->s_info->ri_mutex );
+ if ( freeit ) {
+ ldap_pvt_thread_mutex_destroy( &sr->s_info->ri_mutex );
+ if ( sr->s_info->ri_e )
+ entry_free( sr->s_info->ri_e );
+ if ( !BER_BVISNULL( &sr->s_info->ri_cookie ))
+ ch_free( sr->s_info->ri_cookie.bv_val );
+ ch_free( sr->s_info );
+ }
+}
+
+static int
+syncprov_free_syncop( syncops *so, int unlink )
+{
+ syncres *sr, *srnext;
+ GroupAssertion *ga, *gnext;
+
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ /* already being freed, or still in use */
+ if ( !so->s_inuse || --so->s_inuse > 0 ) {
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ if ( unlink ) {
+ syncops **sop;
+ ldap_pvt_thread_mutex_lock( &so->s_si->si_ops_mutex );
+ for ( sop = &so->s_si->si_ops; *sop; sop = &(*sop)->s_next ) {
+ if ( *sop == so ) {
+ *sop = so->s_next;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_si->si_ops_mutex );
+ }
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ filter_free( so->s_op->ors_filter );
+ for ( ga = so->s_op->o_groups; ga; ga=gnext ) {
+ gnext = ga->ga_next;
+ ch_free( ga );
+ }
+ ch_free( so->s_op );
+ }
+ ch_free( so->s_base.bv_val );
+ for ( sr=so->s_res; sr; sr=srnext ) {
+ srnext = sr->s_next;
+ free_resinfo( sr );
+ ch_free( sr );
+ }
+ ldap_pvt_thread_mutex_destroy( &so->s_mutex );
+ ch_free( so );
+ return 1;
+}
+
+/* Send a persistent search response */
+static int
+syncprov_sendresp( Operation *op, resinfo *ri, syncops *so, int mode )
+{
+ SlapReply rs = { REP_SEARCH };
+ struct berval cookie, csns[2];
+ Entry e_uuid = {0};
+ Attribute a_uuid = {0};
+
+ if ( so->s_op->o_abandon )
+ return SLAPD_ABANDON;
+
+ rs.sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, op->o_tmpmemctx );
+ rs.sr_ctrls[1] = NULL;
+ rs.sr_flags = REP_CTRLS_MUSTBEFREED;
+ csns[0] = ri->ri_csn;
+ BER_BVZERO( &csns[1] );
+ slap_compose_sync_cookie( op, &cookie, csns, so->s_rid, slap_serverID ? slap_serverID : -1 );
+
+#ifdef LDAP_DEBUG
+ if ( so->s_sid > 0 ) {
+ Debug( LDAP_DEBUG_SYNC, "syncprov_sendresp: to=%03x, cookie=%s\n",
+ so->s_sid, cookie.bv_val, 0 );
+ } else {
+ Debug( LDAP_DEBUG_SYNC, "syncprov_sendresp: cookie=%s\n",
+ cookie.bv_val, 0, 0 );
+ }
+#endif
+
+ e_uuid.e_attrs = &a_uuid;
+ a_uuid.a_desc = slap_schema.si_ad_entryUUID;
+ a_uuid.a_nvals = &ri->ri_uuid;
+ rs.sr_err = syncprov_state_ctrl( op, &rs, &e_uuid,
+ mode, rs.sr_ctrls, 0, 1, &cookie );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+
+ rs.sr_entry = &e_uuid;
+ if ( mode == LDAP_SYNC_ADD || mode == LDAP_SYNC_MODIFY ) {
+ e_uuid = *ri->ri_e;
+ e_uuid.e_private = NULL;
+ }
+
+ switch( mode ) {
+ case LDAP_SYNC_ADD:
+ if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
+ rs.sr_ref = get_entry_referrals( op, rs.sr_entry );
+ rs.sr_err = send_search_reference( op, &rs );
+ ber_bvarray_free( rs.sr_ref );
+ break;
+ }
+ /* fallthru */
+ case LDAP_SYNC_MODIFY:
+ rs.sr_attrs = op->ors_attrs;
+ rs.sr_err = send_search_entry( op, &rs );
+ break;
+ case LDAP_SYNC_DELETE:
+ e_uuid.e_attrs = NULL;
+ e_uuid.e_name = ri->ri_dn;
+ e_uuid.e_nname = ri->ri_ndn;
+ if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) {
+ struct berval bv = BER_BVNULL;
+ rs.sr_ref = &bv;
+ rs.sr_err = send_search_reference( op, &rs );
+ } else {
+ rs.sr_err = send_search_entry( op, &rs );
+ }
+ break;
+ default:
+ assert(0);
+ }
+ return rs.sr_err;
+}
+
+static void
+syncprov_qstart( syncops *so );
+
+/* Play back queued responses */
+static int
+syncprov_qplay( Operation *op, syncops *so )
+{
+ syncres *sr;
+ int rc = 0;
+
+ do {
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ sr = so->s_res;
+ /* Exit loop with mutex held */
+ if ( !sr )
+ break;
+ so->s_res = sr->s_next;
+ if ( !so->s_res )
+ so->s_restail = NULL;
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+
+ if ( !so->s_op->o_abandon ) {
+
+ if ( sr->s_mode == LDAP_SYNC_NEW_COOKIE ) {
+ SlapReply rs = { REP_INTERMEDIATE };
+
+ rc = syncprov_sendinfo( op, &rs, LDAP_TAG_SYNC_NEW_COOKIE,
+ &sr->s_info->ri_cookie, 0, NULL, 0 );
+ } else {
+ rc = syncprov_sendresp( op, sr->s_info, so, sr->s_mode );
+ }
+ }
+
+ free_resinfo( sr );
+ ch_free( sr );
+
+ if ( so->s_op->o_abandon )
+ continue;
+
+ /* Exit loop with mutex held */
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ break;
+
+ } while (1);
+
+ /* We now only send one change at a time, to prevent one
+ * psearch from hogging all the CPU. Resubmit this task if
+ * there are more responses queued and no errors occurred.
+ */
+
+ if ( rc == 0 && so->s_res ) {
+ syncprov_qstart( so );
+ } else {
+ so->s_flags ^= PS_TASK_QUEUED;
+ }
+
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return rc;
+}
+
+/* task for playing back queued responses */
+static void *
+syncprov_qtask( void *ctx, void *arg )
+{
+ syncops *so = arg;
+ OperationBuffer opbuf;
+ Operation *op;
+ BackendDB be;
+ int rc;
+
+ op = &opbuf.ob_op;
+ *op = *so->s_op;
+ op->o_hdr = &opbuf.ob_hdr;
+ op->o_controls = opbuf.ob_controls;
+ memset( op->o_controls, 0, sizeof(opbuf.ob_controls) );
+ op->o_sync = SLAP_CONTROL_IGNORED;
+
+ *op->o_hdr = *so->s_op->o_hdr;
+
+ op->o_tmpmemctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 1);
+ op->o_tmpmfuncs = &slap_sl_mfuncs;
+ op->o_threadctx = ctx;
+
+ /* syncprov_qplay expects a fake db */
+ be = *so->s_op->o_bd;
+ be.be_flags |= SLAP_DBFLAG_OVERLAY;
+ op->o_bd = &be;
+ LDAP_SLIST_FIRST(&op->o_extra) = NULL;
+ op->o_callback = NULL;
+
+ rc = syncprov_qplay( op, so );
+
+ /* decrement use count... */
+ syncprov_free_syncop( so, 1 );
+
+ return NULL;
+}
+
+/* Start the task to play back queued psearch responses */
+static void
+syncprov_qstart( syncops *so )
+{
+ so->s_flags |= PS_TASK_QUEUED;
+ so->s_inuse++;
+ ldap_pvt_thread_pool_submit( &connection_pool,
+ syncprov_qtask, so );
+}
+
+/* Queue a persistent search response */
+static int
+syncprov_qresp( opcookie *opc, syncops *so, int mode )
+{
+ syncres *sr;
+ resinfo *ri;
+ int srsize;
+ struct berval csn = opc->sctxcsn;
+
+ sr = ch_malloc( sizeof( syncres ));
+ sr->s_next = NULL;
+ sr->s_mode = mode;
+ if ( !opc->ssres.s_info ) {
+
+ srsize = sizeof( resinfo );
+ if ( csn.bv_len )
+ srsize += csn.bv_len + 1;
+
+ if ( opc->se ) {
+ Attribute *a;
+ ri = ch_malloc( srsize );
+ ri->ri_dn = opc->se->e_name;
+ ri->ri_ndn = opc->se->e_nname;
+ a = attr_find( opc->se->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a )
+ ri->ri_uuid = a->a_nvals[0];
+ else
+ ri->ri_uuid.bv_len = 0;
+ if ( csn.bv_len ) {
+ ri->ri_csn.bv_val = (char *)(ri + 1);
+ ri->ri_csn.bv_len = csn.bv_len;
+ memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len );
+ ri->ri_csn.bv_val[csn.bv_len] = '\0';
+ } else {
+ ri->ri_csn.bv_val = NULL;
+ }
+ } else {
+ srsize += opc->suuid.bv_len +
+ opc->sdn.bv_len + 1 + opc->sndn.bv_len + 1;
+ ri = ch_malloc( srsize );
+ ri->ri_dn.bv_val = (char *)(ri + 1);
+ ri->ri_dn.bv_len = opc->sdn.bv_len;
+ ri->ri_ndn.bv_val = lutil_strcopy( ri->ri_dn.bv_val,
+ opc->sdn.bv_val ) + 1;
+ ri->ri_ndn.bv_len = opc->sndn.bv_len;
+ ri->ri_uuid.bv_val = lutil_strcopy( ri->ri_ndn.bv_val,
+ opc->sndn.bv_val ) + 1;
+ ri->ri_uuid.bv_len = opc->suuid.bv_len;
+ AC_MEMCPY( ri->ri_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len );
+ if ( csn.bv_len ) {
+ ri->ri_csn.bv_val = ri->ri_uuid.bv_val + ri->ri_uuid.bv_len;
+ memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len );
+ ri->ri_csn.bv_val[csn.bv_len] = '\0';
+ } else {
+ ri->ri_csn.bv_val = NULL;
+ }
+ }
+ ri->ri_list = &opc->ssres;
+ ri->ri_e = opc->se;
+ ri->ri_csn.bv_len = csn.bv_len;
+ ri->ri_isref = opc->sreference;
+ BER_BVZERO( &ri->ri_cookie );
+ ldap_pvt_thread_mutex_init( &ri->ri_mutex );
+ opc->se = NULL;
+ opc->ssres.s_info = ri;
+ }
+ ri = opc->ssres.s_info;
+ sr->s_info = ri;
+ ldap_pvt_thread_mutex_lock( &ri->ri_mutex );
+ sr->s_rilist = ri->ri_list;
+ ri->ri_list = sr;
+ if ( mode == LDAP_SYNC_NEW_COOKIE && BER_BVISNULL( &ri->ri_cookie )) {
+ syncprov_info_t *si = opc->son->on_bi.bi_private;
+
+ slap_compose_sync_cookie( NULL, &ri->ri_cookie, si->si_ctxcsn,
+ so->s_rid, slap_serverID ? slap_serverID : -1);
+ }
+ ldap_pvt_thread_mutex_unlock( &ri->ri_mutex );
+
+ ldap_pvt_thread_mutex_lock( &so->s_mutex );
+ if ( !so->s_res ) {
+ so->s_res = sr;
+ } else {
+ so->s_restail->s_next = sr;
+ }
+ so->s_restail = sr;
+
+ /* If the base of the psearch was modified, check it next time round */
+ if ( so->s_flags & PS_WROTE_BASE ) {
+ so->s_flags ^= PS_WROTE_BASE;
+ so->s_flags |= PS_FIND_BASE;
+ }
+ if (( so->s_flags & (PS_IS_DETACHED|PS_TASK_QUEUED)) == PS_IS_DETACHED ) {
+ syncprov_qstart( so );
+ }
+ ldap_pvt_thread_mutex_unlock( &so->s_mutex );
+ return LDAP_SUCCESS;
+}
+
+static int
+syncprov_drop_psearch( syncops *so, int lock )
+{
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ if ( lock )
+ ldap_pvt_thread_mutex_lock( &so->s_op->o_conn->c_mutex );
+ so->s_op->o_conn->c_n_ops_executing--;
+ so->s_op->o_conn->c_n_ops_completed++;
+ LDAP_STAILQ_REMOVE( &so->s_op->o_conn->c_ops, so->s_op, Operation,
+ o_next );
+ if ( lock )
+ ldap_pvt_thread_mutex_unlock( &so->s_op->o_conn->c_mutex );
+ }
+ return syncprov_free_syncop( so, 0 );
+}
+
+static int
+syncprov_ab_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *sc = op->o_callback;
+ op->o_callback = sc->sc_next;
+ syncprov_drop_psearch( sc->sc_private, 0 );
+ op->o_tmpfree( sc, op->o_tmpmemctx );
+ return 0;
+}
+
+static int
+syncprov_op_abandon( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncops *so, **sop;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( sop=&si->si_ops; (so = *sop); sop = &(*sop)->s_next ) {
+ if ( so->s_op->o_connid == op->o_connid &&
+ so->s_op->o_msgid == op->orn_msgid ) {
+ so->s_op->o_abandon = 1;
+ *sop = so->s_next;
+ break;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( so ) {
+ /* Is this really a Cancel exop? */
+ if ( op->o_tag != LDAP_REQ_ABANDON ) {
+ so->s_op->o_cancel = SLAP_CANCEL_ACK;
+ rs->sr_err = LDAP_CANCELLED;
+ send_ldap_result( so->s_op, rs );
+ if ( so->s_flags & PS_IS_DETACHED ) {
+ slap_callback *cb;
+ cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
+ cb->sc_cleanup = syncprov_ab_cleanup;
+ cb->sc_next = op->o_callback;
+ cb->sc_private = so;
+ op->o_callback = cb;
+ return SLAP_CB_CONTINUE;
+ }
+ }
+ syncprov_drop_psearch( so, 0 );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* Find which persistent searches are affected by this operation */
+static void
+syncprov_matchops( Operation *op, opcookie *opc, int saveit )
+{
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+
+ fbase_cookie fc;
+ syncops **pss;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc, gonext;
+ struct berval newdn;
+ int freefdn = 0;
+ BackendDB *b0 = op->o_bd, db;
+
+ fc.fdn = &op->o_req_ndn;
+ /* compute new DN */
+ if ( op->o_tag == LDAP_REQ_MODRDN && !saveit ) {
+ struct berval pdn;
+ if ( op->orr_nnewSup ) pdn = *op->orr_nnewSup;
+ else dnParent( fc.fdn, &pdn );
+ build_new_dn( &newdn, &pdn, &op->orr_nnewrdn, op->o_tmpmemctx );
+ fc.fdn = &newdn;
+ freefdn = 1;
+ }
+ if ( op->o_tag != LDAP_REQ_ADD ) {
+ if ( !SLAP_ISOVERLAY( op->o_bd )) {
+ db = *op->o_bd;
+ op->o_bd = &db;
+ }
+ rc = overlay_entry_get_ov( op, fc.fdn, NULL, NULL, 0, &e, on );
+ /* If we're sending responses now, make a copy and unlock the DB */
+ if ( e && !saveit ) {
+ if ( !opc->se )
+ opc->se = entry_dup( e );
+ overlay_entry_release_ov( op, e, 0, on );
+ e = opc->se;
+ }
+ if ( rc ) {
+ op->o_bd = b0;
+ return;
+ }
+ } else {
+ e = op->ora_e;
+ if ( !saveit ) {
+ if ( !opc->se )
+ opc->se = entry_dup( e );
+ e = opc->se;
+ }
+ }
+
+ if ( saveit || op->o_tag == LDAP_REQ_ADD ) {
+ ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx );
+ opc->sreference = is_entry_referral( e );
+ a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID );
+ if ( a )
+ ber_dupbv_x( &opc->suuid, &a->a_nvals[0], op->o_tmpmemctx );
+ } else if ( op->o_tag == LDAP_REQ_MODRDN && !saveit ) {
+ op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx );
+ op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx );
+ ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx );
+ }
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for (pss = &si->si_ops; *pss; pss = gonext ? &(*pss)->s_next : pss)
+ {
+ Operation op2;
+ Opheader oh;
+ syncmatches *sm;
+ int found = 0;
+ syncops *snext, *ss = *pss;
+
+ gonext = 1;
+ if ( ss->s_op->o_abandon )
+ continue;
+
+ /* Don't send ops back to the originator */
+ if ( opc->osid > 0 && opc->osid == ss->s_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "syncprov_matchops: skipping original sid %03x\n",
+ opc->osid, 0, 0 );
+ continue;
+ }
+
+ /* Don't send ops back to the messenger */
+ if ( opc->rsid > 0 && opc->rsid == ss->s_sid ) {
+ Debug( LDAP_DEBUG_SYNC, "syncprov_matchops: skipping relayed sid %03x\n",
+ opc->rsid, 0, 0 );
+ continue;
+ }
+
+ /* validate base */
+ fc.fss = ss;
+ fc.fbase = 0;
+ fc.fscope = 0;
+
+ /* If the base of the search is missing, signal a refresh */
+ rc = syncprov_findbase( op, &fc );
+ if ( rc != LDAP_SUCCESS ) {
+ SlapReply rs = {REP_RESULT};
+ send_ldap_error( ss->s_op, &rs, LDAP_SYNC_REFRESH_REQUIRED,
+ "search base has changed" );
+ snext = ss->s_next;
+ if ( syncprov_drop_psearch( ss, 1 ) )
+ *pss = snext;
+ gonext = 0;
+ continue;
+ }
+
+ /* If we're sending results now, look for this op in old matches */
+ if ( !saveit ) {
+ syncmatches *old;
+
+ /* Did we modify the search base? */
+ if ( dn_match( &op->o_req_ndn, &ss->s_base )) {
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ ss->s_flags |= PS_WROTE_BASE;
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ }
+
+ for ( sm=opc->smatches, old=(syncmatches *)&opc->smatches; sm;
+ old=sm, sm=sm->sm_next ) {
+ if ( sm->sm_op == ss ) {
+ found = 1;
+ old->sm_next = sm->sm_next;
+ op->o_tmpfree( sm, op->o_tmpmemctx );
+ break;
+ }
+ }
+ }
+
+ if ( fc.fscope ) {
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ op2 = *ss->s_op;
+ oh = *op->o_hdr;
+ oh.oh_conn = ss->s_op->o_conn;
+ oh.oh_connid = ss->s_op->o_connid;
+ op2.o_bd = op->o_bd->bd_self;
+ op2.o_hdr = &oh;
+ op2.o_extra = op->o_extra;
+ op2.o_callback = NULL;
+ if (ss->s_flags & PS_FIX_FILTER) {
+ /* Skip the AND/GE clause that we stuck on in front. We
+ would lose deletes/mods that happen during the refresh
+ phase otherwise (ITS#6555) */
+ op2.ors_filter = ss->s_op->ors_filter->f_and->f_next;
+ }
+ rc = test_filter( &op2, e, op2.ors_filter );
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "syncprov_matchops: sid %03x fscope %d rc %d\n",
+ ss->s_sid, fc.fscope, rc );
+
+ /* check if current o_req_dn is in scope and matches filter */
+ if ( fc.fscope && rc == LDAP_COMPARE_TRUE ) {
+ if ( saveit ) {
+ sm = op->o_tmpalloc( sizeof(syncmatches), op->o_tmpmemctx );
+ sm->sm_next = opc->smatches;
+ sm->sm_op = ss;
+ ldap_pvt_thread_mutex_lock( &ss->s_mutex );
+ ++ss->s_inuse;
+ ldap_pvt_thread_mutex_unlock( &ss->s_mutex );
+ opc->smatches = sm;
+ } else {
+ /* if found send UPDATE else send ADD */
+ syncprov_qresp( opc, ss,
+ found ? LDAP_SYNC_MODIFY : LDAP_SYNC_ADD );
+ }
+ } else if ( !saveit && found ) {
+ /* send DELETE */
+ syncprov_qresp( opc, ss, LDAP_SYNC_DELETE );
+ } else if ( !saveit ) {
+ syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE );
+ }
+ if ( !saveit && found ) {
+ /* Decrement s_inuse, was incremented when called
+ * with saveit == TRUE
+ */
+ snext = ss->s_next;
+ if ( syncprov_free_syncop( ss, 0 ) ) {
+ *pss = snext;
+ gonext = 0;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ if ( op->o_tag != LDAP_REQ_ADD && e ) {
+ if ( !SLAP_ISOVERLAY( op->o_bd )) {
+ op->o_bd = &db;
+ }
+ if ( saveit )
+ overlay_entry_release_ov( op, e, 0, on );
+ op->o_bd = b0;
+ }
+ if ( !saveit ) {
+ if ( opc->ssres.s_info )
+ free_resinfo( &opc->ssres );
+ else if ( opc->se )
+ entry_free( opc->se );
+ }
+ if ( freefdn ) {
+ op->o_tmpfree( fc.fdn->bv_val, op->o_tmpmemctx );
+ }
+ op->o_bd = b0;
+}
+
+static int
+syncprov_op_cleanup( Operation *op, SlapReply *rs )
+{
+ slap_callback *cb = op->o_callback;
+ opcookie *opc = cb->sc_private;
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncmatches *sm, *snext;
+ modtarget *mt;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ if ( si->si_active )
+ si->si_active--;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ for (sm = opc->smatches; sm; sm=snext) {
+ snext = sm->sm_next;
+ syncprov_free_syncop( sm->sm_op, 1 );
+ op->o_tmpfree( sm, op->o_tmpmemctx );
+ }
+
+ /* Remove op from lock table */
+ mt = opc->smt;
+ if ( mt ) {
+ modinst *mi = (modinst *)(opc+1), **m2;
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+ for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) {
+ if ( *m2 == mi ) {
+ *m2 = mi->mi_next;
+ if ( mt->mt_tail == mi )
+ mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2;
+ break;
+ }
+ }
+ /* If there are more, promote the next one */
+ if ( mt->mt_mods ) {
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ } else {
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ ldap_pvt_thread_mutex_lock( &si->si_mods_mutex );
+ avl_delete( &si->si_mods, mt, sp_avl_cmp );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_destroy( &mt->mt_mutex );
+ ch_free( mt->mt_dn.bv_val );
+ ch_free( mt );
+ }
+ }
+ if ( !BER_BVISNULL( &opc->suuid ))
+ op->o_tmpfree( opc->suuid.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &opc->sndn ))
+ op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx );
+ if ( !BER_BVISNULL( &opc->sdn ))
+ op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx );
+ op->o_callback = cb->sc_next;
+ op->o_tmpfree(cb, op->o_tmpmemctx);
+
+ return 0;
+}
+
+static void
+syncprov_checkpoint( Operation *op, slap_overinst *on )
+{
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ Modifications mod;
+ Operation opm;
+ SlapReply rsm = {REP_RESULT};
+ slap_callback cb = {0};
+ BackendDB be;
+ BackendInfo *bi;
+
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+
+ int i;
+ for ( i=0; i<si->si_numcsns; i++ ) {
+ assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i ));
+ }
+#endif
+ 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;
+
+ 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_mutex_lock( &sl->sl_mutex );
+ /* can only do this if no one else is reading the log at the moment */
+ if (!sl->sl_playing) {
+ while ( se = sl->sl_head ) {
+ sl->sl_head = se->se_next;
+ ch_free( se );
+ }
+ sl->sl_tail = NULL;
+ sl->sl_num = 0;
+ }
+ ldap_pvt_thread_mutex_unlock( &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_next = NULL;
+ 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_mutex_lock( &sl->sl_mutex );
+ if ( sl->sl_head ) {
+ /* Keep the list in csn order. */
+ if ( ber_bvcmp( &sl->sl_tail->se_csn, &se->se_csn ) <= 0 ) {
+ sl->sl_tail->se_next = se;
+ sl->sl_tail = se;
+ } else {
+ slog_entry **sep;
+
+ for ( sep = &sl->sl_head; *sep; sep = &(*sep)->se_next ) {
+ if ( ber_bvcmp( &se->se_csn, &(*sep)->se_csn ) < 0 ) {
+ se->se_next = *sep;
+ *sep = se;
+ break;
+ }
+ }
+ }
+ } else {
+ sl->sl_head = se;
+ sl->sl_tail = se;
+ 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] );
+ }
+ }
+ sl->sl_num++;
+ if (!sl->sl_playing) {
+ while ( sl->sl_num > sl->sl_size ) {
+ int i;
+ se = sl->sl_head;
+ sl->sl_head = se->se_next;
+ 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 ) {
+ slap_insert_csn_sids( (struct sync_cookie *)sl,
+ i, se->se_sid, &se->se_csn );
+ } else {
+ ber_bvreplace( &sl->sl_mincsn[i], &se->se_csn );
+ }
+ ch_free( se );
+ sl->sl_num--;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &sl->sl_mutex );
+ }
+}
+
+/* Just set a flag if we found the matching entry */
+static int
+playlog_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ op->o_callback->sc_private = (void *)1;
+ }
+ return rs->sr_err;
+}
+
+/* enter with sl->sl_mutex locked, release before returning */
+static void
+syncprov_playlog( Operation *op, SlapReply *rs, sessionlog *sl,
+ sync_control *srs, BerVarray ctxcsn, int numcsns, int *sids )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ slog_entry *se;
+ int i, j, ndel, num, nmods, mmods;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ BerVarray uuids;
+ struct berval delcsn[2];
+
+ if ( !sl->sl_num ) {
+ ldap_pvt_thread_mutex_unlock( &sl->sl_mutex );
+ return;
+ }
+
+ num = sl->sl_num;
+ i = 0;
+ nmods = 0;
+ sl->sl_playing++;
+ ldap_pvt_thread_mutex_unlock( &sl->sl_mutex );
+
+ uuids = op->o_tmpalloc( (num+1) * sizeof( struct berval ) +
+ num * UUID_LEN, op->o_tmpmemctx );
+ uuids[0].bv_val = (char *)(uuids + num + 1);
+
+ delcsn[0].bv_len = 0;
+ delcsn[0].bv_val = cbuf;
+ BER_BVZERO(&delcsn[1]);
+
+ /* Make a copy of the relevant UUIDs. Put the Deletes up front
+ * and everything else at the end. Do this first so we can
+ * unlock the list mutex.
+ */
+ Debug( LDAP_DEBUG_SYNC, "srs csn %s\n",
+ srs->sr_state.ctxcsn[0].bv_val, 0, 0 );
+ for ( se=sl->sl_head; se; se=se->se_next ) {
+ int k;
+ Debug( LDAP_DEBUG_SYNC, "log csn %s\n", se->se_csn.bv_val, 0, 0 );
+ 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 ) {
+ Debug( LDAP_DEBUG_SYNC, "cmp %d, too old\n", ndel, 0, 0 );
+ 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, "cmp %d, too new\n", ndel, 0, 0 );
+ break;
+ }
+ if ( se->se_tag == LDAP_REQ_DELETE ) {
+ j = i;
+ i++;
+ AC_MEMCPY( cbuf, se->se_csn.bv_val, se->se_csn.bv_len );
+ delcsn[0].bv_len = se->se_csn.bv_len;
+ delcsn[0].bv_val[delcsn[0].bv_len] = '\0';
+ } else {
+ if ( se->se_tag == LDAP_REQ_ADD )
+ 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;
+ }
+ ldap_pvt_thread_mutex_lock( &sl->sl_mutex );
+ sl->sl_playing--;
+ ldap_pvt_thread_mutex_unlock( &sl->sl_mutex );
+
+ ndel = i;
+
+ /* Zero out unused slots */
+ for ( i=ndel; i < num - nmods; i++ )
+ uuids[i].bv_len = 0;
+
+ /* Mods must be validated to see if they belong in this delete set.
+ */
+
+ mmods = nmods;
+ /* Strip any duplicates */
+ for ( i=0; i<nmods; i++ ) {
+ for ( j=0; j<ndel; j++ ) {
+ if ( bvmatch( &uuids[j], &uuids[num - 1 - i] )) {
+ uuids[num - 1 - i].bv_len = 0;
+ mmods --;
+ break;
+ }
+ }
+ if ( uuids[num - 1 - i].bv_len == 0 ) continue;
+ for ( j=0; j<i; j++ ) {
+ if ( bvmatch( &uuids[num - 1 - j], &uuids[num - 1 - i] )) {
+ uuids[num - 1 - i].bv_len = 0;
+ mmods --;
+ break;
+ }
+ }
+ }
+
+ if ( mmods ) {
+ Operation fop;
+ int rc;
+ Filter mf, af;
+ AttributeAssertion eq = ATTRIBUTEASSERTION_INIT;
+ slap_callback cb = {0};
+
+ fop = *op;
+
+ fop.o_sync_mode = 0;
+ fop.o_callback = &cb;
+ fop.ors_limit = NULL;
+ fop.ors_tlimit = SLAP_NO_LIMIT;
+ fop.ors_attrs = slap_anlist_all_attributes;
+ fop.ors_attrsonly = 0;
+ fop.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ af.f_choice = LDAP_FILTER_AND;
+ af.f_next = NULL;
+ af.f_and = &mf;
+ mf.f_choice = LDAP_FILTER_EQUALITY;
+ mf.f_ava = &eq;
+ mf.f_av_desc = slap_schema.si_ad_entryUUID;
+ mf.f_next = fop.ors_filter;
+
+ fop.ors_filter = &af;
+
+ cb.sc_response = playlog_cb;
+ fop.o_bd->bd_info = (BackendInfo *)on->on_info;
+
+ for ( i=ndel; i<num; i++ ) {
+ if ( uuids[i].bv_len != 0 ) {
+ SlapReply frs = { REP_RESULT };
+
+ mf.f_av_value = uuids[i];
+ cb.sc_private = NULL;
+ fop.ors_slimit = 1;
+ rc = fop.o_bd->be_search( &fop, &frs );
+
+ /* If entry was not found, add to delete list */
+ if ( !cb.sc_private ) {
+ uuids[ndel++] = uuids[i];
+ }
+ }
+ }
+ fop.o_bd->bd_info = (BackendInfo *)on;
+ }
+ if ( ndel ) {
+ struct berval cookie;
+
+ if ( delcsn[0].bv_len ) {
+ slap_compose_sync_cookie( op, &cookie, delcsn, srs->sr_state.rid,
+ slap_serverID ? slap_serverID : -1 );
+
+ Debug( LDAP_DEBUG_SYNC, "syncprov_playlog: cookie=%s\n", cookie.bv_val, 0, 0 );
+ }
+
+ uuids[ndel].bv_val = NULL;
+ syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET,
+ delcsn[0].bv_len ? &cookie : NULL, 0, uuids, 1 );
+ if ( delcsn[0].bv_len ) {
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ }
+ }
+ op->o_tmpfree( uuids, op->o_tmpmemctx );
+}
+
+static int
+syncprov_new_ctxcsn( opcookie *opc, syncprov_info_t *si, int csn_changed, int numvals, BerVarray vals )
+{
+ unsigned i;
+ int j, sid;
+
+ for ( i=0; i<numvals; i++ ) {
+ sid = slap_parse_csn_sid( &vals[i] );
+ for ( j=0; j<si->si_numcsns; j++ ) {
+ if ( sid < si->si_sids[j] )
+ break;
+ if ( sid == si->si_sids[j] ) {
+ if ( ber_bvcmp( &vals[i], &si->si_ctxcsn[j] ) > 0 ) {
+ ber_bvreplace( &si->si_ctxcsn[j], &vals[i] );
+ csn_changed = 1;
+ }
+ break;
+ }
+ }
+
+ if ( j == si->si_numcsns || sid != si->si_sids[j] ) {
+ slap_insert_csn_sids( (struct sync_cookie *)&si->si_ctxcsn,
+ j, sid, &vals[i] );
+ csn_changed = 1;
+ }
+ }
+ if ( csn_changed )
+ si->si_dirty = 0;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+
+ if ( csn_changed ) {
+ syncops *ss;
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( ss = si->si_ops; ss; ss = ss->s_next ) {
+ if ( ss->s_op->o_abandon )
+ continue;
+ /* Send the updated csn to all syncrepl consumers,
+ * including the server from which it originated.
+ * The syncrepl consumer and syncprov provider on
+ * the originating server may be configured to store
+ * their csn values in different entries.
+ */
+ syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE );
+ }
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ }
+ return csn_changed;
+}
+
+static int
+syncprov_op_response( Operation *op, SlapReply *rs )
+{
+ opcookie *opc = op->o_callback->sc_private;
+ slap_overinst *on = opc->son;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ syncmatches *sm;
+
+ if ( rs->sr_err == LDAP_SUCCESS )
+ {
+ struct berval maxcsn;
+ char cbuf[LDAP_PVT_CSNSTR_BUFSIZE];
+ int do_check = 0, have_psearches, foundit, csn_changed = 0;
+
+ ldap_pvt_thread_mutex_lock( &si->si_resp_mutex );
+
+ /* Update our context CSN */
+ cbuf[0] = '\0';
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = sizeof(cbuf);
+ ldap_pvt_thread_rdwr_wlock( &si->si_csn_rwlock );
+
+ slap_get_commit_csn( op, &maxcsn, &foundit );
+ if ( BER_BVISEMPTY( &maxcsn ) && SLAP_GLUE_SUBORDINATE( op->o_bd )) {
+ /* syncrepl queues the CSN values in the db where
+ * it is configured , not where the changes are made.
+ * So look for a value in the glue db if we didn't
+ * find any in this db.
+ */
+ BackendDB *be = op->o_bd;
+ op->o_bd = select_backend( &be->be_nsuffix[0], 1);
+ maxcsn.bv_val = cbuf;
+ maxcsn.bv_len = sizeof(cbuf);
+ slap_get_commit_csn( op, &maxcsn, &foundit );
+ op->o_bd = be;
+ }
+ if ( !BER_BVISEMPTY( &maxcsn ) ) {
+ int i, sid;
+#ifdef CHECK_CSN
+ Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax;
+ assert( !syn->ssyn_validate( syn, &maxcsn ));
+#endif
+ sid = slap_parse_csn_sid( &maxcsn );
+ for ( i=0; i<si->si_numcsns; i++ ) {
+ if ( sid < si->si_sids[i] )
+ break;
+ if ( sid == si->si_sids[i] ) {
+ if ( ber_bvcmp( &maxcsn, &si->si_ctxcsn[i] ) > 0 ) {
+ ber_bvreplace( &si->si_ctxcsn[i], &maxcsn );
+ csn_changed = 1;
+ }
+ break;
+ }
+ }
+ /* It's a new SID for us */
+ if ( i == si->si_numcsns || sid != si->si_sids[i] ) {
+ slap_insert_csn_sids((struct sync_cookie *)&(si->si_ctxcsn),
+ i, sid, &maxcsn );
+ csn_changed = 1;
+ }
+ }
+
+ /* Don't do any processing for consumer contextCSN updates */
+ if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) &&
+ op->o_tag == LDAP_REQ_MODIFY &&
+ op->orm_modlist &&
+ op->orm_modlist->sml_op == LDAP_MOD_REPLACE &&
+ op->orm_modlist->sml_desc == slap_schema.si_ad_contextCSN ) {
+ /* Catch contextCSN updates from syncrepl. We have to look at
+ * all the attribute values, as there may be more than one csn
+ * that changed, and only one can be passed in the csn queue.
+ */
+ csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed,
+ op->orm_modlist->sml_numvals, op->orm_modlist->sml_values );
+ if ( csn_changed )
+ si->si_numops++;
+ goto leave;
+ }
+ if ( op->o_dont_replicate ) {
+ if ( csn_changed )
+ si->si_numops++;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+ goto leave;
+ }
+
+ /* If we're adding the context entry, parse all of its contextCSNs */
+ if ( op->o_tag == LDAP_REQ_ADD &&
+ dn_match( &op->o_req_ndn, &si->si_contextdn )) {
+ Attribute *a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_contextCSN );
+ if ( a ) {
+ csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, a->a_numvals, a->a_vals );
+ if ( csn_changed )
+ si->si_numops++;
+ goto added;
+ }
+ }
+
+ if ( csn_changed )
+ si->si_numops++;
+ if ( si->si_chkops || si->si_chktime ) {
+ /* Never checkpoint adding the context entry,
+ * it will deadlock
+ */
+ if ( op->o_tag != LDAP_REQ_ADD ||
+ !dn_match( &op->o_req_ndn, &si->si_contextdn )) {
+ if ( si->si_chkops && si->si_numops >= si->si_chkops ) {
+ do_check = 1;
+ si->si_numops = 0;
+ }
+ if ( si->si_chktime &&
+ (op->o_time - si->si_chklast >= si->si_chktime )) {
+ if ( si->si_chklast ) {
+ do_check = 1;
+ si->si_chklast = op->o_time;
+ } else {
+ si->si_chklast = 1;
+ }
+ }
+ }
+ }
+ si->si_dirty = !csn_changed;
+ ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock );
+
+added:
+ if ( do_check ) {
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+ syncprov_checkpoint( op, on );
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+ }
+
+ /* only update consumer ctx if this is a newer csn */
+ if ( csn_changed ) {
+ opc->sctxcsn = maxcsn;
+ }
+
+ /* Handle any persistent searches */
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ have_psearches = ( si->si_ops != NULL );
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( have_psearches ) {
+ switch(op->o_tag) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_EXTENDED:
+ syncprov_matchops( op, opc, 0 );
+ break;
+ case LDAP_REQ_DELETE:
+ /* for each match in opc->smatches:
+ * send DELETE msg
+ */
+ for ( sm = opc->smatches; sm; sm=sm->sm_next ) {
+ if ( sm->sm_op->s_op->o_abandon )
+ continue;
+ syncprov_qresp( opc, sm->sm_op, LDAP_SYNC_DELETE );
+ }
+ if ( opc->ssres.s_info )
+ free_resinfo( &opc->ssres );
+ break;
+ }
+ }
+
+ /* Add any log records */
+ if ( si->si_logs ) {
+ syncprov_add_slog( op );
+ }
+leave: ldap_pvt_thread_mutex_unlock( &si->si_resp_mutex );
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+/* We don't use a subentry to store the context CSN any more.
+ * We expose the current context CSN as an operational attribute
+ * of the suffix entry.
+ */
+static int
+syncprov_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( dn_match( &op->o_req_ndn, &si->si_contextdn ) &&
+ op->oq_compare.rs_ava->aa_desc == slap_schema.si_ad_contextCSN )
+ {
+ Entry e = {0};
+ Attribute a = {0};
+
+ e.e_name = si->si_contextdn;
+ e.e_nname = si->si_contextdn;
+ e.e_attrs = &a;
+
+ a.a_desc = slap_schema.si_ad_contextCSN;
+
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+
+ a.a_vals = si->si_ctxcsn;
+ a.a_nvals = a.a_vals;
+ a.a_numvals = si->si_numcsns;
+
+ rs->sr_err = access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc,
+ &op->oq_compare.rs_ava->aa_value, ACL_COMPARE, NULL );
+ if ( ! rs->sr_err ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &e, get_assertion( op ) ) != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+
+ rs->sr_err = LDAP_COMPARE_FALSE;
+
+ if ( attr_valfind( &a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->oq_compare.rs_ava->aa_value, NULL, op->o_tmpmemctx ) == 0 )
+ {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ }
+
+return_results:;
+
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+
+ send_ldap_result( op, rs );
+
+ if( rs->sr_err == LDAP_COMPARE_FALSE || rs->sr_err == LDAP_COMPARE_TRUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+ rc = rs->sr_err;
+ }
+
+ return rc;
+}
+
+static int
+syncprov_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = on->on_bi.bi_private;
+ slap_callback *cb;
+ opcookie *opc;
+ int have_psearches, cbsize;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ have_psearches = ( si->si_ops != NULL );
+ si->si_active++;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+
+ cbsize = sizeof(slap_callback) + sizeof(opcookie) +
+ (have_psearches ? sizeof(modinst) : 0 );
+
+ cb = op->o_tmpcalloc(1, cbsize, op->o_tmpmemctx);
+ opc = (opcookie *)(cb+1);
+ opc->son = on;
+ cb->sc_response = syncprov_op_response;
+ cb->sc_cleanup = syncprov_op_cleanup;
+ cb->sc_private = opc;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ opc->osid = -1;
+ opc->rsid = -1;
+ if ( op->o_csn.bv_val ) {
+ opc->osid = slap_parse_csn_sid( &op->o_csn );
+ }
+ if ( op->o_controls ) {
+ struct sync_cookie *scook =
+ op->o_controls[slap_cids.sc_LDAPsync];
+ if ( scook )
+ opc->rsid = scook->sid;
+ }
+
+ if ( op->o_dont_replicate )
+ return SLAP_CB_CONTINUE;
+
+ /* If there are active persistent searches, lock this operation.
+ * See seqmod.c for the locking logic on its own.
+ */
+ if ( have_psearches ) {
+ modtarget *mt, mtdummy;
+ modinst *mi;
+
+ mi = (modinst *)(opc+1);
+ mi->mi_op = op;
+
+ /* See if we're already modifying this entry... */
+ mtdummy.mt_dn = op->o_req_ndn;
+retry:
+ ldap_pvt_thread_mutex_lock( &si->si_mods_mutex );
+ mt = avl_find( si->si_mods, &mtdummy, sp_avl_cmp );
+ if ( mt ) {
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+ if ( mt->mt_mods == NULL ) {
+ /* Cannot reuse this mt, as another thread is about
+ * to release it in syncprov_op_cleanup. Wait for them
+ * to finish; our own insert is required to succeed.
+ */
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ ldap_pvt_thread_yield();
+ goto retry;
+ }
+ }
+ if ( mt ) {
+ mt->mt_tail->mi_next = mi;
+ mt->mt_tail = mi;
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ /* wait for this op to get to head of list */
+ while ( mt->mt_mods != mi ) {
+ modinst *m2;
+ /* don't wait on other mods from the same thread */
+ for ( m2 = mt->mt_mods; m2; m2 = m2->mi_next ) {
+ if ( m2->mi_op->o_threadctx == op->o_threadctx ) {
+ break;
+ }
+ }
+ if ( m2 )
+ break;
+
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ /* FIXME: if dynamic config can delete overlays or
+ * databases we'll have to check for cleanup here.
+ * Currently it's not an issue because there are
+ * no dynamic config deletes...
+ */
+ if ( slapd_shutdown )
+ return SLAPD_ABANDON;
+
+ if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool ))
+ ldap_pvt_thread_yield();
+ ldap_pvt_thread_mutex_lock( &mt->mt_mutex );
+
+ /* clean up if the caller is giving up */
+ if ( op->o_abandon ) {
+ modinst **m2;
+ slap_callback **sc;
+ for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) {
+ if ( *m2 == mi ) {
+ *m2 = mi->mi_next;
+ if ( mt->mt_tail == mi )
+ mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2;
+ break;
+ }
+ }
+ for (sc = &op->o_callback; ; sc = &(*sc)->sc_next) {
+ if ( *sc == cb ) {
+ *sc = cb->sc_next;
+ break;
+ }
+ }
+ op->o_tmpfree( cb, op->o_tmpmemctx );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ return SLAPD_ABANDON;
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mt->mt_mutex );
+ } else {
+ /* Record that we're modifying this entry now */
+ mt = ch_malloc( sizeof(modtarget) );
+ mt->mt_mods = mi;
+ mt->mt_tail = mi;
+ ber_dupbv( &mt->mt_dn, &mi->mi_op->o_req_ndn );
+ ldap_pvt_thread_mutex_init( &mt->mt_mutex );
+ avl_insert( &si->si_mods, mt, sp_avl_cmp, avl_dup_error );
+ ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex );
+ }
+ opc->smt = mt;
+ }
+
+ if (( have_psearches || si->si_logs ) && op->o_tag != LDAP_REQ_ADD )
+ syncprov_matchops( op, opc, 1 );
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_op_extended( Operation *op, SlapReply *rs )
+{
+ if ( exop_is_write( op ))
+ return syncprov_op_mod( op, rs );
+
+ return SLAP_CB_CONTINUE;
+}
+
+typedef struct searchstate {
+ slap_overinst *ss_on;
+ syncops *ss_so;
+ BerVarray ss_ctxcsn;
+ int *ss_sids;
+ int ss_numcsns;
+#define SS_PRESENT 0x01
+#define SS_CHANGED 0x02
+ int ss_flags;
+} searchstate;
+
+typedef struct SyncOperationBuffer {
+ Operation sob_op;
+ Opheader sob_hdr;
+ OpExtra sob_oe;
+ AttributeName sob_extra; /* not always present */
+ /* Further data allocated here */
+} SyncOperationBuffer;
+
+static void
+syncprov_detach_op( Operation *op, syncops *so, slap_overinst *on )
+{
+ SyncOperationBuffer *sopbuf2;
+ Operation *op2;
+ int i, alen = 0;
+ size_t size;
+ char *ptr;
+ GroupAssertion *g1, *g2;
+
+ /* count the search attrs */
+ for (i=0; op->ors_attrs && !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) {
+ alen += op->ors_attrs[i].an_name.bv_len + 1;
+ }
+ /* Make a new copy of the operation */
+ size = offsetof( SyncOperationBuffer, sob_extra ) +
+ (i ? ( (i+1) * sizeof(AttributeName) + alen) : 0) +
+ op->o_req_dn.bv_len + 1 +
+ op->o_req_ndn.bv_len + 1 +
+ op->o_ndn.bv_len + 1 +
+ so->s_filterstr.bv_len + 1;
+ sopbuf2 = ch_calloc( 1, size );
+ op2 = &sopbuf2->sob_op;
+ op2->o_hdr = &sopbuf2->sob_hdr;
+ LDAP_SLIST_FIRST(&op2->o_extra) = &sopbuf2->sob_oe;
+
+ /* Copy the fields we care about explicitly, leave the rest alone */
+ *op2->o_hdr = *op->o_hdr;
+ op2->o_tag = op->o_tag;
+ op2->o_time = op->o_time;
+ op2->o_bd = on->on_info->oi_origdb;
+ op2->o_request = op->o_request;
+ op2->o_managedsait = op->o_managedsait;
+ LDAP_SLIST_FIRST(&op2->o_extra)->oe_key = on;
+ LDAP_SLIST_NEXT(LDAP_SLIST_FIRST(&op2->o_extra), oe_next) = NULL;
+
+ ptr = (char *) sopbuf2 + offsetof( SyncOperationBuffer, sob_extra );
+ if ( i ) {
+ op2->ors_attrs = (AttributeName *) ptr;
+ ptr = (char *) &op2->ors_attrs[i+1];
+ for (i=0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) {
+ op2->ors_attrs[i] = op->ors_attrs[i];
+ op2->ors_attrs[i].an_name.bv_val = ptr;
+ ptr = lutil_strcopy( ptr, op->ors_attrs[i].an_name.bv_val ) + 1;
+ }
+ BER_BVZERO( &op2->ors_attrs[i].an_name );
+ }
+
+ op2->o_authz = op->o_authz;
+ op2->o_ndn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_ndn.bv_val) + 1;
+ op2->o_dn = op2->o_ndn;
+ op2->o_req_dn.bv_len = op->o_req_dn.bv_len;
+ op2->o_req_dn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_req_dn.bv_val) + 1;
+ op2->o_req_ndn.bv_len = op->o_req_ndn.bv_len;
+ op2->o_req_ndn.bv_val = ptr;
+ ptr = lutil_strcopy(ptr, op->o_req_ndn.bv_val) + 1;
+ op2->ors_filterstr.bv_val = ptr;
+ strcpy( ptr, so->s_filterstr.bv_val );
+ op2->ors_filterstr.bv_len = so->s_filterstr.bv_len;
+
+ /* Skip the AND/GE clause that we stuck on in front */
+ if ( so->s_flags & PS_FIX_FILTER ) {
+ op2->ors_filter = op->ors_filter->f_and->f_next;
+ so->s_flags ^= PS_FIX_FILTER;
+ } else {
+ op2->ors_filter = op->ors_filter;
+ }
+ op2->ors_filter = filter_dup( op2->ors_filter, NULL );
+ so->s_op = op2;
+
+ /* Copy any cached group ACLs individually */
+ op2->o_groups = NULL;
+ for ( g1=op->o_groups; g1; g1=g1->ga_next ) {
+ g2 = ch_malloc( sizeof(GroupAssertion) + g1->ga_len );
+ *g2 = *g1;
+ strcpy( g2->ga_ndn, g1->ga_ndn );
+ g2->ga_next = op2->o_groups;
+ op2->o_groups = g2;
+ }
+ /* Don't allow any further group caching */
+ op2->o_do_not_cache = 1;
+
+ /* Add op2 to conn so abandon will find us */
+ op->o_conn->c_n_ops_executing++;
+ op->o_conn->c_n_ops_completed--;
+ LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op2, o_next );
+ so->s_flags |= PS_IS_DETACHED;
+
+ /* Prevent anyone else from trying to send a result for this op */
+ op->o_abandon = 1;
+}
+
+static int
+syncprov_search_response( Operation *op, SlapReply *rs )
+{
+ searchstate *ss = op->o_callback->sc_private;
+ slap_overinst *on = ss->ss_on;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ sync_control *srs = op->o_controls[slap_cids.sc_LDAPsync];
+
+ if ( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF ) {
+ Attribute *a;
+ /* If we got a referral without a referral object, there's
+ * something missing that we cannot replicate. Just ignore it.
+ * The consumer will abort because we didn't send the expected
+ * control.
+ */
+ if ( !rs->sr_entry ) {
+ assert( rs->sr_entry != NULL );
+ Debug( LDAP_DEBUG_ANY, "bogus referral in context\n",0,0,0 );
+ return SLAP_CB_CONTINUE;
+ }
+ a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN );
+ if ( a == NULL && rs->sr_operational_attrs != NULL ) {
+ a = attr_find( rs->sr_operational_attrs, slap_schema.si_ad_entryCSN );
+ }
+ if ( a ) {
+ int i, sid;
+ sid = slap_parse_csn_sid( &a->a_nvals[0] );
+
+ /* If not a persistent search */
+ if ( !ss->ss_so ) {
+ /* Make sure entry is less than the snapshot'd contextCSN */
+ for ( i=0; i<ss->ss_numcsns; i++ ) {
+ if ( sid == ss->ss_sids[i] && ber_bvcmp( &a->a_nvals[0],
+ &ss->ss_ctxcsn[i] ) > 0 ) {
+ Debug( LDAP_DEBUG_SYNC,
+ "Entry %s CSN %s greater than snapshot %s\n",
+ 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,
+ "Entry %s CSN %s older or equal to ctx %s\n",
+ rs->sr_entry->e_name.bv_val,
+ a->a_nvals[0].bv_val,
+ srs->sr_state.ctxcsn[i].bv_val );
+ return LDAP_SUCCESS;
+ }
+ }
+ }
+ }
+ rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2,
+ op->o_tmpmemctx );
+ rs->sr_ctrls[1] = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+ /* If we're in delta-sync mode, always send a cookie */
+ if ( si->si_nopres && si->si_usehint && a ) {
+ struct berval cookie;
+ slap_compose_sync_cookie( op, &cookie, a->a_nvals, srs->sr_state.rid, slap_serverID ? slap_serverID : -1 );
+ rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry,
+ LDAP_SYNC_ADD, rs->sr_ctrls, 0, 1, &cookie );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ } else {
+ rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry,
+ LDAP_SYNC_ADD, rs->sr_ctrls, 0, 0, NULL );
+ }
+ } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) {
+ struct berval cookie = BER_BVNULL;
+
+ if ( ( ss->ss_flags & SS_CHANGED ) &&
+ ss->ss_ctxcsn && !BER_BVISNULL( &ss->ss_ctxcsn[0] )) {
+ slap_compose_sync_cookie( op, &cookie, ss->ss_ctxcsn,
+ srs->sr_state.rid, slap_serverID ? slap_serverID : -1 );
+
+ Debug( LDAP_DEBUG_SYNC, "syncprov_search_response: cookie=%s\n", cookie.bv_val, 0, 0 );
+ }
+
+ /* Is this a regular refresh?
+ * Note: refresh never gets here if there were no changes
+ */
+ if ( !ss->ss_so ) {
+ rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2,
+ op->o_tmpmemctx );
+ rs->sr_ctrls[1] = NULL;
+ rs->sr_flags |= REP_CTRLS_MUSTBEFREED;
+ rs->sr_err = syncprov_done_ctrl( op, rs, rs->sr_ctrls,
+ 0, 1, &cookie, ( ss->ss_flags & SS_PRESENT ) ? LDAP_SYNC_REFRESH_PRESENTS :
+ LDAP_SYNC_REFRESH_DELETES );
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+ } else {
+ /* It's RefreshAndPersist, transition to Persist phase */
+ syncprov_sendinfo( op, rs, ( ss->ss_flags & SS_PRESENT ) ?
+ LDAP_TAG_SYNC_REFRESH_PRESENT : LDAP_TAG_SYNC_REFRESH_DELETE,
+ ( ss->ss_flags & SS_CHANGED ) ? &cookie : NULL,
+ 1, NULL, 0 );
+ if ( !BER_BVISNULL( &cookie ))
+ op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx );
+
+ /* Detach this Op from frontend control */
+ ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex );
+
+ /* But not if this connection was closed along the way */
+ if ( op->o_abandon ) {
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+ /* syncprov_ab_cleanup will free this syncop */
+ return SLAPD_ABANDON;
+
+ } else {
+ ldap_pvt_thread_mutex_lock( &ss->ss_so->s_mutex );
+ /* Turn off the refreshing flag */
+ ss->ss_so->s_flags ^= PS_IS_REFRESHING;
+
+ syncprov_detach_op( op, ss->ss_so, on );
+
+ ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex );
+
+ /* If there are queued responses, fire them off */
+ if ( ss->ss_so->s_res )
+ syncprov_qstart( ss->ss_so );
+ ldap_pvt_thread_mutex_unlock( &ss->ss_so->s_mutex );
+ }
+
+ return LDAP_SUCCESS;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ slap_callback *cb;
+ int gotstate = 0, changed = 0, do_present = 0;
+ syncops *sop = NULL;
+ searchstate *ss;
+ sync_control *srs;
+ BerVarray ctxcsn;
+ int i, *sids, numcsns;
+ struct berval mincsn, maxcsn;
+ int minsid, maxsid;
+ int dirty = 0;
+
+ if ( !(op->o_sync_mode & SLAP_SYNC_REFRESH) ) return SLAP_CB_CONTINUE;
+
+ if ( op->ors_deref & LDAP_DEREF_SEARCHING ) {
+ send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "illegal value for derefAliases" );
+ return rs->sr_err;
+ }
+
+ srs = op->o_controls[slap_cids.sc_LDAPsync];
+
+ /* 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 );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ sop = ch_malloc( sizeof( syncops ));
+ *sop = so;
+ sop->s_rid = srs->sr_state.rid;
+ sop->s_sid = srs->sr_state.sid;
+ /* set refcount=2 to prevent being freed out from under us
+ * by abandons that occur while we're running here
+ */
+ sop->s_inuse = 2;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ while ( si->si_active ) {
+ /* Wait for active mods to finish before proceeding, as they
+ * may already have inspected the si_ops list looking for
+ * consumers to replicate the change to. Using the log
+ * doesn't help, as we may finish playing it before the
+ * active mods gets added to it.
+ */
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ if ( slapd_shutdown ) {
+ ch_free( sop );
+ return SLAPD_ABANDON;
+ }
+ if ( !ldap_pvt_thread_pool_pausecheck( &connection_pool ))
+ ldap_pvt_thread_yield();
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ }
+ if ( op->o_abandon ) {
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ ch_free( sop );
+ return SLAPD_ABANDON;
+ }
+ ldap_pvt_thread_mutex_init( &sop->s_mutex );
+ sop->s_next = si->si_ops;
+ sop->s_si = si;
+ si->si_ops = sop;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ }
+
+ /* 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 */
+ 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!";
+bailout:
+ if ( sop ) {
+ syncops **sp = &si->si_ops;
+
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ while ( *sp != sop )
+ sp = &(*sp)->s_next;
+ *sp = sop->s_next;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ ch_free( sop );
+ }
+ rs->sr_ctrls = NULL;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ }
+ if ( BER_BVISEMPTY( &mincsn )) {
+ mincsn = maxcsn;
+ minsid = maxsid;
+ }
+
+ /* If nothing has changed, shortcut it */
+ if ( !changed && !dirty ) {
+ do_present = 0;
+no_change: if ( !(op->o_sync_mode & SLAP_SYNC_PERSIST) ) {
+ LDAPControl *ctrls[2];
+
+ ctrls[0] = NULL;
+ ctrls[1] = NULL;
+ syncprov_done_ctrl( op, rs, ctrls, 0, 0,
+ NULL, LDAP_SYNC_REFRESH_DELETES );
+ rs->sr_ctrls = ctrls;
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+ rs->sr_ctrls = NULL;
+ return rs->sr_err;
+ }
+ goto shortcut;
+ }
+
+ /* Do we have a sessionlog for this search? */
+ sl=si->si_logs;
+ if ( sl ) {
+ int do_play = 0;
+ ldap_pvt_thread_mutex_lock( &sl->sl_mutex );
+ /* Are there any log entries, and is the consumer state
+ * present in the session log?
+ */
+ if ( sl->sl_num > 0 ) {
+ int i;
+ for ( i=0; i<sl->sl_numcsns; i++ ) {
+ /* SID not present == new enough */
+ if ( minsid < sl->sl_sids[i] ) {
+ do_play = 1;
+ break;
+ }
+ /* SID present */
+ if ( minsid == sl->sl_sids[i] ) {
+ /* new enough? */
+ if ( ber_bvcmp( &mincsn, &sl->sl_mincsn[i] ) >= 0 )
+ do_play = 1;
+ break;
+ }
+ }
+ /* SID not present == new enough */
+ if ( i == sl->sl_numcsns )
+ do_play = 1;
+ }
+ if ( do_play ) {
+ do_present = 0;
+ /* mutex is unlocked in playlog */
+ syncprov_playlog( op, rs, sl, srs, ctxcsn, numcsns, sids );
+ } else {
+ ldap_pvt_thread_mutex_unlock( &sl->sl_mutex );
+ }
+ }
+ /* Is the CSN still present in the database? */
+ if ( syncprov_findcsn( op, FIND_CSN, &mincsn ) != LDAP_SUCCESS ) {
+ /* No, so a reload is required */
+ /* the 2.2 consumer doesn't send this hint */
+ if ( si->si_usehint && srs->sr_rhint == 0 ) {
+ if ( ctxcsn )
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ if ( sids )
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED;
+ rs->sr_text = "sync cookie is stale";
+ goto bailout;
+ }
+ if ( srs->sr_state.ctxcsn ) {
+ ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx );
+ srs->sr_state.ctxcsn = NULL;
+ }
+ if ( srs->sr_state.sids ) {
+ slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx );
+ srs->sr_state.sids = NULL;
+ }
+ srs->sr_state.numcsns = 0;
+ } else {
+ gotstate = 1;
+ /* If changed and doing Present lookup, send Present UUIDs */
+ if ( do_present && syncprov_findcsn( op, FIND_PRESENT, 0 ) !=
+ LDAP_SUCCESS ) {
+ if ( ctxcsn )
+ ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx );
+ if ( sids )
+ op->o_tmpfree( sids, op->o_tmpmemctx );
+ goto bailout;
+ }
+ }
+ } else {
+ /* The consumer knows nothing, we know nothing. OK. */
+ if (!numcsns)
+ goto no_change;
+ /* No consumer state, assume something has changed */
+ changed = SS_CHANGED;
+ }
+
+shortcut:
+ /* Append CSN range to search filter, save original filter
+ * for persistent search evaluation
+ */
+ if ( sop ) {
+ ldap_pvt_thread_mutex_lock( &sop->s_mutex );
+ sop->s_filterstr = op->ors_filterstr;
+ /* correct the refcount that was set to 2 before */
+ sop->s_inuse--;
+ }
+
+ /* If something changed, find the changes */
+ if ( gotstate && ( changed || dirty ) ) {
+ Filter *fand, *fava;
+
+ fand = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ fand->f_choice = LDAP_FILTER_AND;
+ fand->f_next = NULL;
+ fava = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ fand->f_and = fava;
+ fava->f_choice = LDAP_FILTER_GE;
+ fava->f_ava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx );
+ fava->f_ava->aa_desc = slap_schema.si_ad_entryCSN;
+#ifdef LDAP_COMP_MATCH
+ fava->f_ava->aa_cf = NULL;
+#endif
+ ber_dupbv_x( &fava->f_ava->aa_value, &mincsn, op->o_tmpmemctx );
+ fava->f_next = op->ors_filter;
+ op->ors_filter = fand;
+ filter2bv_x( op, op->ors_filter, &op->ors_filterstr );
+ if ( sop ) {
+ sop->s_flags |= PS_FIX_FILTER;
+ }
+ }
+ if ( sop ) {
+ ldap_pvt_thread_mutex_unlock( &sop->s_mutex );
+ }
+
+ /* Let our callback add needed info to returned entries */
+ cb = op->o_tmpcalloc(1, sizeof(slap_callback)+sizeof(searchstate), op->o_tmpmemctx);
+ ss = (searchstate *)(cb+1);
+ ss->ss_on = on;
+ ss->ss_so = sop;
+ ss->ss_flags = do_present | changed;
+ ss->ss_ctxcsn = ctxcsn;
+ ss->ss_numcsns = numcsns;
+ ss->ss_sids = sids;
+ cb->sc_response = syncprov_search_response;
+ cb->sc_private = ss;
+ cb->sc_next = op->o_callback;
+ op->o_callback = cb;
+
+ /* If this is a persistent search and no changes were reported during
+ * the refresh phase, just invoke the response callback to transition
+ * us into persist phase
+ */
+ if ( !changed && !dirty ) {
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_nentries = 0;
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+syncprov_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ /* This prevents generating unnecessarily; frontend will strip
+ * any statically stored copy.
+ */
+ if ( op->o_sync != SLAP_CONTROL_NONE )
+ return SLAP_CB_CONTINUE;
+
+ if ( rs->sr_entry &&
+ dn_match( &rs->sr_entry->e_nname, &si->si_contextdn )) {
+
+ if ( SLAP_OPATTRS( rs->sr_attr_flags ) ||
+ ad_inlist( slap_schema.si_ad_contextCSN, rs->sr_attrs )) {
+ Attribute *a, **ap = NULL;
+
+ for ( a=rs->sr_entry->e_attrs; a; a=a->a_next ) {
+ if ( a->a_desc == slap_schema.si_ad_contextCSN )
+ break;
+ }
+
+ ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock );
+ if ( si->si_ctxcsn ) {
+ if ( !a ) {
+ for ( ap = &rs->sr_operational_attrs; *ap;
+ ap=&(*ap)->a_next );
+
+ a = attr_alloc( slap_schema.si_ad_contextCSN );
+ *ap = a;
+ }
+
+ if ( !ap ) {
+ if ( rs_entry2modifiable( op, rs, on )) {
+ a = attr_find( rs->sr_entry->e_attrs,
+ slap_schema.si_ad_contextCSN );
+ }
+ if ( a->a_nvals != a->a_vals ) {
+ ber_bvarray_free( a->a_nvals );
+ }
+ a->a_nvals = NULL;
+ ber_bvarray_free( a->a_vals );
+ a->a_vals = NULL;
+ a->a_numvals = 0;
+ }
+ attr_valadd( a, si->si_ctxcsn, si->si_ctxcsn, si->si_numcsns );
+ }
+ ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+enum {
+ SP_CHKPT = 1,
+ SP_SESSL,
+ SP_NOPRES,
+ SP_USEHINT
+};
+
+static ConfigDriver sp_cf_gen;
+
+static ConfigTable spcfg[] = {
+ { "syncprov-checkpoint", "ops> <minutes", 3, 3, 0, ARG_MAGIC|SP_CHKPT,
+ sp_cf_gen, "( OLcfgOvAt:1.1 NAME 'olcSpCheckpoint' "
+ "DESC 'ContextCSN checkpoint interval in ops and minutes' "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-sessionlog", "ops", 2, 2, 0, ARG_INT|ARG_MAGIC|SP_SESSL,
+ sp_cf_gen, "( OLcfgOvAt:1.2 NAME 'olcSpSessionlog' "
+ "DESC 'Session log size in ops' "
+ "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-nopresent", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_NOPRES,
+ sp_cf_gen, "( OLcfgOvAt:1.3 NAME 'olcSpNoPresent' "
+ "DESC 'Omit Present phase processing' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "syncprov-reloadhint", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_USEHINT,
+ sp_cf_gen, "( OLcfgOvAt:1.4 NAME 'olcSpReloadHint' "
+ "DESC 'Observe Reload Hint in Request control' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs spocs[] = {
+ { "( OLcfgOvOc:1.1 "
+ "NAME 'olcSyncProvConfig' "
+ "DESC 'SyncRepl Provider configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcSpCheckpoint "
+ "$ olcSpSessionlog "
+ "$ olcSpNoPresent "
+ "$ olcSpReloadHint "
+ ") )",
+ Cft_Overlay, spcfg },
+ { NULL, 0, NULL }
+};
+
+static int
+sp_cf_gen(ConfigArgs *c)
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch ( c->type ) {
+ case SP_CHKPT:
+ if ( si->si_chkops || si->si_chktime ) {
+ struct berval bv;
+ /* we assume si_chktime is a multiple of 60
+ * because the parsed value was originally
+ * multiplied by 60 */
+ bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%d %d", si->si_chkops, si->si_chktime/60 );
+ if ( bv.bv_len >= sizeof( c->cr_msg ) ) {
+ rc = 1;
+ } else {
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_SESSL:
+ if ( si->si_logs ) {
+ c->value_int = si->si_logs->sl_size;
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_NOPRES:
+ if ( si->si_nopres ) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ break;
+ case SP_USEHINT:
+ if ( si->si_usehint ) {
+ c->value_int = 1;
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ switch ( c->type ) {
+ case SP_CHKPT:
+ si->si_chkops = 0;
+ si->si_chktime = 0;
+ break;
+ case SP_SESSL:
+ si->si_logs->sl_size = 0;
+ break;
+ case SP_NOPRES:
+ si->si_nopres = 0;
+ break;
+ case SP_USEHINT:
+ si->si_usehint = 0;
+ break;
+ }
+ return rc;
+ }
+ switch ( c->type ) {
+ case SP_CHKPT:
+ if ( lutil_atoi( &si->si_chkops, c->argv[1] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint ops # \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ if ( si->si_chkops <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint ops # \"%d\"",
+ c->argv[0], si->si_chkops );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ if ( lutil_atoi( &si->si_chktime, c->argv[2] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint time \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ if ( si->si_chktime <= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint time \"%d\"",
+ c->argv[0], si->si_chkops );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ si->si_chktime *= 60;
+ break;
+ case SP_SESSL: {
+ sessionlog *sl;
+ int size = c->value_int;
+
+ if ( size < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s size %d is negative",
+ c->argv[0], size );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ sl = si->si_logs;
+ if ( !sl ) {
+ sl = ch_malloc( sizeof( sessionlog ));
+ sl->sl_mincsn = NULL;
+ sl->sl_sids = NULL;
+ sl->sl_num = 0;
+ sl->sl_numcsns = 0;
+ sl->sl_head = sl->sl_tail = NULL;
+ ldap_pvt_thread_mutex_init( &sl->sl_mutex );
+ si->si_logs = sl;
+ }
+ sl->sl_size = size;
+ }
+ break;
+ case SP_NOPRES:
+ si->si_nopres = c->value_int;
+ break;
+ case SP_USEHINT:
+ si->si_usehint = c->value_int;
+ break;
+ }
+ return rc;
+}
+
+/* ITS#3456 we cannot run this search on the main thread, must use a
+ * child thread in order to insure we have a big enough stack.
+ */
+static void *
+syncprov_db_otask(
+ void *ptr
+)
+{
+ syncprov_findcsn( ptr, FIND_MAXCSN, 0 );
+ return NULL;
+}
+
+
+/* Read any existing contextCSN from the underlying db.
+ * Then search for any entries newer than that. If no value exists,
+ * just generate it. Cache whatever result.
+ */
+static int
+syncprov_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation *op;
+ Entry *e = NULL;
+ Attribute *a;
+ int rc;
+ void *thrctx = NULL;
+
+ if ( !SLAP_LASTMOD( be )) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov_db_open: invalid config, lastmod must be enabled\n", 0, 0, 0 );
+ return -1;
+ }
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+
+ rc = overlay_register_control( be, LDAP_CONTROL_SYNC );
+ if ( rc ) {
+ return rc;
+ }
+
+ 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_serverID || SLAP_SYNC_SHADOW( op->o_bd )) {
+ /* If we're also a consumer, then don't generate anything.
+ * Wait for our provider to send it to us, or for a local
+ * modify if we have multimaster.
+ */
+ 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;
+
+ /* make sure we do a checkpoint on close */
+ si->si_numops++;
+ }
+
+ /* Initialize the sessionlog mincsn */
+ if ( si->si_logs && si->si_numcsns ) {
+ sessionlog *sl = si->si_logs;
+ int i;
+ ber_bvarray_dup_x( &sl->sl_mincsn, si->si_ctxcsn, NULL );
+ sl->sl_numcsns = si->si_numcsns;
+ sl->sl_sids = ch_malloc( si->si_numcsns * sizeof(int) );
+ for ( i=0; i < si->si_numcsns; i++ )
+ sl->sl_sids[i] = si->si_sids[i];
+ }
+
+out:
+ op->o_bd->bd_info = (BackendInfo *)on;
+ return 0;
+}
+
+/* Write the current contextCSN into the underlying db.
+ */
+static int
+syncprov_db_close(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+#ifdef SLAP_CONFIG_DELETE
+ syncops *so, *sonext;
+#endif /* SLAP_CONFIG_DELETE */
+
+ if ( slapMode & SLAP_TOOL_MODE ) {
+ return 0;
+ }
+ if ( si->si_numops ) {
+ Connection conn = {0};
+ OperationBuffer opbuf;
+ Operation *op;
+ void *thrctx;
+
+ thrctx = ldap_pvt_thread_pool_context();
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = be;
+ op->o_dn = be->be_rootdn;
+ op->o_ndn = be->be_rootndn;
+ syncprov_checkpoint( op, on );
+ }
+
+#ifdef SLAP_CONFIG_DELETE
+ if ( !slapd_shutdown ) {
+ ldap_pvt_thread_mutex_lock( &si->si_ops_mutex );
+ for ( so=si->si_ops, sonext=so; so; so=sonext ) {
+ SlapReply rs = {REP_RESULT};
+ rs.sr_err = LDAP_UNAVAILABLE;
+ send_ldap_result( so->s_op, &rs );
+ sonext=so->s_next;
+ syncprov_drop_psearch( so, 0);
+ }
+ si->si_ops=NULL;
+ ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex );
+ }
+ overlay_unregister_control( be, LDAP_CONTROL_SYNC );
+#endif /* SLAP_CONFIG_DELETE */
+
+ return 0;
+}
+
+static int
+syncprov_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ syncprov_info_t *si;
+
+ if ( SLAP_ISGLOBALOVERLAY( be ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov must be instantiated within a database.\n",
+ 0, 0, 0 );
+ return 1;
+ }
+
+ si = ch_calloc(1, sizeof(syncprov_info_t));
+ on->on_bi.bi_private = si;
+ ldap_pvt_thread_rdwr_init( &si->si_csn_rwlock );
+ ldap_pvt_thread_mutex_init( &si->si_ops_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_init( &si->si_resp_mutex );
+
+ csn_anlist[0].an_desc = slap_schema.si_ad_entryCSN;
+ csn_anlist[0].an_name = slap_schema.si_ad_entryCSN->ad_cname;
+ csn_anlist[1].an_desc = slap_schema.si_ad_entryUUID;
+ csn_anlist[1].an_name = slap_schema.si_ad_entryUUID->ad_cname;
+
+ uuid_anlist[0].an_desc = slap_schema.si_ad_entryUUID;
+ uuid_anlist[0].an_name = slap_schema.si_ad_entryUUID->ad_cname;
+
+ return 0;
+}
+
+static int
+syncprov_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private;
+
+ if ( si ) {
+ if ( si->si_logs ) {
+ sessionlog *sl = si->si_logs;
+ slog_entry *se = sl->sl_head;
+
+ while ( se ) {
+ slog_entry *se_next = se->se_next;
+ ch_free( se );
+ se = se_next;
+ }
+ if ( sl->sl_mincsn )
+ ber_bvarray_free( sl->sl_mincsn );
+ if ( sl->sl_sids )
+ ch_free( sl->sl_sids );
+
+ ldap_pvt_thread_mutex_destroy(&si->si_logs->sl_mutex);
+ ch_free( si->si_logs );
+ }
+ if ( si->si_ctxcsn )
+ ber_bvarray_free( si->si_ctxcsn );
+ if ( si->si_sids )
+ ch_free( si->si_sids );
+ ldap_pvt_thread_mutex_destroy( &si->si_resp_mutex );
+ ldap_pvt_thread_mutex_destroy( &si->si_mods_mutex );
+ ldap_pvt_thread_mutex_destroy( &si->si_ops_mutex );
+ ldap_pvt_thread_rdwr_destroy( &si->si_csn_rwlock );
+ ch_free( si );
+ }
+
+ return 0;
+}
+
+static int syncprov_parseCtrl (
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_int_t mode;
+ ber_len_t len;
+ struct berval cookie = BER_BVNULL;
+ sync_control *sr;
+ int rhint = 0;
+
+ if ( op->o_sync != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Sync control specified multiple times";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( op->o_pagedresults != SLAP_CONTROL_NONE ) {
+ rs->sr_text = "Sync control specified with pagedResults control";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Sync control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) {
+ rs->sr_text = "Sync control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ /* Parse the control value
+ * syncRequestValue ::= SEQUENCE {
+ * mode ENUMERATED {
+ * -- 0 unused
+ * refreshOnly (1),
+ * -- 2 reserved
+ * refreshAndPersist (3)
+ * },
+ * cookie syncCookie OPTIONAL
+ * }
+ */
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+
+ if ( (tag = ber_scanf( ber, "{i" /*}*/, &mode )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : mode decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ switch( mode ) {
+ case LDAP_SYNC_REFRESH_ONLY:
+ mode = SLAP_SYNC_REFRESH;
+ break;
+ case LDAP_SYNC_REFRESH_AND_PERSIST:
+ mode = SLAP_SYNC_REFRESH_AND_PERSIST;
+ break;
+ default:
+ rs->sr_text = "Sync control : unknown update mode";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ tag = ber_peek_tag( ber, &len );
+
+ if ( tag == LDAP_TAG_SYNC_COOKIE ) {
+ if (( ber_scanf( ber, /*{*/ "m", &cookie )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : cookie decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ tag = ber_peek_tag( ber, &len );
+ }
+ if ( tag == LDAP_TAG_RELOAD_HINT ) {
+ if (( ber_scanf( ber, /*{*/ "b", &rhint )) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : rhint decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ }
+ if (( ber_scanf( ber, /*{*/ "}")) == LBER_ERROR ) {
+ rs->sr_text = "Sync control : decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+ sr = op->o_tmpcalloc( 1, sizeof(struct sync_control), op->o_tmpmemctx );
+ sr->sr_rhint = rhint;
+ if (!BER_BVISNULL(&cookie)) {
+ ber_dupbv_x( &sr->sr_state.octet_str, &cookie, op->o_tmpmemctx );
+ /* If parse fails, pretend no cookie was sent */
+ if ( slap_parse_sync_cookie( &sr->sr_state, op->o_tmpmemctx ) ||
+ sr->sr_state.rid == -1 ) {
+ if ( sr->sr_state.ctxcsn ) {
+ ber_bvarray_free_x( sr->sr_state.ctxcsn, op->o_tmpmemctx );
+ sr->sr_state.ctxcsn = NULL;
+ }
+ sr->sr_state.numcsns = 0;
+ }
+ }
+
+ op->o_controls[slap_cids.sc_LDAPsync] = sr;
+
+ op->o_sync = ctrl->ldctl_iscritical
+ ? SLAP_CONTROL_CRITICAL
+ : SLAP_CONTROL_NONCRITICAL;
+
+ op->o_sync_mode |= mode; /* o_sync_mode shares o_sync */
+
+ return LDAP_SUCCESS;
+}
+
+/* This overlay is set up for dynamic loading via moduleload. For static
+ * configuration, you'll need to arrange for the slap_overinst to be
+ * initialized and registered by some other function inside slapd.
+ */
+
+static slap_overinst syncprov;
+
+int
+syncprov_initialize()
+{
+ int rc;
+
+ rc = register_supported_control( LDAP_CONTROL_SYNC,
+ SLAP_CTRL_SEARCH, NULL,
+ syncprov_parseCtrl, &slap_cids.sc_LDAPsync );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "syncprov_init: Failed to register control %d\n", rc, 0, 0 );
+ return rc;
+ }
+
+ syncprov.on_bi.bi_type = "syncprov";
+ syncprov.on_bi.bi_db_init = syncprov_db_init;
+ syncprov.on_bi.bi_db_destroy = syncprov_db_destroy;
+ syncprov.on_bi.bi_db_open = syncprov_db_open;
+ syncprov.on_bi.bi_db_close = syncprov_db_close;
+
+ syncprov.on_bi.bi_op_abandon = syncprov_op_abandon;
+ syncprov.on_bi.bi_op_cancel = syncprov_op_abandon;
+
+ syncprov.on_bi.bi_op_add = syncprov_op_mod;
+ syncprov.on_bi.bi_op_compare = syncprov_op_compare;
+ syncprov.on_bi.bi_op_delete = syncprov_op_mod;
+ syncprov.on_bi.bi_op_modify = syncprov_op_mod;
+ syncprov.on_bi.bi_op_modrdn = syncprov_op_mod;
+ syncprov.on_bi.bi_op_search = syncprov_op_search;
+ syncprov.on_bi.bi_extended = syncprov_op_extended;
+ syncprov.on_bi.bi_operational = syncprov_operational;
+
+ syncprov.on_bi.bi_cf_ocs = spocs;
+
+ generic_filter.f_desc = slap_schema.si_ad_objectClass;
+
+ rc = config_register_schema( spcfg, spocs );
+ if ( rc ) return rc;
+
+ return overlay_register( &syncprov );
+}
+
+#if SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return syncprov_initialize();
+}
+#endif /* SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC */
+
+#endif /* defined(SLAPD_OVER_SYNCPROV) */
diff --git a/servers/slapd/overlays/translucent.c b/servers/slapd/overlays/translucent.c
new file mode 100644
index 0000000..9599170
--- /dev/null
+++ b/servers/slapd/overlays/translucent.c
@@ -0,0 +1,1416 @@
+/* translucent.c - translucent proxy module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corp. for inclusion in
+ * OpenLDAP Software. This work was sponsored by Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_TRANSLUCENT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+
+#include "config.h"
+
+/* config block */
+typedef struct translucent_info {
+ BackendDB db; /* captive backend */
+ AttributeName *local; /* valid attrs for local filters */
+ AttributeName *remote; /* valid attrs for remote filters */
+ int strict;
+ int no_glue;
+ int defer_db_open;
+ int bind_local;
+ int pwmod_local;
+} translucent_info;
+
+static ConfigLDAPadd translucent_ldadd;
+static ConfigCfAdd translucent_cfadd;
+
+static ConfigDriver translucent_cf_gen;
+
+enum {
+ TRANS_LOCAL = 1,
+ TRANS_REMOTE
+};
+
+static ConfigTable translucentcfg[] = {
+ { "translucent_strict", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, strict),
+ "( OLcfgOvAt:14.1 NAME 'olcTranslucentStrict' "
+ "DESC 'Reveal attribute deletion constraint violations' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "translucent_no_glue", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, no_glue),
+ "( OLcfgOvAt:14.2 NAME 'olcTranslucentNoGlue' "
+ "DESC 'Disable automatic glue records for ADD and MODRDN' "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "translucent_local", "attr[,attr...]", 1, 2, 0,
+ ARG_MAGIC|TRANS_LOCAL,
+ translucent_cf_gen,
+ "( OLcfgOvAt:14.3 NAME 'olcTranslucentLocal' "
+ "DESC 'Attributes to use in local search filter' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "translucent_remote", "attr[,attr...]", 1, 2, 0,
+ ARG_MAGIC|TRANS_REMOTE,
+ translucent_cf_gen,
+ "( OLcfgOvAt:14.4 NAME 'olcTranslucentRemote' "
+ "DESC 'Attributes to use in remote search filter' "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "translucent_bind_local", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, bind_local),
+ "( OLcfgOvAt:14.5 NAME 'olcTranslucentBindLocal' "
+ "DESC 'Enable local bind' "
+ "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL },
+ { "translucent_pwmod_local", "on|off", 1, 2, 0,
+ ARG_ON_OFF|ARG_OFFSET,
+ (void *)offsetof(translucent_info, pwmod_local),
+ "( OLcfgOvAt:14.6 NAME 'olcTranslucentPwModLocal' "
+ "DESC 'Enable local RFC 3062 Password Modify extended operation' "
+ "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs translucentocs[] = {
+ { "( OLcfgOvOc:14.1 "
+ "NAME 'olcTranslucentConfig' "
+ "DESC 'Translucent configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcTranslucentStrict $ olcTranslucentNoGlue $"
+ " olcTranslucentLocal $ olcTranslucentRemote $"
+ " olcTranslucentBindLocal $ olcTranslucentPwModLocal ) )",
+ Cft_Overlay, translucentcfg, NULL, translucent_cfadd },
+ { "( OLcfgOvOc:14.2 "
+ "NAME 'olcTranslucentDatabase' "
+ "DESC 'Translucent target database configuration' "
+ "AUXILIARY )", Cft_Misc, olcDatabaseDummy, translucent_ldadd },
+ { NULL, 0, NULL }
+};
+/* for translucent_init() */
+
+static int
+translucent_ldadd_cleanup( ConfigArgs *ca )
+{
+ slap_overinst *on = ca->ca_private;
+ translucent_info *ov = on->on_bi.bi_private;
+
+ ov->defer_db_open = 0;
+ return backend_startup_one( ca->be, &ca->reply );
+}
+
+static int
+translucent_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+ slap_overinst *on;
+ translucent_info *ov;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_ldadd\n", 0, 0, 0);
+
+ if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
+ cei->ce_bi->bi_cf_ocs != translucentocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ on = (slap_overinst *)cei->ce_bi;
+ ov = on->on_bi.bi_private;
+ ca->be = &ov->db;
+ ca->ca_private = on;
+ if ( CONFIG_ONLINE_ADD( ca ))
+ ca->cleanup = translucent_ldadd_cleanup;
+ else
+ ov->defer_db_open = 0;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+translucent_cfadd( Operation *op, SlapReply *rs, Entry *e, ConfigArgs *ca )
+{
+ CfEntryInfo *cei = e->e_private;
+ slap_overinst *on = (slap_overinst *)cei->ce_bi;
+ translucent_info *ov = on->on_bi.bi_private;
+ struct berval bv;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_cfadd\n", 0, 0, 0);
+
+ /* FIXME: should not hardcode "olcDatabase" here */
+ bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+ "olcDatabase=" SLAP_X_ORDERED_FMT "%s",
+ 0, ov->db.bd_info->bi_type );
+ if ( bv.bv_len >= sizeof( ca->cr_msg ) ) {
+ return -1;
+ }
+ bv.bv_val = ca->cr_msg;
+ ca->be = &ov->db;
+ ov->defer_db_open = 0;
+
+ /* We can only create this entry if the database is table-driven
+ */
+ if ( ov->db.bd_info->bi_cf_ocs )
+ config_build_entry( op, rs, cei, ca, &bv,
+ ov->db.bd_info->bi_cf_ocs,
+ &translucentocs[1] );
+
+ return 0;
+}
+
+static int
+translucent_cf_gen( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ translucent_info *ov = on->on_bi.bi_private;
+ AttributeName **an, *a2;
+ int i;
+
+ if ( c->type == TRANS_LOCAL )
+ an = &ov->local;
+ else
+ an = &ov->remote;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ if ( !*an )
+ return 1;
+ for ( i = 0; !BER_BVISNULL(&(*an)[i].an_name); i++ ) {
+ value_add_one( &c->rvalue_vals, &(*an)[i].an_name );
+ }
+ return ( i < 1 );
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ anlist_free( *an, 1, NULL );
+ *an = NULL;
+ } else {
+ i = c->valx;
+ ch_free( (*an)[i].an_name.bv_val );
+ do {
+ (*an)[i] = (*an)[i+1];
+ i++;
+ } while ( !BER_BVISNULL( &(*an)[i].an_name ));
+ }
+ return 0;
+ }
+ a2 = str2anlist( *an, c->argv[1], "," );
+ if ( !a2 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse attribute %s",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ return ARG_BAD_CONF;
+ }
+ *an = a2;
+ return 0;
+}
+
+static slap_overinst translucent;
+
+/*
+** glue_parent()
+** call syncrepl_add_glue() with the parent suffix;
+**
+*/
+
+static struct berval glue[] = { BER_BVC("top"), BER_BVC("glue"), BER_BVNULL };
+
+void glue_parent(Operation *op) {
+ Operation nop = *op;
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ struct berval ndn = BER_BVNULL;
+ Attribute *a;
+ Entry *e;
+ struct berval pdn;
+
+ dnParent( &op->o_req_ndn, &pdn );
+ ber_dupbv_x( &ndn, &pdn, op->o_tmpmemctx );
+
+ Debug(LDAP_DEBUG_TRACE, "=> glue_parent: fabricating glue for <%s>\n", ndn.bv_val, 0, 0);
+
+ e = entry_alloc();
+ e->e_id = NOID;
+ ber_dupbv(&e->e_name, &ndn);
+ ber_dupbv(&e->e_nname, &ndn);
+
+ a = attr_alloc( slap_schema.si_ad_objectClass );
+ a->a_numvals = 2;
+ a->a_vals = ch_malloc(sizeof(struct berval) * 3);
+ ber_dupbv(&a->a_vals[0], &glue[0]);
+ ber_dupbv(&a->a_vals[1], &glue[1]);
+ ber_dupbv(&a->a_vals[2], &glue[2]);
+ a->a_nvals = a->a_vals;
+ a->a_next = e->e_attrs;
+ e->e_attrs = a;
+
+ a = attr_alloc( slap_schema.si_ad_structuralObjectClass );
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc(sizeof(struct berval) * 2);
+ ber_dupbv(&a->a_vals[0], &glue[1]);
+ ber_dupbv(&a->a_vals[1], &glue[2]);
+ a->a_nvals = a->a_vals;
+ a->a_next = e->e_attrs;
+ e->e_attrs = a;
+
+ nop.o_req_dn = ndn;
+ nop.o_req_ndn = ndn;
+ nop.ora_e = e;
+
+ nop.o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ syncrepl_add_glue(&nop, e);
+ nop.o_bd->bd_info = (BackendInfo *) on;
+
+ op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
+
+ return;
+}
+
+/*
+** free_attr_chain()
+** free only the Attribute*, not the contents;
+**
+*/
+void free_attr_chain(Attribute *b) {
+ Attribute *a;
+ for(a=b; a; a=a->a_next) {
+ a->a_vals = NULL;
+ a->a_nvals = NULL;
+ }
+ attrs_free( b );
+ return;
+}
+
+/*
+** translucent_add()
+** if not bound as root, send ACCESS error;
+** if glue, glue_parent();
+** return CONTINUE;
+**
+*/
+
+static int translucent_add(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_add: %s\n",
+ op->o_req_dn.bv_val, 0, 0);
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ if(!ov->no_glue) glue_parent(op);
+ return(SLAP_CB_CONTINUE);
+}
+
+/*
+** translucent_modrdn()
+** if not bound as root, send ACCESS error;
+** if !glue, glue_parent();
+** else return CONTINUE;
+**
+*/
+
+static int translucent_modrdn(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_modrdn: %s -> %s\n",
+ op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0);
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ if(!ov->no_glue) {
+ op->o_tag = LDAP_REQ_ADD;
+ glue_parent(op);
+ op->o_tag = LDAP_REQ_MODRDN;
+ }
+ return(SLAP_CB_CONTINUE);
+}
+
+/*
+** translucent_delete()
+** if not bound as root, send ACCESS error;
+** else return CONTINUE;
+**
+*/
+
+static int translucent_delete(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_delete: %s\n",
+ op->o_req_dn.bv_val, 0, 0);
+ if(!be_isroot(op)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS,
+ "user modification of overlay database not permitted");
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return(rs->sr_err);
+ }
+ return(SLAP_CB_CONTINUE);
+}
+
+static int
+translucent_tag_cb( Operation *op, SlapReply *rs )
+{
+ op->o_tag = LDAP_REQ_MODIFY;
+ op->orm_modlist = op->o_callback->sc_private;
+ rs->sr_tag = slap_req2res( op->o_tag );
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** translucent_modify()
+** modify in local backend if exists in both;
+** otherwise, add to local backend;
+** fail if not defined in captive backend;
+**
+*/
+
+static int translucent_modify(Operation *op, SlapReply *rs) {
+ SlapReply nrs = { REP_RESULT };
+
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *re = NULL;
+ Attribute *a, *ax;
+ Modifications *m, **mm;
+ BackendDB *db;
+ int del, rc, erc = 0;
+ slap_callback cb = { 0 };
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_modify: %s\n",
+ op->o_req_dn.bv_val, 0, 0);
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+/*
+** fetch entry from the captive backend;
+** if it did not exist, fail;
+** release it, if captive backend supports this;
+**
+*/
+
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re);
+ op->o_bd = db;
+ if(rc != LDAP_SUCCESS || re == NULL ) {
+ send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT,
+ "attempt to modify nonexistent local record");
+ return(rs->sr_err);
+ }
+/*
+** fetch entry from local backend;
+** if it exists:
+** foreach Modification:
+** if attr not present in local:
+** if Mod == LDAP_MOD_DELETE:
+** if remote attr not present, return NO_SUCH;
+** if remote attr present, drop this Mod;
+** else force this Mod to LDAP_MOD_ADD;
+** return CONTINUE;
+**
+*/
+
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+
+ if(e && rc == LDAP_SUCCESS) {
+ Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: found local entry\n", 0, 0, 0);
+ for(mm = &op->orm_modlist; *mm; ) {
+ m = *mm;
+ for(a = e->e_attrs; a; a = a->a_next)
+ if(a->a_desc == m->sml_desc) break;
+ if(a) {
+ mm = &m->sml_next;
+ continue; /* found local attr */
+ }
+ if(m->sml_op == LDAP_MOD_DELETE) {
+ for(a = re->e_attrs; a; a = a->a_next)
+ if(a->a_desc == m->sml_desc) break;
+ /* not found remote attr */
+ if(!a) {
+ erc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto release;
+ }
+ if(ov->strict) {
+ erc = LDAP_CONSTRAINT_VIOLATION;
+ goto release;
+ }
+ Debug(LDAP_DEBUG_TRACE,
+ "=> translucent_modify: silently dropping delete: %s\n",
+ m->sml_desc->ad_cname.bv_val, 0, 0);
+ *mm = m->sml_next;
+ m->sml_next = NULL;
+ slap_mods_free(m, 1);
+ continue;
+ }
+ m->sml_op = LDAP_MOD_ADD;
+ mm = &m->sml_next;
+ }
+ erc = SLAP_CB_CONTINUE;
+release:
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else
+ entry_free(re);
+ }
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ be_entry_release_r(op, e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+ if(erc == SLAP_CB_CONTINUE) {
+ return(erc);
+ } else if(erc) {
+ send_ldap_error(op, rs, erc,
+ "attempt to delete nonexistent attribute");
+ return(erc);
+ }
+ }
+
+ /* don't leak remote entry copy */
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else
+ entry_free(re);
+ }
+/*
+** foreach Modification:
+** if MOD_ADD or MOD_REPLACE, add Attribute;
+** if no Modifications were suitable:
+** if strict, throw CONSTRAINT_VIOLATION;
+** else, return early SUCCESS;
+** fabricate Entry with new Attribute chain;
+** glue_parent() for this Entry;
+** call bi_op_add() in local backend;
+**
+*/
+
+ Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: fabricating local add\n", 0, 0, 0);
+ a = NULL;
+ for(del = 0, ax = NULL, m = op->orm_modlist; m; m = m->sml_next) {
+ Attribute atmp;
+ if(((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_ADD) &&
+ ((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_REPLACE)) {
+ Debug(LDAP_DEBUG_ANY,
+ "=> translucent_modify: silently dropped modification(%d): %s\n",
+ m->sml_op, m->sml_desc->ad_cname.bv_val, 0);
+ if((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) del++;
+ continue;
+ }
+ atmp.a_desc = m->sml_desc;
+ atmp.a_vals = m->sml_values;
+ atmp.a_nvals = m->sml_nvalues ? m->sml_nvalues : atmp.a_vals;
+ atmp.a_numvals = m->sml_numvals;
+ atmp.a_flags = 0;
+ a = attr_dup( &atmp );
+ a->a_next = ax;
+ ax = a;
+ }
+
+ if(del && ov->strict) {
+ attrs_free( a );
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "attempt to delete attributes from local database");
+ return(rs->sr_err);
+ }
+
+ if(!ax) {
+ if(ov->strict) {
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "modification contained other than ADD or REPLACE");
+ return(rs->sr_err);
+ }
+ /* rs->sr_text = "no valid modification found"; */
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result(op, rs);
+ return(rs->sr_err);
+ }
+
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &op->o_req_dn );
+ ber_dupbv( &e->e_nname, &op->o_req_ndn );
+ e->e_attrs = a;
+
+ op->o_tag = LDAP_REQ_ADD;
+ cb.sc_response = translucent_tag_cb;
+ cb.sc_private = op->orm_modlist;
+ op->oq_add.rs_e = e;
+
+ glue_parent(op);
+
+ cb.sc_next = op->o_callback;
+ op->o_callback = &cb;
+ rc = on->on_info->oi_orig->bi_op_add(op, &nrs);
+ if ( op->ora_e == e )
+ entry_free( e );
+ op->o_callback = cb.sc_next;
+
+ return(rc);
+}
+
+static int translucent_compare(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ AttributeAssertion *ava = op->orc_ava;
+ Entry *e = NULL;
+ BackendDB *db;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_compare: <%s> %s:%s\n",
+ op->o_req_dn.bv_val, ava->aa_desc->ad_cname.bv_val, ava->aa_value.bv_val);
+
+/*
+** if the local backend has an entry for this attribute:
+** CONTINUE and let it do the compare;
+**
+*/
+ rc = overlay_entry_get_ov(op, &op->o_req_ndn, NULL, ava->aa_desc, 0, &e, on);
+ if(rc == LDAP_SUCCESS && e) {
+ overlay_entry_release_ov(op, e, 0, on);
+ return(SLAP_CB_CONTINUE);
+ }
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+/*
+** call compare() in the captive backend;
+** return the result;
+**
+*/
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_op_compare(op, rs);
+ op->o_bd = db;
+
+ return(rc);
+}
+
+static int translucent_pwmod(Operation *op, SlapReply *rs) {
+ SlapReply nrs = { REP_RESULT };
+ Operation nop;
+
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *re = NULL;
+ BackendDB *db;
+ int rc = 0;
+ slap_callback cb = { 0 };
+
+ if (!ov->pwmod_local) {
+ rs->sr_err = LDAP_CONSTRAINT_VIOLATION,
+ rs->sr_text = "attempt to modify password in local database";
+ return rs->sr_err;
+ }
+
+/*
+** fetch entry from the captive backend;
+** if it did not exist, fail;
+** release it, if captive backend supports this;
+**
+*/
+ db = op->o_bd;
+ op->o_bd = &ov->db;
+ ov->db.be_acl = op->o_bd->be_acl;
+ rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re);
+ if(rc != LDAP_SUCCESS || re == NULL ) {
+ send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT,
+ "attempt to modify nonexistent local record");
+ return(rs->sr_err);
+ }
+ op->o_bd = db;
+/*
+** fetch entry from local backend;
+** if it exists:
+** return CONTINUE;
+*/
+
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+
+ if(e && rc == LDAP_SUCCESS) {
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else {
+ entry_free(re);
+ }
+ }
+ op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig;
+ be_entry_release_r(op, e);
+ op->o_bd->bd_info = (BackendInfo *) on;
+ return SLAP_CB_CONTINUE;
+ }
+
+ /* don't leak remote entry copy */
+ if(re) {
+ if(ov->db.bd_info->bi_entry_release_rw) {
+ op->o_bd = &ov->db;
+ ov->db.bd_info->bi_entry_release_rw(op, re, 0);
+ op->o_bd = db;
+ } else {
+ entry_free(re);
+ }
+ }
+/*
+** glue_parent() for this Entry;
+** call bi_op_add() in local backend;
+**
+*/
+ e = entry_alloc();
+ ber_dupbv( &e->e_name, &op->o_req_dn );
+ ber_dupbv( &e->e_nname, &op->o_req_ndn );
+ e->e_attrs = NULL;
+
+ nop = *op;
+ nop.o_tag = LDAP_REQ_ADD;
+ cb.sc_response = slap_null_cb;
+ nop.oq_add.rs_e = e;
+
+ glue_parent(&nop);
+
+ nop.o_callback = &cb;
+ rc = on->on_info->oi_orig->bi_op_add(&nop, &nrs);
+ if ( nop.ora_e == e ) {
+ entry_free( e );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ return SLAP_CB_CONTINUE;
+ }
+
+ return rc;
+}
+
+static int translucent_exop(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ const struct berval bv_exop_pwmod = BER_BVC(LDAP_EXOP_MODIFY_PASSWD);
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_exop: %s\n",
+ op->o_req_dn.bv_val, 0, 0);
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ if ( bvmatch( &bv_exop_pwmod, &op->ore_reqoid ) ) {
+ return translucent_pwmod( op, rs );
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+/*
+** translucent_search_cb()
+** merge local data with remote data
+**
+** Four cases:
+** 1: remote search, no local filter
+** merge data and send immediately
+** 2: remote search, with local filter
+** merge data and save
+** 3: local search, no remote filter
+** merge data and send immediately
+** 4: local search, with remote filter
+** check list, merge, send, delete
+*/
+
+#define RMT_SIDE 0
+#define LCL_SIDE 1
+#define USE_LIST 2
+
+typedef struct trans_ctx {
+ BackendDB *db;
+ slap_overinst *on;
+ Filter *orig;
+ Avlnode *list;
+ int step;
+ int slimit;
+ AttributeName *attrs;
+} trans_ctx;
+
+static int translucent_search_cb(Operation *op, SlapReply *rs) {
+ trans_ctx *tc;
+ BackendDB *db;
+ slap_overinst *on;
+ translucent_info *ov;
+ Entry *le, *re;
+ Attribute *a, *ax, *an, *as = NULL;
+ int rc;
+ int test_f = 0;
+
+ tc = op->o_callback->sc_private;
+
+ /* Don't let the op complete while we're gathering data */
+ if ( rs->sr_type == REP_RESULT && ( tc->step & USE_LIST ))
+ return 0;
+
+ if(rs->sr_type != REP_SEARCH || !rs->sr_entry)
+ return(SLAP_CB_CONTINUE);
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_search_cb: %s\n",
+ rs->sr_entry->e_name.bv_val, 0, 0);
+
+ op->ors_slimit = tc->slimit + ( tc->slimit > 0 ? 1 : 0 );
+ if ( op->ors_attrs == slap_anlist_all_attributes ) {
+ op->ors_attrs = tc->attrs;
+ rs->sr_attrs = tc->attrs;
+ rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs );
+ }
+
+ on = tc->on;
+ ov = on->on_bi.bi_private;
+
+ db = op->o_bd;
+ re = NULL;
+
+ /* If we have local, get remote */
+ if ( tc->step & LCL_SIDE ) {
+ le = rs->sr_entry;
+ /* If entry is already on list, use it */
+ if ( tc->step & USE_LIST ) {
+ re = tavl_delete( &tc->list, le, entry_dn_cmp );
+ if ( re ) {
+ rs_flush_entry( op, rs, on );
+ rc = test_filter( op, re, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rs->sr_flags |= REP_ENTRY_MUSTBEFREED;
+ rs->sr_entry = re;
+
+ if ( tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) {
+ return LDAP_SIZELIMIT_EXCEEDED;
+ }
+
+ return SLAP_CB_CONTINUE;
+ } else {
+ entry_free( re );
+ rs->sr_entry = NULL;
+ return 0;
+ }
+ }
+ }
+ op->o_bd = &ov->db;
+ rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &re );
+ if ( rc == LDAP_SUCCESS && re ) {
+ Entry *tmp = entry_dup( re );
+ be_entry_release_r( op, re );
+ re = tmp;
+ test_f = 1;
+ }
+ } else {
+ /* Else we have remote, get local */
+ op->o_bd = tc->db;
+ le = NULL;
+ rc = overlay_entry_get_ov(op, &rs->sr_entry->e_nname, NULL, NULL, 0, &le, on);
+ if ( rc == LDAP_SUCCESS && le ) {
+ re = entry_dup( rs->sr_entry );
+ rs_flush_entry( op, rs, on );
+ } else {
+ le = NULL;
+ }
+ }
+
+/*
+** if we got remote and local entry:
+** foreach local attr:
+** foreach remote attr:
+** if match, remote attr with local attr;
+** if new local, add to list;
+** append new local attrs to remote;
+**
+*/
+
+ if ( re && le ) {
+ for(ax = le->e_attrs; ax; ax = ax->a_next) {
+ for(a = re->e_attrs; a; a = a->a_next) {
+ if(a->a_desc == ax->a_desc) {
+ test_f = 1;
+ if(a->a_vals != a->a_nvals)
+ ber_bvarray_free(a->a_nvals);
+ ber_bvarray_free(a->a_vals);
+ ber_bvarray_dup_x( &a->a_vals, ax->a_vals, NULL );
+ if ( ax->a_vals == ax->a_nvals ) {
+ a->a_nvals = a->a_vals;
+ } else {
+ ber_bvarray_dup_x( &a->a_nvals, ax->a_nvals, NULL );
+ }
+ break;
+ }
+ }
+ if(a) continue;
+ an = attr_dup(ax);
+ an->a_next = as;
+ as = an;
+ }
+ /* Dispose of local entry */
+ if ( tc->step & LCL_SIDE ) {
+ rs_flush_entry(op, rs, on);
+ } else {
+ overlay_entry_release_ov(op, le, 0, on);
+ }
+
+ /* literally append, so locals are always last */
+ if(as) {
+ if(re->e_attrs) {
+ for(ax = re->e_attrs; ax->a_next; ax = ax->a_next);
+ ax->a_next = as;
+ } else {
+ re->e_attrs = as;
+ }
+ }
+ /* If both filters, save entry for later */
+ if ( tc->step == (USE_LIST|RMT_SIDE) ) {
+ tavl_insert( &tc->list, re, entry_dn_cmp, avl_dup_error );
+ rs->sr_entry = NULL;
+ rc = 0;
+ } else {
+ /* send it now */
+ rs->sr_entry = re;
+ rs->sr_flags |= REP_ENTRY_MUSTBEFREED;
+ if ( test_f ) {
+ rc = test_filter( op, rs->sr_entry, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rc = SLAP_CB_CONTINUE;
+ } else {
+ rc = 0;
+ }
+ } else {
+ rc = SLAP_CB_CONTINUE;
+ }
+ }
+ } else if ( le ) {
+ /* Only a local entry: remote was deleted
+ * Ought to delete the local too...
+ */
+ rc = 0;
+ } else if ( tc->step & USE_LIST ) {
+ /* Only a remote entry, but both filters:
+ * Test the complete filter
+ */
+ rc = test_filter( op, rs->sr_entry, tc->orig );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ rc = SLAP_CB_CONTINUE;
+ } else {
+ rc = 0;
+ }
+ } else {
+ /* Only a remote entry, only remote filter:
+ * just pass thru
+ */
+ rc = SLAP_CB_CONTINUE;
+ }
+
+ op->o_bd = db;
+
+ if ( rc == SLAP_CB_CONTINUE && tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) {
+ return LDAP_SIZELIMIT_EXCEEDED;
+ }
+
+ return rc;
+}
+
+/* Dup the filter, excluding invalid elements */
+static Filter *
+trans_filter_dup(Operation *op, Filter *f, AttributeName *an)
+{
+ Filter *n = NULL;
+
+ if ( !f )
+ return NULL;
+
+ switch( f->f_choice & SLAPD_FILTER_MASK ) {
+ case SLAPD_FILTER_COMPUTED:
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_result = f->f_result;
+ n->f_next = NULL;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ if ( ad_inlist( f->f_desc, an )) {
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_desc = f->f_desc;
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE:
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ case LDAP_FILTER_EXT:
+ if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) {
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_ava = f->f_ava;
+ n->f_next = NULL;
+ }
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT: {
+ Filter **p;
+
+ n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx );
+ n->f_choice = f->f_choice;
+ n->f_next = NULL;
+
+ for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) {
+ *p = trans_filter_dup( op, f, an );
+ if ( !*p )
+ continue;
+ p = &(*p)->f_next;
+ }
+ /* nothing valid in this list */
+ if ( !n->f_list ) {
+ op->o_tmpfree( n, op->o_tmpmemctx );
+ return NULL;
+ }
+ /* Only 1 element in this list */
+ if ((n->f_choice & SLAPD_FILTER_MASK) != LDAP_FILTER_NOT &&
+ !n->f_list->f_next ) {
+ f = n->f_list;
+ *n = *f;
+ op->o_tmpfree( f, op->o_tmpmemctx );
+ }
+ break;
+ }
+ }
+ return n;
+}
+
+static void
+trans_filter_free( Operation *op, Filter *f )
+{
+ Filter *n, *p, *next;
+
+ f->f_choice &= SLAPD_FILTER_MASK;
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ /* Free in reverse order */
+ n = NULL;
+ for ( p = f->f_list; p; p = next ) {
+ next = p->f_next;
+ p->f_next = n;
+ n = p;
+ }
+ for ( p = n; p; p = next ) {
+ next = p->f_next;
+ trans_filter_free( op, p );
+ }
+ break;
+ default:
+ break;
+ }
+ op->o_tmpfree( f, op->o_tmpmemctx );
+}
+
+/*
+** translucent_search()
+** search via captive backend;
+** override results with any local data;
+**
+*/
+
+static int translucent_search(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ slap_callback cb = { NULL, NULL, NULL, NULL };
+ trans_ctx tc;
+ Filter *fl, *fr;
+ struct berval fbv;
+ int rc = 0;
+
+ if ( op->o_managedsait > SLAP_CONTROL_IGNORED )
+ return SLAP_CB_CONTINUE;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_search: <%s> %s\n",
+ op->o_req_dn.bv_val, op->ors_filterstr.bv_val, 0);
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ fr = ov->remote ? trans_filter_dup( op, op->ors_filter, ov->remote ) : NULL;
+ fl = ov->local ? trans_filter_dup( op, op->ors_filter, ov->local ) : NULL;
+ cb.sc_response = (slap_response *) translucent_search_cb;
+ cb.sc_private = &tc;
+ cb.sc_next = op->o_callback;
+
+ ov->db.be_acl = op->o_bd->be_acl;
+ tc.db = op->o_bd;
+ tc.on = on;
+ tc.orig = op->ors_filter;
+ tc.list = NULL;
+ tc.step = 0;
+ tc.slimit = op->ors_slimit;
+ tc.attrs = NULL;
+ fbv = op->ors_filterstr;
+
+ op->o_callback = &cb;
+
+ if ( fr || !fl ) {
+ tc.attrs = op->ors_attrs;
+ op->ors_slimit = SLAP_NO_LIMIT;
+ op->ors_attrs = slap_anlist_all_attributes;
+ op->o_bd = &ov->db;
+ tc.step |= RMT_SIDE;
+ if ( fl ) {
+ tc.step |= USE_LIST;
+ op->ors_filter = fr;
+ filter2bv_x( op, fr, &op->ors_filterstr );
+ }
+ rc = ov->db.bd_info->bi_op_search(op, rs);
+ if ( op->ors_attrs == slap_anlist_all_attributes )
+ op->ors_attrs = tc.attrs;
+ op->o_bd = tc.db;
+ if ( fl ) {
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ }
+ if ( fl && !rc ) {
+ tc.step |= LCL_SIDE;
+ op->ors_filter = fl;
+ filter2bv_x( op, fl, &op->ors_filterstr );
+ rc = overlay_op_walk( op, rs, op_search, on->on_info, on->on_next );
+ op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx );
+ }
+ op->ors_filterstr = fbv;
+ op->ors_filter = tc.orig;
+ op->o_callback = cb.sc_next;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs );
+
+ /* Send out anything remaining on the list and finish */
+ if ( tc.step & USE_LIST ) {
+ if ( tc.list ) {
+ Avlnode *av;
+
+ av = tavl_end( tc.list, TAVL_DIR_LEFT );
+ while ( av ) {
+ rs->sr_entry = av->avl_data;
+ if ( rc == LDAP_SUCCESS && LDAP_COMPARE_TRUE ==
+ test_filter( op, rs->sr_entry, op->ors_filter ))
+ {
+ rs->sr_flags = REP_ENTRY_MUSTBEFREED;
+ rc = send_search_entry( op, rs );
+ } else {
+ entry_free( rs->sr_entry );
+ }
+ av = tavl_next( av, TAVL_DIR_RIGHT );
+ }
+ tavl_free( tc.list, NULL );
+ rs->sr_flags = 0;
+ rs->sr_entry = NULL;
+ }
+ send_ldap_result( op, rs );
+ }
+
+ op->ors_slimit = tc.slimit;
+
+ /* Free in reverse order */
+ if ( fl )
+ trans_filter_free( op, fl );
+ if ( fr )
+ trans_filter_free( op, fr );
+
+ return rc;
+}
+
+
+/*
+** translucent_bind()
+** pass bind request to captive backend;
+**
+*/
+
+static int translucent_bind(Operation *op, SlapReply *rs) {
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ BackendDB *db;
+ slap_callback sc = { 0 }, *save_cb;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "translucent_bind: <%s> method %d\n",
+ op->o_req_dn.bv_val, op->orb_method, 0);
+
+ if(ov->defer_db_open) {
+ send_ldap_error(op, rs, LDAP_UNAVAILABLE,
+ "remote DB not available");
+ return(rs->sr_err);
+ }
+
+ if (ov->bind_local) {
+ sc.sc_response = slap_null_cb;
+ save_cb = op->o_callback;
+ op->o_callback = &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", 0, 0, 0);
+
+ rc = ov->db.bd_info->bi_connection_destroy(&ov->db, conn);
+
+ return(rc);
+}
+
+/*
+** translucent_db_config()
+** pass config directives to captive backend;
+** parse unrecognized directives ourselves;
+**
+*/
+
+static int translucent_db_config(
+ BackendDB *be,
+ const char *fname,
+ int lineno,
+ int argc,
+ char **argv
+)
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_config: %s\n",
+ argc ? argv[0] : "", 0, 0);
+
+ /* Something for the captive database? */
+ if ( ov->db.bd_info && ov->db.bd_info->bi_db_config )
+ return ov->db.bd_info->bi_db_config( &ov->db, fname, lineno,
+ argc, argv );
+ return SLAP_CONF_UNKNOWN;
+}
+
+/*
+** translucent_db_init()
+** initialize the captive backend;
+**
+*/
+
+static int translucent_db_init(BackendDB *be, ConfigReply *cr) {
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_init\n", 0, 0, 0);
+
+ ov = ch_calloc(1, sizeof(translucent_info));
+ on->on_bi.bi_private = ov;
+ ov->db = *be;
+ ov->db.be_private = NULL;
+ ov->defer_db_open = 1;
+
+ if ( !backend_db_init( "ldap", &ov->db, -1, NULL )) {
+ Debug( LDAP_DEBUG_CONFIG, "translucent: unable to open captive back-ldap\n", 0, 0, 0);
+ return 1;
+ }
+ SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NO_SCHEMA_CHECK;
+ SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NOLASTMOD;
+
+ return 0;
+}
+
+/*
+** translucent_db_open()
+** if the captive backend has an open() method, call it;
+**
+*/
+
+static int translucent_db_open(BackendDB *be, ConfigReply *cr) {
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_open\n", 0, 0, 0);
+
+ /* need to inherit something from the original database... */
+ ov->db.be_def_limit = be->be_def_limit;
+ ov->db.be_limits = be->be_limits;
+ ov->db.be_acl = be->be_acl;
+ ov->db.be_dfltaccess = be->be_dfltaccess;
+
+ if ( ov->defer_db_open )
+ return 0;
+
+ rc = backend_startup_one( &ov->db, cr );
+
+ if(rc) Debug(LDAP_DEBUG_TRACE,
+ "translucent: bi_db_open() returned error %d\n", rc, 0, 0);
+
+ return(rc);
+}
+
+/*
+** translucent_db_close()
+** if the captive backend has a close() method, call it
+**
+*/
+
+static int
+translucent_db_close( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_close\n", 0, 0, 0);
+
+ if ( ov && ov->db.bd_info && ov->db.bd_info->bi_db_close ) {
+ rc = ov->db.bd_info->bi_db_close(&ov->db, NULL);
+ }
+
+ return(rc);
+}
+
+/*
+** translucent_db_destroy()
+** if the captive backend has a db_destroy() method, call it;
+** free any config data
+**
+*/
+
+static int
+translucent_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *) be->bd_info;
+ translucent_info *ov = on->on_bi.bi_private;
+ int rc = 0;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_db_destroy\n", 0, 0, 0);
+
+ if ( ov ) {
+ if ( ov->remote )
+ anlist_free( ov->remote, 1, NULL );
+ if ( ov->local )
+ anlist_free( ov->local, 1, NULL );
+ if ( ov->db.be_private != NULL ) {
+ backend_stopdown_one( &ov->db );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &ov->db.be_pcl_mutex );
+ ch_free(ov);
+ on->on_bi.bi_private = NULL;
+ }
+
+ return(rc);
+}
+
+/*
+** translucent_initialize()
+** initialize the slap_overinst with our entry points;
+**
+*/
+
+int translucent_initialize() {
+
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> translucent_initialize\n", 0, 0, 0);
+
+ translucent.on_bi.bi_type = "translucent";
+ translucent.on_bi.bi_db_init = translucent_db_init;
+ translucent.on_bi.bi_db_config = translucent_db_config;
+ translucent.on_bi.bi_db_open = translucent_db_open;
+ translucent.on_bi.bi_db_close = translucent_db_close;
+ translucent.on_bi.bi_db_destroy = translucent_db_destroy;
+ translucent.on_bi.bi_op_bind = translucent_bind;
+ translucent.on_bi.bi_op_add = translucent_add;
+ translucent.on_bi.bi_op_modify = translucent_modify;
+ translucent.on_bi.bi_op_modrdn = translucent_modrdn;
+ translucent.on_bi.bi_op_delete = translucent_delete;
+ translucent.on_bi.bi_op_search = translucent_search;
+ translucent.on_bi.bi_op_compare = translucent_compare;
+ translucent.on_bi.bi_connection_destroy = translucent_connection_destroy;
+ translucent.on_bi.bi_extended = translucent_exop;
+
+ translucent.on_bi.bi_cf_ocs = translucentocs;
+ rc = config_register_schema ( translucentcfg, translucentocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&translucent));
+}
+
+#if SLAPD_OVER_TRANSLUCENT == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return translucent_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_TRANSLUCENT */
diff --git a/servers/slapd/overlays/unique.c b/servers/slapd/overlays/unique.c
new file mode 100644
index 0000000..ed62d03
--- /dev/null
+++ b/servers/slapd/overlays/unique.c
@@ -0,0 +1,1464 @@
+/* unique.c - attribute uniqueness module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2004-2018 The OpenLDAP Foundation.
+ * Portions Copyright 2004,2006-2007 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Symas Corporation for
+ * inclusion in OpenLDAP Software, with subsequent enhancements by
+ * Emily Backes at Symas Corporation. This work was sponsored by
+ * Hewlett-Packard.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_UNIQUE
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "config.h"
+
+#define UNIQUE_DEFAULT_URI ("ldap:///??sub")
+
+static slap_overinst unique;
+
+typedef struct unique_attrs_s {
+ struct unique_attrs_s *next; /* list of attrs */
+ AttributeDescription *attr;
+} unique_attrs;
+
+typedef struct unique_domain_uri_s {
+ struct unique_domain_uri_s *next;
+ struct berval dn;
+ struct berval ndn;
+ struct berval filter;
+ Filter *f;
+ struct unique_attrs_s *attrs;
+ int scope;
+} unique_domain_uri;
+
+typedef struct unique_domain_s {
+ struct unique_domain_s *next;
+ struct berval domain_spec;
+ struct unique_domain_uri_s *uri;
+ char ignore; /* polarity of attributes */
+ char strict; /* null considered unique too */
+} unique_domain;
+
+typedef struct unique_data_s {
+ struct unique_domain_s *domains;
+ struct unique_domain_s *legacy;
+ char legacy_strict_set;
+} unique_data;
+
+typedef struct unique_counter_s {
+ struct berval *ndn;
+ int count;
+} unique_counter;
+
+enum {
+ UNIQUE_BASE = 1,
+ UNIQUE_IGNORE,
+ UNIQUE_ATTR,
+ UNIQUE_STRICT,
+ UNIQUE_URI
+};
+
+static ConfigDriver unique_cf_base;
+static ConfigDriver unique_cf_attrs;
+static ConfigDriver unique_cf_strict;
+static ConfigDriver unique_cf_uri;
+
+static ConfigTable uniquecfg[] = {
+ { "unique_base", "basedn", 2, 2, 0, ARG_DN|ARG_MAGIC|UNIQUE_BASE,
+ unique_cf_base, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' "
+ "DESC 'Subtree for uniqueness searches' "
+ "EQUALITY distinguishedNameMatch "
+ "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL },
+ { "unique_ignore", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_IGNORE,
+ unique_cf_attrs, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' "
+ "DESC 'Attributes for which uniqueness shall not be enforced' "
+ "EQUALITY caseIgnoreMatch "
+ "ORDERING caseIgnoreOrderingMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "unique_attributes", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_ATTR,
+ unique_cf_attrs, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' "
+ "DESC 'Attributes for which uniqueness shall be enforced' "
+ "EQUALITY caseIgnoreMatch "
+ "ORDERING caseIgnoreOrderingMatch "
+ "SUBSTR caseIgnoreSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "unique_strict", "on|off", 1, 2, 0, ARG_MAGIC|UNIQUE_STRICT,
+ unique_cf_strict, "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' "
+ "DESC 'Enforce uniqueness of null values' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "unique_uri", "ldapuri", 2, 3, 0, ARG_MAGIC|UNIQUE_URI,
+ unique_cf_uri, "( OLcfgOvAt:10.5 NAME 'olcUniqueURI' "
+ "DESC 'List of keywords and LDAP URIs for a uniqueness domain' "
+ "EQUALITY caseExactMatch "
+ "ORDERING caseExactOrderingMatch "
+ "SUBSTR caseExactSubstringsMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs uniqueocs[] = {
+ { "( OLcfgOvOc:10.1 "
+ "NAME 'olcUniqueConfig' "
+ "DESC 'Attribute value uniqueness configuration' "
+ "SUP olcOverlayConfig "
+ "MAY ( olcUniqueBase $ olcUniqueIgnore $ "
+ "olcUniqueAttribute $ olcUniqueStrict $ "
+ "olcUniqueURI ) )",
+ Cft_Overlay, uniquecfg },
+ { NULL, 0, NULL }
+};
+
+static void
+unique_free_domain_uri ( unique_domain_uri *uri )
+{
+ unique_domain_uri *next_uri = NULL;
+ unique_attrs *attr, *next_attr = NULL;
+
+ while ( uri ) {
+ next_uri = uri->next;
+ ch_free ( uri->dn.bv_val );
+ ch_free ( uri->ndn.bv_val );
+ ch_free ( uri->filter.bv_val );
+ filter_free( uri->f );
+ attr = uri->attrs;
+ while ( attr ) {
+ next_attr = attr->next;
+ ch_free (attr);
+ attr = next_attr;
+ }
+ ch_free ( uri );
+ uri = next_uri;
+ }
+}
+
+/* free an entire stack of domains */
+static void
+unique_free_domain ( unique_domain *domain )
+{
+ unique_domain *next_domain = NULL;
+
+ while ( domain ) {
+ next_domain = domain->next;
+ ch_free ( domain->domain_spec.bv_val );
+ unique_free_domain_uri ( domain->uri );
+ ch_free ( domain );
+ domain = next_domain;
+ }
+}
+
+static int
+unique_new_domain_uri ( unique_domain_uri **urip,
+ const LDAPURLDesc *url_desc,
+ ConfigArgs *c )
+{
+ int i, rc = LDAP_SUCCESS;
+ unique_domain_uri *uri;
+ struct berval bv = {0, NULL};
+ BackendDB *be = (BackendDB *)c->be;
+ char ** attr_str;
+ AttributeDescription * ad;
+ const char * text;
+
+ uri = ch_calloc ( 1, sizeof ( unique_domain_uri ) );
+
+ if ( url_desc->lud_host && url_desc->lud_host[0] ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "host <%s> not allowed in URI",
+ url_desc->lud_host );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( url_desc->lud_dn && url_desc->lud_dn[0] ) {
+ ber_str2bv( url_desc->lud_dn, 0, 0, &bv );
+ rc = dnPrettyNormal( NULL,
+ &bv,
+ &uri->dn,
+ &uri->ndn,
+ NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid DN %d (%s)",
+ url_desc->lud_dn, rc, ldap_err2string( rc ));
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( !dnIsSuffix ( &uri->ndn, &be->be_nsuffix[0] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn <%s> is not a suffix of backend base dn <%s>",
+ uri->dn.bv_val,
+ be->be_nsuffix[0].bv_val );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if ( BER_BVISNULL( &be->be_rootndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) {
+ Debug( LDAP_DEBUG_ANY,
+ "slapo-unique needs a rootdn; "
+ "backend <%s> has none, YMMV.\n",
+ be->be_nsuffix[0].bv_val, 0, 0 );
+ }
+ }
+
+ attr_str = url_desc->lud_attrs;
+ if ( attr_str ) {
+ for ( i=0; attr_str[i]; ++i ) {
+ unique_attrs * attr;
+ ad = NULL;
+ if ( slap_str2ad ( attr_str[i], &ad, &text )
+ == LDAP_SUCCESS) {
+ attr = ch_calloc ( 1,
+ sizeof ( unique_attrs ) );
+ attr->attr = ad;
+ attr->next = uri->attrs;
+ uri->attrs = attr;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: attribute: %s: %s",
+ attr_str[i], text );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ }
+ }
+
+ uri->scope = url_desc->lud_scope;
+ if ( !uri->scope ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: uri with base scope will always be unique");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ if (url_desc->lud_filter) {
+ char *ptr;
+ uri->f = str2filter( url_desc->lud_filter );
+ if ( !uri->f ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: bad filter");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ /* make sure the strfilter is in normal form (ITS#5581) */
+ filter2bv( uri->f, &uri->filter );
+ ptr = strstr( uri->filter.bv_val, "(?=" /*)*/ );
+ if ( ptr != NULL && ptr <= ( uri->filter.bv_val - STRLENOF( "(?=" /*)*/ ) + uri->filter.bv_len ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: bad filter");
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+ }
+exit:
+ uri->next = *urip;
+ *urip = uri;
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ unique_free_domain_uri ( uri );
+ *urip = NULL;
+ }
+ return rc;
+}
+
+static int
+unique_new_domain_uri_basic ( unique_domain_uri **urip,
+ ConfigArgs *c )
+{
+ LDAPURLDesc *url_desc = NULL;
+ int rc;
+
+ rc = ldap_url_parse ( UNIQUE_DEFAULT_URI, &url_desc );
+ if ( rc ) return rc;
+ rc = unique_new_domain_uri ( urip, url_desc, c );
+ ldap_free_urldesc ( url_desc );
+ return rc;
+}
+
+/* if *domain is non-null, it's pushed down the stack.
+ * note that the entire stack is freed if there is an error,
+ * so build added domains in a separate stack before adding them
+ *
+ * domain_specs look like
+ *
+ * [strict ][ignore ]uri[[ uri]...]
+ * e.g. "ldap:///ou=foo,o=bar?uid?sub ldap:///ou=baz,o=bar?uid?sub"
+ * "strict ldap:///ou=accounts,o=bar?uid,uidNumber?one"
+ * etc
+ *
+ * so finally strictness is per-domain
+ * but so is ignore-state, and that would be better as a per-url thing
+ */
+static int
+unique_new_domain ( unique_domain **domainp,
+ char *domain_spec,
+ ConfigArgs *c )
+{
+ char *uri_start;
+ int rc = LDAP_SUCCESS;
+ int uri_err = 0;
+ unique_domain * domain;
+ LDAPURLDesc *url_desc, *url_descs = NULL;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_new_domain <%s>\n",
+ domain_spec, 0, 0);
+
+ domain = ch_calloc ( 1, sizeof (unique_domain) );
+ ber_str2bv( domain_spec, 0, 1, &domain->domain_spec );
+
+ uri_start = domain_spec;
+ if ( strncasecmp ( uri_start, "ignore ",
+ STRLENOF( "ignore " ) ) == 0 ) {
+ domain->ignore = 1;
+ uri_start += STRLENOF( "ignore " );
+ }
+ if ( strncasecmp ( uri_start, "strict ",
+ STRLENOF( "strict " ) ) == 0 ) {
+ domain->strict = 1;
+ uri_start += STRLENOF( "strict " );
+ if ( !domain->ignore
+ && strncasecmp ( uri_start, "ignore ",
+ STRLENOF( "ignore " ) ) == 0 ) {
+ domain->ignore = 1;
+ uri_start += STRLENOF( "ignore " );
+ }
+ }
+ rc = ldap_url_parselist_ext ( &url_descs, uri_start, " ", 0 );
+ if ( rc ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<%s> invalid ldap urilist",
+ uri_start );
+ rc = ARG_BAD_CONF;
+ goto exit;
+ }
+
+ for ( url_desc = url_descs;
+ url_desc;
+ url_desc = url_descs->lud_next ) {
+ rc = unique_new_domain_uri ( &domain->uri,
+ url_desc,
+ c );
+ if ( rc ) {
+ rc = ARG_BAD_CONF;
+ uri_err = 1;
+ goto exit;
+ }
+ }
+
+exit:
+ if ( url_descs ) ldap_free_urldesc ( url_descs );
+ domain->next = *domainp;
+ *domainp = domain;
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ unique_free_domain ( domain );
+ *domainp = NULL;
+ }
+ return rc;
+}
+
+static int
+unique_cf_base( ConfigArgs *c )
+{
+ BackendDB *be = (BackendDB *)c->be;
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ int rc = ARG_BAD_CONF;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ rc = 0;
+ if ( legacy && legacy->uri && legacy->uri->dn.bv_val ) {
+ rc = value_add_one ( &c->rvalue_vals,
+ &legacy->uri->dn );
+ if ( rc ) return rc;
+ rc = value_add_one ( &c->rvalue_nvals,
+ &legacy->uri->ndn );
+ if ( rc ) return rc;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ assert ( legacy && legacy->uri && legacy->uri->dn.bv_val );
+ rc = 0;
+ ch_free ( legacy->uri->dn.bv_val );
+ ch_free ( legacy->uri->ndn.bv_val );
+ BER_BVZERO( &legacy->uri->dn );
+ BER_BVZERO( &legacy->uri->ndn );
+ if ( !legacy->uri->attrs ) {
+ unique_free_domain_uri ( legacy->uri );
+ legacy->uri = NULL;
+ }
+ if ( !legacy->uri && !private->legacy_strict_set ) {
+ unique_free_domain ( legacy );
+ private->legacy = legacy = NULL;
+ }
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix must be set" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !dnIsSuffix ( &c->value_ndn,
+ &be->be_nsuffix[0] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "dn is not a suffix of backend base" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ if ( !legacy->uri )
+ unique_new_domain_uri_basic ( &legacy->uri, c );
+ ch_free ( legacy->uri->dn.bv_val );
+ ch_free ( legacy->uri->ndn.bv_val );
+ legacy->uri->dn = c->value_dn;
+ legacy->uri->ndn = c->value_ndn;
+ rc = 0;
+ break;
+ default:
+ abort();
+ }
+
+ if ( rc ) {
+ ch_free( c->value_dn.bv_val );
+ BER_BVZERO( &c->value_dn );
+ ch_free( c->value_ndn.bv_val );
+ BER_BVZERO( &c->value_ndn );
+ }
+
+ return rc;
+}
+
+static int
+unique_cf_attrs( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_attrs *new_attrs = NULL;
+ unique_attrs *attr, *next_attr, *reverse_attrs;
+ unique_attrs **attrp;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ if ( legacy
+ && (c->type == UNIQUE_IGNORE) == legacy->ignore
+ && legacy->uri )
+ for ( attr = legacy->uri->attrs;
+ attr;
+ attr = attr->next )
+ value_add_one( &c->rvalue_vals,
+ &attr->attr->ad_cname );
+ rc = 0;
+ break;
+ case LDAP_MOD_DELETE:
+ if ( legacy
+ && (c->type == UNIQUE_IGNORE) == legacy->ignore
+ && legacy->uri
+ && legacy->uri->attrs) {
+ if ( c->valx < 0 ) { /* delete all */
+ for ( attr = legacy->uri->attrs;
+ attr;
+ attr = next_attr ) {
+ next_attr = attr->next;
+ ch_free ( attr );
+ }
+ legacy->uri->attrs = NULL;
+ } else { /* delete by index */
+ attrp = &legacy->uri->attrs;
+ for ( i=0; i < c->valx; ++i )
+ attrp = &(*attrp)->next;
+ attr = *attrp;
+ *attrp = attr->next;
+ ch_free (attr);
+ }
+ if ( !legacy->uri->attrs
+ && !legacy->uri->dn.bv_val ) {
+ unique_free_domain_uri ( legacy->uri );
+ legacy->uri = NULL;
+ }
+ if ( !legacy->uri && !private->legacy_strict_set ) {
+ unique_free_domain ( legacy );
+ private->legacy = legacy = NULL;
+ }
+ }
+ rc = 0;
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( legacy
+ && legacy->uri
+ && legacy->uri->attrs
+ && (c->type == UNIQUE_IGNORE) != legacy->ignore ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set both attrs and ignore-attrs" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( !legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ if ( !legacy->uri )
+ unique_new_domain_uri_basic ( &legacy->uri, c );
+ rc = 0;
+ for ( i=1; c->argv[i]; ++i ) {
+ AttributeDescription * ad = NULL;
+ const char * text;
+ if ( slap_str2ad ( c->argv[i], &ad, &text )
+ == LDAP_SUCCESS) {
+
+ attr = ch_calloc ( 1,
+ sizeof ( unique_attrs ) );
+ attr->attr = ad;
+ attr->next = new_attrs;
+ new_attrs = attr;
+ } else {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unique: attribute: %s: %s",
+ c->argv[i], text );
+ for ( attr = new_attrs;
+ attr;
+ attr=next_attr ) {
+ next_attr = attr->next;
+ ch_free ( attr );
+ }
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ }
+ if ( rc ) break;
+
+ /* (nconc legacy->uri->attrs (nreverse new_attrs)) */
+ reverse_attrs = NULL;
+ for ( attr = new_attrs;
+ attr;
+ attr = next_attr ) {
+ next_attr = attr->next;
+ attr->next = reverse_attrs;
+ reverse_attrs = attr;
+ }
+ for ( attrp = &legacy->uri->attrs;
+ *attrp;
+ attrp = &(*attrp)->next ) ;
+ *attrp = reverse_attrs;
+
+ legacy->ignore = ( c->type == UNIQUE_IGNORE );
+ break;
+ default:
+ abort();
+ }
+
+ if ( rc ) {
+ Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE,
+ "%s: %s\n", c->log, c->cr_msg, 0 );
+ }
+ return rc;
+}
+
+static int
+unique_cf_strict( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ int rc = ARG_BAD_CONF;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ /* We process the boolean manually instead of using
+ * ARG_ON_OFF so that we can three-state it;
+ * olcUniqueStrict is either TRUE, FALSE, or missing,
+ * and missing is necessary to add olcUniqueURIs...
+ */
+ if ( private->legacy_strict_set ) {
+ struct berval bv;
+ bv.bv_val = legacy->strict ? "TRUE" : "FALSE";
+ bv.bv_len = legacy->strict ?
+ STRLENOF("TRUE") :
+ STRLENOF("FALSE");
+ value_add_one ( &c->rvalue_vals, &bv );
+ }
+ rc = 0;
+ break;
+ case LDAP_MOD_DELETE:
+ if ( legacy ) {
+ legacy->strict = 0;
+ if ( ! legacy->uri ) {
+ unique_free_domain ( legacy );
+ private->legacy = NULL;
+ }
+ }
+ private->legacy_strict_set = 0;
+ rc = 0;
+ break;
+ case LDAP_MOD_ADD:
+ case SLAP_CONFIG_ADD:
+ if ( domains ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set legacy attrs when URIs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( ! legacy ) {
+ unique_new_domain ( &private->legacy,
+ UNIQUE_DEFAULT_URI,
+ c );
+ legacy = private->legacy;
+ }
+ /* ... not using ARG_ON_OFF makes this necessary too */
+ assert ( c->argc == 2 );
+ legacy->strict = (strcasecmp ( c->argv[1], "TRUE" ) == 0);
+ private->legacy_strict_set = 1;
+ rc = 0;
+ break;
+ default:
+ abort();
+ }
+
+ return rc;
+}
+
+static int
+unique_cf_uri( ConfigArgs *c )
+{
+ slap_overinst *on = (slap_overinst *)c->bi;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain = NULL, **domainp = NULL;
+ int rc = ARG_BAD_CONF;
+ int i;
+
+ switch ( c->op ) {
+ case SLAP_CONFIG_EMIT:
+ for ( domain = domains;
+ domain;
+ domain = domain->next ) {
+ rc = value_add_one ( &c->rvalue_vals,
+ &domain->domain_spec );
+ if ( rc ) break;
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if ( c->valx < 0 ) { /* delete them all! */
+ unique_free_domain ( domains );
+ private->domains = NULL;
+ } else { /* delete just one */
+ domainp = &private->domains;
+ for ( i=0; i < c->valx && *domainp; ++i )
+ domainp = &(*domainp)->next;
+
+ /* If *domainp is null, we walked off the end
+ * of the list. This happens when back-config
+ * and the overlay are out-of-sync, like when
+ * rejecting changes before ITS#4752 gets
+ * fixed.
+ *
+ * This should never happen, but will appear
+ * if you backport this version of
+ * slapo-unique without the config-undo fixes
+ *
+ * test024 Will hit this case in such a
+ * situation.
+ */
+ assert (*domainp != NULL);
+
+ domain = *domainp;
+ *domainp = domain->next;
+ domain->next = NULL;
+ unique_free_domain ( domain );
+ }
+ rc = 0;
+ break;
+
+ case SLAP_CONFIG_ADD: /* fallthrough */
+ case LDAP_MOD_ADD:
+ if ( legacy ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot set Uri when legacy attrs are present" );
+ Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n",
+ c->cr_msg, NULL, NULL );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ rc = 0;
+ if ( c->line ) rc = unique_new_domain ( &domain, c->line, c );
+ else rc = unique_new_domain ( &domain, c->argv[1], c );
+ if ( rc ) break;
+ assert ( domain->next == NULL );
+ for ( domainp = &private->domains;
+ *domainp;
+ domainp = &(*domainp)->next ) ;
+ *domainp = domain;
+
+ break;
+
+ default:
+ abort ();
+ }
+
+ return rc;
+}
+
+/*
+** allocate new unique_data;
+** initialize, copy basedn;
+** store in on_bi.bi_private;
+**
+*/
+
+static int
+unique_db_init(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ unique_data **privatep = (unique_data **) &on->on_bi.bi_private;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n", 0, 0, 0);
+
+ *privatep = ch_calloc ( 1, sizeof ( unique_data ) );
+
+ return 0;
+}
+
+static int
+unique_db_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ unique_data **privatep = (unique_data **) &on->on_bi.bi_private;
+ unique_data *private = *privatep;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n", 0, 0, 0);
+
+ if ( private ) {
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+
+ unique_free_domain ( domains );
+ unique_free_domain ( legacy );
+ ch_free ( private );
+ *privatep = NULL;
+ }
+
+ return 0;
+}
+
+
+/*
+** search callback
+** if this is a REP_SEARCH, count++;
+**
+*/
+
+static int count_attr_cb(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ unique_counter *uc;
+
+ /* because you never know */
+ if(!op || !rs) return(0);
+
+ /* Only search entries are interesting */
+ if(rs->sr_type != REP_SEARCH) return(0);
+
+ uc = op->o_callback->sc_private;
+
+ /* Ignore the current entry */
+ if ( dn_match( uc->ndn, &rs->sr_entry->e_nname )) return(0);
+
+ Debug(LDAP_DEBUG_TRACE, "==> count_attr_cb <%s>\n",
+ rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN", 0, 0);
+
+ uc->count++;
+
+ return(0);
+}
+
+/* count the length of one attribute ad
+ * (and all of its values b)
+ * in the proposed filter
+ */
+static int
+count_filter_len(
+ unique_domain *domain,
+ unique_domain_uri *uri,
+ AttributeDescription *ad,
+ BerVarray b
+)
+{
+ unique_attrs *attr;
+ int i;
+ int ks = 0;
+
+ while ( !is_at_operational( ad->ad_type ) ) {
+ if ( uri->attrs ) {
+ for ( attr = uri->attrs; attr; attr = attr->next ) {
+ if ( ad == attr->attr ) {
+ break;
+ }
+ }
+ if ( ( domain->ignore && attr )
+ || (!domain->ignore && !attr )) {
+ break;
+ }
+ }
+ if ( b && b[0].bv_val ) {
+ for (i = 0; b[i].bv_val; i++ ) {
+ /* note: make room for filter escaping... */
+ ks += ( 3 * b[i].bv_len ) + ad->ad_cname.bv_len + STRLENOF( "(=)" );
+ }
+ } else if ( domain->strict ) {
+ ks += ad->ad_cname.bv_len + STRLENOF( "(=*)" ); /* (attr=*) */
+ }
+ break;
+ }
+
+ return ks;
+}
+
+static char *
+build_filter(
+ unique_domain *domain,
+ unique_domain_uri *uri,
+ AttributeDescription *ad,
+ BerVarray b,
+ char *kp,
+ int ks,
+ void *ctx
+)
+{
+ unique_attrs *attr;
+ int i;
+
+ while ( !is_at_operational( ad->ad_type ) ) {
+ if ( uri->attrs ) {
+ for ( attr = uri->attrs; attr; attr = attr->next ) {
+ if ( ad == attr->attr ) {
+ break;
+ }
+ }
+ if ( ( domain->ignore && attr )
+ || (!domain->ignore && !attr )) {
+ break;
+ }
+ }
+ if ( b && b[0].bv_val ) {
+ for ( i = 0; b[i].bv_val; i++ ) {
+ struct berval bv;
+ int len;
+
+ ldap_bv2escaped_filter_value_x( &b[i], &bv, 1, ctx );
+ if (!b[i].bv_len)
+ bv.bv_val = b[i].bv_val;
+ len = snprintf( kp, ks, "(%s=%s)", ad->ad_cname.bv_val, bv.bv_val );
+ assert( len >= 0 && len < ks );
+ kp += len;
+ if ( bv.bv_val != b[i].bv_val ) {
+ ber_memfree_x( bv.bv_val, ctx );
+ }
+ }
+ } else if ( domain->strict ) {
+ int len;
+ len = snprintf( kp, ks, "(%s=*)", ad->ad_cname.bv_val );
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ break;
+ }
+ return kp;
+}
+
+static int
+unique_search(
+ Operation *op,
+ Operation *nop,
+ struct berval * dn,
+ int scope,
+ SlapReply *rs,
+ struct berval *key
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ SlapReply nrs = { REP_RESULT };
+ slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */
+ unique_counter uq = { NULL, 0 };
+ int rc;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key->bv_val, 0, 0);
+
+ nop->ors_filter = str2filter_x(nop, key->bv_val);
+ if(nop->ors_filter == NULL) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_OTHER,
+ "unique_search invalid filter");
+ return(rs->sr_err);
+ }
+
+ nop->ors_filterstr = *key;
+
+ cb.sc_response = (slap_response*)count_attr_cb;
+ cb.sc_private = &uq;
+ nop->o_callback = &cb;
+ nop->o_tag = LDAP_REQ_SEARCH;
+ nop->ors_scope = scope;
+ nop->ors_deref = LDAP_DEREF_NEVER;
+ nop->ors_limit = NULL;
+ nop->ors_slimit = SLAP_NO_LIMIT;
+ nop->ors_tlimit = SLAP_NO_LIMIT;
+ nop->ors_attrs = slap_anlist_no_attrs;
+ nop->ors_attrsonly = 1;
+
+ uq.ndn = &op->o_req_ndn;
+
+ nop->o_req_ndn = *dn;
+ nop->o_ndn = op->o_bd->be_rootndn;
+
+ nop->o_bd = on->on_info->oi_origdb;
+ rc = nop->o_bd->be_search(nop, &nrs);
+ filter_free_x(nop, nop->ors_filter, 1);
+ op->o_tmpfree( key->bv_val, op->o_tmpmemctx );
+
+ if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, rc, "unique_search failed");
+ return(rs->sr_err);
+ }
+
+ Debug(LDAP_DEBUG_TRACE, "=> unique_search found %d records\n", uq.count, 0, 0);
+
+ if(uq.count) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "some attributes not unique");
+ return(rs->sr_err);
+ }
+
+ return(SLAP_CB_CONTINUE);
+}
+
+static int
+unique_add(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Attribute *a;
+ char *key, *kp;
+ struct berval bvkey;
+ int rc = SLAP_CB_CONTINUE;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n",
+ op->o_req_dn.bv_val, 0, 0);
+
+ /* skip the checks if the operation has manageDsaIt control in it
+ * (for replication) */
+ if ( op->o_managedsait > SLAP_CONTROL_IGNORED
+ && access_allowed ( op, op->ora_e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_add: administrative bypass, skipping\n", 0, 0, 0);
+ return rc;
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ))
+ continue;
+
+ if ( uri->f ) {
+ if ( test_filter( NULL, op->ora_e, uri->f )
+ == LDAP_COMPARE_FALSE )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ "==> unique_add_skip<%s>\n",
+ op->o_req_dn.bv_val, 0, 0 );
+ continue;
+ }
+ }
+
+ if(!(a = op->ora_e->e_attrs)) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "unique_add() got null op.ora_e.e_attrs");
+ rc = rs->sr_err;
+ break;
+
+ } else {
+ for(; a; a = a->a_next) {
+ ks += count_filter_len ( domain,
+ uri,
+ a->a_desc,
+ a->a_vals);
+ }
+ }
+
+ /* skip this domain-uri if it isn't involved */
+ if ( !ks ) continue;
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for(a = op->ora_e->e_attrs; a; a = a->a_next)
+ kp = build_filter(domain,
+ uri,
+ a->a_desc,
+ a->a_vals,
+ kp,
+ ks - ( kp - key ),
+ op->o_tmpmemctx);
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ return rc;
+}
+
+
+static int
+unique_modify(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Modifications *m;
+ Entry *e = NULL;
+ char *key, *kp;
+ struct berval bvkey;
+ int rc = SLAP_CB_CONTINUE;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n",
+ op->o_req_dn.bv_val, 0, 0);
+
+ if ( !op->orm_modlist ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_modify: got empty modify op\n", 0, 0, 0);
+ return rc;
+ }
+
+ /* skip the checks if the operation has manageDsaIt control in it
+ * (for replication) */
+ if ( op->o_managedsait > SLAP_CONTROL_IGNORED
+ && overlay_entry_get_ov(op, &op->o_req_ndn, NULL, NULL, 0, &e, on) == LDAP_SUCCESS
+ && e
+ && access_allowed ( op, e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_modify: administrative bypass, skipping\n", 0, 0, 0);
+ overlay_entry_release_ov( op, e, 0, on );
+ return rc;
+ }
+ if ( e ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ))
+ continue;
+
+ for ( m = op->orm_modlist; m; m = m->sml_next)
+ if ( (m->sml_op & LDAP_MOD_OP)
+ != LDAP_MOD_DELETE )
+ ks += count_filter_len
+ ( domain,
+ uri,
+ m->sml_desc,
+ m->sml_values);
+
+ /* skip this domain-uri if it isn't involved */
+ if ( !ks ) continue;
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for(m = op->orm_modlist; m; m = m->sml_next)
+ if ( (m->sml_op & LDAP_MOD_OP)
+ != LDAP_MOD_DELETE )
+ kp = build_filter ( domain,
+ uri,
+ m->sml_desc,
+ m->sml_values,
+ kp,
+ ks - (kp - key),
+ op->o_tmpmemctx );
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ return rc;
+}
+
+
+static int
+unique_modrdn(
+ Operation *op,
+ SlapReply *rs
+)
+{
+ slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
+ unique_data *private = (unique_data *) on->on_bi.bi_private;
+ unique_domain *domains = private->domains;
+ unique_domain *legacy = private->legacy;
+ unique_domain *domain;
+ Operation nop = *op;
+ Entry *e = NULL;
+ char *key, *kp;
+ struct berval bvkey;
+ LDAPRDN newrdn;
+ struct berval bv[2];
+ int rc = SLAP_CB_CONTINUE;
+
+ Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n",
+ op->o_req_dn.bv_val, op->orr_newrdn.bv_val, 0);
+
+ /* skip the checks if the operation has manageDsaIt control in it
+ * (for replication) */
+ if ( op->o_managedsait > SLAP_CONTROL_IGNORED
+ && overlay_entry_get_ov(op, &op->o_req_ndn, NULL, NULL, 0, &e, on) == LDAP_SUCCESS
+ && e
+ && access_allowed ( op, e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_MANAGE, NULL ) ) {
+ Debug(LDAP_DEBUG_TRACE, "unique_modrdn: administrative bypass, skipping\n", 0, 0, 0);
+ overlay_entry_release_ov( op, e, 0, on );
+ return rc;
+ }
+ if ( e ) {
+ overlay_entry_release_ov( op, e, 0, on );
+ }
+
+ for ( domain = legacy ? legacy : domains;
+ domain;
+ domain = domain->next )
+ {
+ unique_domain_uri *uri;
+
+ for ( uri = domain->uri;
+ uri;
+ uri = uri->next )
+ {
+ int i, len;
+ int ks = 0;
+
+ if ( uri->ndn.bv_val
+ && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )
+ && (!op->orr_nnewSup
+ || !dnIsSuffix( op->orr_nnewSup, &uri->ndn )))
+ continue;
+
+ if ( ldap_bv2rdn_x ( &op->oq_modrdn.rs_newrdn,
+ &newrdn,
+ (char **)&rs->sr_text,
+ LDAP_DN_FORMAT_LDAP,
+ op->o_tmpmemctx ) ) {
+ op->o_bd->bd_info = (BackendInfo *) on->on_info;
+ send_ldap_error(op, rs, LDAP_INVALID_SYNTAX,
+ "unknown type(s) used in RDN");
+ rc = rs->sr_err;
+ break;
+ }
+
+ rc = SLAP_CB_CONTINUE;
+ for ( i=0; newrdn[i]; i++) {
+ AttributeDescription *ad = NULL;
+ if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) {
+ ldap_rdnfree_x( newrdn, op->o_tmpmemctx );
+ rs->sr_err = LDAP_INVALID_SYNTAX;
+ send_ldap_result( op, rs );
+ rc = rs->sr_err;
+ break;
+ }
+ newrdn[i]->la_private = ad;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+
+ bv[1].bv_val = NULL;
+ bv[1].bv_len = 0;
+
+ for ( i=0; newrdn[i]; i++ ) {
+ bv[0] = newrdn[i]->la_value;
+ ks += count_filter_len ( domain,
+ uri,
+ newrdn[i]->la_private,
+ bv);
+ }
+
+ /* skip this domain if it isn't involved */
+ if ( !ks ) continue;
+
+ /* terminating NUL */
+ ks += sizeof("(|)");
+
+ if ( uri->filter.bv_val && uri->filter.bv_len )
+ ks += uri->filter.bv_len + STRLENOF ("(&)");
+ kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx);
+
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf(kp, ks, "(&%s", uri->filter.bv_val);
+ assert( len >= 0 && len < ks );
+ kp += len;
+ }
+ len = snprintf(kp, ks - (kp - key), "(|");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+
+ for ( i=0; newrdn[i]; i++) {
+ bv[0] = newrdn[i]->la_value;
+ kp = build_filter ( domain,
+ uri,
+ newrdn[i]->la_private,
+ bv,
+ kp,
+ ks - (kp - key ),
+ op->o_tmpmemctx);
+ }
+
+ len = snprintf(kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ if ( uri->filter.bv_val && uri->filter.bv_len ) {
+ len = snprintf (kp, ks - (kp - key), ")");
+ assert( len >= 0 && len < ks - (kp - key) );
+ kp += len;
+ }
+ bvkey.bv_val = key;
+ bvkey.bv_len = kp - key;
+
+ rc = unique_search ( op,
+ &nop,
+ uri->ndn.bv_val ?
+ &uri->ndn :
+ &op->o_bd->be_nsuffix[0],
+ uri->scope,
+ rs,
+ &bvkey);
+
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+ if ( rc != SLAP_CB_CONTINUE ) break;
+ }
+
+ return rc;
+}
+
+/*
+** init_module is last so the symbols resolve "for free" --
+** it expects to be called automagically during dynamic module initialization
+*/
+
+int
+unique_initialize()
+{
+ int rc;
+
+ /* statically declared just after the #includes at top */
+ memset (&unique, 0, sizeof(unique));
+
+ unique.on_bi.bi_type = "unique";
+ unique.on_bi.bi_db_init = unique_db_init;
+ unique.on_bi.bi_db_destroy = unique_db_destroy;
+ unique.on_bi.bi_op_add = unique_add;
+ unique.on_bi.bi_op_modify = unique_modify;
+ unique.on_bi.bi_op_modrdn = unique_modrdn;
+
+ unique.on_bi.bi_cf_ocs = uniqueocs;
+ rc = config_register_schema( uniquecfg, uniqueocs );
+ if ( rc ) return rc;
+
+ return(overlay_register(&unique));
+}
+
+#if SLAPD_OVER_UNIQUE == SLAPD_MOD_DYNAMIC && defined(PIC)
+int init_module(int argc, char *argv[]) {
+ return unique_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_UNIQUE */
diff --git a/servers/slapd/overlays/valsort.c b/servers/slapd/overlays/valsort.c
new file mode 100644
index 0000000..4386ad5
--- /dev/null
+++ b/servers/slapd/overlays/valsort.c
@@ -0,0 +1,580 @@
+/* valsort.c - sort attribute values */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2005-2018 The OpenLDAP Foundation.
+ * Portions copyright 2005 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Howard Chu for inclusion in
+ * OpenLDAP Software. This work was sponsored by Stanford University.
+ */
+
+/*
+ * This overlay sorts the values of multi-valued attributes when returning
+ * them in a search response.
+ */
+#include "portable.h"
+
+#ifdef SLAPD_OVER_VALSORT
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/ctype.h>
+
+#include "slap.h"
+#include "config.h"
+#include "lutil.h"
+
+#define VALSORT_ASCEND 0
+#define VALSORT_DESCEND 1
+
+#define VALSORT_ALPHA 2
+#define VALSORT_NUMERIC 4
+
+#define VALSORT_WEIGHTED 8
+
+typedef struct valsort_info {
+ struct valsort_info *vi_next;
+ struct berval vi_dn;
+ AttributeDescription *vi_ad;
+ slap_mask_t vi_sort;
+} valsort_info;
+
+static int valsort_cid;
+
+static ConfigDriver valsort_cf_func;
+
+static ConfigTable valsort_cfats[] = {
+ { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC,
+ valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' "
+ "DESC 'Sorting rule for attribute under given DN' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { NULL }
+};
+
+static ConfigOCs valsort_cfocs[] = {
+ { "( OLcfgOvOc:5.1 "
+ "NAME 'olcValSortConfig' "
+ "DESC 'Value Sorting configuration' "
+ "SUP olcOverlayConfig "
+ "MUST olcValSortAttr )",
+ Cft_Overlay, valsort_cfats },
+ { NULL }
+};
+
+static slap_verbmasks sorts[] = {
+ { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA },
+ { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA },
+ { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC },
+ { BER_BVC("numeric-descend"), VALSORT_DESCEND|VALSORT_NUMERIC },
+ { BER_BVC("weighted"), VALSORT_WEIGHTED },
+ { BER_BVNULL, 0 }
+};
+
+static Syntax *syn_numericString;
+
+static int
+valsort_cf_func(ConfigArgs *c) {
+ slap_overinst *on = (slap_overinst *)c->bi;
+ valsort_info vitmp, *vi;
+ const char *text = NULL;
+ int i, is_numeric;
+ struct berval bv = BER_BVNULL;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) {
+ struct berval bv2 = BER_BVNULL, bvret;
+ char *ptr;
+ int len;
+
+ len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 2;
+ i = vi->vi_sort;
+ if ( i & VALSORT_WEIGHTED ) {
+ enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 );
+ len += bv2.bv_len + 1;
+ i ^= VALSORT_WEIGHTED;
+ }
+ if ( i ) {
+ enum_to_verb( sorts, i, &bv );
+ len += bv.bv_len + 1;
+ }
+ bvret.bv_val = ch_malloc( len+1 );
+ bvret.bv_len = len;
+
+ ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val );
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val );
+ *ptr++ = '"';
+ if ( vi->vi_sort & VALSORT_WEIGHTED ) {
+ *ptr++ = ' ';
+ ptr = lutil_strcopy( ptr, bv2.bv_val );
+ }
+ if ( i ) {
+ *ptr++ = ' ';
+ strcpy( ptr, bv.bv_val );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bvret );
+ }
+ i = ( c->rvalue_vals != NULL ) ? 0 : 1;
+ return i;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+ if ( c->valx < 0 ) {
+ for ( vi = on->on_bi.bi_private; vi; vi = on->on_bi.bi_private ) {
+ on->on_bi.bi_private = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+ } else {
+ valsort_info **prev;
+
+ for (i=0, prev = (valsort_info **)&on->on_bi.bi_private,
+ vi = *prev; vi && i<c->valx;
+ prev = &vi->vi_next, vi = vi->vi_next, i++ );
+ (*prev)->vi_next = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+ return 0;
+ }
+ vitmp.vi_ad = NULL;
+ i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg), "<%s> %s", c->argv[0], text );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ if ( is_at_single_value( vitmp.vi_ad->ad_type )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> %s is single-valued, ignoring", c->argv[0],
+ vitmp.vi_ad->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(0);
+ }
+ is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString ||
+ vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1
+ : 0;
+ ber_str2bv( c->argv[2], 0, 0, &bv );
+ i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL );
+ if ( i ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to normalize DN", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[2] );
+ return(1);
+ }
+ i = verb_to_mask( c->argv[3], sorts );
+ if ( BER_BVISNULL( &sorts[i].word )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[3] );
+ return(1);
+ }
+ vitmp.vi_sort = sorts[i].mask;
+ if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) {
+ i = verb_to_mask( c->argv[4], sorts );
+ if ( BER_BVISNULL( &sorts[i].word )) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[4] );
+ return(1);
+ }
+ vitmp.vi_sort |= sorts[i].mask;
+ }
+ if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> numeric sort specified for non-numeric syntax",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n",
+ c->log, c->cr_msg, c->argv[1] );
+ return(1);
+ }
+ vi = ch_malloc( sizeof(valsort_info) );
+ *vi = vitmp;
+ vi->vi_next = on->on_bi.bi_private;
+ on->on_bi.bi_private = vi;
+ return 0;
+}
+
+/* Use Insertion Sort algorithm on selected values */
+static void
+do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort )
+{
+ int i, j, gotnvals;
+ struct berval tmp, ntmp, *vals = NULL, *nvals;
+
+ gotnvals = (a->a_vals != a->a_nvals );
+
+ nvals = a->a_nvals + beg;
+ if ( gotnvals )
+ vals = a->a_vals + beg;
+
+ if ( sort & VALSORT_NUMERIC ) {
+ long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ),
+ idx;
+ for (i=0; i<num; i++)
+ numbers[i] = strtol( nvals[i].bv_val, NULL, 0 );
+
+ for (i=1; i<num; i++) {
+ idx = numbers[i];
+ ntmp = nvals[i];
+ if ( gotnvals ) tmp = vals[i];
+ j = i;
+ while ( j>0 ) {
+ int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx :
+ numbers[j-1] > idx;
+ if ( !cmp ) break;
+ numbers[j] = numbers[j-1];
+ nvals[j] = nvals[j-1];
+ if ( gotnvals ) vals[j] = vals[j-1];
+ j--;
+ }
+ numbers[j] = idx;
+ nvals[j] = ntmp;
+ if ( gotnvals ) vals[j] = tmp;
+ }
+ op->o_tmpfree( numbers, op->o_tmpmemctx );
+ } else {
+ for (i=1; i<num; i++) {
+ ntmp = nvals[i];
+ if ( gotnvals ) tmp = vals[i];
+ j = i;
+ while ( j>0 ) {
+ int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val );
+ cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0);
+ if ( !cmp ) break;
+
+ nvals[j] = nvals[j-1];
+ if ( gotnvals ) vals[j] = vals[j-1];
+ j--;
+ }
+ nvals[j] = ntmp;
+ if ( gotnvals ) vals[j] = tmp;
+ }
+ }
+}
+
+static int
+valsort_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on;
+ valsort_info *vi;
+ Attribute *a;
+
+ /* If this is not a search response, or it is a syncrepl response,
+ * or the valsort control wants raw results, pass thru unmodified.
+ */
+ if ( rs->sr_type != REP_SEARCH ||
+ ( _SCM(op->o_sync) > SLAP_CONTROL_IGNORED ) ||
+ ( op->o_ctrlflag[valsort_cid] & SLAP_CONTROL_DATA0))
+ return SLAP_CB_CONTINUE;
+
+ on = (slap_overinst *) op->o_bd->bd_info;
+ vi = on->on_bi.bi_private;
+
+ /* And we must have something configured */
+ if ( !vi ) return SLAP_CB_CONTINUE;
+
+ /* Find a rule whose baseDN matches this entry */
+ for (; vi; vi = vi->vi_next ) {
+ int i, n;
+
+ if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn ))
+ continue;
+
+ /* Find attr that this rule affects */
+ a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
+ if ( !a ) continue;
+
+ if ( rs_entry2modifiable( op, rs, on )) {
+ a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad );
+ }
+
+ n = a->a_numvals;
+ if ( vi->vi_sort & VALSORT_WEIGHTED ) {
+ int j, gotnvals;
+ long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx );
+
+ gotnvals = (a->a_vals != a->a_nvals );
+
+ for (i=0; i<n; i++) {
+ char *ptr = ber_bvchr( &a->a_nvals[i], '{' );
+ char *end = NULL;
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s "
+ "in entry %s\n", vi->vi_ad->ad_cname.bv_val,
+ rs->sr_entry->e_name.bv_val, 0 );
+ break;
+ }
+ index[i] = strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weights misformatted "
+ "in entry %s\n",
+ rs->sr_entry->e_name.bv_val, 0, 0 );
+ break;
+ }
+ /* Strip out weights */
+ ptr = a->a_nvals[i].bv_val;
+ end++;
+ for (;*end;)
+ *ptr++ = *end++;
+ *ptr = '\0';
+ a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val;
+
+ if ( a->a_vals != a->a_nvals ) {
+ ptr = a->a_vals[i].bv_val;
+ end = ber_bvchr( &a->a_vals[i], '}' );
+ assert( end != NULL );
+ end++;
+ for (;*end;)
+ *ptr++ = *end++;
+ *ptr = '\0';
+ a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val;
+ }
+ }
+ /* An attr was missing weights here, ignore it */
+ if ( i<n ) {
+ op->o_tmpfree( index, op->o_tmpmemctx );
+ continue;
+ }
+ /* Insertion sort */
+ for ( i=1; i<n; i++) {
+ long idx = index[i];
+ struct berval tmp = a->a_vals[i], ntmp;
+ if ( gotnvals ) ntmp = a->a_nvals[i];
+ j = i;
+ while (( j>0 ) && (index[j-1] > idx )) {
+ index[j] = index[j-1];
+ a->a_vals[j] = a->a_vals[j-1];
+ if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1];
+ j--;
+ }
+ index[j] = idx;
+ a->a_vals[j] = tmp;
+ if ( gotnvals ) a->a_nvals[j] = ntmp;
+ }
+ /* Check for secondary sort */
+ if ( vi->vi_sort ^ VALSORT_WEIGHTED ) {
+ for ( i=0; i<n;) {
+ for (j=i+1; j<n; j++) {
+ if (index[i] != index[j])
+ break;
+ }
+ if( j-i > 1 )
+ do_sort( op, a, i, j-i, vi->vi_sort );
+ i = j;
+ }
+ }
+ op->o_tmpfree( index, op->o_tmpmemctx );
+ } else {
+ do_sort( op, a, 0, n, vi->vi_sort );
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ valsort_info *vi = on->on_bi.bi_private;
+
+ Attribute *a;
+ int i;
+ char *ptr, *end;
+
+ /* See if any weighted sorting applies to this entry */
+ for ( ;vi;vi=vi->vi_next ) {
+ if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
+ continue;
+ if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
+ continue;
+ a = attr_find( op->ora_e->e_attrs, vi->vi_ad );
+ if ( !a )
+ continue;
+ for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) {
+ ptr = ber_bvchr(&a->a_vals[i], '{' );
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
+ vi->vi_ad->ad_cname.bv_val, 0, 0);
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight missing from attribute" );
+ return rs->sr_err;
+ }
+ strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
+ vi->vi_ad->ad_cname.bv_val, 0, 0);
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight is misformatted" );
+ return rs->sr_err;
+ }
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_modify( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ valsort_info *vi = on->on_bi.bi_private;
+
+ Modifications *ml;
+ int i;
+ char *ptr, *end;
+
+ /* See if any weighted sorting applies to this entry */
+ for ( ;vi;vi=vi->vi_next ) {
+ if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn ))
+ continue;
+ if ( !(vi->vi_sort & VALSORT_WEIGHTED ))
+ continue;
+ for (ml = op->orm_modlist; ml; ml=ml->sml_next ) {
+ /* Must be a Delete Attr op, so no values to consider */
+ if ( !ml->sml_values )
+ continue;
+ if ( ml->sml_desc == vi->vi_ad )
+ break;
+ }
+ if ( !ml )
+ continue;
+ for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) {
+ ptr = ber_bvchr(&ml->sml_values[i], '{' );
+ if ( !ptr ) {
+ Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n",
+ vi->vi_ad->ad_cname.bv_val, 0, 0);
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight missing from attribute" );
+ return rs->sr_err;
+ }
+ strtol( ptr+1, &end, 0 );
+ if ( *end != '}' ) {
+ Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n",
+ vi->vi_ad->ad_cname.bv_val, 0, 0);
+ send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION,
+ "weight is misformatted" );
+ return rs->sr_err;
+ }
+ }
+ }
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+valsort_db_open(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ return overlay_register_control( be, LDAP_CONTROL_VALSORT );
+}
+
+static int
+valsort_destroy(
+ BackendDB *be,
+ ConfigReply *cr
+)
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ valsort_info *vi = on->on_bi.bi_private, *next;
+
+#ifdef SLAP_CONFIG_DELETE
+ overlay_unregister_control( be, LDAP_CONTROL_VALSORT );
+#endif /* SLAP_CONFIG_DELETE */
+
+ for (; vi; vi = next) {
+ next = vi->vi_next;
+ ch_free( vi->vi_dn.bv_val );
+ ch_free( vi );
+ }
+
+ return 0;
+}
+
+static int
+valsort_parseCtrl(
+ Operation *op,
+ SlapReply *rs,
+ LDAPControl *ctrl )
+{
+ ber_tag_t tag;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_int_t flag = 0;
+
+ if ( BER_BVISNULL( &ctrl->ldctl_value )) {
+ rs->sr_text = "valSort control value is absent";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ if ( BER_BVISEMPTY( &ctrl->ldctl_value )) {
+ rs->sr_text = "valSort control value is empty";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ ber_init2( ber, &ctrl->ldctl_value, 0 );
+ if (( tag = ber_scanf( ber, "{b}", &flag )) == LBER_ERROR ) {
+ rs->sr_text = "valSort control: flag decoding error";
+ return LDAP_PROTOCOL_ERROR;
+ }
+
+ op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ?
+ SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL;
+ if ( flag )
+ op->o_ctrlflag[valsort_cid] |= SLAP_CONTROL_DATA0;
+
+ return LDAP_SUCCESS;
+}
+
+static slap_overinst valsort;
+
+int valsort_initialize( void )
+{
+ int rc;
+
+ valsort.on_bi.bi_type = "valsort";
+ valsort.on_bi.bi_db_destroy = valsort_destroy;
+ valsort.on_bi.bi_db_open = valsort_db_open;
+
+ valsort.on_bi.bi_op_add = valsort_add;
+ valsort.on_bi.bi_op_modify = valsort_modify;
+
+ valsort.on_response = valsort_response;
+
+ valsort.on_bi.bi_cf_ocs = valsort_cfocs;
+
+ rc = register_supported_control( LDAP_CONTROL_VALSORT,
+ SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl,
+ &valsort_cid );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", rc, 0, 0 );
+ return rc;
+ }
+
+ syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" );
+
+ rc = config_register_schema( valsort_cfats, valsort_cfocs );
+ if ( rc ) return rc;
+
+ return overlay_register(&valsort);
+}
+
+#if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC
+int init_module( int argc, char *argv[]) {
+ return valsort_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_VALSORT */