diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
commit | 5ea77a75dd2d2158401331879f3c8f47940a732c (patch) | |
tree | d89dc06e9f4850a900f161e25f84e922c4f86cc8 /servers/slapd/overlays | |
parent | Initial commit. (diff) | |
download | openldap-5ea77a75dd2d2158401331879f3c8f47940a732c.tar.xz openldap-5ea77a75dd2d2158401331879f3c8f47940a732c.zip |
Adding upstream version 2.5.13+dfsg.upstream/2.5.13+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers/slapd/overlays')
32 files changed, 44463 insertions, 0 deletions
diff --git a/servers/slapd/overlays/Makefile.in b/servers/slapd/overlays/Makefile.in new file mode 100644 index 0000000..e6711fe --- /dev/null +++ b/servers/slapd/overlays/Makefile.in @@ -0,0 +1,172 @@ +# Makefile.in for overlays +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 2003-2022 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = overlays.c \ + accesslog.c \ + auditlog.c \ + autoca.c \ + constraint.c \ + dds.c \ + deref.c \ + dyngroup.c \ + dynlist.c \ + homedir.c \ + memberof.c \ + otp.c \ + pcache.c \ + collect.c \ + ppolicy.c \ + refint.c \ + remoteauth.c \ + retcode.c \ + rwm.c rwmconf.c rwmdn.c rwmmap.c \ + seqmod.c \ + sssvlv.c \ + syncprov.c \ + translucent.c \ + unique.c \ + valsort.c +OBJS = statover.o \ + @SLAPD_STATIC_OVERLAYS@ \ + overlays.o + +# Add here the objs that are needed by overlays, but do not make it +# into SLAPD_STATIC_OVERLAYS... +OBJDEP=rwm.o rwmconf.o rwmdn.o rwmmap.o + +LTONLY_MOD = $(LTONLY_mod) +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +MOD_DEFS = -DSLAPD_IMPORT + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBRARY = ../liboverlays.a +PROGRAMS = @SLAPD_DYNAMIC_OVERLAYS@ + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +static: $(LIBRARY) + +dynamic: $(PROGRAMS) + +accesslog.la : accesslog.lo + $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS) + +auditlog.la : auditlog.lo + $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS) + +autoca.la : autoca.lo + $(LTLINK_MOD) -module -o $@ autoca.lo version.lo $(LINK_LIBS) + +constraint.la : constraint.lo + $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS) + +dds.la : dds.lo + $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS) + +deref.la : deref.lo + $(LTLINK_MOD) -module -o $@ deref.lo version.lo $(LINK_LIBS) + +dyngroup.la : dyngroup.lo + $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS) + +dynlist.la : dynlist.lo + $(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS) + +homedir.la : homedir.lo + $(LTLINK_MOD) -module -o $@ homedir.lo version.lo $(LINK_LIBS) + +memberof.la : memberof.lo + $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS) + +otp.la : otp.lo + $(LTLINK_MOD) -module -o $@ otp.lo version.lo $(LINK_LIBS) + +pcache.la : pcache.lo + $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS) + +collect.la : collect.lo + $(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS) + +ppolicy.la : ppolicy.lo + $(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS) + +refint.la : refint.lo + $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS) + +remoteauth.la : remoteauth.lo + $(LTLINK_MOD) -module -o $@ remoteauth.lo version.lo $(LINK_LIBS) + +retcode.la : retcode.lo + $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS) + +rwm_x.o: rwm.o rwmconf.o rwmdn.o rwmmap.o + $(LD) -r -o $@ rwm.o rwmconf.o rwmdn.o rwmmap.o + +rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo + $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS) + +seqmod.la : seqmod.lo + $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS) + +sssvlv.la : sssvlv.lo + $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS) + +syncprov.la : syncprov.lo + $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS) + +translucent.la : translucent.lo + $(LTLINK_MOD) -module -o $@ translucent.lo version.lo $(LINK_LIBS) + +unique.la : unique.lo + $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS) + +valsort.la : valsort.lo + $(LTLINK_MOD) -module -o $@ valsort.lo version.lo $(LINK_LIBS) + +install-local: $(PROGRAMS) + @if test -n "$?" ; then \ + $(MKDIR) $(DESTDIR)$(moduledir); \ + $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\ + fi + +MKDEPFLAG = -l + +.SUFFIXES: .c .o .lo + +.c.lo: + $(LTCOMPILE_MOD) $< + +statover.o: statover.c $(srcdir)/../slap.h + +$(LIBRARY): $(OBJS) version.lo + $(AR) rs $@ $(OBJS) + +# Must fixup depends for non-libtool objects +depend-local: depend-common + @if test -n "$(OBJS)"; then \ + OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \ + SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \ + mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \ + $(RM) Makefile.bak; fi + +veryclean-local: + $(RM) statover.c + diff --git a/servers/slapd/overlays/README b/servers/slapd/overlays/README new file mode 100644 index 0000000..e426e4b --- /dev/null +++ b/servers/slapd/overlays/README @@ -0,0 +1,5 @@ +This directory contains a number of SLAPD overlays, some +project-maintained, some not. Some are generally usable, +others are purely experimental. Additional overlays can +be found in the contrib/slapd-modules directory. + diff --git a/servers/slapd/overlays/accesslog.c b/servers/slapd/overlays/accesslog.c new file mode 100644 index 0000000..cbdaa53 --- /dev/null +++ b/servers/slapd/overlays/accesslog.c @@ -0,0 +1,2774 @@ +/* accesslog.c - log operations for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_ACCESSLOG + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" +#include "ldap_rq.h" + +#define LOG_OP_ADD 0x001 +#define LOG_OP_DELETE 0x002 +#define LOG_OP_MODIFY 0x004 +#define LOG_OP_MODRDN 0x008 +#define LOG_OP_COMPARE 0x010 +#define LOG_OP_SEARCH 0x020 +#define LOG_OP_BIND 0x040 +#define LOG_OP_UNBIND 0x080 +#define LOG_OP_ABANDON 0x100 +#define LOG_OP_EXTENDED 0x200 +#define LOG_OP_UNKNOWN 0x400 + +#define LOG_OP_WRITES (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN) +#define LOG_OP_READS (LOG_OP_COMPARE|LOG_OP_SEARCH) +#define LOG_OP_SESSION (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON) +#define LOG_OP_ALL (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \ + LOG_OP_EXTENDED|LOG_OP_UNKNOWN) + +typedef struct log_attr { + struct log_attr *next; + AttributeDescription *attr; +} log_attr; + +typedef struct log_base { + struct log_base *lb_next; + slap_mask_t lb_ops; + struct berval lb_base; + struct berval lb_line; +} log_base; + +typedef struct log_info { + BackendDB *li_db; + struct berval li_db_suffix; + int li_open; + + slap_mask_t li_ops; + int li_age; + int li_cycle; + struct re_s *li_task; + Filter *li_oldf; + Entry *li_old; + log_attr *li_oldattrs; + struct berval li_uuid; + int li_success; + log_base *li_bases; + BerVarray li_mincsn; + int *li_sids, li_numcsns; + ldap_pvt_thread_mutex_t li_op_rmutex; + ldap_pvt_thread_mutex_t li_log_mutex; +} log_info; + +static ConfigDriver log_cf_gen; + +enum { + LOG_DB = 1, + LOG_OPS, + LOG_PURGE, + LOG_SUCCESS, + LOG_OLD, + LOG_OLDATTR, + LOG_BASE +}; + +static ConfigTable log_cfats[] = { + { "logdb", "suffix", 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|LOG_DB, + log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' " + "DESC 'Suffix of database for log content' " + "EQUALITY distinguishedNameMatch " + "SUP distinguishedName SINGLE-VALUE )", NULL, NULL }, + { "logops", "op|writes|reads|session|all", 2, 0, 0, + ARG_MAGIC|LOG_OPS, + log_cf_gen, "( OLcfgOvAt:4.2 NAME 'olcAccessLogOps' " + "DESC 'Operation types to log' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "logpurge", "age> <interval", 3, 3, 0, ARG_MAGIC|LOG_PURGE, + log_cf_gen, "( OLcfgOvAt:4.3 NAME 'olcAccessLogPurge' " + "DESC 'Log cleanup parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "logsuccess", NULL, 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|LOG_SUCCESS, + log_cf_gen, "( OLcfgOvAt:4.4 NAME 'olcAccessLogSuccess' " + "DESC 'Log successful ops only' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "logold", "filter", 2, 2, 0, ARG_MAGIC|LOG_OLD, + log_cf_gen, "( OLcfgOvAt:4.5 NAME 'olcAccessLogOld' " + "DESC 'Log old values when modifying entries matching the filter' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "logoldattr", "attrs", 2, 0, 0, ARG_MAGIC|LOG_OLDATTR, + log_cf_gen, "( OLcfgOvAt:4.6 NAME 'olcAccessLogOldAttr' " + "DESC 'Log old values of these attributes even if unmodified' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "logbase", "op|writes|reads|session|all< <baseDN", 3, 3, 0, + ARG_MAGIC|LOG_BASE, + log_cf_gen, "( OLcfgOvAt:4.7 NAME 'olcAccessLogBase' " + "DESC 'Operation types to log under a specific branch' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL } +}; + +static ConfigOCs log_cfocs[] = { + { "( OLcfgOvOc:4.1 " + "NAME 'olcAccessLogConfig' " + "DESC 'Access log configuration' " + "SUP olcOverlayConfig " + "MUST olcAccessLogDB " + "MAY ( olcAccessLogOps $ olcAccessLogPurge $ olcAccessLogSuccess $ " + "olcAccessLogOld $ olcAccessLogOldAttr $ olcAccessLogBase ) )", + Cft_Overlay, log_cfats }, + { NULL } +}; + +static slap_verbmasks logops[] = { + { BER_BVC("all"), LOG_OP_ALL }, + { BER_BVC("writes"), LOG_OP_WRITES }, + { BER_BVC("session"), LOG_OP_SESSION }, + { BER_BVC("reads"), LOG_OP_READS }, + { BER_BVC("add"), LOG_OP_ADD }, + { BER_BVC("delete"), LOG_OP_DELETE }, + { BER_BVC("modify"), LOG_OP_MODIFY }, + { BER_BVC("modrdn"), LOG_OP_MODRDN }, + { BER_BVC("compare"), LOG_OP_COMPARE }, + { BER_BVC("search"), LOG_OP_SEARCH }, + { BER_BVC("bind"), LOG_OP_BIND }, + { BER_BVC("unbind"), LOG_OP_UNBIND }, + { BER_BVC("abandon"), LOG_OP_ABANDON }, + { BER_BVC("extended"), LOG_OP_EXTENDED }, + { BER_BVC("unknown"), LOG_OP_UNKNOWN }, + { BER_BVNULL, 0 } +}; + +/* Start with "add" in logops */ +#define EN_OFFSET 4 + +enum { + LOG_EN_ADD = 0, + LOG_EN_DELETE, + LOG_EN_MODIFY, + LOG_EN_MODRDN, + LOG_EN_COMPARE, + LOG_EN_SEARCH, + LOG_EN_BIND, + LOG_EN_UNBIND, + LOG_EN_ABANDON, + LOG_EN_EXTENDED, + LOG_EN_UNKNOWN, + LOG_EN__COUNT +}; + +static ObjectClass *log_ocs[LOG_EN__COUNT], *log_container, + *log_oc_read, *log_oc_write; + +#define LOG_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.5" + +#define LOG_SCHEMA_AT LOG_SCHEMA_ROOT ".1" +#define LOG_SCHEMA_OC LOG_SCHEMA_ROOT ".2" +#define LOG_SCHEMA_SYN LOG_SCHEMA_ROOT ".3" + +static AttributeDescription *ad_reqDN, *ad_reqStart, *ad_reqEnd, *ad_reqType, + *ad_reqSession, *ad_reqResult, *ad_reqAuthzID, *ad_reqControls, + *ad_reqRespControls, *ad_reqMethod, *ad_reqAssertion, *ad_reqNewRDN, + *ad_reqNewSuperior, *ad_reqDeleteOldRDN, *ad_reqMod, + *ad_reqScope, *ad_reqFilter, *ad_reqAttr, *ad_reqEntries, + *ad_reqSizeLimit, *ad_reqTimeLimit, *ad_reqAttrsOnly, *ad_reqData, + *ad_reqId, *ad_reqMessage, *ad_reqVersion, *ad_reqDerefAliases, + *ad_reqReferral, *ad_reqOld, *ad_auditContext, *ad_reqEntryUUID, + *ad_minCSN, *ad_reqNewDN; + +static int +logSchemaControlValidate( + Syntax *syntax, + struct berval *val ); + +char *mrControl[] = { + "objectIdentifierFirstComponentMatch", + NULL +}; + +static struct { + char *oid; + slap_syntax_defs_rec syn; + char **mrs; +} lsyntaxes[] = { + { LOG_SCHEMA_SYN ".1" , + { "( " LOG_SCHEMA_SYN ".1 DESC 'Control' )", + SLAP_SYNTAX_HIDE, + NULL, + logSchemaControlValidate, + NULL }, + mrControl }, + { NULL } +}; + +static struct { + char *at; + AttributeDescription **ad; +} lattrs[] = { + { "( " LOG_SCHEMA_AT ".1 NAME 'reqDN' " + "DESC 'Target DN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqDN }, + { "( " LOG_SCHEMA_AT ".2 NAME 'reqStart' " + "DESC 'Start time of request' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE )", &ad_reqStart }, + { "( " LOG_SCHEMA_AT ".3 NAME 'reqEnd' " + "DESC 'End time of request' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE )", &ad_reqEnd }, + { "( " LOG_SCHEMA_AT ".4 NAME 'reqType' " + "DESC 'Type of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqType }, + { "( " LOG_SCHEMA_AT ".5 NAME 'reqSession' " + "DESC 'Session ID of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqSession }, + { "( " LOG_SCHEMA_AT ".6 NAME 'reqAuthzID' " + "DESC 'Authorization ID of requestor' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqAuthzID }, + { "( " LOG_SCHEMA_AT ".7 NAME 'reqResult' " + "DESC 'Result code of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqResult }, + { "( " LOG_SCHEMA_AT ".8 NAME 'reqMessage' " + "DESC 'Error text of request' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqMessage }, + { "( " LOG_SCHEMA_AT ".9 NAME 'reqReferral' " + "DESC 'Referrals returned for request' " + "SUP labeledURI )", &ad_reqReferral }, + { "( " LOG_SCHEMA_AT ".10 NAME 'reqControls' " + "DESC 'Request controls' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX " LOG_SCHEMA_SYN ".1 " + "X-ORDERED 'VALUES' )", &ad_reqControls }, + { "( " LOG_SCHEMA_AT ".11 NAME 'reqRespControls' " + "DESC 'Response controls of request' " + "EQUALITY objectIdentifierFirstComponentMatch " + "SYNTAX " LOG_SCHEMA_SYN ".1 " + "X-ORDERED 'VALUES' )", &ad_reqRespControls }, + { "( " LOG_SCHEMA_AT ".12 NAME 'reqId' " + "DESC 'ID of Request to Abandon' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqId }, + { "( " LOG_SCHEMA_AT ".13 NAME 'reqVersion' " + "DESC 'Protocol version of Bind request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqVersion }, + { "( " LOG_SCHEMA_AT ".14 NAME 'reqMethod' " + "DESC 'Bind method of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqMethod }, + { "( " LOG_SCHEMA_AT ".15 NAME 'reqAssertion' " + "DESC 'Compare Assertion of request' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqAssertion }, + { "( " LOG_SCHEMA_AT ".16 NAME 'reqMod' " + "DESC 'Modifications of request' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString )", &ad_reqMod }, + { "( " LOG_SCHEMA_AT ".17 NAME 'reqOld' " + "DESC 'Old values of entry before request completed' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString )", &ad_reqOld }, + { "( " LOG_SCHEMA_AT ".18 NAME 'reqNewRDN' " + "DESC 'New RDN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqNewRDN }, + { "( " LOG_SCHEMA_AT ".19 NAME 'reqDeleteOldRDN' " + "DESC 'Delete old RDN' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", &ad_reqDeleteOldRDN }, + { "( " LOG_SCHEMA_AT ".20 NAME 'reqNewSuperior' " + "DESC 'New superior DN of request' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqNewSuperior }, + { "( " LOG_SCHEMA_AT ".21 NAME 'reqScope' " + "DESC 'Scope of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqScope }, + { "( " LOG_SCHEMA_AT ".22 NAME 'reqDerefAliases' " + "DESC 'Disposition of Aliases in request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqDerefAliases }, + { "( " LOG_SCHEMA_AT ".23 NAME 'reqAttrsOnly' " + "DESC 'Attributes and values of request' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", &ad_reqAttrsOnly }, + { "( " LOG_SCHEMA_AT ".24 NAME 'reqFilter' " + "DESC 'Filter of request' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", &ad_reqFilter }, + { "( " LOG_SCHEMA_AT ".25 NAME 'reqAttr' " + "DESC 'Attributes of request' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", &ad_reqAttr }, + { "( " LOG_SCHEMA_AT ".26 NAME 'reqSizeLimit' " + "DESC 'Size limit of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqSizeLimit }, + { "( " LOG_SCHEMA_AT ".27 NAME 'reqTimeLimit' " + "DESC 'Time limit of request' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqTimeLimit }, + { "( " LOG_SCHEMA_AT ".28 NAME 'reqEntries' " + "DESC 'Number of entries returned' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", &ad_reqEntries }, + { "( " LOG_SCHEMA_AT ".29 NAME 'reqData' " + "DESC 'Data of extended request' " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX OMsOctetString " + "SINGLE-VALUE )", &ad_reqData }, + + /* + * from <draft-chu-ldap-logschema-01.txt>: + * + + ( LOG_SCHEMA_AT .30 NAME 'auditContext' + DESC 'DN of auditContainer' + EQUALITY distinguishedNameMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 + SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation ) + + * - removed EQUALITY matchingRule + * - changed directoryOperation in dSAOperation + */ + { "( " LOG_SCHEMA_AT ".30 NAME 'auditContext' " + "DESC 'DN of auditContainer' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", &ad_auditContext }, + + /* + * ITS#6656 + */ + { "( " LOG_SCHEMA_AT ".31 NAME 'reqEntryUUID' " + "DESC 'UUID of entry' " + "EQUALITY UUIDMatch " + "ORDERING UUIDOrderingMatch " + "SYNTAX 1.3.6.1.1.16.1 " + "SINGLE-VALUE )", &ad_reqEntryUUID }, + + /* + * ITS#8486 + */ + { "( " LOG_SCHEMA_AT ".32 NAME 'minCSN' " + "DESC 'CSN set that the logs are recorded from' " + "EQUALITY CSNMatch " + "ORDERING CSNOrderingMatch " + "SYNTAX 1.3.6.1.4.1.4203.666.11.2.1{64} " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", &ad_minCSN }, + + /* + * ITS#9552 + */ + { "( " LOG_SCHEMA_AT ".33 NAME 'reqNewDN' " + "DESC 'New DN after rename' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN " + "SINGLE-VALUE )", &ad_reqNewDN }, + { NULL, NULL } +}; + +static struct { + char *ot; + ObjectClass **oc; +} locs[] = { + { "( " LOG_SCHEMA_OC ".0 NAME 'auditContainer' " + "DESC 'AuditLog container' " + "SUP top STRUCTURAL " + "MAY ( cn $ reqStart $ reqEnd ) )", &log_container }, + { "( " LOG_SCHEMA_OC ".1 NAME 'auditObject' " + "DESC 'OpenLDAP request auditing' " + "SUP top STRUCTURAL " + "MUST ( reqStart $ reqType $ reqSession ) " + "MAY ( reqDN $ reqAuthzID $ reqControls $ reqRespControls $ reqEnd $ " + "reqResult $ reqMessage $ reqReferral $ reqEntryUUID ) )", + &log_ocs[LOG_EN_UNBIND] }, + { "( " LOG_SCHEMA_OC ".2 NAME 'auditReadObject' " + "DESC 'OpenLDAP read request record' " + "SUP auditObject STRUCTURAL )", &log_oc_read }, + { "( " LOG_SCHEMA_OC ".3 NAME 'auditWriteObject' " + "DESC 'OpenLDAP write request record' " + "SUP auditObject STRUCTURAL )", &log_oc_write }, + { "( " LOG_SCHEMA_OC ".4 NAME 'auditAbandon' " + "DESC 'Abandon operation' " + "SUP auditObject STRUCTURAL " + "MUST reqId )", &log_ocs[LOG_EN_ABANDON] }, + { "( " LOG_SCHEMA_OC ".5 NAME 'auditAdd' " + "DESC 'Add operation' " + "SUP auditWriteObject STRUCTURAL " + "MUST reqMod )", &log_ocs[LOG_EN_ADD] }, + { "( " LOG_SCHEMA_OC ".6 NAME 'auditBind' " + "DESC 'Bind operation' " + "SUP auditObject STRUCTURAL " + "MUST ( reqVersion $ reqMethod ) )", &log_ocs[LOG_EN_BIND] }, + { "( " LOG_SCHEMA_OC ".7 NAME 'auditCompare' " + "DESC 'Compare operation' " + "SUP auditReadObject STRUCTURAL " + "MUST reqAssertion )", &log_ocs[LOG_EN_COMPARE] }, + { "( " LOG_SCHEMA_OC ".8 NAME 'auditDelete' " + "DESC 'Delete operation' " + "SUP auditWriteObject STRUCTURAL " + "MAY reqOld )", &log_ocs[LOG_EN_DELETE] }, + { "( " LOG_SCHEMA_OC ".9 NAME 'auditModify' " + "DESC 'Modify operation' " + "SUP auditWriteObject STRUCTURAL " + "MAY ( reqOld $ reqMod ) )", &log_ocs[LOG_EN_MODIFY] }, + { "( " LOG_SCHEMA_OC ".10 NAME 'auditModRDN' " + "DESC 'ModRDN operation' " + "SUP auditWriteObject STRUCTURAL " + "MUST ( reqNewRDN $ reqDeleteOldRDN ) " + "MAY ( reqNewSuperior $ reqMod $ reqOld $ reqNewDN ) )", + &log_ocs[LOG_EN_MODRDN] }, + { "( " LOG_SCHEMA_OC ".11 NAME 'auditSearch' " + "DESC 'Search operation' " + "SUP auditReadObject STRUCTURAL " + "MUST ( reqScope $ reqDerefAliases $ reqAttrsonly ) " + "MAY ( reqFilter $ reqAttr $ reqEntries $ reqSizeLimit $ " + "reqTimeLimit ) )", &log_ocs[LOG_EN_SEARCH] }, + { "( " LOG_SCHEMA_OC ".12 NAME 'auditExtended' " + "DESC 'Extended operation' " + "SUP auditObject STRUCTURAL " + "MAY reqData )", &log_ocs[LOG_EN_EXTENDED] }, + { NULL, NULL } +}; + +#define RDNEQ "reqStart=" + +/* Our time intervals are of the form [ddd+]hh:mm[:ss] + * If a field is present, it must be two digits. (Except for + * days, which can be arbitrary width.) + */ +static int +log_age_parse(char *agestr) +{ + int t1, t2; + int gotdays = 0; + char *endptr; + + t1 = strtol( agestr, &endptr, 10 ); + /* Is there a days delimiter? */ + if ( *endptr == '+' ) { + /* 32 bit time only covers about 68 years */ + if ( t1 < 0 || t1 > 25000 ) + return -1; + t1 *= 24; + gotdays = 1; + agestr = endptr + 1; + } else { + if ( agestr[2] != ':' ) { + /* No valid delimiter found, fail */ + return -1; + } + t1 *= 60; + agestr += 3; + } + + t2 = atoi( agestr ); + t1 += t2; + + if ( agestr[2] ) { + /* if there's a delimiter, it can only be a colon */ + if ( agestr[2] != ':' ) + return -1; + } else { + /* If we're at the end of the string, and we started with days, + * fail because we expected to find minutes too. + */ + return gotdays ? -1 : t1 * 60; + } + + agestr += 3; + t2 = atoi( agestr ); + + /* last field can only be seconds */ + if ( agestr[2] && ( agestr[2] != ':' || !gotdays )) + return -1; + t1 *= 60; + t1 += t2; + + if ( agestr[2] ) { + agestr += 3; + if ( agestr[2] ) + return -1; + t1 *= 60; + t1 += atoi( agestr ); + } else if ( gotdays ) { + /* only got days+hh:mm */ + t1 *= 60; + } + return t1; +} + +static void +log_age_unparse( int age, struct berval *agebv, size_t size ) +{ + int dd, hh, mm, ss, len; + char *ptr; + + assert( size > 0 ); + + ss = age % 60; + age /= 60; + mm = age % 60; + age /= 60; + hh = age % 24; + age /= 24; + dd = age; + + ptr = agebv->bv_val; + + if ( dd ) { + len = snprintf( ptr, size, "%d+", dd ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + } + len = snprintf( ptr, size, "%02d:%02d", hh, mm ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + if ( ss ) { + len = snprintf( ptr, size, ":%02d", ss ); + assert( len >= 0 && (unsigned) len < size ); + size -= len; + ptr += len; + } + + agebv->bv_len = ptr - agebv->bv_val; +} + +static slap_callback nullsc; + +#define PURGE_INCREMENT 100 + +typedef struct purge_data { + struct log_info *li; + int slots; + int used; + int mincsn_updated; + BerVarray dn; + BerVarray ndn; +} purge_data; + +static int +log_old_lookup( Operation *op, SlapReply *rs ) +{ + purge_data *pd = op->o_callback->sc_private; + struct log_info *li = pd->li; + Attribute *a; + + if ( rs->sr_type != REP_SEARCH) return 0; + + if ( slapd_shutdown ) return 0; + + /* Update minCSN */ + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + if ( a ) { + ber_len_t len = a->a_nvals[0].bv_len; + int i, sid; + + /* Find the correct sid */ + sid = slap_parse_csn_sid( &a->a_nvals[0] ); + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + for ( i=0; i < li->li_numcsns; i++ ) { + if ( sid <= li->li_sids[i] ) break; + } + if ( i >= li->li_numcsns || sid != li->li_sids[i] ) { + Debug( LDAP_DEBUG_ANY, "log_old_lookup: " + "csn=%s with sid not in minCSN set!\n", + a->a_nvals[0].bv_val ); + slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i, + sid, &a->a_nvals[0] ); + } else { + /* Paranoid len check, normalized CSNs are always the same length */ + if ( len > li->li_mincsn[i].bv_len ) + len = li->li_mincsn[i].bv_len; + if ( ber_bvcmp( &li->li_mincsn[i], &a->a_nvals[0] ) < 0 ) { + pd->mincsn_updated = 1; + AC_MEMCPY( li->li_mincsn[i].bv_val, a->a_nvals[0].bv_val, len ); + } + } + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + } + if ( pd->used >= pd->slots ) { + pd->slots += PURGE_INCREMENT; + pd->dn = ch_realloc( pd->dn, pd->slots * sizeof( struct berval )); + pd->ndn = ch_realloc( pd->ndn, pd->slots * sizeof( struct berval )); + } + ber_dupbv( &pd->dn[pd->used], &rs->sr_entry->e_name ); + ber_dupbv( &pd->ndn[pd->used], &rs->sr_entry->e_nname ); + pd->used++; + return 0; +} + +/* Periodically search for old entries in the log database and delete them */ +static void * +accesslog_purge( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + struct log_info *li = rtask->arg; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + SlapReply rs = {REP_RESULT}; + slap_callback cb = { NULL, log_old_lookup, NULL, NULL, NULL }; + Filter f; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + purge_data pd = { .li = li }; + char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE]; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + time_t old = slap_get_time(); + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + f.f_choice = LDAP_FILTER_LE; + f.f_ava = &ava; + f.f_next = NULL; + + ava.aa_desc = ad_reqStart; + ava.aa_value.bv_val = timebuf; + ava.aa_value.bv_len = sizeof(timebuf); + + old -= li->li_age; + slap_timestamp( &old, &ava.aa_value ); + + op->o_tag = LDAP_REQ_SEARCH; + op->o_bd = li->li_db; + op->o_dn = li->li_db->be_rootdn; + op->o_ndn = li->li_db->be_rootndn; + op->o_req_dn = li->li_db->be_suffix[0]; + op->o_req_ndn = li->li_db->be_nsuffix[0]; + op->o_callback = &cb; + op->ors_scope = LDAP_SCOPE_ONELEVEL; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_filter = &f; + filter2bv_x( op, &f, &op->ors_filterstr ); + op->ors_attrs = slap_anlist_no_attrs; + op->ors_attrsonly = 1; + + cb.sc_private = &pd; + + op->o_bd->be_search( op, &rs ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + + if ( pd.used ) { + int i; + + op->o_callback = &nullsc; + op->o_dont_replicate = 1; + op->o_csn = slap_empty_bv; + + if ( pd.mincsn_updated ) { + Modifications mod; + /* update context's minCSN to reflect oldest CSN */ + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + mod.sml_numvals = li->li_numcsns; + mod.sml_values = li->li_mincsn; + mod.sml_nvalues = li->li_mincsn; + mod.sml_desc = ad_minCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + op->o_req_dn = li->li_db->be_suffix[0]; + op->o_req_ndn = li->li_db->be_nsuffix[0]; + op->o_no_schema_check = 1; + op->o_managedsait = SLAP_CONTROL_NONCRITICAL; + if ( !slapd_shutdown ) { + Debug( LDAP_DEBUG_SYNC, "accesslog_purge: " + "updating minCSN with %d values\n", + li->li_numcsns ); + op->o_bd->be_modify( op, &rs ); + } + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + } + + /* delete the expired entries */ + op->o_tag = LDAP_REQ_DELETE; + for (i=0; i<pd.used; i++) { + op->o_req_dn = pd.dn[i]; + op->o_req_ndn = pd.ndn[i]; + if ( !slapd_shutdown ) { + rs_reinit( &rs, REP_RESULT ); + op->o_bd->be_delete( op, &rs ); + } + ch_free( pd.ndn[i].bv_val ); + ch_free( pd.dn[i].bv_val ); + ldap_pvt_thread_pool_pausecheck( &connection_pool ); + } + ch_free( pd.ndn ); + ch_free( pd.dn ); + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +log_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + struct log_info *li = on->on_bi.bi_private; + int rc = 0; + slap_mask_t tmask = 0; + char agebuf[2*STRLENOF("ddddd+hh:mm:ss ")]; + struct berval agebv, cyclebv; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + switch( c->type ) { + case LOG_DB: + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + value_add_one( &c->rvalue_vals, &li->li_db_suffix ); + value_add_one( &c->rvalue_nvals, &li->li_db_suffix ); + } else if ( li->li_db ) { + value_add_one( &c->rvalue_vals, li->li_db->be_suffix ); + value_add_one( &c->rvalue_nvals, li->li_db->be_nsuffix ); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "accesslog: \"logdb <suffix>\" must be specified" ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + break; + } + break; + case LOG_OPS: + rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals ); + break; + case LOG_PURGE: + if ( !li->li_age ) { + rc = 1; + break; + } + agebv.bv_val = agebuf; + log_age_unparse( li->li_age, &agebv, sizeof( agebuf ) ); + agebv.bv_val[agebv.bv_len] = ' '; + agebv.bv_len++; + cyclebv.bv_val = agebv.bv_val + agebv.bv_len; + log_age_unparse( li->li_cycle, &cyclebv, sizeof( agebuf ) - agebv.bv_len ); + agebv.bv_len += cyclebv.bv_len; + value_add_one( &c->rvalue_vals, &agebv ); + break; + case LOG_SUCCESS: + if ( li->li_success ) + c->value_int = li->li_success; + else + rc = 1; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter2bv( li->li_oldf, &agebv ); + ber_bvarray_add( &c->rvalue_vals, &agebv ); + } + else + rc = 1; + break; + case LOG_OLDATTR: + if ( li->li_oldattrs ) { + log_attr *la; + + for ( la = li->li_oldattrs; la; la=la->next ) + value_add_one( &c->rvalue_vals, &la->attr->ad_cname ); + } + else + rc = 1; + break; + case LOG_BASE: + if ( li->li_bases ) { + log_base *lb; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + value_add_one( &c->rvalue_vals, &lb->lb_line ); + } + else + rc = 1; + break; + } + break; + case LDAP_MOD_DELETE: + switch( c->type ) { + case LOG_DB: + /* noop. this should always be a valid backend. */ + break; + case LOG_OPS: + if ( c->valx < 0 ) { + li->li_ops = 0; + } else { + rc = verbs_to_mask( 1, &c->line, logops, &tmask ); + if ( rc == 0 ) + li->li_ops &= ~tmask; + } + break; + case LOG_PURGE: + if ( li->li_task ) { + struct re_s *re = li->li_task; + li->li_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re )) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + li->li_age = 0; + li->li_cycle = 0; + break; + case LOG_SUCCESS: + li->li_success = 0; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter_free( li->li_oldf ); + li->li_oldf = NULL; + } + break; + case LOG_OLDATTR: + if ( c->valx < 0 ) { + log_attr *la, *ln; + + for ( la = li->li_oldattrs; la; la = ln ) { + ln = la->next; + ch_free( la ); + } + } else { + log_attr *la = 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; + } + if ( !rc && ( li->li_db->bd_self == c->be->bd_self )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid suffix, points to itself", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + } + ch_free( c->value_ndn.bv_val ); + } else { + li->li_db_suffix = c->value_ndn; + } + ch_free( c->value_dn.bv_val ); + break; + case LOG_OPS: + rc = verbs_to_mask( c->argc, c->argv, logops, &tmask ); + if ( rc == 0 ) + li->li_ops |= tmask; + break; + case LOG_PURGE: + li->li_age = log_age_parse( c->argv[1] ); + if ( li->li_age < 1 ) { + rc = 1; + } else { + li->li_cycle = log_age_parse( c->argv[2] ); + if ( li->li_cycle < 1 ) { + rc = 1; + } else if ( slapMode & SLAP_SERVER_MODE ) { + struct re_s *re = li->li_task; + if ( re ) + re->interval.tv_sec = li->li_cycle; + else if ( li->li_open ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + li->li_task = ldap_pvt_runqueue_insert( &slapd_rq, + li->li_cycle, accesslog_purge, li, + "accesslog_purge", li->li_db ? + li->li_db->be_suffix[0].bv_val : + c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } + break; + case LOG_SUCCESS: + li->li_success = c->value_int; + break; + case LOG_OLD: + li->li_oldf = str2filter( c->argv[1] ); + if ( !li->li_oldf ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "bad filter!" ); + rc = 1; + } + break; + case LOG_OLDATTR: { + int i; + AttributeDescription *ad; + const char *text; + + 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 ); + 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 ); + rc = ARG_BAD_CONF; + } + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid ops: %s", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + } + break; + } + break; + } + return rc; +} + +static int +logSchemaControlValidate( + Syntax *syntax, + struct berval *valp ) +{ + struct berval val, bv; + ber_len_t i; + int rc = LDAP_SUCCESS; + + assert( valp != NULL ); + + val = *valp; + + /* check minimal size */ + if ( val.bv_len < STRLENOF( "{*}" ) ) { + return LDAP_INVALID_SYNTAX; + } + + val.bv_len--; + + /* check SEQUENCE boundaries */ + if ( val.bv_val[ 0 ] != '{' /*}*/ || + val.bv_val[ val.bv_len ] != /*{*/ '}' ) + { + return LDAP_INVALID_SYNTAX; + } + + /* extract and check OID */ + for ( i = 1; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( i++; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) + { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + rc = numericoidValidate( NULL, &bv ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + /* extract and check criticality */ + if ( strncasecmp( &val.bv_val[ i ], "criticality ", STRLENOF( "criticality " ) ) == 0 ) + { + i += STRLENOF( "criticality " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( ; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + if ( !bvmatch( &bv, &slap_true_bv ) && !bvmatch( &bv, &slap_false_bv ) ) + { + return LDAP_INVALID_SYNTAX; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + /* extract and check controlValue */ + if ( strncasecmp( &val.bv_val[ i ], "controlValue ", STRLENOF( "controlValue " ) ) == 0 ) + { + ber_len_t valueStart, valueLen; + + i += STRLENOF( "controlValue " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + i++; + valueStart = i; + + for ( ; i < val.bv_len; i++ ) { + if ( val.bv_val[ i ] == '"' ) { + break; + } + + if ( !ASCII_HEX( val.bv_val[ i ] ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + valueLen = i - valueStart; + if ( (valueLen/2)*2 != valueLen ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + return LDAP_INVALID_SYNTAX; +} + +static int +accesslog_ctrls( + LDAPControl **ctrls, + BerVarray *valsp, + BerVarray *nvalsp, + void *memctx ) +{ + long i, rc = 0; + + assert( valsp != NULL ); + assert( ctrls != NULL ); + + *valsp = NULL; + *nvalsp = NULL; + + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + struct berval idx, + oid, + noid, + bv; + char *ptr, + buf[ 32 ]; + + if ( ctrls[ i ]->ldctl_oid == NULL ) { + return LDAP_PROTOCOL_ERROR; + } + + idx.bv_len = snprintf( buf, sizeof( buf ), "{%ld}", i ); + idx.bv_val = buf; + + ber_str2bv( ctrls[ i ]->ldctl_oid, 0, 0, &oid ); + noid.bv_len = idx.bv_len + oid.bv_len; + ptr = noid.bv_val = ber_memalloc_x( noid.bv_len + 1, memctx ); + ptr = lutil_strcopy( ptr, idx.bv_val ); + ptr = lutil_strcopy( ptr, oid.bv_val ); + + bv.bv_len = idx.bv_len + STRLENOF( "{}" ) + oid.bv_len; + + if ( ctrls[ i ]->ldctl_iscritical ) { + bv.bv_len += STRLENOF( " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + bv.bv_len += STRLENOF( " controlValue \"\"" ) + + 2 * ctrls[ i ]->ldctl_value.bv_len; + } + + ptr = bv.bv_val = ber_memalloc_x( bv.bv_len + 1, memctx ); + if ( ptr == NULL ) { + ber_bvarray_free( *valsp ); + *valsp = NULL; + ber_bvarray_free( *nvalsp ); + *nvalsp = NULL; + return LDAP_OTHER; + } + + ptr = lutil_strcopy( ptr, idx.bv_val ); + + *ptr++ = '{' /*}*/ ; + ptr = lutil_strcopy( ptr, oid.bv_val ); + + if ( ctrls[ i ]->ldctl_iscritical ) { + ptr = lutil_strcopy( ptr, " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + ber_len_t j; + + ptr = lutil_strcopy( ptr, " controlValue \"" ); + for ( j = 0; j < ctrls[ i ]->ldctl_value.bv_len; j++ ) { + *ptr++ = SLAP_ESCAPE_HI(ctrls[ i ]->ldctl_value.bv_val[ j ]); + *ptr++ = SLAP_ESCAPE_LO(ctrls[ i ]->ldctl_value.bv_val[ j ]); + } + + *ptr++ = '"'; + } + + *ptr++ = '}'; + *ptr = '\0'; + + ber_bvarray_add_x( valsp, &bv, memctx ); + ber_bvarray_add_x( nvalsp, &noid, memctx ); + } + + return rc; + +} + +static Entry *accesslog_entry( Operation *op, SlapReply *rs, + log_info *li, int logop, Operation *op2 ) { + + char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + + struct berval rdn, nrdn, timestamp, ntimestamp, bv; + slap_verbmasks *lo = logops+logop+EN_OFFSET; + + Entry *e = entry_alloc(); + + strcpy( rdnbuf, RDNEQ ); + rdn.bv_val = rdnbuf; + strcpy( nrdnbuf, RDNEQ ); + nrdn.bv_val = nrdnbuf; + + timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ); + timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ); + slap_timestamp( &op->o_time, ×tamp ); + snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op->o_tincr ); + timestamp.bv_len += STRLENOF(".123456"); + + rdn.bv_len = STRLENOF(RDNEQ)+timestamp.bv_len; + ad_reqStart->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, ad_reqStart->ad_type->sat_syntax, + ad_reqStart->ad_type->sat_equality, ×tamp, &ntimestamp, + op->o_tmpmemctx ); + + strcpy( nrdn.bv_val + STRLENOF(RDNEQ), ntimestamp.bv_val ); + nrdn.bv_len = STRLENOF(RDNEQ)+ntimestamp.bv_len; + build_new_dn( &e->e_name, li->li_db->be_suffix, &rdn, NULL ); + build_new_dn( &e->e_nname, li->li_db->be_nsuffix, &nrdn, NULL ); + + attr_merge_one( e, slap_schema.si_ad_objectClass, + &log_ocs[logop]->soc_cname, NULL ); + attr_merge_one( e, slap_schema.si_ad_structuralObjectClass, + &log_ocs[logop]->soc_cname, NULL ); + attr_merge_one( e, ad_reqStart, ×tamp, &ntimestamp ); + op->o_tmpfree( ntimestamp.bv_val, op->o_tmpmemctx ); + + slap_op_time( &op2->o_time, &op2->o_tincr ); + + timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ); + slap_timestamp( &op2->o_time, ×tamp ); + snprintf( timestamp.bv_val + timestamp.bv_len-1, sizeof(".123456Z"), ".%06dZ", op2->o_tincr ); + timestamp.bv_len += STRLENOF(".123456"); + + attr_merge_normalize_one( e, ad_reqEnd, ×tamp, op->o_tmpmemctx ); + + /* Exops have OID appended */ + if ( logop == LOG_EN_EXTENDED ) { + bv.bv_len = lo->word.bv_len + op->ore_reqoid.bv_len + 2; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + AC_MEMCPY( bv.bv_val, lo->word.bv_val, lo->word.bv_len ); + bv.bv_val[lo->word.bv_len] = '{'; + AC_MEMCPY( bv.bv_val+lo->word.bv_len+1, op->ore_reqoid.bv_val, + op->ore_reqoid.bv_len ); + bv.bv_val[bv.bv_len-1] = '}'; + bv.bv_val[bv.bv_len] = '\0'; + attr_merge_one( e, ad_reqType, &bv, NULL ); + } else { + attr_merge_one( e, ad_reqType, &lo->word, NULL ); + } + + rdn.bv_len = snprintf( rdn.bv_val, sizeof( rdnbuf ), "%lu", op->o_connid ); + if ( rdn.bv_len < sizeof( rdnbuf ) ) { + attr_merge_one( e, ad_reqSession, &rdn, NULL ); + } /* else? */ + + if ( BER_BVISNULL( &op->o_dn ) ) { + attr_merge_one( e, ad_reqAuthzID, (struct berval *)&slap_empty_bv, + (struct berval *)&slap_empty_bv ); + } else { + attr_merge_one( e, ad_reqAuthzID, &op->o_dn, &op->o_ndn ); + } + + /* FIXME: need to add reqControls and reqRespControls */ + if ( op->o_ctrls ) { + BerVarray vals = NULL, + nvals = NULL; + + if ( accesslog_ctrls( op->o_ctrls, &vals, &nvals, + op->o_tmpmemctx ) == LDAP_SUCCESS && vals ) + { + attr_merge( e, ad_reqControls, vals, nvals ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + ber_bvarray_free_x( nvals, op->o_tmpmemctx ); + } + } + + if ( rs->sr_ctrls ) { + BerVarray vals = NULL, + nvals = NULL; + + if ( accesslog_ctrls( rs->sr_ctrls, &vals, &nvals, + op->o_tmpmemctx ) == LDAP_SUCCESS && vals ) + { + attr_merge( e, ad_reqRespControls, vals, nvals ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + ber_bvarray_free_x( nvals, op->o_tmpmemctx ); + } + + } + + return e; +} + +static struct berval scopes[] = { + BER_BVC("base"), + BER_BVC("one"), + BER_BVC("sub"), + BER_BVC("subord") +}; + +static struct berval derefs[] = { + BER_BVC("never"), + BER_BVC("searching"), + BER_BVC("finding"), + BER_BVC("always") +}; + +static struct berval simple = BER_BVC("SIMPLE"); + +static void accesslog_val2val(AttributeDescription *ad, struct berval *val, + char c_op, struct berval *dst) { + char *ptr; + + dst->bv_len = ad->ad_cname.bv_len + val->bv_len + 2; + if ( c_op ) dst->bv_len++; + + dst->bv_val = ch_malloc( dst->bv_len+1 ); + + ptr = lutil_strcopy( dst->bv_val, ad->ad_cname.bv_val ); + *ptr++ = ':'; + if ( c_op ) + *ptr++ = c_op; + *ptr++ = ' '; + AC_MEMCPY( ptr, val->bv_val, val->bv_len ); + dst->bv_val[dst->bv_len] = '\0'; +} + +static int +accesslog_op2logop( Operation *op ) +{ + switch ( op->o_tag ) { + case LDAP_REQ_ADD: return LOG_EN_ADD; + case LDAP_REQ_DELETE: return LOG_EN_DELETE; + case LDAP_REQ_MODIFY: return LOG_EN_MODIFY; + case LDAP_REQ_MODRDN: return LOG_EN_MODRDN; + case LDAP_REQ_COMPARE: return LOG_EN_COMPARE; + case LDAP_REQ_SEARCH: return LOG_EN_SEARCH; + case LDAP_REQ_BIND: return LOG_EN_BIND; + case LDAP_REQ_EXTENDED: return LOG_EN_EXTENDED; + default: /* unknown operation type */ + break; + } /* Unbind and Abandon never reach here */ + return LOG_EN_UNKNOWN; +} + +static int accesslog_response(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *)op->o_callback->sc_private; + log_info *li = on->on_bi.bi_private; + Attribute *a, *last_attr; + Modifications *m; + struct berval *b, uuid = BER_BVNULL; + int i; + int logop; + slap_verbmasks *lo; + Entry *e = NULL, *old = NULL, *e_uuid = NULL; + char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8]; + struct berval bv, bv2 = BER_BVNULL; + char *ptr; + BerVarray vals; + Operation op2 = {0}; + SlapReply rs2 = {REP_RESULT}; + + /* ITS#9051 Make sure we only remove the callback on a final response */ + if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_EXTENDED || + rs->sr_type == REP_SASL ) { + 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; + + /* can't do anything if logDB isn't open */ + if ( !SLAP_DBOPEN( li->li_db )) + return SLAP_CB_CONTINUE; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + if ( !( li->li_ops & lo->mask )) { + log_base *lb; + + 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 ); +#endif + ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex ); + } + + /* ignore these internal reads */ + if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) { + return SLAP_CB_CONTINUE; + } + + /* + * ITS#9051 Technically LDAP_REFERRAL and LDAP_SASL_BIND_IN_PROGRESS + * are not errors, but they aren't really success either + */ + if ( li->li_success && rs->sr_err != LDAP_SUCCESS && + rs->sr_err != LDAP_COMPARE_TRUE && + rs->sr_err != LDAP_COMPARE_FALSE ) + goto done; + + e = accesslog_entry( op, rs, li, logop, &op2 ); + + if ( !BER_BVISNULL( &op->o_req_ndn )) + attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn ); + + if ( rs->sr_text ) { + ber_str2bv( rs->sr_text, 0, 0, &bv ); + attr_merge_normalize_one( e, ad_reqMessage, &bv, op->o_tmpmemctx ); + } + bv.bv_len = snprintf( timebuf, sizeof( timebuf ), "%d", rs->sr_err ); + if ( bv.bv_len < sizeof( timebuf ) ) { + bv.bv_val = timebuf; + attr_merge_one( e, ad_reqResult, &bv, NULL ); + } + + last_attr = attr_find( e->e_attrs, ad_reqResult ); + + e_uuid = old; + switch( logop ) { + case LOG_EN_ADD: + case LOG_EN_DELETE: { + char c_op; + Entry *e2; + + if ( logop == LOG_EN_ADD ) { + e2 = op->ora_e; + e_uuid = op->ora_e; + c_op = '+'; + + } else { + if ( !old ) + break; + e2 = old; + c_op = 0; + } + /* count all the vals */ + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + i += a->a_numvals; + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + if ( a->a_vals ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, c_op, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( logop == LOG_EN_ADD ? ad_reqMod : ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + break; + } + + case LOG_EN_MODRDN: + case LOG_EN_MODIFY: + /* count all the mods + attributes (ITS#6545) */ + i = 0; + for ( m = op->orm_modlist; m; m = m->sml_next ) { + if ( m->sml_values ) { + i += m->sml_numvals; + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + i++; + } + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc ) { + i++; + } + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + + /* init flags on old entry */ + if ( old ) { + for ( a = old->e_attrs; a; a = a->a_next ) { + log_attr *la; + a->a_flags = 0; + + /* look for attrs that are always logged */ + for ( la = li->li_oldattrs; la; la = la->next ) { + if ( a->a_desc == la->attr ) { + a->a_flags = 1; + } + } + } + } + + for ( m = op->orm_modlist; m; m = m->sml_next ) { + /* Mark this attribute as modified */ + if ( old ) { + a = attr_find( old->e_attrs, m->sml_desc ); + if ( a ) { + a->a_flags = 1; + } + } + + /* don't log the RDN mods; they're explicitly logged later */ + if ( logop == LOG_EN_MODRDN && + ( m->sml_op == SLAP_MOD_SOFTADD || + m->sml_op == LDAP_MOD_DELETE ) ) + { + continue; + } + + if ( m->sml_values ) { + for ( b = m->sml_values; !BER_BVISNULL( b ); b++, i++ ) { + char c_op; + + switch ( m->sml_op ) { + case LDAP_MOD_ADD: /* FALLTHRU */ + case SLAP_MOD_SOFTADD: c_op = '+'; break; + case LDAP_MOD_DELETE: /* FALLTHRU */ + case SLAP_MOD_SOFTDEL: c_op = '-'; break; + case LDAP_MOD_REPLACE: c_op = '='; break; + case LDAP_MOD_INCREMENT: c_op = '#'; break; + + /* unknown op. there shouldn't be any of these. we + * don't know what to do with it, but we shouldn't just + * ignore it. + */ + default: c_op = '?'; break; + } + accesslog_val2val( m->sml_desc, b, c_op, &vals[i] ); + } + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + vals[i].bv_len = m->sml_desc->ad_cname.bv_len + 2; + vals[i].bv_val = ch_malloc( vals[i].bv_len + 1 ); + ptr = lutil_strcopy( vals[i].bv_val, + m->sml_desc->ad_cname.bv_val ); + *ptr++ = ':'; + if ( m->sml_op == LDAP_MOD_DELETE || m->sml_op == SLAP_MOD_SOFTDEL ) { + *ptr++ = '-'; + } else { + *ptr++ = '='; + } + *ptr = '\0'; + i++; + } + /* ITS#6545: when the same attribute is edited multiple times, + * record the transition */ + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc && + m->sml_op == m->sml_next->sml_op ) { + ber_str2bv( ":", STRLENOF(":"), 1, &vals[i] ); + i++; + } + } + + if ( i > 0 ) { + BER_BVZERO( &vals[i] ); + a = attr_alloc( ad_reqMod ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + last_attr = a; + + } else { + ch_free( vals ); + } + + if ( old ) { + /* count all the vals */ + i = 0; + for ( a = old->e_attrs; a != NULL; a = a->a_next ) { + if ( a->a_vals && a->a_flags ) { + i += a->a_numvals; + } + } + if ( i ) { + vals = ch_malloc( (i + 1) * sizeof( struct berval ) ); + i = 0; + for ( a=old->e_attrs; a; a=a->a_next ) { + if ( a->a_vals && a->a_flags ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, 0, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + } + } + if ( logop == LOG_EN_MODIFY ) { + break; + } + + /* Now log the actual modRDN info */ + attr_merge_one( e, ad_reqNewRDN, &op->orr_newrdn, &op->orr_nnewrdn ); + attr_merge_one( e, ad_reqDeleteOldRDN, op->orr_deleteoldrdn ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv, + NULL ); + if ( op->orr_newSup ) { + attr_merge_one( e, ad_reqNewSuperior, op->orr_newSup, op->orr_nnewSup ); + bv2 = *op->orr_nnewSup; + } else { + dnParent( &op->o_req_ndn, &bv2 ); + } + build_new_dn( &bv, &bv2, &op->orr_nnewrdn, op->o_tmpmemctx ); + attr_merge_one( e, ad_reqNewDN, &bv, NULL ); + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + 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, "%s accesslog_response: " + "got result 0x%x adding log entry %s\n", + op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val ); + } + if ( e == op2.ora_e ) entry_free( e ); + e = NULL; + + /* TODO: What to do about minCSN when we have an op without a CSN? */ + if ( !BER_BVISEMPTY( &op->o_csn ) ) { + Modifications mod; + int i, sid = slap_parse_csn_sid( &op->o_csn ); + + for ( i=0; i < li->li_numcsns; i++ ) { + if ( sid <= li->li_sids[i] ) break; + } + if ( i >= li->li_numcsns || sid != li->li_sids[i] ) { + /* SID not in minCSN set, add */ + struct berval bv[2]; + + Debug( LDAP_DEBUG_TRACE, "accesslog_response: " + "adding minCSN %s\n", + op->o_csn.bv_val ); + slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i, + sid, &op->o_csn ); + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_req_dn = li->li_db->be_suffix[0]; + op2.o_req_ndn = li->li_db->be_nsuffix[0]; + + bv[0] = op->o_csn; + BER_BVZERO( &bv[1] ); + + mod.sml_numvals = 1; + mod.sml_values = bv; + mod.sml_nvalues = bv; + mod.sml_desc = ad_minCSN; + mod.sml_op = LDAP_MOD_ADD; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op2.orm_modlist = &mod; + op2.orm_no_opattrs = 1; + + Debug( LDAP_DEBUG_SYNC, "accesslog_response: " + "adding a new csn=%s into minCSN\n", + bv[0].bv_val ); + rs_reinit( &rs2, REP_RESULT ); + op2.o_bd->be_modify( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "accesslog_response: " + "got result 0x%x adding minCSN %s\n", + rs2.sr_err, op->o_csn.bv_val ); + } + } else if ( ber_bvcmp( &op->o_csn, &li->li_mincsn[i] ) < 0 ) { + Debug( LDAP_DEBUG_ANY, "accesslog_response: " + "csn=%s older than existing minCSN csn=%s for this sid\n", + op->o_csn.bv_val, li->li_mincsn[i].bv_val ); + } + } + +done: + 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; + + /* can't do anything if logDB isn't open */ + if ( !SLAP_DBOPEN( li->li_db )) + return SLAP_CB_CONTINUE; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + + if ( li->li_ops & lo->mask ) { + 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 ); +#endif + ldap_pvt_thread_mutex_lock( &li->li_op_rmutex ); +#ifdef RMUTEX_DEBUG + Debug( LDAP_DEBUG_STATS, + "accesslog_op_mod: locked rmutex for tid %x\n", + op->o_tid ); +#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_mutex_recursive_init( &li->li_op_rmutex ); + ldap_pvt_thread_mutex_init( &li->li_log_mutex ); + return 0; +} + +static int +accesslog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + log_attr *la; + + if ( li->li_oldf ) + filter_free( li->li_oldf ); + for ( la=li->li_oldattrs; la; la=li->li_oldattrs ) { + li->li_oldattrs = la->next; + ch_free( la ); + } + if ( li->li_sids ) + ch_free( li->li_sids ); + if ( li->li_mincsn ) + ber_bvarray_free( li->li_mincsn ); + if ( li->li_db_suffix.bv_val ) + ch_free( li->li_db_suffix.bv_val ); + ldap_pvt_thread_mutex_destroy( &li->li_log_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_op_rmutex ); + free( li ); + return LDAP_SUCCESS; +} + +/* Create the logdb's root entry if it's missing, load mincsn */ +static void * +accesslog_db_root( + void *ctx, + void *arg ) +{ + struct re_s *rtask = arg; + slap_overinst *on = rtask->arg; + log_info *li = on->on_bi.bi_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + Entry *e; + int rc; + + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + op->o_bd = li->li_db; + op->o_dn = li->li_db->be_rootdn; + op->o_ndn = li->li_db->be_rootndn; + rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e ); + + if ( e ) { + Attribute *a = attr_find( e->e_attrs, ad_minCSN ); + if ( !a ) { + /* TODO: find the lowest CSN we are safe to put in */ + a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + SlapReply rs = {REP_RESULT}; + Modifications mod; + BackendDB db = *li->li_db; + + op->o_bd = &db; + + mod.sml_numvals = a->a_numvals; + mod.sml_values = a->a_vals; + mod.sml_nvalues = a->a_nvals; + mod.sml_desc = ad_minCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->o_callback = &nullsc; + SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD; + + Debug( LDAP_DEBUG_SYNC, "accesslog_db_root: " + "setting up minCSN with %d values\n", + a->a_numvals ); + + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + rc = op->o_bd->be_modify( op, &rs ); + } + } + if ( a ) { + ber_bvarray_dup_x( &li->li_mincsn, a->a_vals, NULL ); + li->li_numcsns = a->a_numvals; + li->li_sids = slap_parse_csn_sids( li->li_mincsn, li->li_numcsns, NULL ); + slap_sort_csn_sids( li->li_mincsn, li->li_sids, li->li_numcsns, NULL ); + } + be_entry_release_rw( op, e, 0 ); + } else { + SlapReply rs = {REP_RESULT}; + struct berval rdn, nrdn, attr; + char *ptr; + AttributeDescription *ad = NULL; + const char *text = NULL; + Entry *e_ctx; + BackendDB db; + + e = entry_alloc(); + ber_dupbv( &e->e_name, li->li_db->be_suffix ); + ber_dupbv( &e->e_nname, li->li_db->be_nsuffix ); + + attr_merge_one( e, slap_schema.si_ad_objectClass, + &log_container->soc_cname, NULL ); + + dnRdn( &e->e_name, &rdn ); + dnRdn( &e->e_nname, &nrdn ); + ptr = ber_bvchr( &rdn, '=' ); + + assert( ptr != NULL ); + + attr.bv_val = rdn.bv_val; + attr.bv_len = ptr - rdn.bv_val; + + slap_bv2ad( &attr, &ad, &text ); + + rdn.bv_val = ptr+1; + rdn.bv_len -= attr.bv_len + 1; + ptr = ber_bvchr( &nrdn, '=' ); + nrdn.bv_len -= ptr - nrdn.bv_val + 1; + nrdn.bv_val = ptr+1; + attr_merge_one( e, ad, &rdn, &nrdn ); + + /* Get contextCSN from main DB */ + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, op->o_bd->be_nsuffix, NULL, + slap_schema.si_ad_contextCSN, 0, &e_ctx ); + + if ( e_ctx ) { + Attribute *a; + + a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + /* FIXME: contextCSN could have multiple values! + * should select the one with the server's SID */ + attr_merge_one( e, slap_schema.si_ad_entryCSN, + &a->a_vals[0], &a->a_nvals[0] ); + attr_merge( e, a->a_desc, a->a_vals, a->a_nvals ); + attr_merge( e, ad_minCSN, a->a_vals, a->a_nvals ); + } + be_entry_release_rw( op, e_ctx, 0 ); + } + db = *li->li_db; + op->o_bd = &db; + + op->o_tag = LDAP_REQ_ADD; + op->ora_e = e; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->o_callback = &nullsc; + SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD; + rc = op->o_bd->be_add( op, &rs ); + if ( e == op->ora_e ) + entry_free( e ); + } + li->li_open = 1; + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + + if ( li->li_age && li->li_cycle ) { + assert( li->li_task == NULL ); + li->li_task = ldap_pvt_runqueue_insert( &slapd_rq, + li->li_cycle, accesslog_purge, li, + "accesslog_purge", li->li_db->be_suffix[0].bv_val ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +accesslog_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + + + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + li->li_db = select_backend( &li->li_db_suffix, 0 ); + ch_free( li->li_db_suffix.bv_val ); + BER_BVZERO( &li->li_db_suffix ); + } + if ( li->li_db == NULL ) { + Debug( LDAP_DEBUG_ANY, + "accesslog: \"logdb <suffix>\" missing or invalid.\n" ); + return 1; + } + if ( li->li_db->bd_self == be->bd_self ) { + Debug( LDAP_DEBUG_ANY, + "accesslog: \"logdb <suffix>\" is this database, cannot log to itself.\n" ); + return 1; + } + + if ( slapMode & SLAP_TOOL_MODE ) + return 0; + + if ( BER_BVISEMPTY( &li->li_db->be_rootndn )) { + ber_dupbv( &li->li_db->be_rootdn, li->li_db->be_suffix ); + ber_dupbv( &li->li_db->be_rootndn, li->li_db->be_nsuffix ); + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_insert( &slapd_rq, 3600, accesslog_db_root, on, + "accesslog_db_root", li->li_db->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return 0; +} + +static int +accesslog_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + struct re_s *re = li->li_task; + + li->li_open = 0; + + if ( re ) { + li->li_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + return 0; +} + +enum { start = 0 }; + +static int +check_rdntime_syntax (struct berval *val, + int *parts, + struct berval *fraction) +{ + /* + * GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) + * GeneralizedTime supports leap seconds, UTCTime does not. + */ + static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 }; + static const int mdays[2][12] = { + /* non-leap years */ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + /* leap years */ + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + char *p, *e; + int part, c, c1, c2, tzoffset, leapyear = 0; + + p = val->bv_val; + e = p + val->bv_len; + + for (part = start; part < 7 && p < e; part++) { + c1 = *p; + if (!ASCII_DIGIT(c1)) { + break; + } + p++; + if (p == e) { + return LDAP_INVALID_SYNTAX; + } + c = *p++; + if (!ASCII_DIGIT(c)) { + return LDAP_INVALID_SYNTAX; + } + c += c1 * 10 - '0' * 11; + if ((part | 1) == 3) { + --c; + if (c < 0) { + return LDAP_INVALID_SYNTAX; + } + } + if (c >= ceiling[part]) { + if (! (c == 60 && part == 6 && start == 0)) + return LDAP_INVALID_SYNTAX; + } + parts[part] = c; + } + if (part < 5 + start) { + return LDAP_INVALID_SYNTAX; + } + for (; part < 9; part++) { + parts[part] = 0; + } + + /* leapyear check for the Gregorian calendar (year>1581) */ + if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) { + leapyear = 1; + } + + if (parts[3] >= mdays[leapyear][parts[2]]) { + return LDAP_INVALID_SYNTAX; + } + + if (start == 0) { + fraction->bv_val = p; + fraction->bv_len = 0; + if (p < e && (*p == '.' || *p == ',')) { + char *end_num; + while (++p < e && ASCII_DIGIT(*p)) { + /* EMPTY */; + } + if (p - fraction->bv_val == 1) { + return LDAP_INVALID_SYNTAX; + } + +#if 0 /* don't truncate trailing zeros */ + for (end_num = p; end_num[-1] == '0'; --end_num) { + /* EMPTY */; + } + c = end_num - fraction->bv_val; +#else + c = p - fraction->bv_val; +#endif + if (c != 1) fraction->bv_len = c; + } + } + + if (p == e) { + /* no time zone */ + return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; + } + + tzoffset = *p++; + switch (tzoffset) { + case 'Z': + /* UTC */ + break; + default: + return LDAP_INVALID_SYNTAX; + } + + return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; +} + +static int +rdnTimestampValidate( + Syntax *syntax, + struct berval *in ) +{ + int parts[9]; + struct berval fraction; + return check_rdntime_syntax(in, parts, &fraction); +} + +static int +rdnTimestampNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int parts[9], rc; + unsigned int len; + struct berval fraction; + + rc = check_rdntime_syntax(val, parts, &fraction); + if (rc != LDAP_SUCCESS) { + return rc; + } + + len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len; + normalized->bv_val = slap_sl_malloc( len + 1, ctx ); + if ( BER_BVISNULL( normalized ) ) { + return LBER_ERROR_MEMORY; + } + + sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d", + parts[0], parts[1], parts[2] + 1, parts[3] + 1, + parts[4], parts[5], parts[6] ); + if ( !BER_BVISEMPTY( &fraction ) ) { + memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1, + fraction.bv_val, fraction.bv_len ); + normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.'; + } + strcpy( normalized->bv_val + len-1, "Z" ); + normalized->bv_len = len; + + return LDAP_SUCCESS; +} + + +int accesslog_initialize() +{ + int i, rc; + Syntax *rdnTimestampSyntax; + MatchingRule *rdnTimestampMatch; + + accesslog.on_bi.bi_type = "accesslog"; + accesslog.on_bi.bi_db_init = accesslog_db_init; + accesslog.on_bi.bi_db_destroy = accesslog_db_destroy; + accesslog.on_bi.bi_db_open = accesslog_db_open; + accesslog.on_bi.bi_db_close = accesslog_db_close; + + accesslog.on_bi.bi_op_add = accesslog_op_mod; + accesslog.on_bi.bi_op_bind = accesslog_op_misc; + accesslog.on_bi.bi_op_compare = accesslog_op_misc; + accesslog.on_bi.bi_op_delete = accesslog_op_mod; + accesslog.on_bi.bi_op_modify = accesslog_op_mod; + accesslog.on_bi.bi_op_modrdn = accesslog_op_mod; + accesslog.on_bi.bi_op_search = accesslog_op_misc; + accesslog.on_bi.bi_extended = accesslog_op_misc; + accesslog.on_bi.bi_op_unbind = accesslog_unbind; + accesslog.on_bi.bi_op_abandon = accesslog_abandon; + accesslog.on_bi.bi_operational = accesslog_operational; + + accesslog.on_bi.bi_cf_ocs = log_cfocs; + + nullsc.sc_response = slap_null_cb; + + rc = config_register_schema( log_cfats, log_cfocs ); + if ( rc ) return rc; + + /* log schema integration */ + for ( i=0; lsyntaxes[i].oid; i++ ) { + int code; + + code = register_syntax( &lsyntaxes[ i ].syn ); + if ( code != 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_syntax failed\n" ); + return code; + } + + if ( lsyntaxes[i].mrs != NULL ) { + code = mr_make_syntax_compat_with_mrs( + lsyntaxes[i].oid, lsyntaxes[i].mrs ); + if ( code < 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: " + "mr_make_syntax_compat_with_mrs " + "failed\n" ); + return code; + } + } + } + + for ( i=0; lattrs[i].at; i++ ) { + int code; + + code = register_at( lattrs[i].at, lattrs[i].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_at failed\n" ); + return -1; + } + } + + /* Inject custom normalizer for reqStart/reqEnd */ + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + rdnTimestampMatch->smr_normalize = rdnTimestampNormalize; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate; + ad_reqStart->ad_type->sat_equality = rdnTimestampMatch; + ad_reqStart->ad_type->sat_syntax = rdnTimestampSyntax; + + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + rdnTimestampMatch->smr_normalize = rdnTimestampNormalize; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate; + ad_reqEnd->ad_type->sat_equality = rdnTimestampMatch; + ad_reqEnd->ad_type->sat_syntax = rdnTimestampSyntax; + + for ( i=0; locs[i].ot; i++ ) { + int code; + + code = register_oc( locs[i].ot, locs[i].oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_oc failed\n" ); + return -1; + } + } + + return overlay_register(&accesslog); +} + +#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return accesslog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_ACCESSLOG */ diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c new file mode 100644 index 0000000..9292d4a --- /dev/null +++ b/servers/slapd/overlays/auditlog.c @@ -0,0 +1,242 @@ +/* auditlog.c - log modifications for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions copyright 2004-2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_AUDITLOG + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "slap-config.h" +#include "ldif.h" + +typedef struct auditlog_data { + ldap_pvt_thread_mutex_t ad_mutex; + char *ad_logfile; +} auditlog_data; + +static ConfigTable auditlogcfg[] = { + { "auditlog", "filename", 2, 2, 0, + ARG_STRING|ARG_OFFSET, + (void *)offsetof(auditlog_data, ad_logfile), + "( OLcfgOvAt:15.1 NAME 'olcAuditlogFile' " + "DESC 'Filename for auditlogging' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs auditlogocs[] = { + { "( OLcfgOvOc:15.1 " + "NAME 'olcAuditlogConfig' " + "DESC 'Auditlog configuration' " + "SUP olcOverlayConfig " + "MAY ( olcAuditlogFile ) )", + Cft_Overlay, auditlogcfg }, + { NULL, 0, NULL } +}; + +static int fprint_ldif(FILE *f, char *name, char *val, ber_len_t len) { + char *s; + if((s = ldif_put(LDIF_PUT_VALUE, name, val, len)) == NULL) + return(-1); + fputs(s, f); + ber_memfree(s); + return(0); +} + +static int auditlog_response(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + FILE *f; + Attribute *a; + Modifications *m; + struct berval *b, *who = NULL, peername; + char *what, *whatm, *suffix; + time_t stamp; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + if ( !ad->ad_logfile ) return SLAP_CB_CONTINUE; + +/* +** add or modify: use modifiersName if present +** +*/ + switch(op->o_tag) { + case LDAP_REQ_MODRDN: what = "modrdn"; break; + case LDAP_REQ_DELETE: what = "delete"; break; + case LDAP_REQ_ADD: + what = "add"; + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if( a->a_desc == slap_schema.si_ad_modifiersName ) { + who = &a->a_vals[0]; + break; + } + break; + case LDAP_REQ_MODIFY: + what = "modify"; + for(m = op->orm_modlist; m; m = m->sml_next) + if( m->sml_desc == slap_schema.si_ad_modifiersName && + ( m->sml_op == LDAP_MOD_ADD || + m->sml_op == LDAP_MOD_REPLACE )) { + who = &m->sml_values[0]; + break; + } + break; + default: + return SLAP_CB_CONTINUE; + } + + suffix = op->o_bd->be_suffix[0].bv_len ? op->o_bd->be_suffix[0].bv_val : + "global"; + +/* +** note: this means requestor's dn when modifiersName is null +*/ + if ( !who ) + who = &op->o_dn; + + peername = op->o_conn->c_peer_name; + ldap_pvt_thread_mutex_lock(&ad->ad_mutex); + if((f = fopen(ad->ad_logfile, "a")) == NULL) { + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; + } + + stamp = slap_get_time(); + fprintf(f, "# %s %ld %s%s%s %s conn=%ld\n", + what, (long)stamp, suffix, who ? " " : "", who ? who->bv_val : "", + peername.bv_val ? peername.bv_val: "", op->o_conn->c_connid); + + if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) && + (!who || !dn_match( who, &op->o_conn->c_dn ))) + fprintf(f, "# realdn: %s\n", op->o_conn->c_dn.bv_val ); + + fprintf(f, "dn: %s\nchangetype: %s\n", + op->o_req_dn.bv_val, what); + + switch(op->o_tag) { + case LDAP_REQ_ADD: + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if((b = a->a_vals) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, a->a_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + break; + + case LDAP_REQ_MODIFY: + for(m = op->orm_modlist; m; m = m->sml_next) { + switch(m->sml_op & LDAP_MOD_OP) { + case LDAP_MOD_ADD: whatm = "add"; break; + case LDAP_MOD_REPLACE: whatm = "replace"; break; + case LDAP_MOD_DELETE: whatm = "delete"; break; + case LDAP_MOD_INCREMENT: whatm = "increment"; break; + default: + fprintf(f, "# MOD_TYPE_UNKNOWN:%02x\n", m->sml_op & LDAP_MOD_OP); + continue; + } + fprintf(f, "%s: %s\n", whatm, m->sml_desc->ad_cname.bv_val); + if((b = m->sml_values) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, m->sml_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + fprintf(f, "-\n"); + } + break; + + case LDAP_REQ_MODRDN: + fprintf(f, "newrdn: %s\ndeleteoldrdn: %s\n", + op->orr_newrdn.bv_val, op->orr_deleteoldrdn ? "1" : "0"); + if(op->orr_newSup) fprintf(f, "newsuperior: %s\n", op->orr_newSup->bv_val); + break; + + case LDAP_REQ_DELETE: + /* nothing else needed */ + break; + } + + fprintf(f, "# end %s %ld\n\n", what, (long)stamp); + + fclose(f); + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; +} + +static slap_overinst auditlog; + +static int +auditlog_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = ch_calloc(1, sizeof(auditlog_data)); + + on->on_bi.bi_private = ad; + ldap_pvt_thread_mutex_init( &ad->ad_mutex ); + return 0; +} + +static int +auditlog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + + ldap_pvt_thread_mutex_destroy( &ad->ad_mutex ); + free( ad->ad_logfile ); + free( ad ); + return 0; +} + +int auditlog_initialize() { + int rc; + + auditlog.on_bi.bi_type = "auditlog"; + auditlog.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + auditlog.on_bi.bi_db_init = auditlog_db_init; + auditlog.on_bi.bi_db_destroy = auditlog_db_destroy; + auditlog.on_response = auditlog_response; + + auditlog.on_bi.bi_cf_ocs = auditlogocs; + rc = config_register_schema( auditlogcfg, auditlogocs ); + if ( rc ) return rc; + + return overlay_register(&auditlog); +} + +#if SLAPD_OVER_AUDITLOG == SLAPD_MOD_DYNAMIC && defined(PIC) +int +init_module( int argc, char *argv[] ) +{ + return auditlog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_AUDITLOG */ diff --git a/servers/slapd/overlays/autoca.c b/servers/slapd/overlays/autoca.c new file mode 100644 index 0000000..50d3ca4 --- /dev/null +++ b/servers/slapd/overlays/autoca.c @@ -0,0 +1,1117 @@ +/* autoca.c - Automatic Certificate Authority */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2009-2022 The OpenLDAP Foundation. + * Copyright 2009-2018 by Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_AUTOCA + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/evp.h> +#include <openssl/bn.h> + +/* Starting with OpenSSL 1.1.0, rsa.h is no longer included in + * x509.h, so we need to explicitly include it for the + * call to EVP_PKEY_CTX_set_rsa_keygen_bits + */ + +#if OPENSSL_VERSION_NUMBER >= 0x10100000 +#include <openssl/rsa.h> +#define X509_get_notBefore(x) X509_getm_notBefore(x) +#define X509_get_notAfter(x) X509_getm_notAfter(x) +#endif + +/* This overlay implements a certificate authority that can generate + * certificates automatically for any entry in the directory. + * On startup it generates a self-signed CA cert for the directory's + * suffix entry and uses this to sign all other certs that it generates. + * User and server certs are generated on demand, using a Search request. + */ + +#define LBER_TAG_OID ((ber_tag_t) 0x06UL) +#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL) + +#define KEYBITS 2048 +#define MIN_KEYBITS 512 + +#define ACA_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.11" + +#define ACA_SCHEMA_AT ACA_SCHEMA_ROOT ".1" +#define ACA_SCHEMA_OC ACA_SCHEMA_ROOT ".2" + +static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey; +static AttributeDescription *ad_mail, *ad_ipaddr; +static ObjectClass *oc_caObj, *oc_usrObj; + +static char *aca_attrs[] = { + "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' " + "DESC 'X.509 CA private key, use ;binary' " + "SUP pKCS8PrivateKey )", + "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' " + "DESC 'X.509 user private key, use ;binary' " + "SUP pKCS8PrivateKey )", + NULL +}; + +static struct { + char *at; + AttributeDescription **ad; +} aca_attr2[] = { + { "cACertificate;binary", &ad_caCert }, + { "cAPrivateKey;binary", &ad_caPkey }, + { "userCertificate;binary", &ad_usrCert }, + { "userPrivateKey;binary", &ad_usrPkey }, + { "mail", &ad_mail }, + { NULL } +}; + +static struct { + char *ot; + ObjectClass **oc; +} aca_ocs[] = { + { "( " ACA_SCHEMA_OC ".1 NAME 'autoCA' " + "DESC 'Automated PKI certificate authority' " + "SUP pkiCA AUXILIARY " + "MAY cAPrivateKey )", &oc_caObj }, + { "( " ACA_SCHEMA_OC ".2 NAME 'autoCAuser' " + "DESC 'Automated PKI CA user' " + "SUP pkiUser AUXILIARY " + "MAY userPrivateKey )", &oc_usrObj }, + { NULL } +}; + +typedef struct autoca_info { + X509 *ai_cert; + EVP_PKEY *ai_pkey; + ObjectClass *ai_usrclass; + ObjectClass *ai_srvclass; + struct berval ai_localdn; + struct berval ai_localndn; + int ai_usrkeybits; + int ai_srvkeybits; + int ai_cakeybits; + int ai_usrdays; + int ai_srvdays; + int ai_cadays; +} autoca_info; + +/* Rewrite an LDAP DN in DER form + * Input must be valid DN, therefore no error checking is done here. + */ +static int autoca_dnbv2der( Operation *op, struct berval *bv, struct berval *der ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPDN dn; + LDAPRDN rdn; + LDAPAVA *ava; + AttributeDescription *ad; + int irdn, iava; + + ldap_bv2dn_x( bv, &dn, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* count RDNs, we need them in reverse order */ + for (irdn = 0; dn[irdn]; irdn++); + irdn--; + + /* DN is a SEQuence of RDNs */ + ber_start_seq( ber, LBER_SEQUENCE ); + for (; irdn >=0; irdn--) + { + /* RDN is a SET of AVAs */ + ber_start_set( ber, LBER_SET ); + rdn = dn[irdn]; + for (iava = 0; rdn[iava]; iava++) + { + const char *text; + char oid[1024]; + struct berval bvo = { sizeof(oid), oid }; + struct berval bva; + + /* AVA is a SEQuence of attr and value */ + ber_start_seq( ber, LBER_SEQUENCE ); + ava = rdn[iava]; + ad = NULL; + slap_bv2ad( &ava->la_attr, &ad, &text ); + ber_str2bv( ad->ad_type->sat_oid, 0, 0, &bva ); + ber_encode_oid( &bva, &bvo ); + ber_put_berval( ber, &bvo, LBER_TAG_OID ); + ber_put_berval( ber, &ava->la_value, LBER_TAG_UTF8 ); + ber_put_seq( ber ); + } + ber_put_set( ber ); + } + ber_put_seq( ber ); + ber_flatten2( ber, der, 0 ); + ldap_dnfree_x( dn, op->o_tmpmemctx ); + return 0; +} + +static int autoca_genpkey(int bits, EVP_PKEY **pkey) +{ + EVP_PKEY_CTX *kctx; + int rc; + + kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (kctx == NULL) + return -1; + if (EVP_PKEY_keygen_init(kctx) <= 0) + { + EVP_PKEY_CTX_free(kctx); + return -1; + } + if (EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, bits) <= 0) + { + EVP_PKEY_CTX_free(kctx); + return -1; + } + rc = EVP_PKEY_keygen(kctx, pkey); + EVP_PKEY_CTX_free(kctx); + return rc; +} + +static int autoca_signcert(X509 *cert, EVP_PKEY *pkey) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_create(); + EVP_PKEY_CTX *pkctx = NULL; + int rc = -1; + + if ( ctx == NULL ) + return -1; + if (EVP_DigestSignInit(ctx, &pkctx, NULL, NULL, pkey)) + { + rc = X509_sign_ctx(cert, ctx); + } + EVP_MD_CTX_destroy(ctx); + return rc; +} + +#define SERIAL_BITS 64 /* should be less than 160 */ + +typedef struct myext { + char *name; + char *value; +} myext; + +static myext CAexts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "critical,CA:true" }, + { "keyUsage", "digitalSignature,cRLSign,keyCertSign" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +static myext usrExts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "CA:false" }, + { "keyUsage", "digitalSignature,nonRepudiation,keyEncipherment" }, + { "extendedKeyUsage", "clientAuth,emailProtection,codeSigning" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +static myext srvExts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "CA:false" }, + { "keyUsage", "digitalSignature,keyEncipherment" }, + { "extendedKeyUsage", "serverAuth,clientAuth" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +typedef struct genargs { + X509 *issuer_cert; + EVP_PKEY *issuer_pkey; + struct berval *subjectDN; + myext *cert_exts; + myext *more_exts; + X509 *newcert; + EVP_PKEY *newpkey; + struct berval dercert; + struct berval derpkey; + int keybits; + int days; +} genargs; + +static int autoca_gencert( Operation *op, genargs *args ) +{ + X509_NAME *subj_name, *issuer_name; + X509 *subj_cert; + struct berval derdn; + unsigned char *pp; + EVP_PKEY *evpk = NULL; + int rc; + + if ((subj_cert = X509_new()) == NULL) + return -1; + + autoca_dnbv2der( op, args->subjectDN, &derdn ); + pp = (unsigned char *)derdn.bv_val; + subj_name = d2i_X509_NAME( NULL, (const unsigned char **)&pp, derdn.bv_len ); + op->o_tmpfree( derdn.bv_val, op->o_tmpmemctx ); + if ( subj_name == NULL ) + { +fail1: + X509_free( subj_cert ); + return -1; + } + + rc = autoca_genpkey( args->keybits, &evpk ); + if ( rc <= 0 ) + { +fail2: + if ( subj_name ) X509_NAME_free( subj_name ); + goto fail1; + } + /* encode DER in PKCS#8 */ + { + PKCS8_PRIV_KEY_INFO *p8inf; + if (( p8inf = EVP_PKEY2PKCS8( evpk )) == NULL ) + goto fail2; + args->derpkey.bv_len = i2d_PKCS8_PRIV_KEY_INFO( p8inf, NULL ); + args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx ); + pp = (unsigned char *)args->derpkey.bv_val; + i2d_PKCS8_PRIV_KEY_INFO( p8inf, &pp ); + PKCS8_PRIV_KEY_INFO_free( p8inf ); + } + args->newpkey = evpk; + + /* set random serial */ + { + BIGNUM *bn = BN_new(); + if ( bn == NULL ) + { +fail3: + EVP_PKEY_free( evpk ); + goto fail2; + } + if (!BN_pseudo_rand(bn, SERIAL_BITS, 0, 0)) + { + BN_free( bn ); + goto fail3; + } + if (!BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(subj_cert))) + { + BN_free( bn ); + goto fail3; + } + BN_free(bn); + } + if (args->issuer_cert) { + issuer_name = X509_get_subject_name(args->issuer_cert); + } else { + issuer_name = subj_name; + args->issuer_cert = subj_cert; + args->issuer_pkey = evpk; + } + if (!X509_set_version(subj_cert, 2) || /* set version to V3 */ + !X509_set_issuer_name(subj_cert, issuer_name) || + !X509_set_subject_name(subj_cert, subj_name) || + !X509_gmtime_adj(X509_get_notBefore(subj_cert), 0) || + !X509_time_adj_ex(X509_get_notAfter(subj_cert), args->days, 0, NULL) || + !X509_set_pubkey(subj_cert, evpk)) + { + goto fail3; + } + X509_NAME_free(subj_name); + subj_name = NULL; + + /* set cert extensions */ + { + X509V3_CTX ctx; + X509_EXTENSION *ext; + int i; + + X509V3_set_ctx(&ctx, args->issuer_cert, subj_cert, NULL, NULL, 0); + for (i=0; args->cert_exts[i].name; i++) { + ext = X509V3_EXT_nconf(NULL, &ctx, args->cert_exts[i].name, args->cert_exts[i].value); + if ( ext == NULL ) + goto fail3; + rc = X509_add_ext(subj_cert, ext, -1); + X509_EXTENSION_free(ext); + if ( !rc ) + goto fail3; + } + if (args->more_exts) { + for (i=0; args->more_exts[i].name; i++) { + ext = X509V3_EXT_nconf(NULL, &ctx, args->more_exts[i].name, args->more_exts[i].value); + if ( ext == NULL ) + goto fail3; + rc = X509_add_ext(subj_cert, ext, -1); + X509_EXTENSION_free(ext); + if ( !rc ) + goto fail3; + } + } + } + rc = autoca_signcert( subj_cert, args->issuer_pkey ); + if ( rc < 0 ) + goto fail3; + args->dercert.bv_len = i2d_X509( subj_cert, NULL ); + args->dercert.bv_val = op->o_tmpalloc( args->dercert.bv_len, op->o_tmpmemctx ); + pp = (unsigned char *)args->dercert.bv_val; + i2d_X509( subj_cert, &pp ); + args->newcert = subj_cert; + return 0; +} + +typedef struct saveargs { + ObjectClass *oc; + struct berval *dercert; + struct berval *derpkey; + slap_overinst *on; + struct berval *dn; + struct berval *ndn; + int isca; +} saveargs; + +static int autoca_savecert( Operation *op, saveargs *args ) +{ + Modifications mod[3], *mp = mod; + struct berval bvs[6], *bp = bvs; + BackendInfo *bi; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + + if ( args->oc ) { + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = slap_schema.si_ad_objectClass; + mp->sml_op = LDAP_MOD_ADD; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = args->oc->soc_cname; + BER_BVZERO( bp ); + bp++; + mp->sml_next = mp+1; + mp++; + } + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = args->isca ? ad_caCert : ad_usrCert; + mp->sml_op = LDAP_MOD_REPLACE; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = *args->dercert; + BER_BVZERO( bp ); + bp++; + mp->sml_next = mp+1; + mp++; + + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = args->isca ? ad_caPkey : ad_usrPkey; + mp->sml_op = LDAP_MOD_ADD; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = *args->derpkey; + BER_BVZERO( bp ); + mp->sml_next = NULL; + + cb.sc_response = slap_null_cb; + bi = op->o_bd->bd_info; + op->o_bd->bd_info = args->on->on_info->oi_orig; + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = mod; + op->orm_no_opattrs = 1; + op->o_req_dn = *args->dn; + op->o_req_ndn = *args->ndn; + op->o_bd->be_modify( op, &rs ); + op->o_bd->bd_info = bi; + return rs.sr_err; +} + +static const struct berval configDN = BER_BVC("cn=config"); + +/* must run as a pool thread to avoid cn=config deadlock */ +static void * +autoca_setca_task( void *ctx, void *arg ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + struct berval *cacert = arg; + Modifications mod; + struct berval bvs[2]; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + const char *text; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + mod.sml_numvals = 1; + mod.sml_values = bvs; + mod.sml_nvalues = NULL; + mod.sml_desc = NULL; + if ( slap_str2ad( "olcTLSCACertificate;binary", &mod.sml_desc, &text )) + goto leave; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + bvs[0] = *cacert; + BER_BVZERO( &bvs[1] ); + mod.sml_next = NULL; + + cb.sc_response = slap_null_cb; + op->o_bd = select_backend( (struct berval *)&configDN, 0 ); + if ( !op->o_bd ) + goto leave; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + op->o_req_dn = configDN; + op->o_req_ndn = configDN; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->be_modify( op, &rs ); +leave: + ch_free( arg ); + return NULL; +} + +static int +autoca_setca( struct berval *cacert ) +{ + struct berval *bv = ch_malloc( sizeof(struct berval) + cacert->bv_len ); + bv->bv_len = cacert->bv_len; + bv->bv_val = (char *)(bv+1); + AC_MEMCPY( bv->bv_val, cacert->bv_val, bv->bv_len ); + return ldap_pvt_thread_pool_submit( &connection_pool, autoca_setca_task, bv ); +} + +static int +autoca_setlocal( Operation *op, struct berval *cert, struct berval *pkey ) +{ + Modifications mod[2]; + struct berval bvs[4]; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + const char *text; + + mod[0].sml_numvals = 1; + mod[0].sml_values = bvs; + mod[0].sml_nvalues = NULL; + mod[0].sml_desc = NULL; + if ( slap_str2ad( "olcTLSCertificate;binary", &mod[0].sml_desc, &text )) + return -1; + mod[0].sml_op = LDAP_MOD_REPLACE; + mod[0].sml_flags = SLAP_MOD_INTERNAL; + bvs[0] = *cert; + BER_BVZERO( &bvs[1] ); + mod[0].sml_next = &mod[1]; + + mod[1].sml_numvals = 1; + mod[1].sml_values = &bvs[2]; + mod[1].sml_nvalues = NULL; + mod[1].sml_desc = NULL; + if ( slap_str2ad( "olcTLSCertificateKey;binary", &mod[1].sml_desc, &text )) + return -1; + mod[1].sml_op = LDAP_MOD_REPLACE; + mod[1].sml_flags = SLAP_MOD_INTERNAL; + bvs[2] = *pkey; + BER_BVZERO( &bvs[3] ); + mod[1].sml_next = NULL; + + cb.sc_response = slap_null_cb; + op->o_bd = select_backend( (struct berval *)&configDN, 0 ); + if ( !op->o_bd ) + return -1; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = mod; + op->orm_no_opattrs = 1; + op->o_req_dn = configDN; + op->o_req_ndn = configDN; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->be_modify( op, &rs ); + return rs.sr_err; +} + +enum { + ACA_USRCLASS = 1, + ACA_SRVCLASS, + ACA_USRKEYBITS, + ACA_SRVKEYBITS, + ACA_CAKEYBITS, + ACA_USRDAYS, + ACA_SRVDAYS, + ACA_CADAYS, + ACA_LOCALDN +}; + +static int autoca_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + autoca_info *ai = on->on_bi.bi_private; + int rc = 0; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + switch( c->type ) { + case ACA_USRCLASS: + if ( ai->ai_usrclass ) { + c->value_string = ch_strdup( ai->ai_usrclass->soc_cname.bv_val ); + } else { + rc = 1; + } + break; + case ACA_SRVCLASS: + if ( ai->ai_srvclass ) { + c->value_string = ch_strdup( ai->ai_srvclass->soc_cname.bv_val ); + } else { + rc = 1; + } + break; + case ACA_USRKEYBITS: + c->value_int = ai->ai_usrkeybits; + break; + case ACA_SRVKEYBITS: + c->value_int = ai->ai_srvkeybits; + break; + case ACA_CAKEYBITS: + c->value_int = ai->ai_cakeybits; + break; + case ACA_USRDAYS: + c->value_int = ai->ai_usrdays; + break; + case ACA_SRVDAYS: + c->value_int = ai->ai_srvdays; + break; + case ACA_CADAYS: + c->value_int = ai->ai_cadays; + break; + case ACA_LOCALDN: + if ( !BER_BVISNULL( &ai->ai_localdn )) { + rc = value_add_one( &c->rvalue_vals, &ai->ai_localdn ); + } else { + rc = 1; + } + break; + } + break; + case LDAP_MOD_DELETE: + switch( c->type ) { + case ACA_USRCLASS: + ai->ai_usrclass = NULL; + break; + case ACA_SRVCLASS: + ai->ai_srvclass = NULL; + break; + case ACA_LOCALDN: + if ( ai->ai_localdn.bv_val ) { + ch_free( ai->ai_localdn.bv_val ); + ch_free( ai->ai_localndn.bv_val ); + BER_BVZERO( &ai->ai_localdn ); + BER_BVZERO( &ai->ai_localndn ); + } + break; + /* single-valued attrs, all no-ops */ + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch( c->type ) { + case ACA_USRCLASS: + { + ObjectClass *oc = oc_find( c->value_string ); + if ( oc ) + ai->ai_usrclass = oc; + else + rc = 1; + } + break; + case ACA_SRVCLASS: + { + ObjectClass *oc = oc_find( c->value_string ); + if ( oc ) + ai->ai_srvclass = oc; + else + rc = 1; + } + case ACA_USRKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_usrkeybits = c->value_int; + break; + case ACA_SRVKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_srvkeybits = c->value_int; + break; + case ACA_CAKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_cakeybits = c->value_int; + break; + case ACA_USRDAYS: + ai->ai_usrdays = c->value_int; + break; + case ACA_SRVDAYS: + ai->ai_srvdays = c->value_int; + break; + case ACA_CADAYS: + ai->ai_cadays = c->value_int; + break; + case ACA_LOCALDN: + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( !dnIsSuffix( &c->value_ndn, c->be->be_nsuffix )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DN is not a subordinate of backend" ); + Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( ai->ai_localdn.bv_val ) { + ch_free( ai->ai_localdn.bv_val ); + ch_free( ai->ai_localndn.bv_val ); + } + ai->ai_localdn = c->value_dn; + ai->ai_localndn = c->value_ndn; + } + } + return rc; +} + +static ConfigTable autoca_cfg[] = { + { "userClass", "objectclass", 2, 2, 0, + ARG_STRING|ARG_MAGIC|ACA_USRCLASS, autoca_cf, + "( OLcfgOvAt:22.1 NAME 'olcAutoCAuserClass' " + "DESC 'ObjectClass of user entries' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "serverClass", "objectclass", 2, 2, 0, + ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf, + "( OLcfgOvAt:22.2 NAME 'olcAutoCAserverClass' " + "DESC 'ObjectClass of server entries' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "userKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_USRKEYBITS, autoca_cf, + "( OLcfgOvAt:22.3 NAME 'olcAutoCAuserKeybits' " + "DESC 'Size of PrivateKey for user entries' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "serverKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_SRVKEYBITS, autoca_cf, + "( OLcfgOvAt:22.4 NAME 'olcAutoCAserverKeybits' " + "DESC 'Size of PrivateKey for server entries' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "caKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_CAKEYBITS, autoca_cf, + "( OLcfgOvAt:22.5 NAME 'olcAutoCAKeybits' " + "DESC 'Size of PrivateKey for CA certificate' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "userDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_USRDAYS, autoca_cf, + "( OLcfgOvAt:22.6 NAME 'olcAutoCAuserDays' " + "DESC 'Lifetime of user certificates in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "serverDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_SRVDAYS, autoca_cf, + "( OLcfgOvAt:22.7 NAME 'olcAutoCAserverDays' " + "DESC 'Lifetime of server certificates in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "caDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_CADAYS, autoca_cf, + "( OLcfgOvAt:22.8 NAME 'olcAutoCADays' " + "DESC 'Lifetime of CA certificate in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "localdn", "dn", 2, 2, 0, + ARG_DN|ARG_QUOTE|ARG_MAGIC|ACA_LOCALDN, autoca_cf, + "( OLcfgOvAt:22.9 NAME 'olcAutoCAlocalDN' " + "DESC 'DN of local server cert' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs autoca_ocs[] = { + { "( OLcfgOvOc:22.1 " + "NAME 'olcAutoCAConfig' " + "DESC 'AutoCA configuration' " + "SUP olcOverlayConfig " + "MAY ( olcAutoCAuserClass $ olcAutoCAserverClass $ " + "olcAutoCAuserKeybits $ olcAutoCAserverKeybits $ olcAutoCAKeyBits $ " + "olcAutoCAuserDays $ olcAutoCAserverDays $ olcAutoCADays $ " + "olcAutoCAlocalDN ) )", + Cft_Overlay, autoca_cfg }, + { NULL, 0, NULL } +}; + +static int +autoca_op_response( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = op->o_callback->sc_private; + autoca_info *ai = on->on_bi.bi_private; + Attribute *a; + int isusr = 0; + + if (rs->sr_type != REP_SEARCH) + return SLAP_CB_CONTINUE; + + /* If root or self */ + if ( !be_isroot( op ) && + !dn_match( &rs->sr_entry->e_nname, &op->o_ndn )) + return SLAP_CB_CONTINUE; + + isusr = is_entry_objectclass( rs->sr_entry, ai->ai_usrclass, SLAP_OCF_CHECK_SUP ); + if ( !isusr ) + { + if (!is_entry_objectclass( rs->sr_entry, ai->ai_srvclass, SLAP_OCF_CHECK_SUP )) + return SLAP_CB_CONTINUE; + } + a = attr_find( rs->sr_entry->e_attrs, ad_usrPkey ); + if ( !a ) + { + Operation op2; + genargs args; + saveargs arg2; + myext extras[2]; + int rc; + + args.issuer_cert = ai->ai_cert; + args.issuer_pkey = ai->ai_pkey; + args.subjectDN = &rs->sr_entry->e_name; + args.more_exts = NULL; + if ( isusr ) + { + args.cert_exts = usrExts; + args.keybits = ai->ai_usrkeybits; + args.days = ai->ai_usrdays; + a = attr_find( rs->sr_entry->e_attrs, ad_mail ); + if ( a ) + { + extras[0].name = "subjectAltName"; + extras[1].name = NULL; + extras[0].value = op->o_tmpalloc( sizeof("email:") + a->a_vals[0].bv_len, op->o_tmpmemctx ); + sprintf(extras[0].value, "email:%s", a->a_vals[0].bv_val); + args.more_exts = extras; + } + } else + { + args.cert_exts = srvExts; + args.keybits = ai->ai_srvkeybits; + args.days = ai->ai_srvdays; + if ( ad_ipaddr && (a = attr_find( rs->sr_entry->e_attrs, ad_ipaddr ))) + { + extras[0].name = "subjectAltName"; + extras[1].name = NULL; + extras[0].value = op->o_tmpalloc( sizeof("IP:") + a->a_vals[0].bv_len, op->o_tmpmemctx ); + sprintf(extras[0].value, "IP:%s", a->a_vals[0].bv_val); + args.more_exts = extras; + } + } + rc = autoca_gencert( op, &args ); + if ( rc ) + return SLAP_CB_CONTINUE; + X509_free( args.newcert ); + EVP_PKEY_free( args.newpkey ); + + if ( is_entry_objectclass( rs->sr_entry, oc_usrObj, 0 )) + arg2.oc = NULL; + else + arg2.oc = oc_usrObj; + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) + { + Entry *e = entry_dup( rs->sr_entry ); + rs_replace_entry( op, rs, on, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + arg2.dercert = &args.dercert; + arg2.derpkey = &args.derpkey; + arg2.on = on; + arg2.dn = &rs->sr_entry->e_name; + arg2.ndn = &rs->sr_entry->e_nname; + arg2.isca = 0; + op2 = *op; + rc = autoca_savecert( &op2, &arg2 ); + if ( !rc ) + { + /* If this is our cert DN, configure it */ + if ( dn_match( &rs->sr_entry->e_nname, &ai->ai_localndn )) + autoca_setlocal( &op2, &args.dercert, &args.derpkey ); + attr_merge_one( rs->sr_entry, ad_usrCert, &args.dercert, NULL ); + attr_merge_one( rs->sr_entry, ad_usrPkey, &args.derpkey, NULL ); + } + op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static int +autoca_op_search( + Operation *op, + SlapReply *rs +) +{ + /* we only act on a search that returns just our cert/key attrs */ + if ( op->ors_attrs && op->ors_attrs[0].an_desc == ad_usrCert && + op->ors_attrs[1].an_desc == ad_usrPkey && + op->ors_attrs[2].an_name.bv_val == NULL ) + { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + sc->sc_response = autoca_op_response; + sc->sc_private = on; + sc->sc_next = op->o_callback; + op->o_callback = sc; + } + return SLAP_CB_CONTINUE; +} + +static int +autoca_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + autoca_info *ai; + + ai = ch_calloc(1, sizeof(autoca_info)); + on->on_bi.bi_private = ai; + + /* set defaults */ + ai->ai_usrclass = oc_find( "person" ); + ai->ai_srvclass = oc_find( "ipHost" ); + ai->ai_usrkeybits = KEYBITS; + ai->ai_srvkeybits = KEYBITS; + ai->ai_cakeybits = KEYBITS; + ai->ai_usrdays = 365; /* 1 year */ + ai->ai_srvdays = 1826; /* 5 years */ + ai->ai_cadays = 3652; /* 10 years */ + return 0; +} + +static int +autoca_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + autoca_info *ai = on->on_bi.bi_private; + + if ( ai->ai_cert ) + X509_free( ai->ai_cert ); + if ( ai->ai_pkey ) + EVP_PKEY_free( ai->ai_pkey ); + ch_free( ai ); + + return 0; +} + +static int +autoca_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + autoca_info *ai = on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + Entry *e = NULL; + Attribute *a; + int rc; + + if (slapMode & SLAP_TOOL_MODE) + return 0; + + if ( ! *aca_attr2[0].ad ) { + int i, code; + const char *text; + + for ( i=0; aca_attr2[i].at; i++ ) { + code = slap_str2ad( aca_attr2[i].at, aca_attr2[i].ad, &text ); + if ( code ) return code; + } + + /* Schema may not be loaded, ignore if missing */ + slap_str2ad( "ipHostNumber", &ad_ipaddr, &text ); + + for ( i=0; aca_ocs[i].ot; i++ ) { + code = register_oc( aca_ocs[i].ot, aca_ocs[i].oc, 0 ); + if ( code ) return code; + } + } + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + rc = overlay_entry_get_ov( op, be->be_nsuffix, NULL, + NULL, 0, &e, on ); + + if ( e ) { + int gotoc = 0, gotat = 0; + if ( is_entry_objectclass( e, oc_caObj, 0 )) { + gotoc = 1; + a = attr_find( e->e_attrs, ad_caPkey ); + if ( a ) { + const unsigned char *pp; + pp = (unsigned char *)a->a_vals[0].bv_val; + ai->ai_pkey = d2i_AutoPrivateKey( NULL, &pp, a->a_vals[0].bv_len ); + if ( ai->ai_pkey ) + { + a = attr_find( e->e_attrs, ad_caCert ); + if ( a ) + { + pp = (unsigned char *)a->a_vals[0].bv_val; + ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len ); + /* If TLS wasn't configured yet, set this as our CA */ + if ( !slap_tls_ctx ) + autoca_setca( a->a_vals ); + } + } + gotat = 1; + } + } + overlay_entry_release_ov( op, e, 0, on ); + /* generate attrs, store... */ + if ( !gotat ) { + genargs args; + saveargs arg2; + + args.issuer_cert = NULL; + args.issuer_pkey = NULL; + args.subjectDN = &be->be_suffix[0]; + args.cert_exts = CAexts; + args.more_exts = NULL; + args.keybits = ai->ai_cakeybits; + args.days = ai->ai_cadays; + + rc = autoca_gencert( op, &args ); + if ( rc ) + return -1; + + ai->ai_cert = args.newcert; + ai->ai_pkey = args.newpkey; + + arg2.dn = be->be_suffix; + arg2.ndn = be->be_nsuffix; + arg2.isca = 1; + if ( !gotoc ) + arg2.oc = oc_caObj; + else + arg2.oc = NULL; + arg2.on = on; + arg2.dercert = &args.dercert; + arg2.derpkey = &args.derpkey; + + autoca_savecert( op, &arg2 ); + + /* If TLS wasn't configured yet, set this as our CA */ + if ( !slap_tls_ctx ) + autoca_setca( &args.dercert ); + + op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx ); + } + } + + return 0; +} + +static slap_overinst autoca; + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +int autoca_initialize() { + int i, code; + + autoca.on_bi.bi_type = "autoca"; + autoca.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + autoca.on_bi.bi_db_init = autoca_db_init; + autoca.on_bi.bi_db_destroy = autoca_db_destroy; + autoca.on_bi.bi_db_open = autoca_db_open; + autoca.on_bi.bi_op_search = autoca_op_search; + + autoca.on_bi.bi_cf_ocs = autoca_ocs; + code = config_register_schema( autoca_cfg, autoca_ocs ); + if ( code ) return code; + + for ( i=0; aca_attrs[i]; i++ ) { + code = register_at( aca_attrs[i], NULL, 0 ); + if ( code ) return code; + } + + return overlay_register( &autoca ); +} + +#if SLAPD_OVER_AUTOCA == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return autoca_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_AUTOCA) */ diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c new file mode 100644 index 0000000..bbc6219 --- /dev/null +++ b/servers/slapd/overlays/collect.c @@ -0,0 +1,440 @@ +/* collect.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_COLLECT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" + +#include "lutil.h" + +/* This is a cheap hack to implement a collective attribute. + * + * This demonstration overlay looks for a specified attribute in an + * ancestor of a given entry and adds that attribute to the given + * entry when it is returned in a search response. It takes no effect + * for any other operations. If the ancestor does not exist, there + * is no effect. If no attribute was configured, there is no effect. + */ + +typedef struct collect_info { + struct collect_info *ci_next; + struct berval ci_dn; + int ci_ad_num; + AttributeDescription *ci_ad[1]; +} collect_info; + +static int collect_cf( ConfigArgs *c ); + +static ConfigTable collectcfg[] = { + { "collectinfo", "dn> <attribute", 3, 3, 0, + ARG_MAGIC, collect_cf, + "( OLcfgOvAt:19.1 NAME 'olcCollectInfo' " + "DESC 'DN of entry and attribute to distribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs collectocs[] = { + { "( OLcfgOvOc:19.1 " + "NAME 'olcCollectConfig' " + "DESC 'Collective Attribute configuration' " + "SUP olcOverlayConfig " + "MAY olcCollectInfo )", + Cft_Overlay, collectcfg }, + { NULL, 0, NULL } +}; + +/* + * inserts a collect_info into on->on_bi.bi_private taking into account + * order. this means longer dn's (i.e. more specific dn's) will be found + * first when searching, allowing some limited overlap of dn's + */ +static void +insert_ordered( slap_overinst *on, collect_info *ci ) { + collect_info *find = on->on_bi.bi_private; + collect_info *prev = NULL; + int found = 0; + + while (!found) { + if (find == NULL) { + if (prev == NULL) { + /* base case - empty list */ + on->on_bi.bi_private = ci; + ci->ci_next = NULL; + } else { + /* final case - end of list */ + prev->ci_next = ci; + ci->ci_next = NULL; + } + found = 1; + } else if (find->ci_dn.bv_len < ci->ci_dn.bv_len) { + /* insert into list here */ + if (prev == NULL) { + /* entry is head of list */ + ci->ci_next = on->on_bi.bi_private; + on->on_bi.bi_private = ci; + } else { + /* entry is not head of list */ + prev->ci_next = ci; + ci->ci_next = find; + } + found = 1; + } else { + /* keep looking */ + prev = find; + find = find->ci_next; + } + } +} + +static int +collect_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + int rc = 1, idx; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + { + collect_info *ci; + for ( ci = on->on_bi.bi_private; ci; ci = ci->ci_next ) { + struct berval bv; + char *ptr; + int len; + + /* calculate the length & malloc memory */ + bv.bv_len = ci->ci_dn.bv_len + STRLENOF("\"\" "); + for (idx=0; idx<ci->ci_ad_num; idx++) { + bv.bv_len += ci->ci_ad[idx]->ad_cname.bv_len; + if (idx<(ci->ci_ad_num-1)) { + bv.bv_len++; + } + } + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + /* copy the value and update len */ + len = snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" ", + ci->ci_dn.bv_val); + ptr = bv.bv_val + len; + for (idx=0; idx<ci->ci_ad_num; idx++) { + ptr = lutil_strncopy( ptr, + ci->ci_ad[idx]->ad_cname.bv_val, + ci->ci_ad[idx]->ad_cname.bv_len); + if (idx<(ci->ci_ad_num-1)) { + *ptr++ = ','; + } + } + *ptr = '\0'; + bv.bv_len = ptr - bv.bv_val; + + ber_bvarray_add( &c->rvalue_vals, &bv ); + rc = 0; + } + } + break; + case LDAP_MOD_DELETE: + if ( c->valx == -1 ) { + /* Delete entire attribute */ + collect_info *ci; + while (( ci = on->on_bi.bi_private )) { + on->on_bi.bi_private = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + } else { + /* Delete just one value */ + collect_info **cip, *ci; + int i; + cip = (collect_info **)&on->on_bi.bi_private; + ci = *cip; + for ( i=0; i < c->valx; i++ ) { + cip = &ci->ci_next; + ci = *cip; + } + *cip = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + rc = 0; + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + { + collect_info *ci; + struct berval bv, dn; + const char *text; + int idx, count=0; + char *arg; + + /* count delimiters in attribute argument */ + arg = strtok(c->argv[2], ","); + while (arg!=NULL) { + count++; + arg = strtok(NULL, ","); + } + + /* validate and normalize dn */ + ber_str2bv( c->argv[1], 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &dn, NULL ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + /* check for duplicate DNs */ + for ( ci = (collect_info *)on->on_bi.bi_private; ci; + ci = ci->ci_next ) { + /* If new DN is longest, there are no possible matches */ + if ( dn.bv_len > ci->ci_dn.bv_len ) { + ci = NULL; + break; + } + if ( bvmatch( &dn, &ci->ci_dn )) { + break; + } + } + if ( ci ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s DN already configured: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + /* allocate config info with room for attribute array */ + ci = ch_malloc( sizeof( collect_info ) + + sizeof( AttributeDescription * ) * count ); + + /* load attribute description for attribute list */ + arg = c->argv[2]; + for( idx=0; idx<count; idx++) { + ci->ci_ad[idx] = NULL; + + if ( slap_str2ad( arg, &ci->ci_ad[idx], &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s attribute description unknown: \"%s\"", + c->argv[0], arg); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + ch_free( ci ); + return ARG_BAD_CONF; + } + while(*arg!='\0') { + arg++; /* skip to end of argument */ + } + if (idx<count-1) { + arg++; /* skip inner delimiters */ + } + } + + /* The on->on_bi.bi_private pointer can be used for + * anything this instance of the overlay needs. + */ + ci->ci_ad[count] = NULL; + ci->ci_ad_num = count; + ci->ci_dn = dn; + + /* creates list of ci's ordered by dn length */ + insert_ordered ( on, ci ); + + /* New ci wasn't simply appended to end, adjust its + * position in the config entry's a_vals + */ + if ( c->ca_entry && ci->ci_next ) { + Attribute *a = attr_find( c->ca_entry->e_attrs, + collectcfg[0].ad ); + if ( a ) { + struct berval bv, nbv; + collect_info *c2 = (collect_info *)on->on_bi.bi_private; + int i, j; + for ( i=0; c2 != ci; i++, c2 = c2->ci_next ); + bv = a->a_vals[a->a_numvals-1]; + nbv = a->a_nvals[a->a_numvals-1]; + for ( j=a->a_numvals-1; j>i; j-- ) { + a->a_vals[j] = a->a_vals[j-1]; + a->a_nvals[j] = a->a_nvals[j-1]; + } + a->a_vals[j] = bv; + a->a_nvals[j] = nbv; + } + } + + rc = 0; + } + } + return rc; +} + +static int +collect_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + collect_info *ci; + + while (( ci = on->on_bi.bi_private )) { + on->on_bi.bi_private = ci->ci_next; + ch_free( ci->ci_dn.bv_val ); + ch_free( ci ); + } + return 0; +} + +static int +collect_modify( Operation *op, SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + collect_info *ci = on->on_bi.bi_private; + Modifications *ml; + char errMsg[100]; + int idx; + + for ( ml = op->orm_modlist; ml != NULL; ml = ml->sml_next) { + for (; ci; ci=ci->ci_next ) { + /* Is this entry an ancestor of this collectinfo ? */ + if (!dnIsSuffix(&op->o_req_ndn, &ci->ci_dn)) { + /* this collectinfo does not match */ + continue; + } + + /* Is this entry the same as the template DN ? */ + if ( dn_match(&op->o_req_ndn, &ci->ci_dn)) { + /* all changes in this ci are allowed */ + continue; + } + + /* check for collect attributes - disallow modify if present */ + for(idx=0; idx<ci->ci_ad_num; idx++) { + if (ml->sml_desc == ci->ci_ad[idx]) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + snprintf( errMsg, sizeof( errMsg ), + "cannot change virtual attribute '%s'", + ci->ci_ad[idx]->ad_cname.bv_val); + rs->sr_text = errMsg; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + } + + } + + return SLAP_CB_CONTINUE; +} + +static int +collect_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + collect_info *ci = on->on_bi.bi_private; + + /* If we've been configured and the current response is + * a search entry + */ + if ( ci && rs->sr_type == REP_SEARCH ) { + int rc; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + for (; ci; ci=ci->ci_next ) { + int idx=0; + + /* Is this entry an ancestor of this collectinfo ? */ + if (!dnIsSuffix(&rs->sr_entry->e_nname, &ci->ci_dn)) { + /* collectinfo does not match */ + continue; + } + + /* Is this entry the same as the template DN ? */ + if ( dn_match(&rs->sr_entry->e_nname, &ci->ci_dn)) { + /* dont apply change to parent */ + continue; + } + + /* The current entry may live in a cache, so + * don't modify it directly. Make a copy and + * work with that instead. + */ + rs_entry2modifiable( op, rs, on ); + + /* Loop for each attribute in this collectinfo */ + for(idx=0; idx<ci->ci_ad_num; idx++) { + BerVarray vals = NULL; + + /* Extract the values of the desired attribute from + * the ancestor entry */ + rc = backend_attribute( op, NULL, &ci->ci_dn, + ci->ci_ad[idx], &vals, ACL_READ ); + + /* If there are any values, merge them into the + * current search result + */ + if ( vals ) { + attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx], + vals, op->o_tmpmemctx ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + } + } + + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static slap_overinst collect; + +int collect_initialize() { + int code; + + collect.on_bi.bi_type = "collect"; + collect.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + collect.on_bi.bi_db_destroy = collect_destroy; + collect.on_bi.bi_op_modify = collect_modify; + collect.on_response = collect_response; + + collect.on_bi.bi_cf_ocs = collectocs; + code = config_register_schema( collectcfg, collectocs ); + if ( code ) return code; + + return overlay_register( &collect ); +} + +#if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return collect_initialize(); +} +#endif + +#endif /* SLAPD_OVER_COLLECT */ diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c new file mode 100644 index 0000000..1feff7a --- /dev/null +++ b/servers/slapd/overlays/constraint.c @@ -0,0 +1,1249 @@ +/* $OpenLDAP$ */ +/* constraint.c - Overlay to constrain attributes to certain values */ +/* + * Copyright 2003-2004 Hewlett-Packard Company + * Copyright 2007 Emmanuel Dreyfus + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* + * Authors: Neil Dunbar <neil.dunbar@hp.com> + * Emmanuel Dreyfus <manu@netbsd.org> + */ +#include "portable.h" + +#ifdef SLAPD_OVER_CONSTRAINT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> +#include <ac/regex.h> + +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +/* + * This overlay limits the values which can be placed into an + * attribute, over and above the limits placed by the schema. + * + * It traps only LDAP adds and modify commands (and only seeks to + * control the add and modify value mods of a modify) + */ + +#define REGEX_STR "regex" +#define NEG_REGEX_STR "negregex" +#define URI_STR "uri" +#define SET_STR "set" +#define SIZE_STR "size" +#define COUNT_STR "count" + +/* + * Linked list of attribute constraints which we should enforce. + * This is probably a sub optimal structure - some form of sorted + * array would be better if the number of attributes constrained is + * likely to be much bigger than 4 or 5. We stick with a list for + * the moment. + */ + +typedef struct constraint { + struct constraint *ap_next; + AttributeDescription **ap; + + LDAPURLDesc *restrict_lud; + struct berval restrict_ndn; + Filter *restrict_filter; + struct berval restrict_val; + + int type; + regex_t *re; + LDAPURLDesc *lud; + int set; + size_t size; + size_t count; + AttributeDescription **attrs; + struct berval val; /* constraint value */ + struct berval dn; + struct berval filter; +} constraint; + +enum { + CONSTRAINT_ATTRIBUTE = 1, + CONSTRAINT_COUNT, + CONSTRAINT_SIZE, + CONSTRAINT_REGEX, + CONSTRAINT_NEG_REGEX, + CONSTRAINT_SET, + CONSTRAINT_URI, +}; + +static ConfigDriver constraint_cf_gen; + +static ConfigTable constraintcfg[] = { + { "constraint_attribute", "attribute[list]> (regex|negregex|uri|set|size|count) <value> [<restrict URI>]", + 4, 0, 0, ARG_MAGIC | CONSTRAINT_ATTRIBUTE, constraint_cf_gen, + "( OLcfgOvAt:13.1 NAME 'olcConstraintAttribute' " + "DESC 'constraint for list of attributes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs constraintocs[] = { + { "( OLcfgOvOc:13.1 " + "NAME 'olcConstraintConfig' " + "DESC 'Constraint overlay configuration' " + "SUP olcOverlayConfig " + "MAY ( olcConstraintAttribute ) )", + Cft_Overlay, constraintcfg }, + { NULL, 0, NULL } +}; + +static void +constraint_free( constraint *cp, int freeme ) +{ + if (cp->restrict_lud) + ldap_free_urldesc(cp->restrict_lud); + if (!BER_BVISNULL(&cp->restrict_ndn)) + ch_free(cp->restrict_ndn.bv_val); + if (cp->restrict_filter != NULL && cp->restrict_filter != slap_filter_objectClass_pres) + filter_free(cp->restrict_filter); + if (!BER_BVISNULL(&cp->restrict_val)) + ch_free(cp->restrict_val.bv_val); + if (cp->re) { + regfree(cp->re); + ch_free(cp->re); + } + if (!BER_BVISNULL(&cp->val)) + ch_free(cp->val.bv_val); + if (cp->lud) + ldap_free_urldesc(cp->lud); + if (cp->attrs) + ch_free(cp->attrs); + if (cp->ap) + ch_free(cp->ap); + if (freeme) + ch_free(cp); +} + +static int +constraint_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)(c->bi); + constraint *cn = on->on_bi.bi_private, *cp; + struct berval bv; + int i, rc = 0; + constraint ap = { NULL }; + const char *text = NULL; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: + for (cp=cn; cp; cp=cp->ap_next) { + char *s; + char *tstr = NULL; + int quotes = 0, numeric = 0; + int j; + size_t val; + char val_buf[SLAP_TEXT_BUFLEN] = { '\0' }; + + bv.bv_len = STRLENOF(" "); + for (j = 0; cp->ap[j]; j++) { + bv.bv_len += cp->ap[j]->ad_cname.bv_len; + } + + /* room for commas */ + bv.bv_len += j - 1; + + switch (cp->type) { + case CONSTRAINT_COUNT: + tstr = COUNT_STR; + val = cp->count; + numeric = 1; + break; + case CONSTRAINT_SIZE: + tstr = SIZE_STR; + val = cp->size; + numeric = 1; + break; + case CONSTRAINT_REGEX: + tstr = REGEX_STR; + quotes = 1; + break; + case CONSTRAINT_NEG_REGEX: + tstr = NEG_REGEX_STR; + quotes = 1; + break; + case CONSTRAINT_SET: + tstr = SET_STR; + quotes = 1; + break; + case CONSTRAINT_URI: + tstr = URI_STR; + quotes = 1; + break; + default: + abort(); + } + + bv.bv_len += strlen(tstr); + bv.bv_len += cp->val.bv_len + 2*quotes; + + if (cp->restrict_lud != NULL) { + bv.bv_len += cp->restrict_val.bv_len + STRLENOF(" restrict=\"\""); + } + + if (numeric) { + int len = snprintf(val_buf, sizeof(val_buf), "%zu", val); + if (len <= 0) { + /* error */ + return -1; + } + bv.bv_len += len; + } + + s = bv.bv_val = ch_malloc(bv.bv_len + 1); + + s = lutil_strncopy( s, cp->ap[0]->ad_cname.bv_val, cp->ap[0]->ad_cname.bv_len ); + for (j = 1; cp->ap[j]; j++) { + *s++ = ','; + s = lutil_strncopy( s, cp->ap[j]->ad_cname.bv_val, cp->ap[j]->ad_cname.bv_len ); + } + *s++ = ' '; + s = lutil_strcopy( s, tstr ); + *s++ = ' '; + if (numeric) { + s = lutil_strcopy( s, val_buf ); + } else { + if ( quotes ) *s++ = '"'; + s = lutil_strncopy( s, cp->val.bv_val, cp->val.bv_len ); + if ( quotes ) *s++ = '"'; + } + if (cp->restrict_lud != NULL) { + s = lutil_strcopy( s, " restrict=\"" ); + s = lutil_strncopy( s, cp->restrict_val.bv_val, cp->restrict_val.bv_len ); + *s++ = '"'; + } + *s = '\0'; + + rc = value_add_one( &c->rvalue_vals, &bv ); + if (rc == LDAP_SUCCESS) + rc = value_add_one( &c->rvalue_nvals, &bv ); + ch_free(bv.bv_val); + if (rc) return rc; + } + break; + default: + abort(); + break; + } + break; + case LDAP_MOD_DELETE: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: + if (!cn) break; /* nothing to do */ + + if (c->valx < 0) { + /* zap all constraints */ + while (cn) { + cp = cn->ap_next; + constraint_free( cn, 1 ); + cn = cp; + } + + on->on_bi.bi_private = NULL; + } else { + constraint **cpp; + + /* zap constraint numbered 'valx' */ + for(i=0, cp = cn, cpp = &cn; + (cp) && (i<c->valx); + i++, cpp = &cp->ap_next, cp = *cpp); + + if (cp) { + /* zap cp, and join cpp to cp->ap_next */ + *cpp = cp->ap_next; + constraint_free( cp, 1 ); + } + on->on_bi.bi_private = cn; + } + break; + + default: + abort(); + break; + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: { + int j; + char **attrs = ldap_str2charray( c->argv[1], "," ); + + for ( j = 0; attrs[j]; j++) + /* just count */ ; + ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 ); + for ( j = 0; attrs[j]; j++) { + if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], attrs[j], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + + int is_regex = strcasecmp( c->argv[2], REGEX_STR ) == 0; + int is_neg_regex = strcasecmp( c->argv[2], NEG_REGEX_STR ) == 0; + if ( is_regex || is_neg_regex ) { + int err; + + ap.type = is_regex ? CONSTRAINT_REGEX : CONSTRAINT_NEG_REGEX; + ap.re = ch_malloc( sizeof(regex_t) ); + if ((err = regcomp( ap.re, + c->argv[3], REG_EXTENDED )) != 0) { + char errmsg[1024]; + + regerror( err, ap.re, errmsg, sizeof(errmsg) ); + ch_free(ap.re); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Illegal regular expression \"%s\": Error %s", + c->argv[0], c->argv[1], c->argv[3], errmsg); + ap.re = NULL; + rc = ARG_BAD_CONF; + goto done; + } + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + } else if ( strcasecmp( c->argv[2], SIZE_STR ) == 0 ) { + size_t size; + char *endptr; + + ap.type = CONSTRAINT_SIZE; + ap.size = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], COUNT_STR ) == 0 ) { + size_t count; + char *endptr; + + ap.type = CONSTRAINT_COUNT; + ap.count = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) { + int err; + + ap.type = CONSTRAINT_URI; + err = ldap_url_parse(c->argv[3], &ap.lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + ldap_free_urldesc(ap.lud); + rc = ARG_BAD_CONF; + goto done; + } + + for ( i=0; ap.lud->lud_attrs[i]; i++); + /* FIXME: This is worthless without at least one attr */ + if ( i ) { + ap.attrs = ch_malloc( (i+1)*sizeof(AttributeDescription *)); + for ( i=0; ap.lud->lud_attrs[i]; i++) { + ap.attrs[i] = NULL; + if ( slap_str2ad( ap.lud->lud_attrs[i], &ap.attrs[i], &text ) ) { + ch_free( ap.attrs ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], ap.lud->lud_attrs[i], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + ap.attrs[i] = NULL; + } + + if (ap.lud->lud_dn == NULL) { + ap.lud->lud_dn = ch_strdup(""); + } else { + struct berval dn, ndn; + + ber_str2bv( ap.lud->lud_dn, 0, 0, &dn ); + if (dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) ) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: URI %s DN normalization failed", + c->argv[0], c->argv[1], c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memfree( ap.lud->lud_dn ); + ap.lud->lud_dn = ndn.bv_val; + } + + if (ap.lud->lud_filter == NULL) { + ap.lud->lud_filter = ch_strdup("objectClass=*"); + } else if ( ap.lud->lud_filter[0] == '(' ) { + ber_len_t len = strlen( ap.lud->lud_filter ); + if ( ap.lud->lud_filter[len - 1] != ')' ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: invalid URI filter: %s", + c->argv[0], c->argv[1], ap.lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + AC_MEMCPY( &ap.lud->lud_filter[0], &ap.lud->lud_filter[1], len - 2 ); + ap.lud->lud_filter[len - 2] = '\0'; + } + + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + + } else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) { + ap.set = 1; + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + ap.type = CONSTRAINT_SET; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Unknown constraint type: %s", + c->argv[0], c->argv[1], c->argv[2] ); + rc = ARG_BAD_CONF; + goto done; + } + + if ( c->argc > 4 ) { + int argidx; + + for ( argidx = 4; argidx < c->argc; argidx++ ) { + if ( strncasecmp( c->argv[argidx], "restrict=", STRLENOF("restrict=") ) == 0 ) { + int err; + char *arg = c->argv[argidx] + STRLENOF("restrict="); + + err = ldap_url_parse(arg, &ap.restrict_lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.restrict_lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if ( ap.restrict_lud->lud_attrs != NULL ) { + if ( ap.restrict_lud->lud_attrs[0] != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: attrs not allowed in restrict URI %s\n", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memvfree((void *)ap.restrict_lud->lud_attrs); + ap.restrict_lud->lud_attrs = NULL; + } + + if (ap.restrict_lud->lud_dn != NULL) { + if (ap.restrict_lud->lud_dn[0] == '\0') { + ldap_memfree(ap.restrict_lud->lud_dn); + ap.restrict_lud->lud_dn = NULL; + + } else { + struct berval dn, ndn; + int j; + + ber_str2bv(ap.restrict_lud->lud_dn, 0, 0, &dn); + if (dnNormalize(0, NULL, NULL, &dn, &ndn, NULL)) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI %s DN normalization failed", + c->argv[0], c->argv[1], arg ); + rc = ARG_BAD_CONF; + goto done; + } + + assert(c->be != NULL); + if (c->be->be_nsuffix == NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI requires suffix", + c->argv[0], c->argv[1] ); + rc = ARG_BAD_CONF; + goto done; + } + + for ( j = 0; !BER_BVISNULL(&c->be->be_nsuffix[j]); j++) { + if (dnIsSuffix(&ndn, &c->be->be_nsuffix[j])) break; + } + + if (BER_BVISNULL(&c->be->be_nsuffix[j])) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI DN %s not within database naming context(s)", + c->argv[0], c->argv[1], dn.bv_val ); + rc = ARG_BAD_CONF; + goto done; + } + + ap.restrict_ndn = ndn; + } + } + + if (ap.restrict_lud->lud_filter != NULL) { + ap.restrict_filter = str2filter(ap.restrict_lud->lud_filter); + if (ap.restrict_filter == NULL) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI filter %s invalid", + c->argv[0], c->argv[1], ap.restrict_lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + } + + ber_str2bv(c->argv[argidx] + STRLENOF("restrict="), 0, 1, &ap.restrict_val); + + } else { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unrecognized arg #%d (%s)", + c->argv[0], c->argv[1], argidx, c->argv[argidx] ); + rc = ARG_BAD_CONF; + goto done; + } + } + } + +done:; + if ( rc == LDAP_SUCCESS ) { + constraint **app, *a2 = ch_calloc( sizeof(constraint), 1 ); + + a2->ap = ap.ap; + a2->type = ap.type; + a2->re = ap.re; + a2->val = ap.val; + a2->lud = ap.lud; + a2->set = ap.set; + a2->size = ap.size; + a2->count = ap.count; + if ( a2->lud ) { + ber_str2bv(a2->lud->lud_dn, 0, 0, &a2->dn); + ber_str2bv(a2->lud->lud_filter, 0, 0, &a2->filter); + } + a2->attrs = ap.attrs; + a2->restrict_lud = ap.restrict_lud; + a2->restrict_ndn = ap.restrict_ndn; + a2->restrict_filter = ap.restrict_filter; + a2->restrict_val = ap.restrict_val; + + for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next ) + /* Get to the end */ ; + + a2->ap_next = *app; + *app = a2; + + } else { + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + constraint_free( &ap, 0 ); + } + + ldap_memvfree((void**)attrs); + } break; + default: + abort(); + break; + } + break; + default: + abort(); + } + + return rc; +} + +static int +constraint_uri_cb( Operation *op, SlapReply *rs ) +{ + if(rs->sr_type == REP_SEARCH) { + int *foundp = op->o_callback->sc_private; + + *foundp = 1; + + Debug(LDAP_DEBUG_TRACE, "==> constraint_uri_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN" ); + } + return 0; +} + +static int +constraint_violation( constraint *c, struct berval *bv, Operation *op ) +{ + if ((!c) || (!bv)) return LDAP_SUCCESS; + + switch (c->type) { + case CONSTRAINT_SIZE: + if (bv->bv_len > c->size) + return LDAP_CONSTRAINT_VIOLATION; /* size violation */ + break; + case CONSTRAINT_REGEX: + if (regexec(c->re, bv->bv_val, 0, NULL, 0) == REG_NOMATCH) + return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */ + break; + case CONSTRAINT_NEG_REGEX: + if (regexec(c->re, bv->bv_val, 0, NULL, 0) != REG_NOMATCH) + return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */ + break; + case CONSTRAINT_URI: { + Operation nop = *op; + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + slap_callback cb = { 0 }; + int i; + int found = 0; + int rc; + size_t len; + struct berval filterstr; + char *ptr; + + cb.sc_response = constraint_uri_cb; + cb.sc_private = &found; + + nop.o_protocol = LDAP_VERSION3; + nop.o_tag = LDAP_REQ_SEARCH; + nop.o_time = slap_get_time(); + if (c->lud->lud_dn) { + struct berval dn; + + ber_str2bv(c->lud->lud_dn, 0, 0, &dn); + nop.o_req_dn = dn; + nop.o_req_ndn = dn; + nop.o_bd = select_backend(&nop.o_req_ndn, 1 ); + if (!nop.o_bd) { + return LDAP_NO_SUCH_OBJECT; /* unexpected error */ + } + if (!nop.o_bd->be_search) { + return LDAP_OTHER; /* unexpected error */ + } + } else { + nop.o_req_dn = nop.o_bd->be_nsuffix[0]; + nop.o_req_ndn = nop.o_bd->be_nsuffix[0]; + nop.o_bd = on->on_info->oi_origdb; + } + nop.o_do_not_cache = 1; + nop.o_callback = &cb; + + nop.ors_scope = c->lud->lud_scope; + nop.ors_deref = LDAP_DEREF_NEVER; + nop.ors_slimit = SLAP_NO_LIMIT; + nop.ors_tlimit = SLAP_NO_LIMIT; + nop.ors_limit = NULL; + + nop.ors_attrsonly = 0; + nop.ors_attrs = slap_anlist_no_attrs; + + len = STRLENOF("(&(") + + c->filter.bv_len + + STRLENOF(")(|"); + + for (i = 0; c->attrs[i]; i++) { + len += STRLENOF("(") + + c->attrs[i]->ad_cname.bv_len + + STRLENOF("=") + + bv->bv_len + + STRLENOF(")"); + } + + len += STRLENOF("))"); + filterstr.bv_len = len; + filterstr.bv_val = op->o_tmpalloc(len + 1, op->o_tmpmemctx); + + ptr = filterstr.bv_val + + snprintf(filterstr.bv_val, len, "(&(%s)(|", c->lud->lud_filter); + for (i = 0; c->attrs[i]; i++) { + *ptr++ = '('; + ptr = lutil_strcopy( ptr, c->attrs[i]->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, bv->bv_val ); + *ptr++ = ')'; + } + *ptr++ = ')'; + *ptr++ = ')'; + *ptr++ = '\0'; + + nop.ors_filterstr = filterstr; + nop.ors_filter = str2filter_x(&nop, filterstr.bv_val); + if ( nop.ors_filter == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s constraint_violation uri filter=\"%s\" invalid\n", + op->o_log_prefix, filterstr.bv_val ); + rc = LDAP_OTHER; + + } else { + SlapReply nrs = { REP_RESULT }; + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri filter = %s\n", + filterstr.bv_val ); + + rc = nop.o_bd->be_search( &nop, &nrs ); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri rc = %d, found = %d\n", + rc, found ); + } + op->o_tmpfree(filterstr.bv_val, op->o_tmpmemctx); + + if ((rc != LDAP_SUCCESS) && (rc != LDAP_NO_SUCH_OBJECT)) { + return rc; /* unexpected error */ + } + + if (!found) + return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */ + break; + } + } + + return LDAP_SUCCESS; +} + +static char * +print_message( struct berval *errtext, AttributeDescription *a ) +{ + char *ret; + int sz; + + sz = errtext->bv_len + sizeof(" on ") + a->ad_cname.bv_len; + ret = ch_malloc(sz); + snprintf( ret, sz, "%s on %s", errtext->bv_val, a->ad_cname.bv_val ); + return ret; +} + +static unsigned +constraint_count_attr(Entry *e, AttributeDescription *ad) +{ + struct Attribute *a; + + if ((a = attr_find(e->e_attrs, ad)) != NULL) + return a->a_numvals; + return 0; +} + +static int +constraint_check_restrict( Operation *op, constraint *c, Entry *e ) +{ + assert( c->restrict_lud != NULL ); + + if ( c->restrict_lud->lud_dn != NULL ) { + int diff = e->e_nname.bv_len - c->restrict_ndn.bv_len; + + if ( diff < 0 ) { + return 0; + } + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_BASE ) { + return bvmatch( &e->e_nname, &c->restrict_ndn ); + } + + if ( !dnIsSuffix( &e->e_nname, &c->restrict_ndn ) ) { + return 0; + } + + if ( c->restrict_lud->lud_scope != LDAP_SCOPE_SUBTREE ) { + struct berval pdn; + + if ( diff == 0 ) { + return 0; + } + + dnParent( &e->e_nname, &pdn ); + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_ONELEVEL + && pdn.bv_len != c->restrict_ndn.bv_len ) + { + return 0; + } + } + } + + if ( c->restrict_filter != NULL ) { + int rc; + struct berval save_dn = op->o_dn, save_ndn = op->o_ndn; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + rc = test_filter( op, e, c->restrict_filter ); + op->o_dn = save_dn; + op->o_ndn = save_ndn; + + if ( rc != LDAP_COMPARE_TRUE ) { + return 0; + } + } + + return 1; +} + +static int +constraint_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Attribute *a; + constraint *c = on->on_bi.bi_private, *cp; + BerVarray b = NULL; + int i; + struct berval rsv = BER_BVC("add breaks constraint"); + int rc = 0; + char *msg = NULL; + + if (get_relax(op) || 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 ); + + 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" ); + if ((m = modlist) == NULL) { + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "constraint_update() got null modlist"); + return(rs->sr_err); + } + + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry ); + op->o_bd = be; + + /* let the backend send the error */ + if ( target_entry == NULL ) + return SLAP_CB_CONTINUE; + + /* Do we need to count attributes? */ + for(cp = c; cp; cp = cp->ap_next) { + if (cp->type == CONSTRAINT_COUNT) { + if (cp->restrict_lud && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + is_v = constraint_check_count_violation(m, target_entry, cp); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_update is_v: %d\n", is_v ); + + if (is_v) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto mod_violation; + } + } + } + + rc = LDAP_CONSTRAINT_VIOLATION; + for(;m; m = m->sml_next) { + unsigned ce = 0; + + if (is_at_operational( m->sml_desc->ad_type )) continue; + + if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE)) + continue; + /* we only care about ADD and REPLACE modifications */ + /* and DELETE are used to track attribute count */ + if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL)) + continue; + + for(cp = c; cp; cp = cp->ap_next) { + int j; + for (j = 0; cp->ap[j]; j++) { + if (cp->ap[j] == m->sml_desc) { + break; + } + } + if (cp->ap[j] == NULL) continue; + + if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + /* DELETE are to be ignored beyond this point */ + if (( m->sml_op & LDAP_MOD_OP ) == LDAP_MOD_DELETE) + continue; + + for ( i = 0; b[i].bv_val; i++ ) { + rc = constraint_violation( cp, &b[i], op ); + if ( rc ) { + goto mod_violation; + } + } + + if (cp->type == CONSTRAINT_SET && target_entry) { + if (target_entry_copy == NULL) { + Modifications *ml; + + target_entry_copy = entry_dup(target_entry); + + /* if rename, set the new entry's name + * (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_flags = SLAPO_BFLAG_SINGLE; + constraint_ovl.on_bi.bi_db_destroy = constraint_destroy; + constraint_ovl.on_bi.bi_op_add = constraint_add; + constraint_ovl.on_bi.bi_op_modify = constraint_update; + constraint_ovl.on_bi.bi_op_modrdn = constraint_update; + + constraint_ovl.on_bi.bi_private = NULL; + + constraint_ovl.on_bi.bi_cf_ocs = constraintocs; + rc = config_register_schema( constraintcfg, constraintocs ); + if (rc) return rc; + + return overlay_register( &constraint_ovl ); +} + +#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return constraint_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_CONSTRAINT) */ + diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c new file mode 100644 index 0000000..c19f042 --- /dev/null +++ b/servers/slapd/overlays/dds.c @@ -0,0 +1,2056 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions Copyright 2005-2006 SysNet s.n.c. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software, sponsored by SysNet s.n.c. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DDS + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" +#include "ldap_rq.h" + +#include "slap-config.h" + +#define DDS_RF2589_MAX_TTL (31557600) /* 1 year + 6 hours */ +#define DDS_RF2589_DEFAULT_TTL (86400) /* 1 day */ +#define DDS_DEFAULT_INTERVAL (3600) /* 1 hour */ + +typedef struct dds_info_t { + unsigned di_flags; +#define DDS_FOFF (0x1U) /* is this really needed? */ +#define DDS_SET(di, f) ( (di)->di_flags & (f) ) + +#define DDS_OFF(di) DDS_SET( (di), DDS_FOFF ) + + time_t di_max_ttl; + time_t di_min_ttl; + time_t di_default_ttl; +#define DDS_DEFAULT_TTL(di) \ + ( (di)->di_default_ttl ? (di)->di_default_ttl : (di)->di_max_ttl ) + + time_t di_tolerance; + + /* expire check interval and task */ + time_t di_interval; +#define DDS_INTERVAL(di) \ + ( (di)->di_interval ? (di)->di_interval : DDS_DEFAULT_INTERVAL ) + struct re_s *di_expire_task; + + /* allows to limit the maximum number of dynamic objects */ + ldap_pvt_thread_mutex_t di_mutex; + int di_num_dynamicObjects; + int di_max_dynamicObjects; + + /* used to advertise the dynamicSubtrees in the root DSE, + * and to select the database in the expiration task */ + BerVarray di_suffix; + BerVarray di_nsuffix; +} dds_info_t; + +static struct berval slap_EXOP_REFRESH = BER_BVC( LDAP_EXOP_REFRESH ); +static AttributeDescription *ad_entryExpireTimestamp; + +/* list of expired DNs */ +typedef struct dds_expire_t { + struct berval de_ndn; + struct dds_expire_t *de_next; +} dds_expire_t; + +typedef struct dds_cb_t { + dds_expire_t *dc_ndnlist; +} dds_cb_t; + +static int +dds_expire_cb( Operation *op, SlapReply *rs ) +{ + dds_cb_t *dc = (dds_cb_t *)op->o_callback->sc_private; + dds_expire_t *de; + int rc; + + switch ( rs->sr_type ) { + case REP_SEARCH: + /* alloc list and buffer for berval all in one */ + de = op->o_tmpalloc( sizeof( dds_expire_t ) + rs->sr_entry->e_nname.bv_len + 1, + op->o_tmpmemctx ); + + de->de_next = dc->dc_ndnlist; + dc->dc_ndnlist = de; + + de->de_ndn.bv_len = rs->sr_entry->e_nname.bv_len; + de->de_ndn.bv_val = (char *)&de[ 1 ]; + AC_MEMCPY( de->de_ndn.bv_val, rs->sr_entry->e_nname.bv_val, + rs->sr_entry->e_nname.bv_len + 1 ); + rc = 0; + break; + + case REP_SEARCHREF: + case REP_RESULT: + rc = rs->sr_err; + break; + + default: + assert( 0 ); + } + + return rc; +} + +static int +dds_expire( void *ctx, dds_info_t *di ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback sc = { 0 }; + dds_cb_t dc = { 0 }; + dds_expire_t *de = NULL, **dep; + SlapReply rs = { REP_RESULT }; + + time_t expire; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval ts; + + int ndeletes, ntotdeletes; + + int rc; + char *extra = ""; + + connection_fake_init2( &conn, &opbuf, ctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + + op->o_bd = select_backend( &di->di_nsuffix[ 0 ], 0 ); + + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_tlimit = DDS_INTERVAL( di )/2 + 1; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + op->o_do_not_cache = 1; + + expire = slap_get_time() - di->di_tolerance; + ts.bv_val = tsbuf; + ts.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &ts ); + + op->ors_filterstr.bv_len = STRLENOF( "(&(objectClass=" ")(" "<=" "))" ) + + slap_schema.si_oc_dynamicObject->soc_cname.bv_len + + ad_entryExpireTimestamp->ad_cname.bv_len + + ts.bv_len; + op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, + "(&(objectClass=%s)(%s<=%s))", + slap_schema.si_oc_dynamicObject->soc_cname.bv_val, + ad_entryExpireTimestamp->ad_cname.bv_val, ts.bv_val ); + + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + if ( op->ors_filter == NULL ) { + rs.sr_err = LDAP_OTHER; + goto done_search; + } + + op->o_callback = ≻ + sc.sc_response = dds_expire_cb; + sc.sc_private = &dc; + + (void)op->o_bd->bd_info->bi_op_search( op, &rs ); + +done_search:; + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter_free_x( op, op->ors_filter, 1 ); + + rc = rs.sr_err; + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + break; + + case LDAP_NO_SUCH_OBJECT: + /* (ITS#5267) database not created yet? */ + rs.sr_err = LDAP_SUCCESS; + extra = " (ignored)"; + /* fallthru */ + + default: + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS expired objects lookup failed err=%d%s\n", + rc, extra ); + goto done; + } + + op->o_tag = LDAP_REQ_DELETE; + op->o_callback = ≻ + sc.sc_response = slap_null_cb; + sc.sc_private = NULL; + + for ( ntotdeletes = 0, ndeletes = 1; dc.dc_ndnlist != NULL && ndeletes > 0; ) { + ndeletes = 0; + + for ( dep = &dc.dc_ndnlist; *dep != NULL; ) { + de = *dep; + + op->o_req_dn = de->de_ndn; + op->o_req_ndn = de->de_ndn; + (void)op->o_bd->bd_info->bi_op_delete( op, &rs ); + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS dn=\"%s\" expired.\n", + de->de_ndn.bv_val ); + ndeletes++; + break; + + case LDAP_NOT_ALLOWED_ON_NONLEAF: + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "DDS dn=\"%s\" is non-leaf; " + "deferring.\n", + de->de_ndn.bv_val ); + dep = &de->de_next; + de = NULL; + break; + + default: + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "DDS dn=\"%s\" err=%d; " + "deferring.\n", + de->de_ndn.bv_val, rs.sr_err ); + break; + } + + if ( de != NULL ) { + *dep = de->de_next; + op->o_tmpfree( de, op->o_tmpmemctx ); + } + } + + ntotdeletes += ndeletes; + } + + rs.sr_err = LDAP_SUCCESS; + + Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS expired=%d\n", ntotdeletes ); + +done:; + return rs.sr_err; +} + +static void * +dds_expire_fn( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + dds_info_t *di = rtask->arg; + + assert( di->di_expire_task == rtask ); + + (void)dds_expire( ctx, di ); + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + } + ldap_pvt_runqueue_resched( &slapd_rq, rtask, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* frees the callback */ +static int +dds_freeit_cb( Operation *op, SlapReply *rs ) +{ + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +/* updates counter - installed on add/delete only if required */ +static int +dds_counter_cb( Operation *op, SlapReply *rs ) +{ + assert( rs->sr_type == REP_RESULT ); + + if ( rs->sr_err == LDAP_SUCCESS ) { + dds_info_t *di = op->o_callback->sc_private; + + ldap_pvt_thread_mutex_lock( &di->di_mutex ); + switch ( op->o_tag ) { + case LDAP_REQ_DELETE: + assert( di->di_num_dynamicObjects > 0 ); + di->di_num_dynamicObjects--; + break; + + case LDAP_REQ_ADD: + assert( di->di_num_dynamicObjects < di->di_max_dynamicObjects ); + di->di_num_dynamicObjects++; + break; + + default: + assert( 0 ); + } + ldap_pvt_thread_mutex_unlock( &di->di_mutex ); + } + + return dds_freeit_cb( op, rs ); +} + +static int +dds_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int is_dynamicObject; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + is_dynamicObject = is_entry_dynamicObject( op->ora_e ); + + /* FIXME: do not allow this right now, pending clarification */ + if ( is_dynamicObject ) { + rs->sr_err = LDAP_SUCCESS; + + if ( is_entry_referral( op->ora_e ) ) { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "a referral cannot be a dynamicObject"; + + } else if ( is_entry_alias( op->ora_e ) ) { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "an alias cannot be a dynamicObject"; + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + + /* we don't allow dynamicObjects to have static subordinates */ + if ( !dn_match( &op->o_req_ndn, &op->o_bd->be_nsuffix[ 0 ] ) ) { + struct berval p_ndn; + Entry *e = NULL; + int rc; + BackendInfo *bi = op->o_bd->bd_info; + + dnParent( &op->o_req_ndn, &p_ndn ); + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &p_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + if ( !is_dynamicObject ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + + } else { + rc = rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + send_ldap_error( op, rs, rc, "no static subordinate entries allowed for dynamicObject" ); + } + } + + be_entry_release_r( op, e ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + } + op->o_bd->bd_info = bi; + } + + /* handle dynamic object operational attr(s) */ + if ( is_dynamicObject ) { + time_t ttl, expire; + char ttlbuf[STRLENOF("31557600") + 1]; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval bv; + + if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) { + ldap_pvt_thread_mutex_lock( &di->di_mutex ); + rs->sr_err = ( di->di_max_dynamicObjects && + di->di_num_dynamicObjects >= di->di_max_dynamicObjects ); + ldap_pvt_thread_mutex_unlock( &di->di_mutex ); + if ( rs->sr_err ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "too many dynamicObjects in context" ); + return rs->sr_err; + } + } + + ttl = DDS_DEFAULT_TTL( di ); + + /* assert because should be checked at configure */ + assert( ttl <= DDS_RF2589_MAX_TTL ); + + bv.bv_val = ttlbuf; + bv.bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); + assert( bv.bv_len < sizeof( ttlbuf ) ); + + /* FIXME: apparently, values in op->ora_e are malloc'ed + * on the thread's slab; works fine by chance, + * only because the attribute doesn't exist yet. */ + assert( attr_find( op->ora_e->e_attrs, slap_schema.si_ad_entryTtl ) == NULL ); + attr_merge_one( op->ora_e, slap_schema.si_ad_entryTtl, &bv, &bv ); + + expire = slap_get_time() + ttl; + bv.bv_val = tsbuf; + bv.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &bv ); + assert( attr_find( op->ora_e->e_attrs, ad_entryExpireTimestamp ) == NULL ); + attr_merge_one( op->ora_e, ad_entryExpireTimestamp, &bv, &bv ); + + /* if required, install counter callback */ + if ( di->di_max_dynamicObjects > 0) { + slap_callback *sc; + + sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); + sc->sc_cleanup = dds_freeit_cb; + sc->sc_response = dds_counter_cb; + sc->sc_private = di; + sc->sc_next = op->o_callback; + sc->sc_writewait = 0; + + op->o_callback = sc; + } + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + /* if required, install counter callback */ + if ( !DDS_OFF( di ) && di->di_max_dynamicObjects > 0 ) { + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + + /* FIXME: couldn't the entry be added before deletion? */ + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + slap_callback *sc; + + be_entry_release_r( op, e ); + e = NULL; + + sc = op->o_tmpalloc( sizeof( slap_callback ), op->o_tmpmemctx ); + sc->sc_cleanup = dds_freeit_cb; + sc->sc_response = dds_counter_cb; + sc->sc_private = di; + sc->sc_writewait = 0; + sc->sc_next = op->o_callback; + + op->o_callback = sc; + } + op->o_bd->bd_info = bi; + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; + Modifications *mod; + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + int was_dynamicObject = 0, + is_dynamicObject = 0; + struct berval bv_entryTtl = BER_BVNULL; + time_t entryTtl = 0; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + /* bv_entryTtl stores the string representation of the entryTtl + * across modifies for consistency checks of the final value; + * the bv_val points to a static buffer; the bv_len is zero when + * the attribute is deleted. + * entryTtl stores the integer representation of the entryTtl; + * its value is -1 when the attribute is deleted; it is 0 only + * if no modifications of the entryTtl occurred, as an entryTtl + * of 0 is invalid. */ + bv_entryTtl.bv_val = textbuf; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, slap_schema.si_ad_entryTtl, 0, &e ); + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryTtl ); + + /* the value of the entryTtl is saved for later checks */ + if ( a != NULL ) { + unsigned long ttl; + int rc; + + bv_entryTtl.bv_len = a->a_nvals[ 0 ].bv_len; + AC_MEMCPY( bv_entryTtl.bv_val, a->a_nvals[ 0 ].bv_val, bv_entryTtl.bv_len ); + bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; + rc = lutil_atoul( &ttl, bv_entryTtl.bv_val ); + assert( rc == 0 ); + entryTtl = (time_t)ttl; + } + + be_entry_release_r( op, e ); + e = NULL; + was_dynamicObject = is_dynamicObject = 1; + } + op->o_bd->bd_info = bi; + + rs->sr_err = LDAP_SUCCESS; + for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) { + if ( mod->sml_desc == slap_schema.si_ad_objectClass ) { + int i; + ObjectClass *oc; + + switch ( mod->sml_op ) { + case LDAP_MOD_DELETE: + if ( mod->sml_values == NULL ) { + is_dynamicObject = 0; + break; + } + + for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { + oc = oc_bvfind( &mod->sml_values[ i ] ); + if ( oc == slap_schema.si_oc_dynamicObject ) { + is_dynamicObject = 0; + break; + } + } + + break; + + case LDAP_MOD_REPLACE: + if ( mod->sml_values == NULL ) { + is_dynamicObject = 0; + break; + } + /* fallthru */ + + case LDAP_MOD_ADD: + for ( i = 0; !BER_BVISNULL( &mod->sml_values[ i ] ); i++ ) { + oc = oc_bvfind( &mod->sml_values[ i ] ); + if ( oc == slap_schema.si_oc_dynamicObject ) { + is_dynamicObject = 1; + break; + } + } + break; + } + + } else if ( mod->sml_desc == slap_schema.si_ad_entryTtl ) { + unsigned long uttl; + time_t ttl; + int rc; + + switch ( mod->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* FIXME? */ + if ( mod->sml_values != NULL ) { + if ( BER_BVISEMPTY( &bv_entryTtl ) + || !bvmatch( &bv_entryTtl, &mod->sml_values[ 0 ] ) ) + { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + } + goto done; + } + } + bv_entryTtl.bv_len = 0; + entryTtl = -1; + break; + + case LDAP_MOD_REPLACE: + bv_entryTtl.bv_len = 0; + entryTtl = -1; + /* fallthru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* FIXME? */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* FIXME? */ + assert( mod->sml_values != NULL ); + assert( BER_BVISNULL( &mod->sml_values[ 1 ] ) ); + + if ( !BER_BVISEMPTY( &bv_entryTtl ) ) { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_text = "attribute 'entryTtl' cannot have multiple values"; + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + } + goto done; + } + + rc = lutil_atoul( &uttl, mod->sml_values[ 0 ].bv_val ); + ttl = (time_t)uttl; + assert( rc == 0 ); + if ( ttl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + goto done; + } + + if ( ttl <= 0 || ttl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; + goto done; + } + + entryTtl = ttl; + bv_entryTtl.bv_len = mod->sml_values[ 0 ].bv_len; + AC_MEMCPY( bv_entryTtl.bv_val, mod->sml_values[ 0 ].bv_val, bv_entryTtl.bv_len ); + bv_entryTtl.bv_val[ bv_entryTtl.bv_len ] = '\0'; + break; + + case LDAP_MOD_INCREMENT: + if ( BER_BVISEMPTY( &bv_entryTtl ) ) { + rs->sr_err = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rs->sr_err == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + rs->sr_text = "modify/increment: entryTtl: no such attribute"; + } + goto done; + } + + entryTtl++; + if ( entryTtl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + + } else if ( entryTtl <= 0 || entryTtl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds administrative limit"; + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + rc = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rc == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_text = NULL; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } + goto done; + } + + bv_entryTtl.bv_len = snprintf( textbuf, sizeof( textbuf ), "%ld", entryTtl ); + break; + + default: + assert( 0 ); + break; + } + + } else if ( mod->sml_desc == ad_entryExpireTimestamp ) { + /* should have been trapped earlier */ + assert( mod->sml_flags & SLAP_MOD_INTERNAL ); + } + } + +done:; + if ( rs->sr_err == LDAP_SUCCESS ) { + int rc; + + /* FIXME: this could be allowed when the Relax control is used... + * in that case: + * + * TODO + * + * static => dynamic: + * entryTtl must be provided; add + * entryExpireTimestamp accordingly + * + * dynamic => static: + * entryTtl must be removed; remove + * entryTimestamp accordingly + * + * ... but we need to make sure that there are no subordinate + * issues... + */ + rc = is_dynamicObject - was_dynamicObject; + if ( rc ) { +#if 0 /* fix subordinate issues first */ + if ( get_relax( op ) ) { + switch ( rc ) { + case -1: + /* need to delete entryTtl to have a consistent entry */ + if ( entryTtl != -1 ) { + rs->sr_text = "objectClass modification from dynamicObject to static entry requires entryTtl deletion"; + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + break; + + case 1: + /* need to add entryTtl to have a consistent entry */ + if ( entryTtl <= 0 ) { + rs->sr_text = "objectClass modification from static entry to dynamicObject requires entryTtl addition"; + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + break; + } + + } else +#endif + { + switch ( rc ) { + case -1: + rs->sr_text = "objectClass modification cannot turn dynamicObject into static entry"; + break; + + case 1: + rs->sr_text = "objectClass modification cannot turn static entry into dynamicObject"; + break; + } + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + } + + if ( rc != LDAP_SUCCESS ) { + rc = backend_attribute( op, NULL, &op->o_req_ndn, + slap_schema.si_ad_entry, NULL, ACL_DISCLOSE ); + if ( rc == LDAP_INSUFFICIENT_ACCESS ) { + rs->sr_text = NULL; + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + } + } + } + + if ( rs->sr_err == LDAP_SUCCESS && entryTtl != 0 ) { + Modifications *tmpmod = NULL, **modp; + + for ( modp = &op->orm_modlist; *modp; modp = &(*modp)->sml_next ) + ; + + tmpmod = ch_calloc( 1, sizeof( Modifications ) ); + tmpmod->sml_flags = SLAP_MOD_INTERNAL; + tmpmod->sml_type = ad_entryExpireTimestamp->ad_cname; + tmpmod->sml_desc = ad_entryExpireTimestamp; + + *modp = tmpmod; + + if ( entryTtl == -1 ) { + /* delete entryExpireTimestamp */ + tmpmod->sml_op = LDAP_MOD_DELETE; + + } else { + time_t expire; + char tsbuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + struct berval bv; + + /* keep entryExpireTimestamp consistent + * with entryTtl */ + expire = slap_get_time() + entryTtl; + bv.bv_val = tsbuf; + bv.bv_len = sizeof( tsbuf ); + slap_timestamp( &expire, &bv ); + + tmpmod->sml_op = LDAP_MOD_REPLACE; + value_add_one( &tmpmod->sml_values, &bv ); + value_add_one( &tmpmod->sml_nvalues, &bv ); + tmpmod->sml_numvals = 1; + } + } + + if ( rs->sr_err ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_result( op, rs ); + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +static int +dds_op_rename( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + /* we don't allow dynamicObjects to have static subordinates */ + if ( op->orr_nnewSup != NULL ) { + Entry *e = NULL; + BackendInfo *bi = op->o_bd->bd_info; + int is_dynamicObject = 0, + rc; + + rs->sr_err = LDAP_SUCCESS; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + be_entry_release_r( op, e ); + e = NULL; + is_dynamicObject = 1; + } + + rc = be_entry_get_rw( op, op->orr_nnewSup, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + if ( !is_dynamicObject ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + + } else { + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "static entry cannot have dynamicObject as newSuperior" ); + } + } + be_entry_release_r( op, e ); + } + op->o_bd->bd_info = bi; + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + } + + return SLAP_CB_CONTINUE; +} + +/* entryTtl update for client */ +static int +dds_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int rc; + + if ( !DDS_OFF( di ) + && rs->sr_type == REP_SEARCH + && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryTtl ) ) + { + BerVarray vals = NULL; + struct lutil_tm tm; + struct lutil_timet tt; + char ttlbuf[STRLENOF("31557600") + 1]; + struct berval ttlvalue; + time_t ttl; + int len; + + /* User already has access to entryTtl, skip ACL checks on + * entryExpireTimestamp */ + rc = backend_attribute( op, NULL, &rs->sr_entry->e_nname, + ad_entryExpireTimestamp, &vals, ACL_NONE ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + assert( vals[0].bv_val[vals[0].bv_len] == '\0' ); + if ( lutil_parsetime( vals[0].bv_val, &tm ) ) { + goto done; + } + + lutil_tm2time( &tm, &tt ); + ttl = tt.tt_sec - op->o_time; + ttl = (ttl < 0) ? 0 : ttl; + assert( ttl <= DDS_RF2589_MAX_TTL ); + + len = snprintf( ttlbuf, sizeof(ttlbuf), "%ld", ttl ); + if ( len < 0 ) + { + goto done; + } + ttlvalue.bv_val = ttlbuf; + ttlvalue.bv_len = len; + + rs_entry2modifiable( op, rs, on ); + + if ( attr_delete( &rs->sr_entry->e_attrs, + slap_schema.si_ad_entryTtl ) ) + { + goto done; + } + if ( attr_merge_normalize_one( rs->sr_entry, + slap_schema.si_ad_entryTtl, + &ttlvalue, op->o_tmpmemctx ) ) + { + goto done; + } + +done:; + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; +} + +static int +slap_parse_refresh( + struct berval *in, + struct berval *ndn, + time_t *ttl, + const char **text, + void *ctx ) +{ + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval reqdata = BER_BVNULL; + int tmp; + + *text = NULL; + + if ( ndn ) { + BER_BVZERO( ndn ); + } + + if ( in == NULL || in->bv_len == 0 ) { + *text = "empty request data field in refresh exop"; + return LDAP_PROTOCOL_ERROR; + } + + ber_dupbv_x( &reqdata, in, ctx ); + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, &reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_DN ) { + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + if ( ndn ) { + struct berval dn; + + tag = ber_scanf( ber, "m", &dn ); + if ( tag == LBER_ERROR ) { + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: DN parse failed.\n" ); + goto decoding_error; + } + + rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx ); + if ( rc != LDAP_SUCCESS ) { + *text = "invalid DN in refresh exop request data"; + goto done; + } + + } else { + tag = ber_scanf( ber, "x" /* "m" */ ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag != LDAP_TAG_EXOP_REFRESH_REQ_TTL ) { + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error.\n" ); + goto decoding_error; + } + + tag = ber_scanf( ber, "i", &tmp ); + if ( tag == LBER_ERROR ) { + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: TTL parse failed.\n" ); + goto decoding_error; + } + + if ( ttl ) { + *ttl = tmp; + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag != LBER_DEFAULT || len != 0 ) { +decoding_error:; + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_ERR, + "slap_parse_refresh: decoding error, len=%ld\n", + (long)len ); + rc = LDAP_PROTOCOL_ERROR; + *text = "data decoding error"; + +done:; + if ( ndn && !BER_BVISNULL( ndn ) ) { + slap_sl_free( ndn->bv_val, ctx ); + BER_BVZERO( ndn ); + } + } + + if ( !BER_BVISNULL( &reqdata ) ) { + ber_memfree_x( reqdata.bv_val, ctx ); + } + + return rc; +} + +static int +dds_op_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( DDS_OFF( di ) ) { + return SLAP_CB_CONTINUE; + } + + if ( bvmatch( &op->ore_reqoid, &slap_EXOP_REFRESH ) ) { + Entry *e = NULL; + time_t ttl; + BackendDB db = *op->o_bd; + SlapReply rs2 = { REP_RESULT }; + Operation op2 = *op; + slap_callback sc = { 0 }; + Modifications ttlmod = { { 0 } }; + struct berval ttlvalues[ 2 ]; + char ttlbuf[STRLENOF("31557600") + 1]; + + rs->sr_err = slap_parse_refresh( op->ore_reqdata, NULL, &ttl, + &rs->sr_text, NULL ); + assert( rs->sr_err == LDAP_SUCCESS ); + + if ( ttl <= 0 || ttl > DDS_RF2589_MAX_TTL ) { + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = "invalid time-to-live for dynamicObject"; + return rs->sr_err; + } + + if ( ttl > di->di_max_ttl ) { + /* FIXME: I don't understand if this has to be an error, + * or an indication that the requested Ttl has been + * shortened to di->di_max_ttl >= 1 day */ + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + rs->sr_text = "time-to-live for dynamicObject exceeds limit"; + return rs->sr_err; + } + + if ( di->di_min_ttl && ttl < di->di_min_ttl ) { + ttl = di->di_min_ttl; + } + + /* This does not apply to multi-provider case */ + if ( !( !SLAP_SINGLE_SHADOW( op->o_bd ) || be_isupdate( op ) ) ) { + /* we SHOULD return a referral in this case */ + BerVarray defref = op->o_bd->be_update_refs + ? op->o_bd->be_update_refs : default_referral; + + if ( defref != NULL ) { + rs->sr_ref = referral_rewrite( op->o_bd->be_update_refs, + NULL, NULL, LDAP_SCOPE_DEFAULT ); + if ( rs->sr_ref ) { + rs->sr_flags |= REP_REF_MUSTBEFREED; + } else { + rs->sr_ref = defref; + } + rs->sr_err = LDAP_REFERRAL; + + } else { + rs->sr_text = "shadow context; no update referral"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + return rs->sr_err; + } + + assert( !BER_BVISNULL( &op->o_req_ndn ) ); + + + + /* check if exists but not dynamicObject */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + slap_schema.si_oc_dynamicObject, NULL, 0, &e ); + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_err = be_entry_get_rw( op, &op->o_req_ndn, + NULL, NULL, 0, &e ); + if ( rs->sr_err == LDAP_SUCCESS && e != NULL ) { + /* return referral only if "disclose" + * is granted on the object */ + if ( ! access_allowed( op, e, + slap_schema.si_ad_entry, + NULL, ACL_DISCLOSE, NULL ) ) + { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + + } else { + rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION; + rs->sr_text = "refresh operation only applies to dynamic objects"; + } + be_entry_release_r( op, e ); + + } else { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + return rs->sr_err; + + } else if ( e != NULL ) { + be_entry_release_r( op, e ); + } + + /* we require manage privileges on the entryTtl, + * and fake a Relax control */ + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_bd = &db; + db.bd_info = (BackendInfo *)on->on_info; + op2.o_callback = ≻ + sc.sc_response = slap_null_cb; + op2.o_relax = SLAP_CONTROL_CRITICAL; + op2.orm_modlist = &ttlmod; + + ttlmod.sml_op = LDAP_MOD_REPLACE; + ttlmod.sml_flags = SLAP_MOD_MANAGING; + ttlmod.sml_desc = slap_schema.si_ad_entryTtl; + ttlmod.sml_values = ttlvalues; + ttlmod.sml_numvals = 1; + ttlvalues[ 0 ].bv_val = ttlbuf; + ttlvalues[ 0 ].bv_len = snprintf( ttlbuf, sizeof( ttlbuf ), "%ld", ttl ); + BER_BVZERO( &ttlvalues[ 1 ] ); + + /* the entryExpireTimestamp is added by modify */ + rs->sr_err = op2.o_bd->be_modify( &op2, &rs2 ); + + if ( ttlmod.sml_next != NULL ) { + slap_mods_free( ttlmod.sml_next, 1 ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + int rc; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + + ber_init_w_nullc( ber, LBER_USE_DER ); + + rc = ber_printf( ber, "{tiN}", LDAP_TAG_EXOP_REFRESH_RES_TTL, (int)ttl ); + + if ( rc < 0 ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "internal error"; + + } else { + (void)ber_flatten( ber, &rs->sr_rspdata ); + rs->sr_rspoid = ch_strdup( slap_EXOP_REFRESH.bv_val ); + + Log( LDAP_DEBUG_TRACE, LDAP_LEVEL_INFO, + "%s REFRESH dn=\"%s\" TTL=%ld\n", + op->o_log_prefix, op->o_req_ndn.bv_val, ttl ); + } + + ber_free_buf( ber ); + } + + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +enum { + DDS_STATE = 1, + DDS_MAXTTL, + DDS_MINTTL, + DDS_DEFAULTTTL, + DDS_INTERVAL, + DDS_TOLERANCE, + DDS_MAXDYNAMICOBJS, + + DDS_LAST +}; + +static ConfigDriver dds_cfgen; +#if 0 +static ConfigLDAPadd dds_ldadd; +static ConfigCfAdd dds_cfadd; +#endif + +static ConfigTable dds_cfg[] = { + { "dds-state", "on|off", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|DDS_STATE, dds_cfgen, + "( OLcfgOvAt:9.1 NAME 'olcDDSstate' " + "DESC 'RFC2589 Dynamic directory services state' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-max-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_MAXTTL, dds_cfgen, + "( OLcfgOvAt:9.2 NAME 'olcDDSmaxTtl' " + "DESC 'RFC2589 Dynamic directory services max TTL' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-min-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_MINTTL, dds_cfgen, + "( OLcfgOvAt:9.3 NAME 'olcDDSminTtl' " + "DESC 'RFC2589 Dynamic directory services min TTL' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-default-ttl", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_DEFAULTTTL, dds_cfgen, + "( OLcfgOvAt:9.4 NAME 'olcDDSdefaultTtl' " + "DESC 'RFC2589 Dynamic directory services default TTL' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-interval", "interval", + 2, 2, 0, ARG_MAGIC|DDS_INTERVAL, dds_cfgen, + "( OLcfgOvAt:9.5 NAME 'olcDDSinterval' " + "DESC 'RFC2589 Dynamic directory services expiration " + "task run interval' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-tolerance", "ttl", + 2, 2, 0, ARG_MAGIC|DDS_TOLERANCE, dds_cfgen, + "( OLcfgOvAt:9.6 NAME 'olcDDStolerance' " + "DESC 'RFC2589 Dynamic directory services additional " + "TTL in expiration scheduling' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", NULL, NULL }, + { "dds-max-dynamicObjects", "num", + 2, 2, 0, ARG_MAGIC|ARG_INT|DDS_MAXDYNAMICOBJS, dds_cfgen, + "( OLcfgOvAt:9.7 NAME 'olcDDSmaxDynamicObjects' " + "DESC 'RFC2589 Dynamic directory services max number of dynamic objects' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dds_ocs[] = { + { "( OLcfgOvOc:9.1 " + "NAME 'olcDDSConfig' " + "DESC 'RFC2589 Dynamic directory services configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcDDSstate " + "$ olcDDSmaxTtl " + "$ olcDDSminTtl " + "$ olcDDSdefaultTtl " + "$ olcDDSinterval " + "$ olcDDStolerance " + "$ olcDDSmaxDynamicObjects " + " ) " + ")", Cft_Overlay, dds_cfg, NULL, NULL /* dds_cfadd */ }, + { NULL, 0, NULL } +}; + +#if 0 +static int +dds_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + return LDAP_SUCCESS; +} + +static int +dds_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + return 0; +} +#endif + +static int +dds_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + dds_info_t *di = on->on_bi.bi_private; + int rc = 0; + unsigned long t; + + + if ( c->op == SLAP_CONFIG_EMIT ) { + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + + switch( c->type ) { + case DDS_STATE: + c->value_int = !DDS_OFF( di ); + break; + + case DDS_MAXTTL: + lutil_unparse_time( buf, sizeof( buf ), di->di_max_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + break; + + case DDS_MINTTL: + if ( di->di_min_ttl ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_min_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_DEFAULTTTL: + if ( di->di_default_ttl ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_default_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_INTERVAL: + if ( di->di_interval ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_interval ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_TOLERANCE: + if ( di->di_tolerance ) { + lutil_unparse_time( buf, sizeof( buf ), di->di_tolerance ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + + case DDS_MAXDYNAMICOBJS: + if ( di->di_max_dynamicObjects > 0 ) { + c->value_int = di->di_max_dynamicObjects; + + } else { + rc = 1; + } + break; + + default: + rc = 1; + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DDS_STATE: + di->di_flags &= ~DDS_FOFF; + break; + + case DDS_MAXTTL: + di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; + break; + + case DDS_MINTTL: + di->di_min_ttl = 0; + break; + + case DDS_DEFAULTTTL: + di->di_default_ttl = 0; + break; + + case DDS_INTERVAL: + di->di_interval = 0; + break; + + case DDS_TOLERANCE: + di->di_tolerance = 0; + break; + + case DDS_MAXDYNAMICOBJS: + di->di_max_dynamicObjects = 0; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + switch ( c->type ) { + case DDS_STATE: + if ( c->value_int ) { + di->di_flags &= ~DDS_FOFF; + + } else { + di->di_flags |= DDS_FOFF; + } + break; + + case DDS_MAXTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-max-ttl \"%s\"", + c->argv[ 1 ] ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t < DDS_RF2589_DEFAULT_TTL || t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-max-ttl=%lu; must be between %d and %d", + t, DDS_RF2589_DEFAULT_TTL, DDS_RF2589_MAX_TTL ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + di->di_max_ttl = (time_t)t; + break; + + case DDS_MINTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-min-ttl \"%s\"", + c->argv[ 1 ] ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-min-ttl=%lu", + t ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t == 0 ) { + di->di_min_ttl = DDS_RF2589_DEFAULT_TTL; + + } else { + di->di_min_ttl = (time_t)t; + } + break; + + case DDS_DEFAULTTTL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-default-ttl \"%s\"", + c->argv[ 1 ] ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-default-ttl=%lu", + t ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t == 0 ) { + di->di_default_ttl = DDS_RF2589_DEFAULT_TTL; + + } else { + di->di_default_ttl = (time_t)t; + } + break; + + case DDS_INTERVAL: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-interval \"%s\"", + c->argv[ 1 ] ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-interval=%lu", + t ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t < 60 ) { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_NOTICE, + "%s: dds-interval=%lu may be too small.\n", + c->log, t ); + } + + di->di_interval = (time_t)t; + if ( di->di_expire_task ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); + } + di->di_expire_task->interval.tv_sec = DDS_INTERVAL( di ); + ldap_pvt_runqueue_resched( &slapd_rq, di->di_expire_task, 0 ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + break; + + case DDS_TOLERANCE: + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "DDS unable to parse dds-tolerance \"%s\"", + c->argv[ 1 ] ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + if ( t > DDS_RF2589_MAX_TTL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-tolerance=%lu", + t ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + + di->di_tolerance = (time_t)t; + break; + + case DDS_MAXDYNAMICOBJS: + if ( c->value_int < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DDS invalid dds-max-dynamicObjects=%d", c->value_int ); + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + di->di_max_dynamicObjects = c->value_int; + break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dds_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di; + BackendInfo *bi = on->on_info->oi_orig; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS cannot be used as global overlay.\n" ); + return 1; + } + + /* check support for required functions */ + /* FIXME: some could be provided by other overlays in between */ + if ( bi->bi_op_add == NULL /* object creation */ + || bi->bi_op_delete == NULL /* object deletion */ + || bi->bi_op_modify == NULL /* object refresh */ + || bi->bi_op_search == NULL /* object expiration */ + || bi->bi_entry_get_rw == NULL ) /* object type/existence checking */ + { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS backend \"%s\" does not provide " + "required functionality.\n", + bi->bi_type ); + return 1; + } + + di = (dds_info_t *)ch_calloc( 1, sizeof( dds_info_t ) ); + on->on_bi.bi_private = di; + + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + + ldap_pvt_thread_mutex_init( &di->di_mutex ); + + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_DYNAMIC; + + return 0; +} + +/* adds dynamicSubtrees to root DSE */ +static int +dds_entry_info( void *arg, Entry *e ) +{ + dds_info_t *di = (dds_info_t *)arg; + + attr_merge( e, slap_schema.si_ad_dynamicSubtrees, + di->di_suffix, di->di_nsuffix ); + + return 0; +} + +/* callback that counts the returned entries, since the search + * does not get to the point in slap_send_search_entries where + * the actual count occurs */ +static int +dds_count_cb( Operation *op, SlapReply *rs ) +{ + int *nump = (int *)op->o_callback->sc_private; + + switch ( rs->sr_type ) { + case REP_SEARCH: + (*nump)++; + break; + + case REP_SEARCHREF: + case REP_RESULT: + break; + + default: + assert( 0 ); + } + + return 0; +} + +/* count dynamic objects existing in the database at startup */ +static int +dds_count( void *ctx, BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = (dds_info_t *)on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback sc = { 0 }; + SlapReply rs = { REP_RESULT }; + + int rc; + char *extra = ""; + + connection_fake_init2( &conn, &opbuf, ctx, 0 ); + op = &opbuf.ob_op; + + op->o_tag = LDAP_REQ_SEARCH; + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + + op->o_bd = be; + + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + op->o_do_not_cache = 1; + + op->ors_filterstr.bv_len = STRLENOF( "(objectClass=" ")" ) + + slap_schema.si_oc_dynamicObject->soc_cname.bv_len; + op->ors_filterstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len + 1, op->o_tmpmemctx ); + snprintf( op->ors_filterstr.bv_val, op->ors_filterstr.bv_len + 1, + "(objectClass=%s)", + slap_schema.si_oc_dynamicObject->soc_cname.bv_val ); + + op->ors_filter = str2filter_x( op, op->ors_filterstr.bv_val ); + if ( op->ors_filter == NULL ) { + rs.sr_err = LDAP_OTHER; + goto done_search; + } + + op->o_callback = ≻ + sc.sc_response = dds_count_cb; + sc.sc_private = &di->di_num_dynamicObjects; + di->di_num_dynamicObjects = 0; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + (void)op->o_bd->bd_info->bi_op_search( op, &rs ); + op->o_bd->bd_info = (BackendInfo *)on; + +done_search:; + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter_free_x( op, op->ors_filter, 1 ); + + rc = rs.sr_err; + switch ( rs.sr_err ) { + case LDAP_SUCCESS: + Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "DDS non-expired=%d\n", + di->di_num_dynamicObjects ); + break; + + case LDAP_NO_SUCH_OBJECT: + /* (ITS#5267) database not created yet? */ + rs.sr_err = LDAP_SUCCESS; + extra = " (ignored)"; + /* fallthru */ + + default: + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS non-expired objects lookup failed err=%d%s\n", + rc, extra ); + break; + } + + return rs.sr_err; +} + +static int +dds_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + int rc = 0; + void *thrctx = ldap_pvt_thread_pool_context(); + + if ( slapMode & SLAP_TOOL_MODE ) + return 0; + + if ( DDS_OFF( di ) ) { + goto done; + } + + if ( SLAP_SINGLE_SHADOW( be ) ) { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS incompatible with shadow database \"%s\".\n", + be->be_suffix[ 0 ].bv_val ); + return 1; + } + + if ( di->di_max_ttl == 0 ) { + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + } + + if ( di->di_min_ttl == 0 ) { + di->di_max_ttl = DDS_RF2589_DEFAULT_TTL; + } + + di->di_suffix = be->be_suffix; + di->di_nsuffix = be->be_nsuffix; + + /* count the dynamic objects first */ + rc = dds_count( thrctx, be ); + if ( rc != LDAP_SUCCESS ) { + rc = 1; + goto done; + } + + /* start expire task */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + di->di_expire_task = ldap_pvt_runqueue_insert( &slapd_rq, + DDS_INTERVAL( di ), + dds_expire_fn, di, "dds_expire_fn", + be->be_suffix[ 0 ].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + /* register dinamicSubtrees root DSE info support */ + rc = entry_info_register( dds_entry_info, (void *)di ); + +done:; + + return rc; +} + +static int +dds_db_close( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + /* stop expire task */ + if ( di && di->di_expire_task ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, di->di_expire_task ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, di->di_expire_task ); + } + ldap_pvt_runqueue_remove( &slapd_rq, di->di_expire_task ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + di->di_expire_task = NULL; + } + + (void)entry_info_unregister( dds_entry_info, (void *)di ); + + return 0; +} + +static int +dds_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dds_info_t *di = on->on_bi.bi_private; + + if ( di != NULL ) { + ldap_pvt_thread_mutex_destroy( &di->di_mutex ); + + free( di ); + } + + return 0; +} + +static int +slap_exop_refresh( + Operation *op, + SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + rs->sr_err = slap_parse_refresh( op->ore_reqdata, &op->o_req_ndn, NULL, + &rs->sr_text, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + Log( LDAP_DEBUG_STATS, LDAP_LEVEL_INFO, + "%s REFRESH dn=\"%s\"\n", + op->o_log_prefix, op->o_req_ndn.bv_val ); + op->o_req_dn = op->o_req_ndn; + + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) { + send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, + "no global superior knowledge" ); + goto done; + } + + if ( !SLAP_DYNAMIC( op->o_bd ) ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support dynamic directory services" ); + goto done; + } + + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&slap_EXOP_REFRESH ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + if ( op->o_bd->be_extended == NULL ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support extended operations" ); + goto done; + } + + op->o_bd->be_extended( op, rs ); + +done:; + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + BER_BVZERO( &op->o_req_dn ); + } + op->o_bd = bd; + + return rs->sr_err; +} + +static slap_overinst dds; + +static int do_not_load_exop; +static int do_not_replace_exop; +static int do_not_load_schema; + +#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ +int +dds_initialize() +{ + int rc = 0; + int i, code; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( DDS_LAST ); + + if ( !do_not_load_schema ) { + static struct { + char *desc; + slap_mask_t flags; + AttributeDescription **ad; + } s_at[] = { + { "( 1.3.6.1.4.1.4203.666.1.57 " + "NAME ( 'entryExpireTimestamp' ) " + "DESC 'RFC2589 OpenLDAP extension: expire time of a dynamic object, " + "computed as now + entryTtl' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE dSAOperation )", + SLAP_AT_HIDE, + &ad_entryExpireTimestamp }, + { NULL } + }; + + for ( i = 0; s_at[ i ].desc != NULL; i++ ) { + code = register_at( s_at[ i ].desc, s_at[ i ].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "dds_initialize: register_at failed\n" ); + return code; + } + (*s_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + } + + if ( !do_not_load_exop ) { + rc = load_extop2( (struct berval *)&slap_EXOP_REFRESH, + SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, slap_exop_refresh, + !do_not_replace_exop ); + if ( rc != LDAP_SUCCESS ) { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS unable to register refresh exop: %d.\n", + rc ); + return rc; + } + } + + dds.on_bi.bi_type = "dds"; + + dds.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + dds.on_bi.bi_db_init = dds_db_init; + dds.on_bi.bi_db_open = dds_db_open; + dds.on_bi.bi_db_close = dds_db_close; + dds.on_bi.bi_db_destroy = dds_db_destroy; + + dds.on_bi.bi_op_add = dds_op_add; + dds.on_bi.bi_op_delete = dds_op_delete; + dds.on_bi.bi_op_modify = dds_op_modify; + dds.on_bi.bi_op_modrdn = dds_op_rename; + dds.on_bi.bi_extended = dds_op_extended; + dds.on_response = dds_response; + + dds.on_bi.bi_cf_ocs = dds_ocs; + + rc = config_register_schema( dds_cfg, dds_ocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &dds ); +} + +#if SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + int i; + + for ( i = 0; i < argc; i++ ) { + char *arg = argv[ i ]; + int no = 0; + + if ( strncasecmp( arg, "no-", STRLENOF( "no-" ) ) == 0 ) { + arg += STRLENOF( "no-" ); + no = 1; + } + + if ( strcasecmp( arg, "exop" ) == 0 ) { + do_not_load_exop = no; + + } else if ( strcasecmp( arg, "replace" ) == 0 ) { + do_not_replace_exop = no; + + } else if ( strcasecmp( arg, "schema" ) == 0 ) { + do_not_load_schema = no; + + } else { + Log( LDAP_DEBUG_ANY, LDAP_LEVEL_ERR, + "DDS unknown module arg[#%d]=\"%s\".\n", + i, argv[ i ] ); + return 1; + } + } + + return dds_initialize(); +} +#endif /* SLAPD_OVER_DDS == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_DDS) */ diff --git a/servers/slapd/overlays/deref.c b/servers/slapd/overlays/deref.c new file mode 100644 index 0000000..89dc227 --- /dev/null +++ b/servers/slapd/overlays/deref.c @@ -0,0 +1,586 @@ +/* deref.c - dereference overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2022 The OpenLDAP Foundation. + * Portions Copyright 2008 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DEREF + +#include <stdio.h> + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "slap-config.h" + +#include "lutil.h" + +/* + * 1. Specification + * + * 1.1. Request + * + * controlValue ::= SEQUENCE OF derefSpec DerefSpec + * + * DerefSpec ::= SEQUENCE { + * derefAttr attributeDescription, ; DN-valued + * attributes AttributeList } + * + * AttributeList ::= SEQUENCE OF attr AttributeDescription + * + * derefAttr MUST be unique within controlValue + * + * + * 1.2. Response + * + * controlValue ::= SEQUENCE OF DerefRes + * + * From RFC 4511: + * PartialAttribute ::= SEQUENCE { + * type AttributeDescription, + * vals SET OF value AttributeValue } + * + * PartialAttributeList ::= SEQUENCE OF + * partialAttribute PartialAttribute + * + * DerefRes ::= SEQUENCE { + * derefAttr AttributeDescription, + * derefVal LDAPDN, + * attrVals [0] PartialAttributeList OPTIONAL } + * + * If vals is empty, partialAttribute is omitted. + * If all vals in attrVals are empty, attrVals is omitted. + * + * 2. Examples + * + * 2.1. Example + * + * 2.1.1. Request + * + * { { member, { GUID, SID } }, { memberOf, { GUID, SID } } } + * + * 2.1.2. Response + * + * { { memberOf, "cn=abartlet,cn=users,dc=abartlet,dc=net", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fa" ] }, + * { SID, [ "S-1-2-3-2345" ] } } }, + * { memberOf, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fb" ] }, + * { SID, [ "S-1-2-3-2346" ] } } } } + * + * 2.2. Example + * + * 2.2.1. Request + * + * { { member, { cn, uid, drink } } } + * + * 2.2.2. Response + * + * { { member, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { cn, [ "ando", "Pierangelo Masarati" ] }, + * { uid, [ "ando" ] } } }, + * { member, "dc=sys-net,dc=it" } } + * + * + * 3. Security considerations + * + * The control result must not disclose information the client's + * identity could not have accessed directly by performing the related + * search operations. The presence of a derefVal in the control + * response does not imply neither the existence of nor any access + * privilege to the corresponding entry. It is merely a consequence + * of the read access the client's identity has on the corresponding + * attribute's value. + */ + +#define o_deref o_ctrlflag[deref_cid] +#define o_ctrlderef o_controls[deref_cid] + +typedef struct DerefSpec { + AttributeDescription *ds_derefAttr; + AttributeDescription **ds_attributes; + int ds_nattrs; + struct DerefSpec *ds_next; +} DerefSpec; + +typedef struct DerefVal { + struct berval dv_derefSpecVal; + BerVarray *dv_attrVals; +} DerefVal; + +typedef struct DerefRes { + DerefSpec dr_spec; + DerefVal *dr_vals; + struct DerefRes *dr_next; +} DerefRes; + +typedef struct deref_cb_t { + slap_overinst *dc_on; + DerefSpec *dc_ds; +} deref_cb_t; + +static int deref_cid; +static slap_overinst deref; +static int ov_count; + +static int +deref_parseCtrl ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_len_t len; + char *last; + DerefSpec *dshead = NULL, **dsp = &dshead; + BerVarray attributes = NULL; + + if ( op->o_deref != SLAP_CONTROL_NONE ) { + rs->sr_text = "Dereference control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + struct berval derefAttr; + DerefSpec *ds, *dstmp; + const char *text; + int rc; + ber_len_t cnt = sizeof(struct berval); + ber_len_t off = 0; + + if ( ber_scanf( ber, "{m{M}}", &derefAttr, &attributes, &cnt, off ) == LBER_ERROR + || !cnt ) + { + rs->sr_text = "Dereference control: derefSpec decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + ds = (DerefSpec *)op->o_tmpcalloc( 1, + sizeof(DerefSpec) + sizeof(AttributeDescription *)*(cnt + 1), + op->o_tmpmemctx ); + ds->ds_attributes = (AttributeDescription **)&ds[ 1 ]; + ds->ds_nattrs = cnt; + + rc = slap_bv2ad( &derefAttr, &ds->ds_derefAttr, &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: derefAttr decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + for ( dstmp = dshead; dstmp && dstmp != ds; dstmp = dstmp->ds_next ) { + if ( dstmp->ds_derefAttr == ds->ds_derefAttr ) { + rs->sr_text = "Dereference control: derefAttr must be unique within control"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + if ( !( ds->ds_derefAttr->ad_type->sat_syntax->ssyn_flags & SLAP_SYNTAX_DN )) { + if ( ctrl->ldctl_iscritical ) { + rs->sr_text = "Dereference control: derefAttr syntax not distinguishedName"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + rs->sr_err = LDAP_SUCCESS; + goto justcleanup; + } + + for ( cnt = 0; !BER_BVISNULL( &attributes[ cnt ] ); cnt++ ) { + rc = slap_bv2ad( &attributes[ cnt ], &ds->ds_attributes[ cnt ], &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + ber_memfree_x( attributes, op->o_tmpmemctx ); + attributes = NULL; + + *dsp = ds; + dsp = &ds->ds_next; + } + + op->o_ctrlderef = (void *)dshead; + + op->o_deref = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + rs->sr_err = LDAP_SUCCESS; + +done:; + if ( rs->sr_err != LDAP_SUCCESS ) { +justcleanup:; + for ( ; dshead; ) { + DerefSpec *dsnext = dshead->ds_next; + op->o_tmpfree( dshead, op->o_tmpmemctx ); + dshead = dsnext; + } + } + + if ( attributes != NULL ) { + ber_memfree_x( attributes, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +static int +deref_cleanup( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) { + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + op->o_tmpfree( op->o_ctrlderef, op->o_tmpmemctx ); + op->o_ctrlderef = NULL; + } + + return SLAP_CB_CONTINUE; +} + +static int +deref_response( Operation *op, SlapReply *rs ) +{ + int rc = SLAP_CB_CONTINUE; + + if ( rs->sr_type == REP_SEARCH ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + deref_cb_t *dc = (deref_cb_t *)op->o_callback->sc_private; + DerefSpec *ds; + DerefRes *dr, *drhead = NULL, **drp = &drhead; + struct berval bv = BER_BVNULL; + int nDerefRes = 0, nDerefVals = 0, nAttrs = 0, nVals = 0; + struct berval ctrlval; + LDAPControl *ctrl, *ctrlsp[2]; + AccessControlState acl_state = ACL_STATE_INIT; + static char dummy = '\0'; + Entry *ebase; + int i; + + rc = overlay_entry_get_ov( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &ebase, dc->dc_on ); + if ( rc != LDAP_SUCCESS || ebase == NULL ) { + return SLAP_CB_CONTINUE; + } + + for ( ds = dc->dc_ds; ds; ds = ds->ds_next ) { + Attribute *a = attr_find( ebase->e_attrs, ds->ds_derefAttr ); + + if ( a != NULL ) { + DerefVal *dv; + BerVarray *bva; + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + NULL, ACL_READ, &acl_state ) ) + { + continue; + } + + dr = op->o_tmpcalloc( 1, + sizeof( DerefRes ) + ( sizeof( DerefVal ) + sizeof( BerVarray * ) * ds->ds_nattrs ) * ( a->a_numvals + 1 ), + op->o_tmpmemctx ); + dr->dr_spec = *ds; + dv = dr->dr_vals = (DerefVal *)&dr[ 1 ]; + bva = (BerVarray *)&dv[ a->a_numvals + 1 ]; + + bv.bv_len += ds->ds_derefAttr->ad_cname.bv_len; + nAttrs++; + nDerefRes++; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + dv[ i ].dv_attrVals = bva; + bva += ds->ds_nattrs; + + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[ i ], ACL_READ, &acl_state ) ) + { + dv[ i ].dv_derefSpecVal.bv_val = &dummy; + continue; + } + + ber_dupbv_x( &dv[ i ].dv_derefSpecVal, &a->a_vals[ i ], op->o_tmpmemctx ); + bv.bv_len += dv[ i ].dv_derefSpecVal.bv_len; + nVals++; + nDerefVals++; + + rc = overlay_entry_get_ov( op, &a->a_nvals[ i ], NULL, NULL, 0, &e, dc->dc_on ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + int j; + + if ( access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + for ( j = 0; j < ds->ds_nattrs; j++ ) { + Attribute *aa; + + if ( !access_allowed( op, e, ds->ds_attributes[ j ], NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + + aa = attr_find( e->e_attrs, ds->ds_attributes[ j ] ); + if ( aa != NULL ) { + unsigned k, h, last = aa->a_numvals; + + ber_bvarray_dup_x( &dv[ i ].dv_attrVals[ j ], + aa->a_vals, op->o_tmpmemctx ); + + bv.bv_len += ds->ds_attributes[ j ]->ad_cname.bv_len; + + for ( k = 0, h = 0; k < aa->a_numvals; k++ ) { + if ( !access_allowed( op, e, + aa->a_desc, + &aa->a_nvals[ k ], + ACL_READ, &acl_state ) ) + { + op->o_tmpfree( dv[ i ].dv_attrVals[ j ][ h ].bv_val, + op->o_tmpmemctx ); + dv[ i ].dv_attrVals[ j ][ h ] = dv[ i ].dv_attrVals[ j ][ --last ]; + BER_BVZERO( &dv[ i ].dv_attrVals[ j ][ last ] ); + continue; + } + bv.bv_len += dv[ i ].dv_attrVals[ j ][ h ].bv_len; + nVals++; + h++; + } + nAttrs++; + } + } + } + + overlay_entry_release_ov( op, e, 0, dc->dc_on ); + } + } + + *drp = dr; + drp = &dr->dr_next; + } + } + overlay_entry_release_ov( op, ebase, 0, dc->dc_on ); + + if ( drhead == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* cook the control value */ + bv.bv_len += nVals * sizeof(struct berval) + + nAttrs * sizeof(struct berval) + + nDerefVals * sizeof(DerefVal) + + nDerefRes * sizeof(DerefRes); + bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx ); + + ber_init2( ber, &bv, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{" /*}*/ ); + for ( dr = drhead; dr != NULL; dr = dr->dr_next ) { + for ( i = 0; !BER_BVISNULL( &dr->dr_vals[ i ].dv_derefSpecVal ); i++ ) { + int j, first = 1; + + if ( dr->dr_vals[ i ].dv_derefSpecVal.bv_val == &dummy ) { + continue; + } + + rc = ber_printf( ber, "{OO" /*}*/, + &dr->dr_spec.ds_derefAttr->ad_cname, + &dr->dr_vals[ i ].dv_derefSpecVal ); + op->o_tmpfree( dr->dr_vals[ i ].dv_derefSpecVal.bv_val, op->o_tmpmemctx ); + for ( j = 0; j < dr->dr_spec.ds_nattrs; j++ ) { + if ( dr->dr_vals[ i ].dv_attrVals[ j ] != NULL ) { + if ( first ) { + rc = ber_printf( ber, "t{" /*}*/, + (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) ); + first = 0; + } + rc = ber_printf( ber, "{O[W]}", + &dr->dr_spec.ds_attributes[ j ]->ad_cname, + dr->dr_vals[ i ].dv_attrVals[ j ] ); + 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 ); + return rc; + } + } + ov_count++; + return LDAP_SUCCESS; +} + +static int +deref_db_open( BackendDB *be, ConfigReply *cr) +{ + return overlay_register_control( be, LDAP_CONTROL_X_DEREF ); +} + +#ifdef SLAP_CONFIG_DELETE +static int +deref_db_destroy( BackendDB *be, ConfigReply *cr) +{ + ov_count--; + overlay_unregister_control( be, LDAP_CONTROL_X_DEREF ); + if ( ov_count == 0 ) { + unregister_supported_control( LDAP_CONTROL_X_DEREF ); + } + return 0; +} +#endif /* SLAP_CONFIG_DELETE */ + +int +deref_initialize(void) +{ + deref.on_bi.bi_type = "deref"; + deref.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + deref.on_bi.bi_db_init = deref_db_init; + deref.on_bi.bi_db_open = deref_db_open; +#ifdef SLAP_CONFIG_DELETE + deref.on_bi.bi_db_destroy = deref_db_destroy; +#endif /* SLAP_CONFIG_DELETE */ + deref.on_bi.bi_op_search = deref_op_search; + + return overlay_register( &deref ); +} + +#if SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return deref_initialize(); +} +#endif /* SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_DEREF */ diff --git a/servers/slapd/overlays/dyngroup.c b/servers/slapd/overlays/dyngroup.c new file mode 100644 index 0000000..5d890d6 --- /dev/null +++ b/servers/slapd/overlays/dyngroup.c @@ -0,0 +1,234 @@ +/* dyngroup.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Copyright 2003 by Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DYNGROUP + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +/* This overlay extends the Compare operation to detect members of a + * dynamic group. It has no effect on any other operations. It must + * be configured with a pair of attributes to trigger on, e.g. + * attrpair member memberURL + * will cause compares on "member" to trigger a compare on "memberURL". + */ + +typedef struct adpair { + struct adpair *ap_next; + AttributeDescription *ap_mem; + AttributeDescription *ap_uri; +} adpair; + +static int dgroup_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + int rc = 1; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + { + adpair *ap; + for ( ap = on->on_bi.bi_private; ap; ap = ap->ap_next ) { + struct berval bv; + char *ptr; + bv.bv_len = ap->ap_mem->ad_cname.bv_len + 1 + + ap->ap_uri->ad_cname.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( bv.bv_val, ap->ap_mem->ad_cname.bv_val ); + *ptr++ = ' '; + strcpy( ptr, ap->ap_uri->ad_cname.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + rc = 0; + } + } + break; + case LDAP_MOD_DELETE: + if ( c->valx == -1 ) { + adpair *ap; + while (( ap = on->on_bi.bi_private )) { + on->on_bi.bi_private = ap->ap_next; + ch_free( ap ); + } + } else { + adpair **app, *ap; + int i; + app = (adpair **)&on->on_bi.bi_private; + for (i=0; i<=c->valx; i++, app = &ap->ap_next) { + ap = *app; + } + *app = ap->ap_next; + ch_free( ap ); + } + rc = 0; + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + { + adpair ap = { NULL, NULL, NULL }, **app, *a2; + const char *text; + if ( slap_str2ad( c->argv[1], &ap.ap_mem, &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( slap_str2ad( c->argv[2], &ap.ap_uri, &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s attribute description unknown: \"%s\"", + c->argv[0], c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + /* The on->on_bi.bi_private pointer can be used for + * anything this instance of the overlay needs. + */ + a2 = ch_malloc( sizeof(adpair) ); + + for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next ) + /* Get to the end */ ; + + a2->ap_mem = ap.ap_mem; + a2->ap_uri = ap.ap_uri; + a2->ap_next = *app; + *app = a2; + rc = 0; + } + } + return rc; +} + +static ConfigTable dgroupcfg[] = { + { "attrpair", "member-attribute> <URL-attribute", 3, 3, 0, + ARG_MAGIC, dgroup_cf, + "( OLcfgOvAt:17.1 NAME ( 'olcDynGroupAttrPair' 'olcDGAttrPair' ) " + "EQUALITY caseIgnoreMatch " + "DESC 'Member and MemberURL attribute pair' " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dgroupocs[] = { + { "( OLcfgOvOc:17.1 " + "NAME ( 'olcDynGroupConfig' 'olcDGConfig' ) " + "DESC 'Dynamic Group configuration' " + "SUP olcOverlayConfig " + "MAY olcDynGroupAttrPair)", + Cft_Overlay, dgroupcfg }, + { NULL, 0, NULL } +}; + +static int +dyngroup_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + adpair *ap = on->on_bi.bi_private; + + /* If we've been configured and the current response is + * what we're looking for... + */ + if ( ap && op->o_tag == LDAP_REQ_COMPARE && + rs->sr_err == LDAP_NO_SUCH_ATTRIBUTE ) { + + for (;ap;ap=ap->ap_next) { + if ( op->oq_compare.rs_ava->aa_desc == ap->ap_mem ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + int cache = op->o_do_not_cache; + + op->o_do_not_cache = 1; + rs->sr_err = backend_group( op, NULL, &op->o_req_ndn, + &op->oq_compare.rs_ava->aa_value, NULL, ap->ap_uri ); + op->o_do_not_cache = cache; + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + case LDAP_NO_SUCH_OBJECT: + rs->sr_err = LDAP_COMPARE_FALSE; + break; + } + break; + } + } + } + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static int +dyngroup_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + adpair *ap, *a2; + + for ( ap = on->on_bi.bi_private; ap; ap = a2 ) { + a2 = ap->ap_next; + ch_free( ap ); + } + return 0; +} + +static slap_overinst dyngroup; + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +int dyngroup_initialize() { + int code; + + dyngroup.on_bi.bi_type = "dyngroup"; + dyngroup.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + dyngroup.on_bi.bi_db_destroy = dyngroup_destroy; + dyngroup.on_response = dyngroup_response; + + dyngroup.on_bi.bi_cf_ocs = dgroupocs; + code = config_register_schema( dgroupcfg, dgroupocs ); + if ( code ) return code; + + return overlay_register( &dyngroup ); +} + +#if SLAPD_OVER_DYNGROUP == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dyngroup_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_DYNGROUP) */ diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c new file mode 100644 index 0000000..3490cfb --- /dev/null +++ b/servers/slapd/overlays/dynlist.c @@ -0,0 +1,2706 @@ +/* dynlist.c - dynamic list overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Pierangelo Masarati. + * Portions Copyright 2008 Emmanuel Dreyfus. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for SysNet s.n.c., for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DYNLIST + +#if SLAPD_OVER_DYNGROUP != SLAPD_MOD_STATIC +#define TAKEOVER_DYNGROUP +#endif + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" + +static AttributeDescription *ad_dgIdentity, *ad_dgAuthz; +static AttributeDescription *ad_memberOf; + +typedef struct dynlist_map_t { + AttributeDescription *dlm_member_ad; + AttributeDescription *dlm_mapped_ad; + AttributeDescription *dlm_memberOf_ad; + ObjectClass *dlm_static_oc; + int dlm_memberOf_nested; + int dlm_member_oper; + int dlm_memberOf_oper; + struct dynlist_map_t *dlm_next; +} dynlist_map_t; + +typedef struct dynlist_info_t { + ObjectClass *dli_oc; + AttributeDescription *dli_ad; + struct dynlist_map_t *dli_dlm; + struct berval dli_uri; + LDAPURLDesc *dli_lud; + struct berval dli_uri_nbase; + Filter *dli_uri_filter; + struct berval dli_default_filter; + struct dynlist_info_t *dli_next; +} dynlist_info_t; + +typedef struct dynlist_gen_t { + dynlist_info_t *dlg_dli; + int dlg_memberOf; +} dynlist_gen_t; + +#define DYNLIST_USAGE \ + "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad>[+<memberOf-ad>[@<static-oc>[*]] ...]\": " + +static int +ad_infilter( AttributeDescription *ad, Filter *f ) +{ + if ( !f ) + return 0; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + return 0; + case LDAP_FILTER_PRESENT: + return f->f_desc == ad; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + return f->f_av_desc == ad; + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + for ( f = f->f_list; f; f = f->f_next ) + if ( ad_infilter( ad, f )) + return 1; + } + } + return 0; +} + +static int +dynlist_make_filter( Operation *op, Entry *e, dynlist_info_t *dli, const char *url, struct berval *oldf, struct berval *newf ) +{ + char *ptr; + int needBrackets = 0; + + assert( oldf != NULL ); + assert( newf != NULL ); + assert( !BER_BVISNULL( oldf ) ); + assert( !BER_BVISEMPTY( oldf ) ); + + if ( oldf->bv_val[0] != '(' ) { + Debug( LDAP_DEBUG_ANY, "%s: dynlist, DN=\"%s\": missing parentheses in URI=\"%s\" filter\n", + op->o_log_prefix, e->e_name.bv_val, url ); + needBrackets = 2; + } + + newf->bv_len = STRLENOF( "(&(!(objectClass=" "))" ")" ) + + dli->dli_oc->soc_cname.bv_len + oldf->bv_len + needBrackets; + newf->bv_val = op->o_tmpalloc( newf->bv_len + 1, op->o_tmpmemctx ); + if ( newf->bv_val == NULL ) { + return -1; + } + ptr = lutil_strcopy( newf->bv_val, "(&(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + if ( needBrackets ) *ptr++ = '('; + ptr = lutil_strcopy( ptr, oldf->bv_val ); + if ( needBrackets ) *ptr++ = ')'; + ptr = lutil_strcopy( ptr, ")" ); + newf->bv_len = ptr - newf->bv_val; + + return 0; +} + +/* dynlist_sc_update() callback info set by dynlist_prepare_entry() */ +typedef struct dynlist_sc_t { + dynlist_info_t *dlc_dli; + Entry *dlc_e; + char **dlc_attrs; +} dynlist_sc_t; + +static int +dynlist_sc_update( Operation *op, SlapReply *rs ) +{ + Entry *e; + Attribute *a; + int opattrs, + userattrs; + AccessControlState acl_state = ACL_STATE_INIT; + + dynlist_sc_t *dlc; + dynlist_map_t *dlm; + + if ( rs->sr_type != REP_SEARCH ) { + return 0; + } + + dlc = (dynlist_sc_t *)op->o_callback->sc_private; + e = dlc->dlc_e; + + assert( e != NULL ); + assert( rs->sr_entry != NULL ); + + /* test access to entry */ + if ( !access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + goto done; + } + + /* if there is only one member_ad, and it's not mapped, + * consider it as old-style member listing */ + dlm = dlc->dlc_dli->dli_dlm; + if ( dlm && dlm->dlm_mapped_ad == NULL && dlm->dlm_next == NULL && dlc->dlc_attrs == NULL ) { + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + struct berval vals[ 2 ], nvals[ 2 ]; + + vals[ 0 ] = rs->sr_entry->e_name; + BER_BVZERO( &vals[ 1 ] ); + nvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &nvals[ 1 ] ); + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = dlm->dlm_member_ad; + mod.sm_type = dlm->dlm_member_ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = 1; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + goto done; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + for ( a = rs->sr_entry->e_attrs; a != NULL; a = a->a_next ) { + BerVarray vals, nvals = NULL; + int i, j, + is_oc = a->a_desc == slap_schema.si_ad_objectClass; + + /* if attribute is not requested, skip it */ + if ( rs->sr_attrs == NULL ) { + if ( is_at_operational( a->a_desc->ad_type ) ) { + continue; + } + + } else { + if ( is_at_operational( a->a_desc->ad_type ) ) { + if ( !opattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + + } else { + if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + } + } + + /* test access to attribute */ + if ( op->ors_attrsonly ) { + if ( !access_allowed( op, rs->sr_entry, a->a_desc, NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + } + + /* single-value check: keep first only */ + if ( is_at_single_value( a->a_desc->ad_type ) ) { + if ( attr_find( e->e_attrs, a->a_desc ) != NULL ) { + continue; + } + } + + /* test access to attribute */ + i = a->a_numvals; + + vals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + if ( a->a_nvals != a->a_vals ) { + nvals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + } + + for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) { + if ( is_oc ) { + ObjectClass *soc = oc_bvfind( &a->a_vals[i] ); + + if ( soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) { + continue; + } + } + + if ( access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[i], ACL_READ, &acl_state ) ) + { + vals[j] = a->a_vals[i]; + if ( nvals ) { + nvals[j] = a->a_nvals[i]; + } + j++; + } + } + + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( j != 0 ) { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + dynlist_map_t *dlm; + AttributeDescription *ad; + + BER_BVZERO( &vals[j] ); + if ( nvals ) { + BER_BVZERO( &nvals[j] ); + } + + ad = a->a_desc; + for ( dlm = dlc->dlc_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == a->a_desc ) { + if ( dlm->dlm_mapped_ad ) { + ad = dlm->dlm_mapped_ad; + } + break; + } + } + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = ad; + mod.sm_type = ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = j; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( nvals ) { + op->o_tmpfree( nvals, op->o_tmpmemctx ); + } + } + +done:; + if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) { + entry_free( rs->sr_entry ); + rs->sr_entry = NULL; + rs->sr_flags &= ~REP_ENTRY_MASK; + } + + return 0; +} + +typedef struct dynlist_name_t { + struct berval dy_name; + dynlist_info_t *dy_dli; + AttributeDescription *dy_staticmember; + int dy_seen; + int dy_numuris; + TAvlnode *dy_subs; + TAvlnode *dy_sups; + LDAPURLDesc *dy_uris[]; +} dynlist_name_t; + +static void +dynlist_urlmembers( Operation *op, dynlist_name_t *dyn, slap_callback *sc ) +{ + Operation o = *op; + LDAPURLDesc *ludp; + int i; + + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + o.ors_attrs = NULL; + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + o.o_callback = sc; + + for (i=0; i<dyn->dy_numuris; i++) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_attrs ) + continue; + o.o_req_dn.bv_val = ludp->lud_dn; + o.o_req_dn.bv_len = ludp->lud_port; + o.o_req_ndn = o.o_req_dn; + o.ors_scope = ludp->lud_scope; + o.ors_filter = (Filter *)ludp->lud_filter; + filter2bv_x( op, o.ors_filter, &o.ors_filterstr ); + o.o_bd = select_backend( &o.o_req_ndn, 1 ); + if ( o.o_bd && o.o_bd->be_search ) { + SlapReply r = { REP_SEARCH }; + r.sr_attr_flags = slap_attr_flags( o.ors_attrs ); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + (void)o.o_bd->be_search( &o, &r ); + } + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } +} + +static void +dynlist_nested_memberOf( Entry *e, AttributeDescription *ad, TAvlnode *sups ) +{ + TAvlnode *ptr; + dynlist_name_t *dyn; + Attribute *a; + + a = attr_find( e->e_attrs, ad ); + for ( ptr = ldap_tavl_end( sups, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + if ( a ) { + unsigned slot; + if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &dyn->dy_name, &slot, NULL ) == LDAP_SUCCESS ) + continue; + } + attr_merge_one( e, ad, &dyn->dy_name, &dyn->dy_name ); + if ( !a ) + a = attr_find( e->e_attrs, ad ); + if ( dyn->dy_sups ) + dynlist_nested_memberOf( e, ad, dyn->dy_sups ); + } +} + +typedef struct dynlist_member_t { + Entry *dm_e; + AttributeDescription *dm_ad; + Modification dm_mod; + TAvlnode *dm_groups; + struct berval dm_bv[2]; + struct berval dm_nbv[2]; + const char *dm_text; + char dm_textbuf[1024]; +} dynlist_member_t; + +static int +dynlist_ptr_cmp( const void *c1, const void *c2 ) +{ + return ( c1 < c2 ) ? -1 : c1 > c2; +} + +static int +dynlist_nested_member_dg( Operation *op, SlapReply *rs ) +{ + dynlist_member_t *dm = op->o_callback->sc_private; + + if ( rs->sr_type != REP_SEARCH ) + return LDAP_SUCCESS; + + dm->dm_bv[0] = rs->sr_entry->e_name; + dm->dm_nbv[0] = rs->sr_entry->e_nname; + modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1, + &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf )); + + return LDAP_SUCCESS; +} + +static void +dynlist_nested_member( Operation *op, dynlist_member_t *dm, TAvlnode *subs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + TAvlnode *ptr; + dynlist_name_t *dyn; + Entry *ne; + Attribute *a, *b; + + a = attr_find( dm->dm_e->e_attrs, dm->dm_ad ); + if ( !a ) + return; + + for ( ptr = ldap_tavl_end( subs, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + if ( ldap_tavl_insert( &dm->dm_groups, dyn, dynlist_ptr_cmp, ldap_avl_dup_error )) + continue; + if ( overlay_entry_get_ov( op, &dyn->dy_name, NULL, NULL, 0, &ne, on ) != LDAP_SUCCESS || ne == NULL ) + continue; + b = attr_find( ne->e_attrs, dm->dm_ad ); + if ( b ) { + dm->dm_mod.sm_values = b->a_vals; + dm->dm_mod.sm_nvalues = b->a_nvals; + dm->dm_mod.sm_numvals = b->a_numvals; + modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1, + &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf )); + } + overlay_entry_release_ov( op, ne, 0, on ); + if ( dyn->dy_numuris ) { + slap_callback cb = { 0 }; + cb.sc_private = dm; + BER_BVZERO( &dm->dm_bv[1] ); + BER_BVZERO( &dm->dm_nbv[1] ); + dm->dm_mod.sm_values = dm->dm_bv; + dm->dm_mod.sm_nvalues = dm->dm_nbv; + dm->dm_mod.sm_numvals = 1; + cb.sc_response = dynlist_nested_member_dg; + dynlist_urlmembers( op, dyn, &cb ); + } + if ( dyn->dy_subs ) + dynlist_nested_member( op, dm, dyn->dy_subs ); + } +} + +static int +dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli, dynlist_name_t *dyn ) +{ + Attribute *a, *id = NULL; + slap_callback cb = { 0 }; + Operation o = *op; + struct berval *url; + Entry *e; + int opattrs, + userattrs; + dynlist_sc_t dlc = { 0 }; + dynlist_map_t *dlm; + + e = rs->sr_entry; + a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad ); + if ( a == NULL ) { + /* FIXME: error? */ + goto checkdyn; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + /* Don't generate member list if it wasn't requested */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( userattrs || ad_inlist( ad, rs->sr_attrs ) + || ad_infilter( ad, op->ors_filter )) + break; + } + if ( dli->dli_dlm && !dlm ) + goto checkdyn; + + if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) { + Attribute *authz = NULL; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( rs->sr_entry->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + goto checkdyn; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + + /* ensure e is modifiable, but do not replace + * sr_entry yet since we have pointers into it */ + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + e = entry_dup( rs->sr_entry ); + } + + dlc.dlc_e = e; + dlc.dlc_dli = dli; + cb.sc_private = &dlc; + cb.sc_response = dynlist_sc_update; + + o.o_callback = &cb; + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + + for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) { + LDAPURLDesc *lud = NULL; + int i, j; + struct berval dn; + int rc; + + BER_BVZERO( &o.o_req_dn ); + BER_BVZERO( &o.o_req_ndn ); + o.ors_filter = NULL; + o.ors_attrs = NULL; + BER_BVZERO( &o.ors_filterstr ); + + if ( ldap_url_parse( url->bv_val, &lud ) != LDAP_URL_SUCCESS ) { + /* FIXME: error? */ + continue; + } + + if ( lud->lud_host != NULL ) { + /* FIXME: host not allowed; reject as illegal? */ + Debug( LDAP_DEBUG_ANY, "dynlist_prepare_entry(\"%s\"): " + "illegal URI \"%s\"\n", + e->e_name.bv_val, url->bv_val ); + goto cleanup; + } + + if ( lud->lud_dn == NULL ) { + /* note that an empty base is not honored in terms + * of defaultSearchBase, because select_backend() + * is not aware of the defaultSearchBase option; + * this can be useful in case of a database serving + * the empty suffix */ + BER_BVSTR( &dn, "" ); + + } else { + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + } + rc = dnPrettyNormal( NULL, &dn, &o.o_req_dn, &o.o_req_ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + /* FIXME: error? */ + goto cleanup; + } + o.ors_scope = lud->lud_scope; + + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_mapped_ad != NULL ) { + break; + } + } + + if ( dli->dli_dlm && !dlm ) { + /* if ( lud->lud_attrs != NULL ), + * the URL should be ignored */ + o.ors_attrs = slap_anlist_no_attrs; + + } else if ( lud->lud_attrs == NULL ) { + o.ors_attrs = rs->sr_attrs; + + } else { + for ( i = 0; lud->lud_attrs[i]; i++) + /* just count */ ; + + o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx ); + for ( i = 0, j = 0; lud->lud_attrs[i]; i++) { + const char *text = NULL; + + ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name ); + o.ors_attrs[j].an_desc = NULL; + (void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text ); + /* FIXME: ignore errors... */ + + if ( ad_infilter( o.ors_attrs[j].an_desc, op->ors_filter )) { + /* if referenced in filter, must retrieve */ + } else if ( rs->sr_attrs == NULL ) { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + continue; + } + + } else { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + if ( !opattrs ) { + continue; + } + + if ( !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + + } else { + if ( !userattrs && + o.ors_attrs[j].an_desc != NULL && + !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) + { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + } + } + + j++; + } + + if ( j == 0 ) { + goto cleanup; + } + + BER_BVZERO( &o.ors_attrs[j].an_name ); + } + dlc.dlc_attrs = lud->lud_attrs; + + if ( lud->lud_filter == NULL ) { + ber_dupbv_x( &o.ors_filterstr, + &dli->dli_default_filter, op->o_tmpmemctx ); + + } else { + /* don't allow recursion in lists */ + if ( lud->lud_attrs ) { + struct berval flt; + ber_str2bv( lud->lud_filter, 0, 0, &flt ); + if ( dynlist_make_filter( op, rs->sr_entry, dli, url->bv_val, &flt, &o.ors_filterstr ) ) { + /* error */ + goto cleanup; + } + } else { + ber_str2bv( lud->lud_filter, 0, 0, &o.ors_filterstr ); + } + } + o.ors_filter = str2filter_x( op, o.ors_filterstr.bv_val ); + if ( o.ors_filter == NULL ) { + goto cleanup; + } + + o.o_bd = select_backend( &o.o_req_ndn, 1 ); + if ( o.o_bd && o.o_bd->be_search ) { + SlapReply r = { REP_SEARCH }; + r.sr_attr_flags = slap_attr_flags( o.ors_attrs ); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + (void)o.o_bd->be_search( &o, &r ); + } + +cleanup:; + if ( id ) { + slap_op_groups_free( &o ); + } + if ( o.ors_filter ) { + filter_free_x( &o, o.ors_filter, 1 ); + } + if ( o.ors_attrs && o.ors_attrs != rs->sr_attrs + && o.ors_attrs != slap_anlist_no_attrs ) + { + op->o_tmpfree( o.ors_attrs, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_dn ) ) { + op->o_tmpfree( o.o_req_dn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_ndn ) ) { + op->o_tmpfree( o.o_req_ndn.bv_val, op->o_tmpmemctx ); + } + if ( lud->lud_attrs ) { + assert( BER_BVISNULL( &o.ors_filterstr ) + || o.ors_filterstr.bv_val != lud->lud_filter ); + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } else { + if ( o.ors_filterstr.bv_val != lud->lud_filter ) + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } + ldap_free_urldesc( lud ); + } + +checkdyn: + /* handle nested groups */ + if ( dyn && ( dyn->dy_sups || dyn->dy_subs )) { + /* ensure e is modifiable */ + if ( e == rs->sr_entry && !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + e = entry_dup( rs->sr_entry ); + rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + if ( dyn->dy_subs ) { + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad ) { + dynlist_member_t dm; + dm.dm_groups = NULL; + dm.dm_mod.sm_op = LDAP_MOD_ADD; + dm.dm_mod.sm_desc = dlm->dlm_member_ad; + dm.dm_mod.sm_type = dlm->dlm_member_ad->ad_cname; + dm.dm_e = e; + dm.dm_ad = dlm->dlm_member_ad; + dynlist_nested_member( op, &dm, dyn->dy_subs ); + if ( dm.dm_groups ) + ldap_tavl_free( dm.dm_groups, NULL ); + } + } + } + if ( dyn->dy_sups ) { + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups ); + } + } + } + } + + if ( e != rs->sr_entry ) { + rs_replace_entry( op, rs, (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_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli; + Operation o = *op; + Entry *e = NULL; + dynlist_map_t *dlm; + BackendDB *be; + int ret = SLAP_CB_CONTINUE; + + if ( get_manageDSAit( op ) ) + return SLAP_CB_CONTINUE; + + for ( ; dli != NULL; dli = dli->dli_next ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + /* builtin dyngroup evaluator only works for DNs */ + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) + continue; + if ( op->oq_compare.rs_ava->aa_desc == ad ) + break; + } + + if ( dlm ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + BerVarray id = NULL, authz = NULL; + + 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; + + /* If also using static groups, fallback to + * vanilla compare + */ + if ( dlm->dlm_static_oc ) + return SLAP_CB_CONTINUE; + + break; + } + +done:; + if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx ); + + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + + be = select_backend( &o.o_req_ndn, 1 ); + if ( !be || !be->be_search ) { + return SLAP_CB_CONTINUE; + } + + if ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) + { + return SLAP_CB_CONTINUE; + } + + /* check for dynlist objectClass; done if not found */ + dli = (dynlist_info_t *)dlg->dlg_dli; + while ( dli != NULL && !is_entry_objectclass_or_sub( e, dli->dli_oc ) ) { + dli = dli->dli_next; + } + if ( dli == NULL ) { + goto release; + } + + if ( ad_dgIdentity ) { + Attribute *id = attrs_find( e->e_attrs, ad_dgIdentity ); + if ( id ) { + Attribute *authz; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( e->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + goto release; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + } + + /* generate dynamic list with dynlist_response() and compare */ + { + SlapReply r = { REP_SEARCH }; + Attribute *a; + AttributeName an[2]; + + o.o_tag = LDAP_REQ_SEARCH; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + + o.ors_filterstr = *slap_filterstr_objectClass_pres; + o.ors_filter = (Filter *) slap_filter_objectClass_pres; + + o.ors_scope = LDAP_SCOPE_BASE; + o.ors_deref = LDAP_DEREF_NEVER; + an[0].an_name = op->orc_ava->aa_desc->ad_cname; + an[0].an_desc = op->orc_ava->aa_desc; + BER_BVZERO( &an[1].an_name ); + o.ors_attrs = an; + o.ors_attrsonly = 0; + r.sr_entry = e; + r.sr_attrs = an; + + o.o_acl_priv = ACL_COMPARE; + dynlist_prepare_entry( &o, &r, dli, NULL ); + a = attrs_find( r.sr_entry->e_attrs, op->orc_ava->aa_desc ); + + ret = LDAP_NO_SUCH_ATTRIBUTE; + for ( ; a ; a = attrs_find( a->a_next, op->orc_ava->aa_desc )) { + ret = LDAP_COMPARE_FALSE; + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->orc_ava->aa_value, NULL, op->o_tmpmemctx ) == LDAP_SUCCESS ) { + ret = LDAP_COMPARE_TRUE; + break; + } + } + rs->sr_err = ret; + + if ( r.sr_entry != e ) + entry_free( r.sr_entry ); + send_ldap_result( op, rs ); + } + +release:; + if ( e != NULL ) { + overlay_entry_release_ov( &o, e, 0, on ); + } + + return ret; +} + +#define WANT_MEMBEROF 1 +#define WANT_MEMBER 2 + +typedef struct dynlist_search_t { + TAvlnode *ds_names; + TAvlnode *ds_fnodes; + dynlist_info_t *ds_dli; + dynlist_map_t *ds_dlm; + Filter *ds_origfilter; + struct berval ds_origfilterbv; + int ds_want; + int ds_found; +} dynlist_search_t; + +static int +dynlist_avl_cmp( const void *c1, const void *c2 ) +{ + const dynlist_name_t *n1, *n2; + int rc; + n1 = c1; n2 = c2; + + rc = n1->dy_name.bv_len - n2->dy_name.bv_len; + if ( rc ) return rc; + return ber_bvcmp( &n1->dy_name, &n2->dy_name ); +} + +/* build a list of dynamic entries */ +static int +dynlist_search1resp( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) { + dynlist_search_t *ds = op->o_callback->sc_private; + Attribute *a, *b = NULL; + + if ( ds->ds_dlm && ds->ds_dlm->dlm_static_oc && is_entry_objectclass( rs->sr_entry, ds->ds_dlm->dlm_static_oc, 0 )) + b = attr_find( rs->sr_entry->e_attrs, ds->ds_dlm->dlm_member_ad ); + a = attr_find( rs->sr_entry->e_attrs, ds->ds_dli->dli_ad ); + if ( a || b ) { + unsigned len; + dynlist_name_t *dyn; + struct berval bv, nbase; + LDAPURLDesc *ludp; + int i, j = 0; + + if ( a ) + len = a->a_numvals * sizeof(LDAPURLDesc *); + else + len = 0; + + dyn = ch_calloc(1, sizeof(dynlist_name_t)+rs->sr_entry->e_nname.bv_len + 1 + len); + dyn->dy_name.bv_val = ((char *)(dyn+1)) + len; + dyn->dy_dli = ds->ds_dli; + dyn->dy_name.bv_len = rs->sr_entry->e_nname.bv_len; + if ( a ) { + Filter *f; + /* parse and validate the URIs */ + for (i=0; i<a->a_numvals; i++) { + if (ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS ) + continue; + if (( ludp->lud_host && *ludp->lud_host) + || ludp->lud_exts ) { + skipit: + ldap_free_urldesc( ludp ); + continue; + } + ber_str2bv( ludp->lud_dn, 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS ) + goto skipit; + ldap_memfree( ludp->lud_dn ); + ludp->lud_dn = ldap_strdup( nbase.bv_val ); + op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx ); + /* cheat here, reuse fields */ + ludp->lud_port = nbase.bv_len; + if ( ludp->lud_filter && *ludp->lud_filter ) { + f = str2filter( ludp->lud_filter ); + if ( f == NULL ) + goto skipit; + ldap_memfree( ludp->lud_filter ); + } else { + f = ch_malloc( sizeof( Filter )); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = LDAP_COMPARE_TRUE; + f->f_next = NULL; + } + ludp->lud_filter = (char *)f; + dyn->dy_uris[j] = ludp; + j++; + } + } + dyn->dy_numuris = j; + memcpy(dyn->dy_name.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len ); + if ( b ) + dyn->dy_staticmember = ds->ds_dlm->dlm_member_ad; + + if ( ldap_tavl_insert( &ds->ds_names, dyn, dynlist_avl_cmp, ldap_avl_dup_error )) { + for (i=dyn->dy_numuris-1; i>=0; i--) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_filter ) { + filter_free( (Filter *)ludp->lud_filter ); + ludp->lud_filter = NULL; + } + ldap_free_urldesc( ludp ); + } + ch_free( dyn ); + } else { + ds->ds_found++; + } + } + } + return 0; +} + +/* replace a filter clause (memberOf=<groupDN>) with an expansion + * of its dynamic members + * using (&(entryDN=<groupURIbase>)<groupURIfilter>) + */ +static int +dynlist_filter_dyngroup( Operation *op, Filter *n, Attribute *a ) +{ + Filter *andf = NULL, *dnf, *urif, *orf = NULL; + LDAPURLDesc *ludp; + struct berval bv, nbase; + int i; + + for (i=0; i<a->a_numvals; i++) { + if ( ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS ) + continue; + if (( ludp->lud_host && *ludp->lud_host ) + || ludp->lud_attrs + || ludp->lud_exts ) { + skip: + ldap_free_urldesc( ludp ); + continue; + } + ber_str2bv( ludp->lud_dn, 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS ) + goto skip; + if ( ludp->lud_filter && *ludp->lud_filter ) { + urif = str2filter_x( op, ludp->lud_filter ); + if ( urif == NULL ) { + op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx ); + goto skip; + } + } else { + urif = NULL; + } + if ( !andf && n->f_choice == SLAPD_FILTER_COMPUTED ) { + andf = n; + andf->f_next = NULL; + } else { + orf = n; + if ( n->f_choice != LDAP_FILTER_OR ) { + andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + *andf = *n; + orf->f_choice = LDAP_FILTER_OR; + orf->f_next = NULL; + orf->f_list = andf; + } + andf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + andf->f_next = orf->f_list; + orf->f_list = andf; + } + dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + andf->f_choice = LDAP_FILTER_AND; + andf->f_list = dnf; + dnf->f_next = urif; + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + dnf->f_choice = LDAP_FILTER_EQUALITY; + dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx ); + dnf->f_av_desc = slap_schema.si_ad_entryDN; + dnf->f_av_value = nbase; + } else { + dnf->f_choice = LDAP_FILTER_EXT; + dnf->f_mra = op->o_tmpcalloc( 1, sizeof(MatchingRuleAssertion), op->o_tmpmemctx ); + dnf->f_mr_desc = slap_schema.si_ad_entryDN; + dnf->f_mr_value = nbase; + switch ( ludp->lud_scope ) { + case LDAP_SCOPE_ONELEVEL: + dnf->f_mr_rule = slap_schema.si_mr_dnOneLevelMatch; + break; + case LDAP_SCOPE_SUBTREE: + dnf->f_mr_rule = slap_schema.si_mr_dnSubtreeMatch; + break; + case LDAP_SCOPE_SUBORDINATE: + dnf->f_mr_rule = slap_schema.si_mr_dnSubordinateMatch; + break; + } + ber_str2bv( dnf->f_mr_rule->smr_names[0], 0, 0, &dnf->f_mr_rule_text ); + } + ldap_free_urldesc( ludp ); + } + if ( !andf ) + return -1; + return 0; +} + +/* replace a filter clause (memberOf=<groupDN>) with an expansion + * of its static members + * using (|(entryDN=<memberN>)[...]) + */ +static int +dynlist_filter_stgroup( Operation *op, Filter *n, Attribute *a ) +{ + Filter *dnf, *orf = NULL; + int i; + + if ( a->a_numvals == 1 && n->f_choice == SLAPD_FILTER_COMPUTED ) { + dnf = n; + } else { + orf = n; + if ( n->f_choice != LDAP_FILTER_OR ) { + orf->f_choice = LDAP_FILTER_OR; + orf->f_list = NULL; + } + dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + dnf->f_next = orf->f_list; + orf->f_list = dnf; + } + + for (i=0; i<a->a_numvals; i++) { + if ( i ) { + dnf = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + dnf->f_next = orf->f_list; + orf->f_list = dnf; + } + dnf->f_choice = LDAP_FILTER_EQUALITY; + dnf->f_ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx ); + dnf->f_av_desc = slap_schema.si_ad_entryDN; + ber_dupbv_x( &dnf->f_av_value, &a->a_nvals[i], op->o_tmpmemctx ); + } + return 0; +} + +/* replace a filter clause (memberOf=<groupDN>) with an expansion of + * its members. + */ +static int +dynlist_filter_group( Operation *op, dynlist_name_t *dyn, Filter *n, dynlist_search_t *ds ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Entry *e; + Attribute *a; + int rc = -1; + + if ( ldap_tavl_insert( &ds->ds_fnodes, dyn, dynlist_ptr_cmp, ldap_avl_dup_error )) + return 0; + + if ( overlay_entry_get_ov( op, &dyn->dy_name, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + return -1; + } + if ( ds->ds_dlm->dlm_static_oc && is_entry_objectclass( e, ds->ds_dlm->dlm_static_oc, 0 )) { + a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad ); + if ( a ) { + rc = dynlist_filter_stgroup( op, n, a ); + } + } else { + a = attr_find( e->e_attrs, ds->ds_dli->dli_ad ); + if ( a ) { + rc = dynlist_filter_dyngroup( op, n, a ); + } + } + overlay_entry_release_ov( op, e, 0, on ); + if ( dyn->dy_subs && !rc ) { + TAvlnode *ptr; + for ( ptr = ldap_tavl_end( dyn->dy_subs, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + rc = dynlist_filter_group( op, dyn, n, ds ); + if ( rc ) + break; + } + } + return rc; +} + +/* Dup the filter, replacing any references to given ad with group evaluation */ +static Filter * +dynlist_filter_dup( Operation *op, Filter *f, AttributeDescription *ad, dynlist_search_t *ds ) +{ + Filter *n = NULL; + + if ( !f ) + return NULL; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_next = NULL; + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + n->f_choice = f->f_choice; + n->f_result = f->f_result; + break; + + case LDAP_FILTER_PRESENT: + n->f_choice = f->f_choice; + n->f_desc = f->f_desc; + break; + + case LDAP_FILTER_EQUALITY: + if ( f->f_av_desc == ad ) { + dynlist_name_t *dyn = ldap_tavl_find( ds->ds_names, &f->f_av_value, dynlist_avl_cmp ); + n->f_choice = SLAPD_FILTER_COMPUTED; + if ( dyn && !dynlist_filter_group( op, dyn, n, ds )) + break; + } + /* FALLTHRU */ + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + n->f_choice = f->f_choice; + n->f_ava = f->f_ava; + break; + + case LDAP_FILTER_SUBSTRINGS: + n->f_choice = f->f_choice; + n->f_sub = f->f_sub; + break; + + case LDAP_FILTER_EXT: + n->f_choice = f->f_choice; + n->f_mra = f->f_mra; + break; + + case LDAP_FILTER_NOT: + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: { + Filter **p; + + n->f_choice = f->f_choice; + + for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) { + *p = dynlist_filter_dup( op, f, ad, ds ); + if ( !*p ) + continue; + p = &(*p)->f_next; + } + } + break; + } + return n; +} + +static void +dynlist_filter_free( Operation *op, Filter *f ) +{ + Filter *p, *next; + + if ( f == NULL ) + return; + + f->f_choice &= SLAPD_FILTER_MASK; + switch( f->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + for ( p = f->f_list; p; p = next ) { + next = p->f_next; + op->o_tmpfree( p, op->o_tmpmemctx ); + } + break; + default: + op->o_tmpfree( f, op->o_tmpmemctx ); + } +} + +static void +dynlist_search_free( void *ptr ) +{ + dynlist_name_t *dyn = (dynlist_name_t *)ptr; + LDAPURLDesc *ludp; + int i; + + for (i=dyn->dy_numuris-1; i>=0; i--) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_filter ) { + filter_free( (Filter *)ludp->lud_filter ); + ludp->lud_filter = NULL; + } + ldap_free_urldesc( ludp ); + } + if ( dyn->dy_subs ) + ldap_tavl_free( dyn->dy_subs, NULL ); + if ( dyn->dy_sups ) + ldap_tavl_free( dyn->dy_sups, NULL ); + ch_free( ptr ); +} + +static int +dynlist_search_cleanup( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_RESULT || op->o_abandon || + rs->sr_err == SLAPD_ABANDON ) { + slap_callback *sc = op->o_callback; + dynlist_search_t *ds = op->o_callback->sc_private; + ldap_tavl_free( ds->ds_names, dynlist_search_free ); + if ( ds->ds_fnodes ) + ldap_tavl_free( ds->ds_fnodes, NULL ); + if ( ds->ds_origfilter ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + dynlist_filter_free( op, op->ors_filter ); + op->ors_filter = ds->ds_origfilter; + op->ors_filterstr = ds->ds_origfilterbv; + } + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + } + return 0; +} + +static int +dynlist_test_membership(Operation *op, dynlist_name_t *dyn, Entry *e) +{ + LDAPURLDesc *ludp; + struct berval nbase, bv; + int i, rc = LDAP_COMPARE_FALSE; + if ( dyn->dy_staticmember ) { + Entry *grp; + if ( overlay_entry_get_ov( op, &dyn->dy_name, NULL, NULL, 0, &grp, (slap_overinst *)op->o_bd->bd_info ) == LDAP_SUCCESS && grp ) { + Attribute *a = attr_find( grp->e_attrs, dyn->dy_staticmember ); + if ( a ) { + i = value_find_ex( dyn->dy_staticmember, SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, a->a_nvals, &e->e_nname, op->o_tmpmemctx ); + } + overlay_entry_release_ov( op, grp, 0, (slap_overinst *)op->o_bd->bd_info ); + return i == LDAP_SUCCESS ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + } + } + for (i=0; i<dyn->dy_numuris; i++) { + ludp = dyn->dy_uris[i]; + nbase.bv_val = ludp->lud_dn; + nbase.bv_len = ludp->lud_port; + if ( ludp->lud_attrs ) + continue; + switch( ludp->lud_scope ) { + case LDAP_SCOPE_BASE: + if ( !dn_match( &nbase, &e->e_nname )) + continue; + break; + case LDAP_SCOPE_ONELEVEL: + dnParent( &e->e_nname, &bv ); + if ( !dn_match( &nbase, &bv )) + continue; + break; + case LDAP_SCOPE_SUBTREE: + if ( !dnIsSuffix( &e->e_nname, &nbase )) + continue; + break; + case LDAP_SCOPE_SUBORDINATE: + if ( dn_match( &nbase, &e->e_nname ) || + !dnIsSuffix( &e->e_nname, &nbase )) + continue; + break; + } + if ( !ludp->lud_filter ) /* there really should always be a filter */ + rc = LDAP_COMPARE_TRUE; + else + rc = test_filter( op, e, (Filter *)ludp->lud_filter ); + if ( rc == LDAP_COMPARE_TRUE ) + break; + } + return rc; +} + +static void +dynlist_add_memberOf(Operation *op, SlapReply *rs, dynlist_search_t *ds) +{ + TAvlnode *ptr; + Entry *e = rs->sr_entry; + dynlist_name_t *dyn; + Attribute *a; + + /* See if there are any memberOf values to attach to this entry */ + for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dynlist_map_t *dlm; + dyn = ptr->avl_data; + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + if ( dynlist_test_membership( op, dyn, e ) == LDAP_COMPARE_TRUE ) { + /* ensure e is modifiable, but do not replace + * sr_entry yet since we have pointers into it */ + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) && e == rs->sr_entry ) { + e = entry_dup( rs->sr_entry ); + } + a = attr_find( e->e_attrs, dlm->dlm_memberOf_ad ); + if ( a ) { + unsigned slot; + if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &dyn->dy_name, &slot, NULL ) != LDAP_SUCCESS ) + a = NULL; + } + if ( !a ) + attr_merge_one( e, dlm->dlm_memberOf_ad, &dyn->dy_name, &dyn->dy_name ); + if ( dyn->dy_sups ) { + dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups ); + } + break; + } + } + } + } + if ( e != rs->sr_entry ) { + rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } +} + +/* process the search responses */ +static int +dynlist_search2resp( Operation *op, SlapReply *rs ) +{ + dynlist_search_t *ds = op->o_callback->sc_private; + dynlist_name_t *dyn; + int rc; + + if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) { + rc = SLAP_CB_CONTINUE; + /* See if this is one of our dynamic entries */ + dyn = ldap_tavl_find( ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp ); + if ( dyn ) { + dyn->dy_seen = 1; + rc = dynlist_prepare_entry( op, rs, dyn->dy_dli, dyn ); + } else if ( ds->ds_want ) + dynlist_add_memberOf( op, rs, ds ); + if ( ds->ds_origfilter && test_filter( op, rs->sr_entry, ds->ds_origfilter ) != LDAP_COMPARE_TRUE ) { + rs_flush_entry( op, rs, NULL ); + return LDAP_SUCCESS; + } + return rc; + } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) { + TAvlnode *ptr; + SlapReply r = *rs; + Filter *f = ds->ds_origfilter ? ds->ds_origfilter : op->ors_filter; + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) + return SLAP_CB_CONTINUE; + + /* Check for any unexpanded dynamic group entries that weren't picked up + * by the original search filter. + */ + for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + if ( dyn->dy_seen ) + continue; + if ( !dnIsSuffixScope( &dyn->dy_name, &op->o_req_ndn, op->ors_scope )) + continue; + if ( overlay_entry_get_ov( op, &dyn->dy_name, NULL, NULL, 0, &r.sr_entry, (slap_overinst *)op->o_bd->bd_info ) != LDAP_SUCCESS || + r.sr_entry == NULL ) + continue; + r.sr_flags = REP_ENTRY_MUSTRELEASE; + dynlist_prepare_entry( op, &r, dyn->dy_dli, dyn ); + if ( test_filter( op, r.sr_entry, f ) == LDAP_COMPARE_TRUE ) { + r.sr_attrs = op->ors_attrs; + rs->sr_err = send_search_entry( op, &r ); + if ( rs->sr_err != LDAP_SUCCESS ) + break; + } else { + rs_flush_entry( op, &r, NULL ); + } + } + rs->sr_nentries = r.sr_nentries; + } + return SLAP_CB_CONTINUE; +} + +static void +dynlist_fix_filter( Operation *op, AttributeDescription *ad, dynlist_search_t *ds ) +{ + Filter *f; + f = dynlist_filter_dup( op, op->ors_filter, ad, ds ); + if ( ds->ds_origfilter ) { + dynlist_filter_free( op, op->ors_filter ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } else { + ds->ds_origfilter = op->ors_filter; + ds->ds_origfilterbv = op->ors_filterstr; + } + op->ors_filter = f; + filter2bv_x( op, f, &op->ors_filterstr ); +} + +typedef struct dynlist_link_t { + dynlist_search_t *dl_ds; + dynlist_name_t *dl_sup; +} dynlist_link_t; + +static int +dynlist_nestlink_dg( Operation *op, SlapReply *rs ) +{ + dynlist_link_t *dll = op->o_callback->sc_private; + dynlist_search_t *ds = dll->dl_ds; + dynlist_name_t *di = dll->dl_sup, *dj; + + if ( rs->sr_type != REP_SEARCH ) + return LDAP_SUCCESS; + + dj = ldap_tavl_find( dll->dl_ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp ); + if ( dj ) { + if ( ds->ds_want & WANT_MEMBEROF ) { + ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + if ( ds->ds_want & WANT_MEMBER ) { + ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + } + return LDAP_SUCCESS; +} + +/* Connect all nested groups to their parents/children */ +static void +dynlist_nestlink( Operation *op, dynlist_search_t *ds ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_name_t *di, *dj; + TAvlnode *ptr; + Entry *e; + Attribute *a; + int i; + + for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + di = ptr->avl_data; + if ( ds->ds_dlm ) { + if ( overlay_entry_get_ov( op, &di->dy_name, NULL, NULL, 0, &e, on ) != LDAP_SUCCESS || e == NULL ) + continue; + a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad ); + if ( a ) { + for ( i=0; i < a->a_numvals; i++ ) { + dj = ldap_tavl_find( ds->ds_names, &a->a_nvals[i], dynlist_avl_cmp ); + if ( dj ) { + if ( ds->ds_want & WANT_MEMBEROF ) { + ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + if ( ds->ds_want & WANT_MEMBER ) { + ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + } + } + } + overlay_entry_release_ov( op, e, 0, on ); + } + + if ( di->dy_numuris ) { + slap_callback cb = { 0 }; + dynlist_link_t dll; + dll.dl_ds = ds; + dll.dl_sup = di; + cb.sc_private = &dll; + cb.sc_response = dynlist_nestlink_dg; + dynlist_urlmembers( op, di, &cb ); + } + } +} + +static int +dynlist_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli; + Operation o = *op; + dynlist_map_t *dlm; + Filter f[3]; + AttributeAssertion ava[2]; + AttributeName an[2] = {0}; + + slap_callback *sc; + dynlist_search_t *ds; + ObjectClass *static_oc; + int nested, found, tmpwant; + int opattrs, userattrs; + + if ( get_manageDSAit( op ) ) + return SLAP_CB_CONTINUE; + + sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(dynlist_search_t), op->o_tmpmemctx ); + sc->sc_private = (void *)(sc+1); + ds = sc->sc_private; + + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + + /* Are we using memberOf, and does it affect this request? */ + if ( dlg->dlg_memberOf ) { + int attrflags = slap_attr_flags( op->ors_attrs ); + opattrs = SLAP_OPATTRS( attrflags ); + userattrs = SLAP_USERATTRS( attrflags ); + } + + /* Find all groups in scope. For group expansion + * we only need the groups within the search scope, but + * for memberOf populating, we need all dyngroups. + */ + for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) { + static_oc = NULL; + nested = 0; + tmpwant = 0; + if ( dlg->dlg_memberOf ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + int want = 0; + + /* is attribute in filter? */ + if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) { + want |= WANT_MEMBEROF; + /* with nesting, filter attributes also require nestlink */ + if ( dlm->dlm_memberOf_nested ) { + /* WANT_ flags have inverted meaning here: + * to satisfy (memberOf=) filter, we need to also + * find all subordinate groups. No special + * treatment is needed for (member=) since we + * already search all group entries. + */ + want |= WANT_MEMBER; + } + } + + /* if attribute is not requested, skip it */ + if ( op->ors_attrs == NULL ) { + if ( !dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } else { + if ( ad_inlist( dlm->dlm_memberOf_ad, op->ors_attrs )) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && ad_inlist( dlm->dlm_member_ad, op->ors_attrs )) + want |= WANT_MEMBER; + } else { + if ( opattrs ) { + if ( dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } + if ( userattrs ) { + if ( !dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } + } + } + if ( want ) { + nested = dlm->dlm_memberOf_nested; + ds->ds_want = tmpwant = want; + if ( dlm->dlm_static_oc ) { + static_oc = dlm->dlm_static_oc; + ds->ds_dlm = dlm; + } + } + } + } + } + + if ( static_oc ) { + f[0].f_choice = LDAP_FILTER_OR; + f[0].f_list = &f[1]; + f[0].f_next = NULL; + f[1].f_choice = LDAP_FILTER_EQUALITY; + f[1].f_next = &f[2]; + f[1].f_ava = &ava[0]; + f[1].f_av_desc = slap_schema.si_ad_objectClass; + f[1].f_av_value = dli->dli_oc->soc_cname; + f[2].f_choice = LDAP_FILTER_EQUALITY; + f[2].f_ava = &ava[1]; + f[2].f_av_desc = slap_schema.si_ad_objectClass; + f[2].f_av_value = static_oc->soc_cname; + f[2].f_next = NULL; + } else { + f[0].f_choice = LDAP_FILTER_EQUALITY; + f[0].f_ava = ava; + f[0].f_av_desc = slap_schema.si_ad_objectClass; + f[0].f_av_value = dli->dli_oc->soc_cname; + f[0].f_next = NULL; + } + + if ( o.o_callback != sc ) { + o.o_callback = sc; + o.ors_filter = f; + if ( tmpwant ) { + o.o_req_dn = op->o_bd->be_suffix[0]; + o.o_req_ndn = op->o_bd->be_nsuffix[0]; + o.ors_scope = LDAP_SCOPE_SUBTREE; + } else { + o.o_req_dn = op->o_req_dn; + o.o_req_ndn = op->o_req_ndn; + o.ors_scope = op->ors_scope; + } + o.ors_attrsonly = 0; + o.ors_attrs = an; + o.o_bd = select_backend( op->o_bd->be_nsuffix, 1 ); + BER_BVZERO( &o.ors_filterstr ); + sc->sc_response = dynlist_search1resp; + } + + ds->ds_dli = dli; + if ( o.ors_filterstr.bv_val ) + o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx ); + filter2bv_x( &o, f, &o.ors_filterstr ); + an[0].an_desc = dli->dli_ad; + an[0].an_name = dli->dli_ad->ad_cname; + found = ds->ds_found; + { + SlapReply r = { REP_SEARCH }; + (void)o.o_bd->be_search( &o, &r ); + } + if ( found != ds->ds_found && nested ) + dynlist_nestlink( op, ds ); + } + + if ( ds->ds_names != NULL ) { + sc->sc_response = dynlist_search2resp; + sc->sc_cleanup = dynlist_search_cleanup; + sc->sc_next = op->o_callback; + op->o_callback = sc; + + /* see if filter needs fixing */ + if ( dlg->dlg_memberOf ) { + for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + + /* if attribute is in filter, fix it */ + if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) { + ds->ds_dli = dli; + ds->ds_dlm = dlm; + dynlist_fix_filter( op, dlm->dlm_memberOf_ad, ds ); + } + } + } + } + } + + } else { + op->o_tmpfree( sc, op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; +} + +static int +dynlist_build_def_filter( dynlist_info_t *dli ) +{ + char *ptr; + + dli->dli_default_filter.bv_len = STRLENOF( "(!(objectClass=" "))" ) + + dli->dli_oc->soc_cname.bv_len; + dli->dli_default_filter.bv_val = ch_malloc( dli->dli_default_filter.bv_len + 1 ); + if ( dli->dli_default_filter.bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: malloc failed.\n" ); + return -1; + } + + ptr = lutil_strcopy( dli->dli_default_filter.bv_val, "(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + + assert( ptr == &dli->dli_default_filter.bv_val[dli->dli_default_filter.bv_len] ); + + return 0; +} + +enum { + DL_ATTRSET = 1, + DL_ATTRPAIR, + DL_ATTRPAIR_COMPAT, + DL_LAST +}; + +static ConfigDriver dl_cfgen; + +/* XXXmanu 255 is the maximum arguments we allow. Can we go beyond? */ +static ConfigTable dlcfg[] = { + { "dynlist-attrset", "group-oc> [uri] <URL-ad> <[mapped:]member-ad> [...]", + 3, 0, 0, ARG_MAGIC|DL_ATTRSET, dl_cfgen, + "( OLcfgOvAt:8.1 NAME ( 'olcDynListAttrSet' 'olcDlAttrSet' ) " + "DESC 'Dynamic list: <group objectClass>, <URL attributeDescription>, <member attributeDescription>' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "dynlist-attrpair", "member-ad> <URL-ad", + 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR, dl_cfgen, + NULL, NULL, NULL }, +#ifdef TAKEOVER_DYNGROUP + { "attrpair", "member-ad> <URL-ad", + 3, 3, 0, ARG_MAGIC|DL_ATTRPAIR_COMPAT, dl_cfgen, + NULL, NULL, NULL }, +#endif + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs dlocs[] = { + { "( OLcfgOvOc:8.1 " + "NAME ( 'olcDynListConfig' 'olcDynamicList' ) " + "DESC 'Dynamic list configuration' " + "SUP olcOverlayConfig " + "MAY olcDynListAttrSet )", + Cft_Overlay, dlcfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int +dl_cfgen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli; + + int rc = 0, i; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case DL_ATTRSET: + for ( i = 0; dli; i++, dli = dli->dli_next ) { + struct berval bv; + char *ptr = c->cr_msg; + dynlist_map_t *dlm; + + assert( dli->dli_oc != NULL ); + assert( dli->dli_ad != NULL ); + + /* FIXME: check buffer overflow! */ + ptr += snprintf( c->cr_msg, sizeof( c->cr_msg ), + SLAP_X_ORDERED_FMT "%s", i, + dli->dli_oc->soc_cname.bv_val ); + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strncopy( ptr, dli->dli_uri.bv_val, + dli->dli_uri.bv_len ); + *ptr++ = '"'; + } + + *ptr++ = ' '; + ptr = lutil_strncopy( ptr, dli->dli_ad->ad_cname.bv_val, + dli->dli_ad->ad_cname.bv_len ); + + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + ptr[ 0 ] = ' '; + ptr++; + if ( dlm->dlm_mapped_ad ) { + ptr = lutil_strcopy( ptr, dlm->dlm_mapped_ad->ad_cname.bv_val ); + ptr[ 0 ] = ':'; + ptr++; + } + + ptr = lutil_strcopy( ptr, dlm->dlm_member_ad->ad_cname.bv_val ); + + if ( dlm->dlm_memberOf_ad ) { + *ptr++ = '+'; + ptr = lutil_strcopy( ptr, dlm->dlm_memberOf_ad->ad_cname.bv_val ); + if ( dlm->dlm_static_oc ) { + *ptr++ = '@'; + ptr = lutil_strcopy( ptr, dlm->dlm_static_oc->soc_cname.bv_val ); + } + if ( dlm->dlm_memberOf_nested ) { + *ptr++ = '*'; + } + } + } + + bv.bv_val = c->cr_msg; + bv.bv_len = ptr - bv.bv_val; + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case DL_ATTRPAIR_COMPAT: + case DL_ATTRPAIR: + rc = 1; + break; + + default: + rc = 1; + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case DL_ATTRSET: + if ( c->valx < 0 ) { + dynlist_info_t *dli_next; + + for ( dli_next = dli; dli_next; dli = dli_next ) { + dynlist_map_t *dlm = dli->dli_dlm; + dynlist_map_t *dlm_next; + + dli_next = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + } + + dlg->dlg_dli = NULL; + dlg->dlg_memberOf = 0; + + } else { + dynlist_info_t **dlip; + dynlist_map_t *dlm; + dynlist_map_t *dlm_next; + + for ( i = 0, dlip = (dynlist_info_t **)&dlg->dlg_dli; + i < c->valx; i++ ) + { + if ( *dlip == NULL ) { + return 1; + } + dlip = &(*dlip)->dli_next; + } + + dli = *dlip; + *dlip = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + dlm = dli->dli_dlm; + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + if ( dlm->dlm_memberOf_ad ) + dlg->dlg_memberOf--; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + + dli = (dynlist_info_t *)dlg->dlg_dli; + } + break; + + case DL_ATTRPAIR_COMPAT: + case DL_ATTRPAIR: + rc = 1; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + switch( c->type ) { + case DL_ATTRSET: { + dynlist_info_t **dlip, + *dli_next = NULL; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + int attridx = 2; + LDAPURLDesc *lud = NULL; + struct berval nbase = BER_BVNULL; + Filter *filter = NULL; + struct berval uri = BER_BVNULL; + dynlist_map_t *dlm = NULL, *dlml = NULL; + const char *text; + + oc = oc_find( c->argv[ 1 ] ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to find ObjectClass \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + if ( strncasecmp( c->argv[ attridx ], "ldap://", STRLENOF("ldap://") ) == 0 ) { + if ( ldap_url_parse( c->argv[ attridx ], &lud ) != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to parse URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_host != NULL ) { + if ( lud->lud_host[0] == '\0' ) { + ch_free( lud->lud_host ); + lud->lud_host = NULL; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "host not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + } + + if ( lud->lud_attrs != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "attrs not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_exts != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "extensions not allowed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + + if ( lud->lud_dn != NULL && lud->lud_dn[ 0 ] != '\0' ) { + struct berval dn; + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &nbase, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "DN normalization failed in URI \"%s\"", + c->argv[ attridx ] ); + goto done_uri; + } + } + + if ( lud->lud_filter != NULL && lud->lud_filter[ 0 ] != '\0' ) { + filter = str2filter( lud->lud_filter ); + if ( filter == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "filter parsing failed in URI \"%s\"", + c->argv[ attridx ] ); + rc = 1; + goto done_uri; + } + } + + ber_str2bv( c->argv[ attridx ], 0, 1, &uri ); + +done_uri:; + if ( rc ) { + if ( lud ) { + ldap_free_urldesc( lud ); + } + + if ( !BER_BVISNULL( &nbase ) ) { + ber_memfree( nbase.bv_val ); + } + + if ( filter != NULL ) { + filter_free( filter ); + } + + while ( dlm != NULL ) { + dlml = dlm; + dlm = dlm->dlm_next; + ch_free( dlml ); + } + + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + + return rc; + } + + attridx++; + } + + rc = slap_str2ad( c->argv[ attridx ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "unable to find AttributeDescription \"%s\"", + c->argv[ attridx ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + + if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), DYNLIST_USAGE + "AttributeDescription \"%s\" " + "must be a subtype of \"labeledURI\"", + c->argv[ attridx ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + + attridx++; + + for ( i = attridx; i < c->argc; i++ ) { + char *arg; + char *cp; + AttributeDescription *member_ad = NULL; + AttributeDescription *mapped_ad = NULL; + AttributeDescription *memberOf_ad = NULL; + ObjectClass *static_oc = NULL; + int nested = 0; + dynlist_map_t *dlmp; + + + /* + * If no mapped attribute is given, dn is used + * for backward compatibility. + */ + arg = c->argv[i]; + if ( ( cp = strchr( arg, ':' ) ) != NULL ) { + struct berval bv; + ber_str2bv( arg, cp - arg, 0, &bv ); + rc = slap_bv2ad( &bv, &mapped_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find mapped AttributeDescription #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + arg = cp + 1; + } + if ( ( cp = strchr( arg, '+' ) ) != NULL ) { + struct berval bv; + char *ocp, *np; + np = strrchr( cp+1, '*' ); + if ( np ) { + nested = 1; + *np = '\0'; + } + ocp = strchr( cp+1, '@' ); + if ( ocp ) { + static_oc = oc_find( ocp+1 ); + if ( !static_oc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find static-oc ObjectClass #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + *ocp = '\0'; + } + ber_str2bv( cp+1, 0, 0, &bv ); + rc = slap_bv2ad( &bv, &memberOf_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find memberOf AttributeDescription #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + dlg->dlg_memberOf++; + *cp = '\0'; + } + + rc = slap_str2ad( arg, &member_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "unable to find AttributeDescription #%d \"%s\"\n", + i - 3, c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + + dlmp = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) ); + if ( dlm == NULL ) { + dlm = dlmp; + } + dlmp->dlm_member_ad = member_ad; + dlmp->dlm_mapped_ad = mapped_ad; + dlmp->dlm_memberOf_ad = memberOf_ad; + dlmp->dlm_static_oc = static_oc; + dlmp->dlm_memberOf_nested = nested; + dlmp->dlm_member_oper = is_at_operational( member_ad->ad_type ); + if ( memberOf_ad ) { + dlmp->dlm_memberOf_oper = is_at_operational( memberOf_ad->ad_type ); + } else { + dlmp->dlm_memberOf_oper = 0; + } + dlmp->dlm_next = NULL; + + if ( dlml != NULL ) + dlml->dlm_next = dlmp; + dlml = dlmp; + } + + if ( c->valx > 0 ) { + int i; + + for ( i = 0, dlip = (dynlist_info_t **)&dlg->dlg_dli; + i < c->valx; i++ ) + { + if ( *dlip == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "invalid index {%d}\n", + c->valx ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + rc = 1; + goto done_uri; + } + dlip = &(*dlip)->dli_next; + } + dli_next = *dlip; + + } else { + for ( dlip = (dynlist_info_t **)&dlg->dlg_dli; + *dlip; dlip = &(*dlip)->dli_next ) + /* goto last */; + } + + *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) ); + + (*dlip)->dli_oc = oc; + (*dlip)->dli_ad = ad; + (*dlip)->dli_dlm = dlm; + (*dlip)->dli_next = dli_next; + + (*dlip)->dli_lud = lud; + (*dlip)->dli_uri_nbase = nbase; + (*dlip)->dli_uri_filter = filter; + (*dlip)->dli_uri = uri; + + rc = dynlist_build_def_filter( *dlip ); + + } break; + + case DL_ATTRPAIR_COMPAT: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning: \"attrpair\" only supported for limited " + "backward compatibility with overlay \"dyngroup\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + /* fallthru */ + + case DL_ATTRPAIR: { + dynlist_info_t **dlip; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL, + *member_ad = NULL; + const char *text; + + oc = oc_find( "groupOfURLs" ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find default ObjectClass \"groupOfURLs\"" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + rc = slap_str2ad( c->argv[ 1 ], &member_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find AttributeDescription \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + rc = slap_str2ad( c->argv[ 2 ], &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "unable to find AttributeDescription \"%s\"\n", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + if ( !is_at_subtype( ad->ad_type, slap_schema.si_ad_labeledURI->ad_type ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + DYNLIST_USAGE + "AttributeDescription \"%s\" " + "must be a subtype of \"labeledURI\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + for ( dlip = (dynlist_info_t **)&dlg->dlg_dli; + *dlip; dlip = &(*dlip)->dli_next ) + { + /* + * The same URL attribute / member attribute pair + * cannot be repeated, but we enforce this only + * when the member attribute is unique. Performing + * the check for multiple values would require + * sorting and comparing the lists, which is left + * as a future improvement + */ + if ( (*dlip)->dli_ad == ad && + (*dlip)->dli_dlm->dlm_next == NULL && + member_ad == (*dlip)->dli_dlm->dlm_member_ad ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"dynlist-attrpair <member-ad> <URL-ad>\": " + "URL attributeDescription \"%s\" already mapped.\n", + ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); +#if 0 + /* make it a warning... */ + return 1; +#endif + } + } + + *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) ); + + (*dlip)->dli_oc = oc; + (*dlip)->dli_ad = ad; + (*dlip)->dli_dlm = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) ); + (*dlip)->dli_dlm->dlm_member_ad = member_ad; + (*dlip)->dli_dlm->dlm_mapped_ad = NULL; + + rc = dynlist_build_def_filter( *dlip ); + + } break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dynlist_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dynlist_gen_t *dlg; + + dlg = (dynlist_gen_t *)ch_malloc( sizeof( *dlg )); + on->on_bi.bi_private = dlg; + dlg->dlg_dli = NULL; + dlg->dlg_memberOf = 0; + + return 0; +} + +static int +dynlist_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + const char *text; + int rc; + + if ( dli == NULL ) { + dli = ch_calloc( 1, sizeof( dynlist_info_t ) ); + dlg->dlg_dli = dli; + } + + for ( ; dli; dli = dli->dli_next ) { + if ( dli->dli_oc == NULL ) { + if ( oc == NULL ) { + oc = oc_find( "groupOfURLs" ); + if ( oc == NULL ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch objectClass \"groupOfURLs\"" ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg ); + return 1; + } + } + + dli->dli_oc = oc; + } + + if ( dli->dli_ad == NULL ) { + if ( ad == NULL ) { + rc = slap_str2ad( "memberURL", &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"memberURL\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg ); + return 1; + } + } + + dli->dli_ad = ad; + } + + if ( BER_BVISNULL( &dli->dli_default_filter ) ) { + rc = dynlist_build_def_filter( dli ); + if ( rc != 0 ) { + return rc; + } + } + } + + if ( ad_dgIdentity == NULL ) { + rc = slap_str2ad( "dgIdentity", &ad_dgIdentity, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgIdentity\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg ); + /* Just a warning */ + } + } + + if ( ad_dgAuthz == NULL ) { + rc = slap_str2ad( "dgAuthz", &ad_dgAuthz, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgAuthz\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg ); + /* Just a warning */ + } + } + + return 0; +} + +static int +dynlist_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + + if ( on->on_bi.bi_private ) { + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli, + *dli_next; + + for ( dli_next = dli; dli_next; dli = dli_next ) { + dynlist_map_t *dlm; + dynlist_map_t *dlm_next; + + dli_next = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + dlm = dli->dli_dlm; + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + } + ch_free( dlg ); + } + + return 0; +} + +static slap_overinst dynlist = { { NULL } }; +#ifdef TAKEOVER_DYNGROUP +static char *obsolete_names[] = { + "dyngroup", + NULL +}; +#endif + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC */ +int +dynlist_initialize(void) +{ + const char *text; + int rc = 0; + + /* See if we need to define memberOf opattr */ + rc = slap_str2ad( "memberOf", &ad_memberOf, &text ); + if ( rc ) { + rc = register_at( + "( 1.2.840.113556.1.2.102 " + "NAME 'memberOf' " + "DESC 'Group that the entry belongs to' " + "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' " + "EQUALITY distinguishedNameMatch " /* added */ + "USAGE dSAOperation " /* added; questioned */ + "NO-USER-MODIFICATION " /* added */ + "X-ORIGIN 'iPlanet Delegated Administrator' )", + &ad_memberOf, 0 ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "dynlist_initialize: register_at (memberOf) failed\n" ); + return rc; + } + } + + dynlist.on_bi.bi_type = "dynlist"; + +#ifdef TAKEOVER_DYNGROUP + /* makes dynlist incompatible with dyngroup */ + dynlist.on_bi.bi_obsolete_names = obsolete_names; +#endif + + dynlist.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + dynlist.on_bi.bi_db_init = dynlist_db_init; + dynlist.on_bi.bi_db_config = config_generic_wrapper; + dynlist.on_bi.bi_db_open = dynlist_db_open; + dynlist.on_bi.bi_db_destroy = dynlist_db_destroy; + + dynlist.on_bi.bi_op_search = dynlist_search; + dynlist.on_bi.bi_op_compare = dynlist_compare; + + dynlist.on_bi.bi_cf_ocs = dlocs; + + rc = config_register_schema( dlcfg, dlocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &dynlist ); +} + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dynlist_initialize(); +} +#endif + +#endif /* SLAPD_OVER_DYNLIST */ diff --git a/servers/slapd/overlays/homedir.c b/servers/slapd/overlays/homedir.c new file mode 100644 index 0000000..159090e --- /dev/null +++ b/servers/slapd/overlays/homedir.c @@ -0,0 +1,2074 @@ +/* homedir.c - create/remove user home directories */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2009-2022 The OpenLDAP Foundation. + * Portions copyright 2009-2010 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Emily Backes at Symas + * Corp. for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_HOMEDIR + +#define _FILE_OFFSET_BITS 64 + +#include <stdio.h> +#include <fcntl.h> + +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/errno.h> +#include <sys/stat.h> +#include <ac/unistd.h> +#include <ac/dirent.h> +#include <ac/time.h> + +#include "slap.h" +#include "slap-config.h" + +#define DEFAULT_MIN_UID ( 100 ) +#define DEFAULT_SKEL ( LDAP_DIRSEP "etc" LDAP_DIRSEP "skel" ) + +typedef struct homedir_regexp { + char *match; + char *replace; + regex_t compiled; + struct homedir_regexp *next; +} homedir_regexp; + +typedef enum { + DEL_IGNORE, + DEL_DELETE, + DEL_ARCHIVE +} delete_style; + +typedef struct homedir_data { + char *skeleton_path; + unsigned min_uid; + AttributeDescription *home_ad; + AttributeDescription *uidn_ad; + AttributeDescription *gidn_ad; + homedir_regexp *regexps; + delete_style style; + char *archive_path; +} homedir_data; + +typedef struct homedir_cb_data { + slap_overinst *on; + Entry *entry; +} homedir_cb_data; + +typedef struct name_list { + char *name; + struct stat st; + struct name_list *next; +} name_list; + +typedef struct name_list_list { + name_list *list; + struct name_list_list *next; +} name_list_list; + +typedef enum { + TRAVERSE_CB_CONTINUE, + TRAVERSE_CB_DONE, + TRAVERSE_CB_FAIL +} traverse_cb_ret; + +/* private, file info, context */ +typedef traverse_cb_ret (*traverse_cb_func)( + void *, + const char *, + const struct stat *, + void * ); +typedef struct traverse_cb { + traverse_cb_func pre_func; + traverse_cb_func post_func; + void *pre_private; + void *post_private; +} traverse_cb; + +typedef struct copy_private { + int source_prefix_len; + const char *dest_prefix; + int dest_prefix_len; + uid_t uidn; + gid_t gidn; +} copy_private; + +typedef struct chown_private { + uid_t old_uidn; + uid_t new_uidn; + gid_t old_gidn; + gid_t new_gidn; +} chown_private; + +typedef struct ustar_header { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char checksum[8]; + char typeflag[1]; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +} ustar_header; + +typedef struct tar_private { + FILE *file; + const char *name; +} tar_private; + +/* FIXME: This mutex really needs to be executable-global, but this + * will have to do for now. + */ +static ldap_pvt_thread_mutex_t readdir_mutex; +static ConfigDriver homedir_regexp_cfg; +static ConfigDriver homedir_style_cfg; +static slap_overinst homedir; + +static ConfigTable homedircfg[] = { + { "homedir-skeleton-path", "pathname", 2, 2, 0, + ARG_STRING|ARG_OFFSET, + (void *)offsetof(homedir_data, skeleton_path), + "( OLcfgCtAt:8.1 " + "NAME 'olcSkeletonPath' " + "DESC 'Pathname for home directory skeleton template' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, { .v_string = DEFAULT_SKEL } + }, + + { "homedir-min-uidnumber", "uid number", 2, 2, 0, + ARG_UINT|ARG_OFFSET, + (void *)offsetof(homedir_data, min_uid), + "( OLcfgCtAt:8.2 " + "NAME 'olcMinimumUidNumber' " + "DESC 'Minimum uidNumber attribute to consider' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, { .v_uint = DEFAULT_MIN_UID } + }, + + { "homedir-regexp", "regexp> <path", 3, 3, 0, + ARG_MAGIC, + homedir_regexp_cfg, + "( OLcfgCtAt:8.3 " + "NAME 'olcHomedirRegexp' " + "DESC 'Regular expression for matching and transforming paths' " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL + }, + + { "homedir-delete-style", "style", 2, 2, 0, + ARG_MAGIC, + homedir_style_cfg, + "( OLcfgCtAt:8.4 " + "NAME 'olcHomedirDeleteStyle' " + "DESC 'Action to perform when removing a home directory' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + + { "homedir-archive-path", "pathname", 2, 2, 0, + ARG_STRING|ARG_OFFSET, + (void *)offsetof(homedir_data, archive_path), + "( OLcfgCtAt:8.5 " + "NAME 'olcHomedirArchivePath' " + "DESC 'Pathname for home directory archival' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL + }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs homedirocs[] = { + { "( OLcfgCtOc:8.1 " + "NAME 'olcHomedirConfig' " + "DESC 'Homedir configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSkeletonPath $ olcMinimumUidNumber " + "$ olcHomedirRegexp $ olcHomedirDeleteStyle " + "$ olcHomedirArchivePath ) )", + Cft_Overlay, homedircfg }, + + { NULL, 0, NULL } +}; + +static int +homedir_regexp_cfg( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + homedir_data *data = (homedir_data *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + assert( data != NULL ); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: { + int i; + homedir_regexp *r; + struct berval bv; + char buf[4096]; + + bv.bv_val = buf; + for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) { + bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i, + r->match, r->replace ); + if ( bv.bv_len >= sizeof(buf) ) { + Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: " + "emit serialization failed: size %lu\n", + (unsigned long)bv.bv_len ); + return ARG_BAD_CONF; + } + value_add_one( &c->rvalue_vals, &bv ); + } + rc = 0; + } break; + + case LDAP_MOD_DELETE: + if ( c->valx < 0 ) { /* delete all values */ + homedir_regexp *r, *rnext; + + for ( r = data->regexps; r != NULL; r = rnext ) { + rnext = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + } + data->regexps = NULL; + rc = 0; + + } else { /* delete value by index*/ + homedir_regexp **rp, *r; + int i; + + for ( i = 0, rp = &data->regexps; i < c->valx; + ++i, rp = &(*rp)->next ) + ; + + r = *rp; + *rp = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + + rc = 0; + } + break; + + case LDAP_MOD_ADD: /* fallthrough */ + case SLAP_CONFIG_ADD: { /* add values */ + char *match = c->argv[1]; + char *replace = c->argv[2]; + regex_t compiled; + homedir_regexp **rp, *r; + + memset( &compiled, 0, sizeof(compiled) ); + rc = regcomp( &compiled, match, REG_EXTENDED ); + if ( rc ) { + regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) ); + regfree( &compiled ); + return ARG_BAD_CONF; + } + + r = ch_calloc( 1, sizeof(homedir_regexp) ); + r->match = strdup( match ); + r->replace = strdup( replace ); + r->compiled = compiled; + + if ( c->valx == -1 ) { /* append */ + for ( rp = &data->regexps; ( *rp ) != NULL; + rp = &(*rp)->next ) + ; + *rp = r; + + } else { /* insert at valx */ + int i; + for ( i = 0, rp = &data->regexps; i < c->valx; + rp = &(*rp)->next, ++i ) + ; + r->next = *rp; + *rp = r; + } + rc = 0; + break; + } + default: + abort(); + } + + return rc; +} + +static int +homedir_style_cfg( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + homedir_data *data = (homedir_data *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + struct berval bv; + + assert( data != NULL ); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" : + data->style == DEL_DELETE ? "DELETE" : + "ARCHIVE"; + bv.bv_len = strlen( bv.bv_val ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc != 0 ) return ARG_BAD_CONF; + break; + + case LDAP_MOD_DELETE: + data->style = DEL_IGNORE; + rc = 0; + break; + + case LDAP_MOD_ADD: /* fallthrough */ + case SLAP_CONFIG_ADD: /* add values */ + if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 ) + data->style = DEL_IGNORE; + else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 ) + data->style = DEL_DELETE; + else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 ) + data->style = DEL_ARCHIVE; + else { + Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: " + "unrecognized style keyword\n" ); + return ARG_BAD_CONF; + } + rc = 0; + break; + + default: + abort(); + } + + return rc; +} + +#define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) ) +static void +report_errno( const char *parent_func, const char *func, const char *filename ) +{ + int save_errno = errno; + char ebuf[1024]; + + Debug( LDAP_DEBUG_ANY, "homedir: " + "%s: %s: \"%s\": %d (%s)\n", + HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func), + HOMEDIR_NULLWRAP(filename), save_errno, + AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) ); +} + +static int +copy_link( + const char *dest_file, + const char *source_file, + const struct stat *st, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + char *buf = NULL; + int rc; + + assert( dest_file != NULL ); + assert( source_file != NULL ); + assert( st != NULL ); + assert( (st->st_mode & S_IFMT) == S_IFLNK ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_link: %s to %s\n", + source_file, dest_file ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_link: %s uid %ld gid %ld\n", + dest_file, (long)uidn, (long)gidn ); + + /* calloc +1 for terminator */ + buf = ber_memcalloc_x( 1, st->st_size + 1, ctx ); + if ( buf == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "copy_link: alloc failed\n" ); + return 1; + } + rc = readlink( source_file, buf, st->st_size ); + if ( rc == -1 ) { + report_errno( "copy_link", "readlink", source_file ); + goto fail; + } + rc = symlink( buf, dest_file ); + if ( rc ) { + report_errno( "copy_link", "symlink", dest_file ); + goto fail; + } + rc = lchown( dest_file, uidn, gidn ); + if ( rc ) { + report_errno( "copy_link", "lchown", dest_file ); + goto fail; + } + goto out; + +fail: + rc = 1; + +out: + if ( buf != NULL ) ber_memfree_x( buf, ctx ); + return rc; +} + +static int +copy_blocks( + FILE *source, + FILE *dest, + const char *source_file, + const char *dest_file ) +{ + char buf[4096]; + size_t nread = 0; + int done = 0; + + while ( !done ) { + nread = fread( buf, 1, sizeof(buf), source ); + if ( nread == 0 ) { + if ( feof( source ) ) { + done = 1; + } else if ( ferror( source ) ) { + if ( source_file != NULL ) + Debug( LDAP_DEBUG_ANY, "homedir: " + "read error on %s\n", + source_file ); + goto fail; + } + } else { + size_t nwritten = 0; + nwritten = fwrite( buf, 1, nread, dest ); + if ( nwritten < nread ) { + if ( dest_file != NULL ) + Debug( LDAP_DEBUG_ANY, "homedir: " + "write error on %s\n", + dest_file ); + goto fail; + } + } + } + return 0; +fail: + return 1; +} + +static int +copy_file( + const char *dest_file, + const char *source_file, + uid_t uid, + gid_t gid, + int mode ) +{ + FILE *source = NULL; + FILE *dest = NULL; + int rc; + + assert( dest_file != NULL ); + assert( source_file != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s to %s mode 0%o\n", + source_file, dest_file, mode ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s uid %ld gid %ld\n", + dest_file, (long)uid, (long)gid ); + + source = fopen( source_file, "rb" ); + if ( source == NULL ) { + report_errno( "copy_file", "fopen", source_file ); + goto fail; + } + dest = fopen( dest_file, "wb" ); + if ( dest == NULL ) { + report_errno( "copy_file", "fopen", dest_file ); + goto fail; + } + + rc = copy_blocks( source, dest, source_file, dest_file ); + if ( rc != 0 ) goto fail; + + fclose( source ); + source = NULL; + rc = fclose( dest ); + dest = NULL; + if ( rc != 0 ) { + report_errno( "copy_file", "fclose", dest_file ); + goto fail; + } + + /* set owner/permission */ + rc = lchown( dest_file, uid, gid ); + if ( rc != 0 ) { + report_errno( "copy_file", "lchown", dest_file ); + goto fail; + } + rc = chmod( dest_file, mode ); + if ( rc != 0 ) { + report_errno( "copy_file", "chmod", dest_file ); + goto fail; + } + + rc = 0; + goto out; +fail: + rc = 1; +out: + if ( source != NULL ) fclose( source ); + if ( dest != NULL ) fclose( dest ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s to %s exit %d\n", + source_file, dest_file, rc ); + return rc; +} + +static void +free_name_list( name_list *names, void *ctx ) +{ + name_list *next; + + while ( names != NULL ) { + next = names->next; + if ( names->name != NULL ) ber_memfree_x( names->name, ctx ); + ber_memfree_x( names, ctx ); + names = next; + } +} + +static int +grab_names( const char *dir_path, name_list **names, void *ctx ) +{ + int locked = 0; + DIR *dir = NULL; + struct dirent *entry = NULL; + name_list **tail = NULL; + int dir_path_len = 0; + int rc = 0; + + assert( dir_path != NULL ); + assert( names != NULL ); + assert( *names == NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: %s\n", dir_path ); + + tail = names; + dir_path_len = strlen( dir_path ); + ldap_pvt_thread_mutex_lock( &readdir_mutex ); + locked = 1; + + dir = opendir( dir_path ); + if ( dir == NULL ) { + report_errno( "grab_names", "opendir", dir_path ); + goto fail; + } + + while ( ( entry = readdir( dir ) ) != NULL ) { + /* no d_namelen in ac/dirent.h */ + int d_namelen = strlen( entry->d_name ); + int full_len; + + /* Skip . and .. */ + if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) || + ( d_namelen == 2 && entry->d_name[0] == '.' && + entry->d_name[1] == '.' ) ) { + continue; + } + + *tail = ber_memcalloc_x( 1, sizeof(**tail), ctx ); + if ( *tail == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "grab_names: list alloc failed\n" ); + goto fail; + } + (*tail)->next = NULL; + + /* +1 for dirsep, +1 for term */ + full_len = dir_path_len + 1 + d_namelen + 1; + (*tail)->name = ber_memalloc_x( full_len, ctx ); + if ( (*tail)->name == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "grab_names: name alloc failed\n" ); + goto fail; + } + snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s", + dir_path, entry->d_name ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: found \"%s\"\n", + (*tail)->name ); + + rc = lstat( (*tail)->name, &(*tail)->st ); + if ( rc ) { + report_errno( "grab_names", "lstat", (*tail)->name ); + goto fail; + } + + tail = &(*tail)->next; + } + closedir( dir ); + ldap_pvt_thread_mutex_unlock( &readdir_mutex ); + locked = 0; + + dir = NULL; + goto success; + +success: + rc = 0; + goto out; +fail: + rc = 1; + goto out; +out: + if ( dir != NULL ) closedir( dir ); + if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex ); + if ( rc != 0 && *names != NULL ) { + free_name_list( *names, ctx ); + *names = NULL; + } + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: %s exit %d\n", + dir_path, rc ); + return rc; +} + +static int +traverse( const char *path, const traverse_cb *cb, void *ctx ) +{ + name_list *next_name = NULL; + name_list_list *dir_stack = NULL; + name_list_list *next_dir; + int rc = 0; + + assert( path != NULL ); + assert( cb != NULL ); + assert( cb->pre_func || cb->post_func ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: %s\n", path ); + + dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx ); + if ( dir_stack == NULL ) goto alloc_fail; + dir_stack->next = NULL; + dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx ); + if ( dir_stack->list == NULL ) goto alloc_fail; + rc = lstat( path, &dir_stack->list->st ); + if ( rc != 0 ) { + report_errno( "traverse", "lstat", path ); + goto fail; + } + dir_stack->list->next = NULL; + dir_stack->list->name = ber_strdup_x( path, ctx ); + if ( dir_stack->list->name == NULL ) goto alloc_fail; + + while ( dir_stack != NULL ) { + while ( dir_stack->list != NULL ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: top of loop with \"%s\"\n", + dir_stack->list->name ); + + if ( cb->pre_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name, + &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) { + /* push dir onto stack */ + next_dir = dir_stack; + dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx ); + if ( dir_stack == NULL ) { + dir_stack = next_dir; + goto alloc_fail; + } + dir_stack->list = NULL; + dir_stack->next = next_dir; + rc = grab_names( + dir_stack->next->list->name, &dir_stack->list, ctx ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: grab_names %s failed\n", + dir_stack->next->list->name ); + goto fail; + } + } else { + /* just a file */ + if ( cb->post_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->post_func( cb->post_private, + dir_stack->list->name, &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + next_name = dir_stack->list->next; + ber_memfree_x( dir_stack->list->name, ctx ); + ber_memfree_x( dir_stack->list, ctx ); + dir_stack->list = next_name; + } + } + /* Time to pop a directory off the stack */ + next_dir = dir_stack->next; + ber_memfree_x( dir_stack, ctx ); + dir_stack = next_dir; + if ( dir_stack != NULL ) { + if ( cb->post_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->post_func( cb->post_private, dir_stack->list->name, + &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + next_name = dir_stack->list->next; + ber_memfree_x( dir_stack->list->name, ctx ); + ber_memfree_x( dir_stack->list, ctx ); + dir_stack->list = next_name; + } + } + + goto success; + +cb_done: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: cb signaled completion\n" ); +success: + rc = 0; + goto out; + +cb_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: cb signaled failure\n" ); + goto fail; +alloc_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: allocation failed\n" ); +fail: + rc = 1; + goto out; + +out: + while ( dir_stack != NULL ) { + free_name_list( dir_stack->list, ctx ); + next_dir = dir_stack->next; + ber_memfree_x( dir_stack, ctx ); + dir_stack = next_dir; + } + return rc; +} + +static traverse_cb_ret +traverse_copy_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + copy_private *cp = private; + char *dest_name = NULL; + int source_name_len; + int dest_name_len; + int rc; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: %s entering\n", + name ); + + assert( cp->source_prefix_len >= 0 ); + assert( cp->dest_prefix != NULL ); + assert( cp->dest_prefix_len > 1 ); + + source_name_len = strlen( name ); + assert( source_name_len >= cp->source_prefix_len ); + /* +1 for terminator */ + dest_name_len = + source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1; + dest_name = ber_memalloc_x( dest_name_len, ctx ); + if ( dest_name == NULL ) goto alloc_fail; + + snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix, + name + cp->source_prefix_len ); + + switch ( st->st_mode & S_IFMT ) { + case S_IFDIR: + rc = mkdir( dest_name, st->st_mode & 06775 ); + if ( rc ) { + int save_errno = errno; + switch ( save_errno ) { + case EEXIST: + /* directory already present; nothing to do */ + goto exists; + break; + case ENOENT: + /* FIXME: should mkdir -p here */ + /* fallthrough for now */ + default: + report_errno( "traverse_copy_pre", "mkdir", dest_name ); + goto fail; + } + } + rc = lchown( dest_name, cp->uidn, cp->gidn ); + if ( rc ) { + report_errno( "traverse_copy_pre", "lchown", dest_name ); + goto fail; + } + rc = chmod( dest_name, st->st_mode & 07777 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "chmod", dest_name ); + goto fail; + } + break; + case S_IFREG: + rc = copy_file( + dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 ); + if ( rc ) goto fail; + break; + case S_IFIFO: + rc = mkfifo( dest_name, 0700 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "mkfifo", dest_name ); + goto fail; + } + rc = lchown( dest_name, cp->uidn, cp->gidn ); + if ( rc ) { + report_errno( "traverse_copy_pre", "lchown", dest_name ); + goto fail; + } + rc = chmod( dest_name, st->st_mode & 07777 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "chmod", dest_name ); + goto fail; + } + break; + case S_IFLNK: + rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx ); + if ( rc ) goto fail; + break; + default: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: skipping special: %s\n", + name ); + } + + goto success; + +alloc_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse_copy_pre: allocation failed\n" ); +fail: + rc = TRAVERSE_CB_FAIL; + goto out; + +exists: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: \"%s\" already exists," + " skipping the rest\n", + dest_name ); + rc = TRAVERSE_CB_DONE; + goto out; + +success: + rc = TRAVERSE_CB_CONTINUE; +out: + if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: exit %d\n", rc ); + return rc; +} + +static int +copy_tree( + const char *dest_path, + const char *source_path, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + traverse_cb cb; + copy_private cp; + int rc; + + assert( dest_path != NULL ); + assert( source_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_tree: %s to %s entering\n", + source_path, dest_path ); + + cb.pre_func = traverse_copy_pre; + cb.post_func = NULL; + cb.pre_private = &cp; + cb.post_private = NULL; + + cp.source_prefix_len = strlen( source_path ); + cp.dest_prefix = dest_path; + cp.dest_prefix_len = strlen( dest_path ); + cp.uidn = uidn; + cp.gidn = gidn; + + if ( cp.source_prefix_len <= cp.dest_prefix_len && + strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 && + ( cp.source_prefix_len == cp.dest_prefix_len || + dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "copy_tree: aborting: %s contains %s\n", + source_path, dest_path ); + return 1; + } + + rc = traverse( source_path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_tree: %s exit %d\n", source_path, + rc ); + + return rc; +} + +static int +homedir_provision( + const char *dest_path, + const char *skel_path, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + int rc; + + assert( dest_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s from skeleton %s\n", + dest_path, skel_path == NULL ? "(none)" : skel_path ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s uidn %ld gidn %ld\n", + dest_path, (long)uidn, (long)gidn ); + + if ( skel_path == NULL ) { + rc = mkdir( dest_path, 0700 ); + if ( rc ) { + int save_errno = errno; + switch ( save_errno ) { + case EEXIST: + /* directory already present; nothing to do */ + /* but down chown either */ + rc = 0; + goto out; + break; + default: + report_errno( "provision_homedir", "mkdir", dest_path ); + goto fail; + } + } + rc = lchown( dest_path, uidn, gidn ); + if ( rc ) { + report_errno( "provision_homedir", "lchown", dest_path ); + goto fail; + } + + } else { + rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx ); + } + + goto out; + +fail: + rc = 1; + goto out; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s to %s exit %d\n", + skel_path, dest_path, rc ); + return rc; +} + +/* traverse func for rm -rf */ +static traverse_cb_ret +traverse_remove_post( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s entering\n", + name ); + + if ( (st->st_mode & S_IFMT) == S_IFDIR ) { + rc = rmdir( name ); + if ( rc != 0 ) { + report_errno( "traverse_remove_post", "rmdir", name ); + goto fail; + } + } else { + rc = unlink( name ); + if ( rc != 0 ) { + report_errno( "traverse_remove_post", "unlink", name ); + goto fail; + } + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s exit continue\n", + name ); + return TRAVERSE_CB_CONTINUE; + +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s exit failure\n", + name ); + return TRAVERSE_CB_FAIL; +} + +static int +delete_tree( const char *path, void *ctx ) +{ + const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL }; + int rc; + + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "delete_tree: %s entering\n", path ); + + rc = traverse( path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "delete_tree: %s exit %d\n", path, rc ); + + return rc; +} + +static int +get_tar_name( + const char *path, + const char *tar_path, + char *tar_name, + int name_size ) +{ + int rc = 0; + const char *ch; + int fd = -1; + int counter = 0; + time_t now; + + assert( path != NULL ); + assert( tar_path != NULL ); + assert( tar_name != NULL ); + + for ( ch = path + strlen( path ); + *ch != LDAP_DIRSEP[0] && ch > path; + --ch ) + ; + if ( ch <= path || strlen( ch ) < 2 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "get_tar_name: unable to construct a tar name from input " + "path \"%s\"\n", + path ); + goto fail; + } + ++ch; /* skip past sep */ + time( &now ); + + while ( fd < 0 ) { + snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar", + tar_path, ch, (long)now, counter ); + fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 ); + if ( fd < 0 ) { + int save_errno = errno; + if ( save_errno != EEXIST ) { + report_errno( "get_tar_name", "open", tar_name ); + goto fail; + } + ++counter; + } + } + + rc = 0; + goto out; + +fail: + rc = 1; + *tar_name = '\0'; +out: + if ( fd >= 0 ) close( fd ); + return rc; +} + +/* traverse func for rechown */ +static traverse_cb_ret +traverse_chown_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + chown_private *cp = private; + uid_t set_uidn = -1; + gid_t set_gidn = -1; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s entering\n", + name ); + + if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn; + if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn; + + if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) { + rc = lchown( name, set_uidn, set_gidn ); + if ( rc ) { + report_errno( "traverse_chown_pre", "lchown", name ); + goto fail; + } + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s exit continue\n", + name ); + return TRAVERSE_CB_CONTINUE; + +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s exit failure\n", + name ); + return TRAVERSE_CB_FAIL; +} + +static int +chown_tree( + const char *path, + uid_t old_uidn, + uid_t new_uidn, + gid_t old_gidn, + gid_t new_gidn, + void *ctx ) +{ + traverse_cb cb; + chown_private cp; + int rc; + + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "chown_tree: %s entering\n", path ); + + cb.pre_func = traverse_chown_pre; + cb.post_func = NULL; + cb.pre_private = &cp; + cb.post_private = NULL; + + cp.old_uidn = old_uidn; + cp.new_uidn = new_uidn; + cp.old_gidn = old_gidn; + cp.new_gidn = new_gidn; + + rc = traverse( path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "chown_tree: %s exit %d\n", path, rc ); + + return rc; +} + +static int +homedir_rename( const char *source_path, const char *dest_path ) +{ + int rc = 0; + + assert( source_path != NULL ); + assert( dest_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_rename: %s to %s\n", + source_path, dest_path ); + rc = rename( source_path, dest_path ); + if ( rc != 0 ) { + char ebuf[1024]; + int save_errno = errno; + + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_rename: rename(\"%s\", \"%s\"): (%s)\n", + source_path, dest_path, + AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_rename: %s to %s exit %d\n", + source_path, dest_path, rc ); + return rc; +} + +/* FIXME: This assumes ASCII; needs fixing for z/OS */ +static int +tar_set_header( ustar_header *tar, const struct stat *st, const char *name ) +{ + int name_len; + int rc; + const char *ch, *end; + + assert( tar != NULL ); + assert( st != NULL ); + assert( name != NULL ); + assert( sizeof(*tar) == 512 ); + assert( sizeof(tar->name) == 100 ); + assert( sizeof(tar->prefix) == 155 ); + assert( sizeof(tar->checksum) == 8 ); + + memset( tar, 0, sizeof(*tar) ); + + assert( name[0] == LDAP_DIRSEP[0] ); + name += 1; /* skip leading / */ + + name_len = strlen( name ); + + /* fits in tar->name? */ + /* Yes, name and prefix do not need a trailing nul. */ + if ( name_len <= 100 ) { + strncpy( tar->name, name, 100 ); + + /* try fit in tar->name + tar->prefix */ + } else { + /* try to find something to stick into tar->name */ + for ( ch = name + name_len - 100, end = name + name_len; + ch < end && *ch != LDAP_DIRSEP[0]; + ++ch ) + ; + if ( end - ch > 0 ) /* +1 skip past sep */ + ch++; + else { + /* reset; name too long for UStar */ + Debug( LDAP_DEBUG_ANY, "homedir: " + "tar_set_header: name too long: \"%s\"\n", + name ); + ch = name + name_len - 100; + } + strncpy( tar->name, ch + 1, 100 ); + { + int prefix_len = ( ch - 1 ) - name; + if ( prefix_len > 155 ) prefix_len = 155; + strncpy( tar->prefix, name, prefix_len ); + } + } + + snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 ); + snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid ); + snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid ); + snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime ); + snprintf( tar->size, 12, "%010lo ", (long)0 ); + switch ( st->st_mode & S_IFMT ) { + case S_IFREG: + tar->typeflag[0] = '0'; + snprintf( tar->size, 12, "%010lo ", (long)st->st_size ); + break; + case S_IFLNK: + tar->typeflag[0] = '2'; + rc = readlink( name - 1, tar->linkname, 99 ); + if ( rc == -1 ) { + report_errno( "tar_set_header", "readlink", name ); + goto fail; + } + break; + case S_IFCHR: + tar->typeflag[0] = '3'; + /* FIXME: this is probably wrong but shouldn't likely be an issue */ + snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 ); + snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff ); + break; + case S_IFBLK: + tar->typeflag[0] = '4'; + /* FIXME: this is probably wrong but shouldn't likely be an issue */ + snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 ); + snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff ); + break; + case S_IFDIR: + tar->typeflag[0] = '5'; + break; + case S_IFIFO: + tar->typeflag[0] = '6'; + break; + default: + goto fail; + } + snprintf( tar->magic, 6, "ustar" ); + tar->version[0] = '0'; + tar->version[1] = '0'; + + { + unsigned char *uch = (unsigned char *)tar; + unsigned char *uend = uch + 512; + unsigned long sum = 0; + + memset( &tar->checksum, ' ', sizeof(tar->checksum) ); + + for ( ; uch < uend; ++uch ) + sum += *uch; + + /* zero-padded, six octal digits, followed by NUL then space (!) */ + /* Yes, that's terminated exactly reverse of the others. */ + snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum ); + } + + return 0; +fail: + return 1; +} + +static traverse_cb_ret +traverse_tar_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + traverse_cb_ret cbrc; + tar_private *tp = private; + ustar_header tar; + FILE *source = NULL; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s entering\n", name ); + + switch ( st->st_mode & S_IFMT ) { + case S_IFREG: + if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s is larger than 8GiB POSIX UStar " + "file size limit\n", + name ); + goto fail; + } + /* fallthrough */ + case S_IFDIR: + case S_IFLNK: + case S_IFIFO: + case S_IFCHR: + case S_IFBLK: + rc = tar_set_header( &tar, st, name ); + if ( rc ) goto fail; + break; + default: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: skipping \"%s\" mode %o\n", + name, st->st_mode ); + goto done; + } + + rc = fwrite( &tar, 1, 512, tp->file ); + if ( rc != 512 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: write error in tar header\n" ); + goto fail; + } + + if ( (st->st_mode & S_IFMT) == S_IFREG ) { + source = fopen( name, "rb" ); + if ( source == NULL ) { + report_errno( "traverse_tar_pre", "fopen", name ); + goto fail; + } + rc = copy_blocks( source, tp->file, name, tp->name ); + if ( rc != 0 ) goto fail; + fclose( source ); + source = NULL; + } + + { /* advance to end of record */ + off_t pos = ftello( tp->file ); + if ( pos == -1 ) { + report_errno( "traverse_tar_pre", "ftello", tp->name ); + goto fail; + } + pos += ( 512 - ( pos % 512 ) ) % 512; + rc = fseeko( tp->file, pos, SEEK_SET ); + if ( rc != 0 ) { + report_errno( "traverse_tar_pre", "fseeko", tp->name ); + goto fail; + } + } + +done: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s exit continue\n", + name ); + cbrc = TRAVERSE_CB_CONTINUE; + goto out; +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s exit failure\n", + name ); + cbrc = TRAVERSE_CB_FAIL; + +out: + if ( source != NULL ) fclose( source ); + return cbrc; +} + +static int +tar_tree( const char *path, const char *tar_name, void *ctx ) +{ + traverse_cb cb; + tar_private tp; + int rc; + + assert( path != NULL ); + assert( tar_name != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "tar_tree: %s into %s entering\n", path, + tar_name ); + + cb.pre_func = traverse_tar_pre; + cb.post_func = NULL; + cb.pre_private = &tp; + cb.post_private = NULL; + + tp.name = tar_name; + tp.file = fopen( tar_name, "wb" ); + if ( tp.file == NULL ) { + report_errno( "tar_tree", "fopen", tar_name ); + goto fail; + } + + rc = traverse( path, &cb, ctx ); + if ( rc != 0 ) goto fail; + + { + off_t pos = ftello( tp.file ); + if ( pos == -1 ) { + report_errno( "tar_tree", "ftello", tp.name ); + goto fail; + } + pos += 1024; /* two zero records */ + pos += ( 10240 - ( pos % 10240 ) ) % 10240; + rc = ftruncate( fileno( tp.file ), pos ); + if ( rc != 0 ) { + report_errno( "tar_tree", "ftrunctate", tp.name ); + goto fail; + } + } + + rc = fclose( tp.file ); + tp.file = NULL; + if ( rc != 0 ) { + report_errno( "tar_tree", "fclose", tp.name ); + goto fail; + } + goto out; + +fail: + rc = 1; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "tar_tree: %s exit %d\n", path, rc ); + if ( tp.file != NULL ) fclose( tp.file ); + return rc; +} + +static int +homedir_deprovision( const homedir_data *data, const char *path, void *ctx ) +{ + int rc = 0; + char tar_name[1024]; + + assert( data != NULL ); + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: %s entering\n", + path ); + + switch ( data->style ) { + case DEL_IGNORE: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: style is ignore\n" ); + break; + case DEL_ARCHIVE: + if ( data->archive_path == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_deprovision: archive path not set\n" ); + goto fail; + } + rc = get_tar_name( path, data->archive_path, tar_name, 1024 ); + if ( rc != 0 ) goto fail; + rc = tar_tree( path, tar_name, ctx ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_deprovision: archive failed, not deleting\n" ); + goto fail; + } + /* fall-through */ + case DEL_DELETE: + rc = delete_tree( path, ctx ); + break; + default: + abort(); + } + + rc = 0; + goto out; + +fail: + rc = 1; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: %s leaving\n", + path ); + + return rc; +} + +/* FIXME: This assumes ASCII; needs fixing for z/OS */ +/* FIXME: This should also be in a slapd library function somewhere */ +#define MAX_MATCHES ( 10 ) +static int +homedir_match( + const homedir_regexp *r, + const char *homedir, + char *result, + size_t result_size ) +{ + int rc; + int n; + regmatch_t matches[MAX_MATCHES]; + char *resc, *repc; + + assert( r != NULL ); + assert( homedir != NULL ); + assert( result_size > 1 ); + + memset( matches, 0, sizeof(matches) ); + rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 ); + if ( rc ) { + if ( rc != REG_NOMATCH ) { + char msg[256]; + regerror( rc, &r->compiled, msg, sizeof(msg) ); + Debug( LDAP_DEBUG_ANY, "homedir_match: " + "%s\n", msg ); + } + return rc; + } + + for ( resc = result, repc = r->replace; + result_size > 1 && *repc != '\0'; + ++repc, ++resc, --result_size ) { + switch ( *repc ) { + case '$': + ++repc; + n = ( *repc ) - '0'; + if ( n < 0 || n > ( MAX_MATCHES - 1 ) || + matches[n].rm_so < 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "invalid regex term expansion in \"%s\" " + "at char %ld, n is %d\n", + r->replace, (long)( repc - r->replace ), n ); + return 1; + } + { + size_t match_len = matches[n].rm_eo - matches[n].rm_so; + const char *match_start = homedir + matches[n].rm_so; + if ( match_len >= result_size ) goto too_long; + + memcpy( resc, match_start, match_len ); + result_size -= match_len; + resc += match_len - 1; + } + break; + + case '\\': + ++repc; + /* fallthrough */ + + default: + *resc = *repc; + } + } + *resc = '\0'; + if ( *repc != '\0' ) goto too_long; + + return 0; + +too_long: + Debug( LDAP_DEBUG_ANY, "homedir: " + "regex expansion of %s too long\n", + r->replace ); + *result = '\0'; + return 1; +} + +/* Sift through an entry for interesting values + * return 0 on success and set vars + * return 1 if homedir is not present or not valid + * sets presence if any homedir attributes are noticed + */ +static int +harvest_values( + const homedir_data *data, + const Entry *e, + char *home_buf, + int home_buf_size, + uid_t *uidn, + gid_t *gidn, + int *presence ) +{ + Attribute *a; + char *homedir = NULL; + + assert( data != NULL ); + assert( e != NULL ); + assert( home_buf != NULL ); + assert( home_buf_size > 1 ); + assert( uidn != NULL ); + assert( gidn != NULL ); + assert( presence != NULL ); + + *presence = 0; + if ( e == NULL ) return 1; + *uidn = 0; + *gidn = 0; + + for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) { + if ( a->a_desc == data->home_ad ) { + homedir = a->a_vals[0].bv_val; + *presence = 1; + } else if ( a->a_desc == data->uidn_ad ) { + *uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 ); + *presence = 1; + } else if ( a->a_desc == data->gidn_ad ) { + *gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 ); + *presence = 1; + } + } + if ( homedir != NULL ) { + homedir_regexp *r; + + for ( r = data->regexps; r != NULL; r = r->next ) { + int rc = homedir_match( r, homedir, home_buf, home_buf_size ); + if ( rc == 0 ) return 0; + } + } + + return 1; +} + +static int +homedir_mod_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = NULL; + slap_callback **cbp = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: entering\n" ); + + for ( cbp = &op->o_callback; + *cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup; + cbp = &(*cbp)->sc_next ) + ; + + if ( *cbp == NULL ) goto out; + cb = *cbp; + + cb_data = (homedir_cb_data *)cb->sc_private; + e = cb_data->entry; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: found <%s>\n", + e->e_nname.bv_val ); + entry_free( e ); + op->o_tmpfree( cb_data, op->o_tmpmemctx ); + *cbp = cb->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + +out: + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: leaving\n" ); + return SLAP_CB_CONTINUE; +} + +static int +homedir_mod_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = NULL; + homedir_data *data = NULL; + slap_callback *cb = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + int rc = SLAP_CB_CONTINUE; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: entering\n" ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: op was not successful\n" ); + goto out; + } + + /* Retrieve stashed entry */ + for ( cb = op->o_callback; + cb != NULL && cb->sc_cleanup != homedir_mod_cleanup; + cb = cb->sc_next ) + ; + if ( cb == NULL ) goto out; + cb_data = (homedir_cb_data *)cb->sc_private; + e = cb_data->entry; + on = cb_data->on; + data = on->on_bi.bi_private; + assert( e != NULL ); + assert( data != NULL ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: found <%s>\n", + e->e_nname.bv_val ); + + switch ( op->o_tag ) { + case LDAP_REQ_DELETE: { + char home_buf[1024]; + uid_t uidn = 0; + gid_t gidn = 0; + int presence; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: successful delete found\n" ); + rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn, + &gidn, &presence ); + if ( rc == 0 && uidn >= data->min_uid ) { + homedir_deprovision( data, home_buf, op->o_tmpmemctx ); + } else { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: skipping\n" ); + } + rc = SLAP_CB_CONTINUE; + break; + } + + case LDAP_REQ_MODIFY: + case LDAP_REQ_MODRDN: { + Operation nop = *op; + Entry *old_entry = e; + Entry *new_entry = NULL; + Entry *etmp; + char old_home[1024]; + char new_home[1024]; + uid_t old_uidn, new_uidn; + uid_t old_gidn, new_gidn; + int old_valid = 0; + int new_valid = 0; + int old_presence, new_presence; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: successful modify/modrdn found\n" ); + + /* retrieve the revised entry */ + nop.o_bd = on->on_info->oi_origdb; + rc = overlay_entry_get_ov( + &nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on ); + if ( etmp != NULL ) { + new_entry = entry_dup( etmp ); + overlay_entry_release_ov( &nop, etmp, 0, on ); + } + if ( rc || new_entry == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: unable to get revised <%s>\n", + op->o_req_ndn.bv_val ); + if ( new_entry != NULL ) { + entry_free( new_entry ); + new_entry = NULL; + } + } + + /* analyze old and new */ + rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn, + &old_gidn, &old_presence ); + if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1; + if ( new_entry != NULL ) { + rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn, + &new_gidn, &new_presence ); + if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1; + entry_free( new_entry ); + new_entry = NULL; + } + + if ( new_valid && !old_valid ) { /* like an add */ + if ( old_presence ) + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: old entry is now valid\n" ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like an add\n" ); + homedir_provision( new_home, data->skeleton_path, new_uidn, + new_gidn, op->o_tmpmemctx ); + + } else if ( old_valid && !new_valid && + !new_presence ) { /* like a del */ + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like a del\n" ); + homedir_deprovision( data, old_home, op->o_tmpmemctx ); + + } else if ( new_valid && old_valid ) { /* change */ + int did_something = 0; + + if ( strcmp( old_home, new_home ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like a rename\n" ); + homedir_rename( old_home, new_home ); + did_something = 1; + } + if ( old_uidn != new_uidn || old_gidn != new_gidn ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: rechowning\n" ); + chown_tree( new_home, old_uidn, new_uidn, old_gidn, + new_gidn, op->o_tmpmemctx ); + did_something = 1; + } + if ( !did_something ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: nothing to do\n" ); + } + } else if ( old_presence || new_presence ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: <%s> values present " + "but invalid; ignoring\n", + op->o_req_ndn.bv_val ); + } + rc = SLAP_CB_CONTINUE; + break; + } + + default: + rc = SLAP_CB_CONTINUE; + } + +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: leaving\n" ); + return rc; +} + +static int +homedir_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *cb = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + Entry *se = NULL; + Operation nop = *op; + int rc; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_op_mod: entering\n" ); + + /* retrieve the entry */ + nop.o_bd = on->on_info->oi_origdb; + rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on ); + if ( e != NULL ) { + se = entry_dup( e ); + overlay_entry_release_ov( &nop, e, 0, on ); + e = se; + } + if ( rc || e == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_op_mod: unable to get <%s>\n", + op->o_req_ndn.bv_val ); + goto out; + } + + /* Allocate the callback to hold the entry */ + cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx ); + cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx ); + cb->sc_cleanup = homedir_mod_cleanup; + cb->sc_response = homedir_mod_response; + cb->sc_private = cb_data; + cb_data->entry = e; + e = NULL; + cb_data->on = on; + cb->sc_next = op->o_callback; + op->o_callback = cb; + +out: + if ( e != NULL ) entry_free( e ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_op_mod: leaving\n" ); + return SLAP_CB_CONTINUE; +} + +static int +homedir_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + homedir_data *data = on->on_bi.bi_private; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_response: entering\n" ); + if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE; + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: { /* Check for new homedir */ + char home_buf[1024]; + uid_t uidn = 0; + gid_t gidn = 0; + int rc, presence; + + rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf), + &uidn, &gidn, &presence ); + if ( rc == 0 && uidn >= data->min_uid ) { + homedir_provision( home_buf, data->skeleton_path, uidn, gidn, + op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; + } + + default: + return SLAP_CB_CONTINUE; + } + + return SLAP_CB_CONTINUE; +} + +static int +homedir_db_init( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + homedir_data *data = ch_calloc( 1, sizeof(homedir_data) ); + const char *text; + + if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) || + slap_str2ad( "uidNumber", &data->uidn_ad, &text ) || + slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "nis schema not available\n" ); + return 1; + } + + data->skeleton_path = strdup( DEFAULT_SKEL ); + data->min_uid = DEFAULT_MIN_UID; + data->archive_path = NULL; + + on->on_bi.bi_private = data; + return 0; +} + +static int +homedir_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + homedir_data *data = on->on_bi.bi_private; + homedir_regexp *r, *rnext; + + if ( data != NULL ) { + for ( r = data->regexps; r != NULL; r = rnext ) { + rnext = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + } + data->regexps = NULL; + if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path ); + if ( data->archive_path != NULL ) ch_free( data->archive_path ); + ch_free( data ); + } + + return 0; +} + +int +homedir_initialize() +{ + int rc; + + assert( ' ' == 32 ); /* Lots of ASCII requirements for now */ + + memset( &homedir, 0, sizeof(homedir) ); + + homedir.on_bi.bi_type = "homedir"; + homedir.on_bi.bi_db_init = homedir_db_init; + homedir.on_bi.bi_db_destroy = homedir_db_destroy; + homedir.on_bi.bi_op_delete = homedir_op_mod; + homedir.on_bi.bi_op_modify = homedir_op_mod; + homedir.on_response = homedir_response; + + homedir.on_bi.bi_cf_ocs = homedirocs; + rc = config_register_schema( homedircfg, homedirocs ); + if ( rc ) return rc; + + ldap_pvt_thread_mutex_init( &readdir_mutex ); + + return overlay_register( &homedir ); +} + +int +homedir_terminate() +{ + ldap_pvt_thread_mutex_destroy( &readdir_mutex ); + return 0; +} + +#if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC) +int +init_module( int argc, char *argv[] ) +{ + return homedir_initialize(); +} + +int +term_module() +{ + return homedir_terminate(); +} +#endif + +#endif /* SLAPD_OVER_HOMEDIR */ diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c new file mode 100644 index 0000000..d76f8f4 --- /dev/null +++ b/servers/slapd/overlays/memberof.c @@ -0,0 +1,2209 @@ +/* memberof.c - back-reference for group membership */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2007 Pierangelo Masarati <ando@sys-net.it> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software, sponsored by SysNet s.r.l. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_MEMBEROF + +#include <stdio.h> + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" + +/* + * Glossary: + * + * GROUP a group object (an entry with GROUP_OC + * objectClass) + * MEMBER a member object (an entry whose DN is + * listed as MEMBER_AT value of a GROUP) + * GROUP_OC the objectClass of the group object + * (default: groupOfNames) + * MEMBER_AT the membership attribute, DN-valued; + * note: nameAndOptionalUID is tolerated + * as soon as the optionalUID is absent + * (default: member) + * MEMBER_OF reverse membership attribute + * (default: memberOf) + * + * - add: + * - if the entry that is being added is a GROUP, + * the MEMBER_AT defined as values of the add operation + * get the MEMBER_OF value directly from the request. + * + * if configured to do so, the MEMBER objects do not exist, + * and no relax control is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if (configured to do so,) the referenced GROUP exists, + * the relax control is set and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries. + * + * - modify: + * - if the entry being modified is a GROUP_OC and the + * MEMBER_AT attribute is modified, the MEMBER_OF value + * of the (existing) MEMBER_AT entries that are affected + * is modified according to the request: + * - if a MEMBER is removed from the group, + * delete the corresponding MEMBER_OF + * - if a MEMBER is added to a group, + * add the corresponding MEMBER_OF + * + * We need to determine, from the database, if it is + * a GROUP_OC, and we need to check, from the + * modification list, if the MEMBER_AT attribute is being + * affected, and what MEMBER_AT values are affected. + * + * if configured to do so, the entries corresponding to + * the MEMBER_AT values do not exist, and no relax control + * is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if configured to do so, the referenced GROUP exists, + * (the relax control is set) and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries; the change is NOT automatically reflected + * in the MEMBER attribute of the GROUP referenced + * by the value of MEMBER_OF; a separate modification, + * with or without relax control, needs to be performed. + * + * - modrdn: + * - if the entry being renamed is a GROUP, the MEMBER_OF + * value of the (existing) MEMBER objects is modified + * accordingly based on the newDN of the GROUP. + * + * We need to determine, from the database, if it is + * a GROUP; the list of MEMBER objects is obtained from + * the database. + * + * Non-existing MEMBER objects are ignored, since the + * MEMBER_AT is not being addressed by the operation. + * + * - if the entry being renamed has the MEMBER_OF attribute, + * the corresponding MEMBER value must be modified in the + * respective group entries. + * + * + * - delete: + * - if the entry being deleted is a GROUP, the (existing) + * MEMBER objects are modified accordingly; a copy of the + * values of the MEMBER_AT is saved and, if the delete + * succeeds, the MEMBER_OF value of the (existing) MEMBER + * objects is deleted. + * + * We need to determine, from the database, if it is + * a GROUP. + * + * Non-existing MEMBER objects are ignored, since the entry + * is being deleted. + * + * - if the entry being deleted has the MEMBER_OF attribute, + * the corresponding value of the MEMBER_AT must be deleted + * from the respective GROUP entries. + */ + +#define SLAPD_MEMBEROF_ATTR "memberOf" + +static AttributeDescription *ad_member; +static AttributeDescription *ad_memberOf; + +static ObjectClass *oc_group; + +static slap_overinst memberof; + +typedef struct memberof_t { + struct berval mo_dn; + struct berval mo_ndn; + + ObjectClass *mo_oc_group; + AttributeDescription *mo_ad_member; + AttributeDescription *mo_ad_memberof; + + struct berval mo_groupFilterstr; + AttributeAssertion mo_groupAVA; + Filter mo_groupFilter; + + struct berval mo_memberFilterstr; + Filter mo_memberFilter; + + unsigned mo_flags; +#define MEMBEROF_NONE 0x00U +#define MEMBEROF_FDANGLING_DROP 0x01U +#define MEMBEROF_FDANGLING_ERROR 0x02U +#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_FREFINT 0x04U +#define MEMBEROF_FREVERSE 0x08U + + ber_int_t mo_dangling_err; + +#define MEMBEROF_CHK(mo,f) \ + (((mo)->mo_flags & (f)) == (f)) +#define MEMBEROF_DANGLING_CHECK(mo) \ + ((mo)->mo_flags & MEMBEROF_FDANGLING_MASK) +#define MEMBEROF_DANGLING_DROP(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP) +#define MEMBEROF_DANGLING_ERROR(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_REFINT(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREFINT) +#define MEMBEROF_REVERSE(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREVERSE) +} memberof_t; + +typedef enum memberof_is_t { + MEMBEROF_IS_NONE = 0x00, + MEMBEROF_IS_GROUP = 0x01, + MEMBEROF_IS_MEMBER = 0x02, + MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER) +} memberof_is_t; + +typedef struct memberof_cookie_t { + AttributeDescription *ad; + BerVarray vals; + int foundit; +} memberof_cookie_t; + +typedef struct memberof_cbinfo_t { + slap_overinst *on; + BerVarray member; + BerVarray memberof; + memberof_is_t what; +} memberof_cbinfo_t; + +static void +memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on ) +{ + BackendInfo *bi = op->o_bd->bd_info; + + if ( bi->bi_type == memberof.on_bi.bi_type ) + op_target->o_bd->bd_info = (BackendInfo *)on->on_info; +} + +static int +memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + } + + return 0; +} + +/* + * callback for internal search that saves the member attribute values + * of groups being deleted. + */ +static int +memberof_saveMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + Attribute *a; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + + assert( rs->sr_entry != NULL ); + assert( rs->sr_entry->e_attrs != NULL ); + + a = attr_find( rs->sr_entry->e_attrs, mc->ad ); + if ( a != NULL ) { + ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx ); + + assert( attr_find( a->a_next, mc->ad ) == NULL ); + } + } + + return 0; +} + +/* + * the delete hook performs an internal search that saves the member + * attribute values of groups being deleted. + */ +static int +memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci ) +{ + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + slap_callback cb = { 0 }; + BackendInfo *bi = op->o_bd->bd_info; + AttributeName an[ 2 ]; + + memberof_is_t iswhat = MEMBEROF_IS_NONE; + memberof_cookie_t mc; + + assert( mci->what != MEMBEROF_IS_NONE ); + + cb.sc_private = &mc; + if ( op->o_tag == LDAP_REQ_DELETE ) { + cb.sc_response = memberof_saveMember_cb; + + } else { + cb.sc_response = memberof_isGroupOrMember_cb; + } + + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + BER_BVZERO( &an[ 1 ].an_name ); + op2.ors_attrs = an; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + if ( mci->what & MEMBEROF_IS_GROUP ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_member; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_member; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_groupFilterstr; + op2.ors_filter = &mo->mo_groupFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_GROUP; + if ( mc.vals ) mci->member = mc.vals; + + } + } + + if ( mci->what & MEMBEROF_IS_MEMBER ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_memberof; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_memberof; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_memberFilterstr; + op2.ors_filter = &mo->mo_memberFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_MEMBER; + if ( mc.vals ) mci->memberof = mc.vals; + + } + } + + mci->what = iswhat; + + return LDAP_SUCCESS; +} + +/* + * response callback that adds memberof values when a group is modified. + */ +static void +memberof_value_modify( + Operation *op, + struct berval *ndn, + AttributeDescription *ad, + struct berval *old_dn, + struct berval *old_ndn, + struct berval *new_dn, + struct berval *new_ndn ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + unsigned long opid = op->o_opid; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Modifications mod[ 2 ] = { { { 0 } } }, *ml; + struct berval values[ 4 ], nvalues[ 4 ]; + int mcnt = 0; + + if ( old_ndn != NULL && new_ndn != NULL && + ber_bvcmp( old_ndn, new_ndn ) == 0 ) { + /* DNs compare equal, it's a noop */ + return; + } + + op2.o_tag = LDAP_REQ_MODIFY; + + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + op2.orm_modlist = NULL; + + /* Internal ops, never replicate these */ + op2.o_opid = 0; /* shared with op, saved above */ + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + + if ( !BER_BVISNULL( &mo->mo_ndn ) ) { + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 0 ]; + ml->sml_values[ 0 ] = mo->mo_dn; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 0 ]; + ml->sml_nvalues[ 0 ] = mo->mo_ndn; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = slap_schema.si_ad_modifiersName; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_op = LDAP_MOD_REPLACE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + mcnt++; + } + + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 2 ]; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 2 ]; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = ad; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + if ( new_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( new_dn ) ); + assert( !BER_BVISNULL( new_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_ADD; + + ml->sml_values[ 0 ] = *new_dn; + ml->sml_nvalues[ 0 ] = *new_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d\n", + op->o_log_prefix, op2.o_req_dn.bv_val, + ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + + mod[ 0 ].sml_next = NULL; + } + + if ( old_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( old_dn ) ); + assert( !BER_BVISNULL( old_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_DELETE; + + ml->sml_values[ 0 ] = *old_dn; + ml->sml_nvalues[ 0 ] = *old_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d\n", + op->o_log_prefix, op2.o_req_dn.bv_val, + ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + } + /* restore original opid */ + op->o_opid = opid; + + /* FIXME: if old_group_ndn doesn't exist, both delete __and__ + * add will fail; better split in two operations, although + * not optimal in terms of performance. At least it would + * move towards self-repairing capabilities. */ +} + +static int +memberof_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + memberof_cbinfo_t *mci = sc->sc_private; + + op->o_callback = sc->sc_next; + if ( mci->memberof ) + ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx ); + if ( mci->member ) + ber_bvarray_free_x( mci->member, op->o_tmpmemctx ); + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int memberof_res_add( Operation *op, SlapReply *rs ); +static int memberof_res_delete( Operation *op, SlapReply *rs ); +static int memberof_res_modify( Operation *op, SlapReply *rs ); +static int memberof_res_modrdn( Operation *op, SlapReply *rs ); + +static int +memberof_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Attribute **ap, **map = NULL; + int rc = SLAP_CB_CONTINUE; + int i; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( op->ora_e->e_attrs == NULL ) { + /* FIXME: global overlay; need to deal with */ + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "consistency checks not implemented when overlay " + "is instantiated as global.\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) { + Attribute *a = *ap; + + if ( a->a_desc == mo->mo_ad_memberof ) { + map = ap; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) + && is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + for ( ap = &op->ora_e->e_attrs; *ap; ) { + Attribute *a = *ap; + + if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) { + ap = &a->a_next; + continue; + } + + assert( a->a_nvals != NULL ); + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS ) { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_vals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + a->a_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + } + + /* If all values have been removed, + * remove the attribute itself. */ + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *ap = a->a_next; + attr_free( a ); + + } else { + ap = &a->a_next; + } + } + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + } + + if ( map != NULL ) { + Attribute *a = *map; + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof, + &a->a_nvals[ i ], ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done; + } + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_nvals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, ACL_WADD, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *map = a->a_next; + attr_free( a ); + } + } + + rc = SLAP_CB_CONTINUE; + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_add; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + sc->sc_next = op->o_callback; + op->o_callback = sc; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_delete; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = MEMBEROF_IS_GROUP; + if ( MEMBEROF_REFINT( mo ) ) { + mci->what = MEMBEROF_IS_BOTH; + } + + memberof_isGroupOrMember( op, mci ); + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +static int +memberof_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Modifications **mlp, **mmlp = NULL; + int rc = SLAP_CB_CONTINUE, save_member = 0; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci, mcis; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) { + Modifications *ml = *mlp; + + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mmlp = mlp; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + mcis.on = on; + mcis.what = MEMBEROF_IS_GROUP; + + if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS + && ( mcis.what & MEMBEROF_IS_GROUP ) ) + { + Modifications *ml; + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_member ) { + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + save_member = 1; + break; + } + } + } + + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + assert( op->orm_modlist != NULL ); + + for ( mlp = &op->orm_modlist; *mlp; ) { + Modifications *ml = *mlp; + int i; + + if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) { + mlp = &ml->sml_next; + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + /* we don't care about cancellations: if the value + * exists, fine; if it doesn't, we let the underlying + * database fail as appropriate; */ + mlp = &ml->sml_next; + break; + + case LDAP_MOD_REPLACE: + /* Handle this just like a delete (see above) */ + if ( !ml->sml_values ) { + mlp = &ml->sml_next; + break; + } + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + /* NOTE: right now, the attributeType we use + * for member must have a normalized value */ + assert( ml->sml_nvalues != NULL ); + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ) == LDAP_SUCCESS ) + { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_dn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + i--; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + + } else { + mlp = &ml->sml_next; + } + + break; + + default: + assert( 0 ); + } + } + } + } + + if ( mmlp != NULL ) { + Modifications *ml = *mmlp; + int i; + Entry *target; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, + NULL, NULL, 0, &target ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + goto done; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( ml->sml_nvalues != NULL ) { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WDEL, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "deleting non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + NULL, + ACL_WDEL, NULL ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + } break; + + default: + assert( 0 ); + } + +done2:; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, target ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modify; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = mcis.what; + + if ( save_member ) { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_member, &mci->member, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + rc = SLAP_CB_CONTINUE; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_modrdn( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modrdn; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds memberof values when a group is added. + */ +static int +memberof_res_add( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + Attribute *ma; + + ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof ); + if ( ma != NULL ) { + /* relax is required to allow to add + * a non-existing member */ + op->o_relax = SLAP_CONTROL_CRITICAL; + + for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) { + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn )) + continue; + + /* the modification is attempted + * with the original identity */ + memberof_value_modify( op, + &ma->a_nvals[ i ], mo->mo_ad_member, + NULL, NULL, &op->o_req_dn, &op->o_req_ndn ); + } + } + } + + if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) { + Attribute *a; + + for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member ); + a != NULL; + a = attrs_find( a->a_next, mo->mo_ad_member ) ) + { + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &op->o_req_ndn )) + continue; + + memberof_value_modify( op, + &a->a_nvals[ i ], + mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, + &op->o_req_ndn ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that deletes memberof values when a group is deleted. + */ +static int +memberof_res_delete( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + BerVarray vals; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + vals = mci->member; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( MEMBEROF_REFINT( mo ) ) { + vals = mci->memberof; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes memberof values when a group + * is modified. + */ +static int +memberof_res_modify( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc; + Modifications *ml, *mml = NULL; + BerVarray vals; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mml = ml; + break; + } + } + } + + if ( mml != NULL ) { + BerVarray vals = mml->sml_nvalues; + + switch ( mml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + /* delete all ... */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_memberof, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + assert( vals != NULL ); + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + + if ( mci->what & MEMBEROF_IS_GROUP ) + { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc != mo->mo_ad_member ) { + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + vals = ml->sml_nvalues; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + vals = mci->member; + + /* delete all ... */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */ + assert( ml->sml_nvalues != NULL ); + vals = ml->sml_nvalues; + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes member values when a group member + * is renamed. + */ +static int +memberof_res_modrdn( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + 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 ); + return 1; + } + } + + on->on_bi.bi_private = (void *)mo; + + return 0; +} + +enum { + MO_DN = 1, + MO_DANGLING, + MO_REFINT, + MO_GROUP_OC, + MO_MEMBER_AD, + MO_MEMBER_OF_AD, +#if 0 + MO_REVERSE, +#endif + + MO_DANGLING_ERROR, + + MO_LAST +}; + +static ConfigDriver mo_cf_gen; + +#define OID "1.3.6.1.4.1.7136.2.666.4" +#define OIDAT OID ".1.1" +#define OIDCFGAT OID ".1.2" +#define OIDOC OID ".2.1" +#define OIDCFGOC OID ".2.2" + + +static ConfigTable mo_cfg[] = { + { "memberof-dn", "modifiersName", + 2, 2, 0, ARG_MAGIC|ARG_QUOTE|ARG_DN|MO_DN, mo_cf_gen, + "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' " + "DESC 'DN to be used as modifiersName' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-dangling", "ignore|drop|error", + 2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen, + "( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' " + "DESC 'Behavior with respect to dangling members, " + "constrained to ignore, drop, error' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-refint", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen, + "( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' " + "DESC 'Take care of referential integrity' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-group-oc", "objectClass", + 2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen, + "( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' " + "DESC 'Group objectClass' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-member-ad", "member attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen, + "( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' " + "DESC 'member attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-memberof-ad", "memberOf attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen, + "( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' " + "DESC 'memberOf attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + +#if 0 + { "memberof-reverse", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen, + "( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' " + "DESC 'Take care of referential integrity " + "also when directly modifying memberOf' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, +#endif + + { "memberof-dangling-error", "error code", + 2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen, + "( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' " + "DESC 'Error code returned in case of dangling back reference' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs mo_ocs[] = { + { "( OLcfgOvOc:18.1 " + "NAME ( 'olcMemberOfConfig' 'olcMemberOf' ) " + "DESC 'Member-of configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcMemberOfDN " + "$ olcMemberOfDangling " + "$ olcMemberOfDanglingError" + "$ olcMemberOfRefInt " + "$ olcMemberOfGroupOC " + "$ olcMemberOfMemberAD " + "$ olcMemberOfMemberOfAD " +#if 0 + "$ olcMemberOfReverse " +#endif + ") " + ")", + Cft_Overlay, mo_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static slap_verbmasks dangling_mode[] = { + { BER_BVC( "ignore" ), MEMBEROF_NONE }, + { BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP }, + { BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR }, + { BER_BVNULL, 0 } +}; + +static int +memberof_make_group_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ch_free( mo->mo_groupFilterstr.bv_val ); + } + + mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY; + mo->mo_groupFilter.f_ava = &mo->mo_groupAVA; + + mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass; + mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname; + + mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" ) + + slap_schema.si_ad_objectClass->ad_cname.bv_len + + mo->mo_oc_group->soc_cname.bv_len; + ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + + return 0; +} + +static int +memberof_make_member_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ch_free( mo->mo_memberFilterstr.bv_val ); + } + + mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT; + mo->mo_memberFilter.f_desc = mo->mo_ad_memberof; + + mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" ) + + mo->mo_ad_memberof->ad_cname.bv_len; + ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val ); + ptr = lutil_strcopy( ptr, "=*)" ); + + return 0; +} + +static int +mo_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch( c->type ) { + case MO_DN: + if ( mo->mo_dn.bv_val != NULL) { + value_add_one( &c->rvalue_vals, &mo->mo_dn ); + value_add_one( &c->rvalue_nvals, &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case MO_DANGLING_ERROR: + if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) { + char buf[ SLAP_TEXT_BUFLEN ]; + enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv ); + if ( BER_BVISNULL( &bv ) ) { + bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err ); + if ( bv.bv_len < sizeof( buf ) ) { + bv.bv_val = buf; + } else { + rc = 1; + break; + } + } + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + break; + + case MO_REFINT: + c->value_int = MEMBEROF_REFINT( mo ); + break; + +#if 0 + case MO_REVERSE: + c->value_int = MEMBEROF_REVERSE( mo ); + break; +#endif + + case MO_GROUP_OC: + if ( mo->mo_oc_group != NULL ){ + value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname ); + } + break; + + case MO_MEMBER_AD: + c->value_ad = mo->mo_ad_member; + break; + + case MO_MEMBER_OF_AD: + c->value_ad = mo->mo_ad_memberof; + break; + + default: + assert( 0 ); + return 1; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + BER_BVZERO( &mo->mo_dn ); + BER_BVZERO( &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + break; + + case MO_DANGLING_ERROR: + mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION; + break; + + case MO_REFINT: + mo->mo_flags &= ~MEMBEROF_FREFINT; + break; + +#if 0 + case MO_REVERSE: + mo->mo_flags &= ~MEMBEROF_FREVERSE; + break; +#endif + + case MO_GROUP_OC: + mo->mo_oc_group = oc_group; + memberof_make_group_filter( mo ); + break; + + case MO_MEMBER_AD: + mo->mo_ad_member = ad_member; + break; + + case MO_MEMBER_OF_AD: + mo->mo_ad_memberof = ad_memberOf; + memberof_make_member_filter( mo ); + break; + + default: + assert( 0 ); + return 1; + } + + } else { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + mo->mo_dn = c->value_dn; + mo->mo_ndn = c->value_ndn; + break; + + case MO_DANGLING: + i = verb_to_mask( c->argv[ 1 ], dangling_mode ); + if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) { + return 1; + } + + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + mo->mo_flags |= dangling_mode[ i ].mask; + break; + + case MO_DANGLING_ERROR: + i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code ); + if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) { + mo->mo_dangling_err = slap_ldap_response_code[ i ].mask; + } else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) { + return 1; + } + break; + + case MO_REFINT: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREFINT; + + } else { + mo->mo_flags &= ~MEMBEROF_FREFINT; + } + break; + +#if 0 + case MO_REVERSE: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREVERSE; + + } else { + mo->mo_flags &= ~MEMBEROF_FREVERSE; + } + break; +#endif + + case MO_GROUP_OC: { + ObjectClass *oc = NULL; + + oc = oc_find( c->argv[ 1 ] ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to find group objectClass=\"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_oc_group = oc; + memberof_make_group_filter( mo ); + } break; + + case MO_MEMBER_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "member attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_ad_member = ad; + } break; + + case MO_MEMBER_OF_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "memberof attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_ad_memberof = ad; + memberof_make_member_filter( mo ); + } break; + + default: + assert( 0 ); + return 1; + } + } + + return 0; +} + +static int +memberof_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int rc; + + if ( !mo->mo_ad_memberof ) { + mo->mo_ad_memberof = ad_memberOf; + } + + if ( ! mo->mo_ad_member ) { + mo->mo_ad_member = ad_member; + } + + if ( ! mo->mo_oc_group ) { + mo->mo_oc_group = oc_group; + } + + if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) { + ber_dupbv( &mo->mo_dn, &be->be_rootdn ); + ber_dupbv( &mo->mo_ndn, &be->be_rootndn ); + } + + if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + memberof_make_group_filter( mo ); + } + + if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + memberof_make_member_filter( mo ); + } + + return 0; +} + +static int +memberof_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + if ( mo ) { + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ber_memfree( mo->mo_groupFilterstr.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ber_memfree( mo->mo_memberFilterstr.bv_val ); + } + + ber_memfree( mo ); + } + + return 0; +} + +static struct { + char *desc; + AttributeDescription **adp; +} as[] = { + { "( 1.2.840.113556.1.2.102 " + "NAME 'memberOf' " + "DESC 'Group that the entry belongs to' " + "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' " + "EQUALITY distinguishedNameMatch " /* added */ + "USAGE dSAOperation " /* added; questioned */ + "NO-USER-MODIFICATION " /* added */ + "X-ORIGIN 'iPlanet Delegated Administrator' )", + &ad_memberOf }, + { NULL } +}; + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ +int +memberof_initialize( void ) +{ + int code, i; + + for ( i = 0; as[ i ].desc != NULL; i++ ) { + code = register_at( as[ i ].desc, as[ i ].adp, 1 ); + if ( code && code != SLAP_SCHERR_ATTR_DUP ) { + Debug( LDAP_DEBUG_ANY, + "memberof_initialize: register_at #%d failed\n", + i ); + return code; + } + } + + memberof.on_bi.bi_type = "memberof"; + + memberof.on_bi.bi_db_init = memberof_db_init; + memberof.on_bi.bi_db_open = memberof_db_open; + memberof.on_bi.bi_db_destroy = memberof_db_destroy; + + memberof.on_bi.bi_op_add = memberof_op_add; + memberof.on_bi.bi_op_delete = memberof_op_delete; + memberof.on_bi.bi_op_modify = memberof_op_modify; + memberof.on_bi.bi_op_modrdn = memberof_op_modrdn; + + memberof.on_bi.bi_cf_ocs = mo_ocs; + + code = config_register_schema( mo_cfg, mo_ocs ); + if ( code ) return code; + + return overlay_register( &memberof ); +} + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return memberof_initialize(); +} +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_MEMBEROF */ diff --git a/servers/slapd/overlays/otp.c b/servers/slapd/overlays/otp.c new file mode 100644 index 0000000..c9c0332 --- /dev/null +++ b/servers/slapd/overlays/otp.c @@ -0,0 +1,974 @@ +/* otp.c - OATH 2-factor authentication module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2015-2022 The OpenLDAP Foundation. + * Portions Copyright 2015 by Howard Chu, Symas Corp. + * Portions Copyright 2016-2017 by Michael Ströder <michael@stroeder.com> + * Portions Copyright 2018 by OndÅ™ej KuznÃk, Symas Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work includes code from the lastbind overlay. + */ + +#include <portable.h> + +#ifdef SLAPD_OVER_OTP + +#if HAVE_STDINT_H +#include <stdint.h> +#endif + +#include <lber.h> +#include <lber_pvt.h> +#include "lutil.h" +#include <ac/stdlib.h> +#include <ac/ctype.h> +#include <ac/string.h> +/* include socket.h to get sys/types.h and/or winsock2.h */ +#include <ac/socket.h> + +#if HAVE_OPENSSL +#include <openssl/sha.h> +#include <openssl/hmac.h> + +#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_LENGTH +#define TOTP_SHA1 EVP_sha1() +#define TOTP_SHA224 EVP_sha224() +#define TOTP_SHA256 EVP_sha256() +#define TOTP_SHA384 EVP_sha384() +#define TOTP_SHA512 EVP_sha512() +#define TOTP_HMAC_CTX HMAC_CTX * + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static HMAC_CTX * +HMAC_CTX_new( void ) +{ + HMAC_CTX *ctx = OPENSSL_malloc( sizeof(*ctx) ); + if ( ctx != NULL ) { + HMAC_CTX_init( ctx ); + } + return ctx; +} + +static void +HMAC_CTX_free( HMAC_CTX *ctx ) +{ + if ( ctx != NULL ) { + HMAC_CTX_cleanup( ctx ); + OPENSSL_free( ctx ); + } +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +#define HMAC_setup( ctx, key, len, hash ) \ + ctx = HMAC_CTX_new(); \ + HMAC_Init_ex( ctx, key, len, hash, 0 ) +#define HMAC_crunch( ctx, buf, len ) HMAC_Update( ctx, buf, len ) +#define HMAC_finish( ctx, dig, dlen ) \ + HMAC_Final( ctx, dig, &dlen ); \ + HMAC_CTX_free( ctx ) + +#elif HAVE_GNUTLS +#include <nettle/hmac.h> + +#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_SIZE +#define TOTP_SHA1 &nettle_sha1 +#define TOTP_SHA224 &nettle_sha224 +#define TOTP_SHA256 &nettle_sha256 +#define TOTP_SHA384 &nettle_sha384 +#define TOTP_SHA512 &nettle_sha512 +#define TOTP_HMAC_CTX struct hmac_sha512_ctx + +#define HMAC_setup( ctx, key, len, hash ) \ + const struct nettle_hash *h = hash; \ + hmac_set_key( &ctx.outer, &ctx.inner, &ctx.state, h, len, key ) +#define HMAC_crunch( ctx, buf, len ) hmac_update( &ctx.state, h, len, buf ) +#define HMAC_finish( ctx, dig, dlen ) \ + hmac_digest( &ctx.outer, &ctx.inner, &ctx.state, h, h->digest_size, dig ); \ + dlen = h->digest_size + +#else +#error Unsupported crypto backend. +#endif + +#include "slap.h" +#include "slap-config.h" + +/* Schema from OATH-LDAP project by Michael Ströder */ + +static struct { + char *name, *oid; +} otp_oid[] = { + { "oath-ldap", "1.3.6.1.4.1.5427.1.389.4226" }, + { "oath-ldap-at", "oath-ldap:4" }, + { "oath-ldap-oc", "oath-ldap:6" }, + { NULL } +}; + +AttributeDescription *ad_oathOTPToken; +AttributeDescription *ad_oathSecret; +AttributeDescription *ad_oathOTPLength; +AttributeDescription *ad_oathHMACAlgorithm; + +AttributeDescription *ad_oathHOTPParams; +AttributeDescription *ad_oathHOTPToken; +AttributeDescription *ad_oathHOTPCounter; +AttributeDescription *ad_oathHOTPLookahead; + +AttributeDescription *ad_oathTOTPTimeStepPeriod; +AttributeDescription *ad_oathTOTPParams; +AttributeDescription *ad_oathTOTPToken; +AttributeDescription *ad_oathTOTPLastTimeStep; +AttributeDescription *ad_oathTOTPTimeStepWindow; +AttributeDescription *ad_oathTOTPTimeStepDrift; + +static struct otp_at { + char *schema; + AttributeDescription **adp; +} otp_at[] = { + { "( oath-ldap-at:1 " + "NAME 'oathSecret' " + "DESC 'OATH-LDAP: Shared Secret (possibly encrypted with public key in oathEncKey)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY octetStringMatch " + "SUBSTR octetStringSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )", + &ad_oathSecret }, + + { "( oath-ldap-at:2 " + "NAME 'oathTokenSerialNumber' " + "DESC 'OATH-LDAP: Proprietary hardware token serial number assigned by vendor' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64})" }, + + { "( oath-ldap-at:3 " + "NAME 'oathTokenIdentifier' " + "DESC 'OATH-LDAP: Globally unique OATH token identifier' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY caseIgnoreMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )" }, + + { "( oath-ldap-at:4 " + "NAME 'oathParamsEntry' " + "DESC 'OATH-LDAP: DN pointing to OATH parameter/policy object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP distinguishedName )" }, + { "( oath-ldap-at:4.1 " + "NAME 'oathTOTPTimeStepPeriod' " + "DESC 'OATH-LDAP: Time window for TOTP (seconds)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", + &ad_oathTOTPTimeStepPeriod }, + + { "( oath-ldap-at:5 " + "NAME 'oathOTPLength' " + "DESC 'OATH-LDAP: Length of OTP (number of digits)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )", + &ad_oathOTPLength }, + { "( oath-ldap-at:5.1 " + "NAME 'oathHOTPParams' " + "DESC 'OATH-LDAP: DN pointing to HOTP parameter object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathParamsEntry )", + &ad_oathHOTPParams }, + { "( oath-ldap-at:5.2 " + "NAME 'oathTOTPParams' " + "DESC 'OATH-LDAP: DN pointing to TOTP parameter object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathParamsEntry )", + &ad_oathTOTPParams }, + + { "( oath-ldap-at:6 " + "NAME 'oathHMACAlgorithm' " + "DESC 'OATH-LDAP: HMAC algorithm used for generating OTP values' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + &ad_oathHMACAlgorithm }, + + { "( oath-ldap-at:7 " + "NAME 'oathTimestamp' " + "DESC 'OATH-LDAP: Timestamp (not directly used).' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )" }, + { "( oath-ldap-at:7.1 " + "NAME 'oathLastFailure' " + "DESC 'OATH-LDAP: Timestamp of last failed OATH validation' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathTimestamp )" }, + { "( oath-ldap-at:7.2 " + "NAME 'oathLastLogin' " + "DESC 'OATH-LDAP: Timestamp of last successful OATH validation' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathTimestamp )" }, + { "( oath-ldap-at:7.3 " + "NAME 'oathSecretTime' " + "DESC 'OATH-LDAP: Timestamp of generation of oathSecret attribute.' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathTimestamp )" }, + + { "( oath-ldap-at:8 " + "NAME 'oathSecretMaxAge' " + "DESC 'OATH-LDAP: Time in seconds for which the shared secret (oathSecret) will be valid from oathSecretTime value.' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" }, + + { "( oath-ldap-at:9 " + "NAME 'oathToken' " + "DESC 'OATH-LDAP: DN pointing to OATH token object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP distinguishedName )" }, + { "( oath-ldap-at:9.1 " + "NAME 'oathHOTPToken' " + "DESC 'OATH-LDAP: DN pointing to OATH/HOTP token object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathToken )", + &ad_oathHOTPToken }, + { "( oath-ldap-at:9.2 " + "NAME 'oathTOTPToken' " + "DESC 'OATH-LDAP: DN pointing to OATH/TOTP token object' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathToken )", + &ad_oathTOTPToken }, + + { "( oath-ldap-at:10 " + "NAME 'oathCounter' " + "DESC 'OATH-LDAP: Counter for OATH data (not directly used)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" }, + { "( oath-ldap-at:10.1 " + "NAME 'oathFailureCount' " + "DESC 'OATH-LDAP: OATH failure counter' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )" }, + { "( oath-ldap-at:10.2 " + "NAME 'oathHOTPCounter' " + "DESC 'OATH-LDAP: Counter for HOTP' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )", + &ad_oathHOTPCounter }, + { "( oath-ldap-at:10.3 " + "NAME 'oathHOTPLookAhead' " + "DESC 'OATH-LDAP: Look-ahead window for HOTP' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )", + &ad_oathHOTPLookahead }, + { "( oath-ldap-at:10.5 " + "NAME 'oathThrottleLimit' " + "DESC 'OATH-LDAP: Failure throttle limit' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )" }, + { "( oath-ldap-at:10.6 " + "NAME 'oathTOTPLastTimeStep' " + "DESC 'OATH-LDAP: Last time step seen for TOTP (time/period)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )", + &ad_oathTOTPLastTimeStep }, + { "( oath-ldap-at:10.7 " + "NAME 'oathMaxUsageCount' " + "DESC 'OATH-LDAP: Maximum number of times a token can be used' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )" }, + { "( oath-ldap-at:10.8 " + "NAME 'oathTOTPTimeStepWindow' " + "DESC 'OATH-LDAP: Size of time step +/- tolerance window used for TOTP validation' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )", + &ad_oathTOTPTimeStepWindow }, + { "( oath-ldap-at:10.9 " + "NAME 'oathTOTPTimeStepDrift' " + "DESC 'OATH-LDAP: Last observed time step shift seen for TOTP' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "SUP oathCounter )", + &ad_oathTOTPTimeStepDrift }, + + { "( oath-ldap-at:11 " + "NAME 'oathSecretLength' " + "DESC 'OATH-LDAP: Length of plain-text shared secret (number of bytes)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" }, + + { "( oath-ldap-at:12 " + "NAME 'oathEncKey' " + "DESC 'OATH-LDAP: public key to be used for encrypting new shared secrets' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" }, + + { "( oath-ldap-at:13 " + "NAME 'oathResultCode' " + "DESC 'OATH-LDAP: LDAP resultCode to use in response' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" }, + { "( oath-ldap-at:13.1 " + "NAME 'oathSuccessResultCode' " + "DESC 'OATH-LDAP: success resultCode to use in bind/compare response' " + "X-ORIGIN 'OATH-LDAP' " + "SUP oathResultCode )" }, + { "( oath-ldap-at:13.2 " + "NAME 'oathFailureResultCode' " + "DESC 'OATH-LDAP: failure resultCode to use in bind/compare response' " + "X-ORIGIN 'OATH-LDAP' " + "SUP oathResultCode )" }, + + { "( oath-ldap-at:14 " + "NAME 'oathTokenPIN' " + "DESC 'OATH-LDAP: Configuration PIN (possibly encrypted with oathEncKey)' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" }, + + { "( oath-ldap-at:15 " + "NAME 'oathMessage' " + "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' " + "X-ORIGIN 'OATH-LDAP' " + "SINGLE-VALUE " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )" }, + { "( oath-ldap-at:15.1 " + "NAME 'oathSuccessMessage' " + "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' " + "X-ORIGIN 'OATH-LDAP' " + "SUP oathMessage )" }, + { "( oath-ldap-at:15.2 " + "NAME 'oathFailureMessage' " + "DESC 'OATH-LDAP: failure diagnosticMessage to use in bind/compare response' " + "X-ORIGIN 'OATH-LDAP' " + "SUP oathMessage )" }, + + { NULL } +}; + +ObjectClass *oc_oathOTPUser; +ObjectClass *oc_oathHOTPToken; +ObjectClass *oc_oathTOTPToken; +ObjectClass *oc_oathHOTPParams; +ObjectClass *oc_oathTOTPParams; + +static struct otp_oc { + char *schema; + ObjectClass **ocp; +} otp_oc[] = { + { "( oath-ldap-oc:1 " + "NAME 'oathUser' " + "DESC 'OATH-LDAP: User Object' " + "X-ORIGIN 'OATH-LDAP' " + "ABSTRACT )", + &oc_oathOTPUser }, + { "( oath-ldap-oc:1.1 " + "NAME 'oathHOTPUser' " + "DESC 'OATH-LDAP: HOTP user object' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathUser " + "MAY ( oathHOTPToken ) )" }, + { "( oath-ldap-oc:1.2 " + "NAME 'oathTOTPUser' " + "DESC 'OATH-LDAP: TOTP user object' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathUser " + "MUST ( oathTOTPToken ) )" }, + { "( oath-ldap-oc:2 " + "NAME 'oathParams' " + "DESC 'OATH-LDAP: Parameter object' " + "X-ORIGIN 'OATH-LDAP' " + "ABSTRACT " + "MUST ( oathOTPLength $ oathHMACAlgorithm ) " + "MAY ( oathSecretMaxAge $ oathSecretLength $ " + "oathMaxUsageCount $ oathThrottleLimit $ oathEncKey $ " + "oathSuccessResultCode $ oathSuccessMessage $ " + "oathFailureResultCode $ oathFailureMessage ) )" }, + { "( oath-ldap-oc:2.1 " + "NAME 'oathHOTPParams' " + "DESC 'OATH-LDAP: HOTP parameter object' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathParams " + "MUST ( oathHOTPLookAhead ) )", + &oc_oathHOTPParams }, + { "( oath-ldap-oc:2.2 " + "NAME 'oathTOTPParams' " + "DESC 'OATH-LDAP: TOTP parameter object' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathParams " + "MUST ( oathTOTPTimeStepPeriod ) " + "MAY ( oathTOTPTimeStepWindow ) )", + &oc_oathTOTPParams }, + { "( oath-ldap-oc:3 " + "NAME 'oathToken' " + "DESC 'OATH-LDAP: User Object' " + "X-ORIGIN 'OATH-LDAP' " + "ABSTRACT " + "MAY ( oathSecret $ oathSecretTime $ " + "oathLastLogin $ oathFailureCount $ oathLastFailure $ " + "oathTokenSerialNumber $ oathTokenIdentifier $ oathTokenPIN ) )" }, + { "( oath-ldap-oc:3.1 " + "NAME 'oathHOTPToken' " + "DESC 'OATH-LDAP: HOTP token object' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathToken " + "MAY ( oathHOTPParams $ oathHOTPCounter ) )", + &oc_oathHOTPToken }, + { "( oath-ldap-oc:3.2 " + "NAME 'oathTOTPToken' " + "DESC 'OATH-LDAP: TOTP token' " + "X-ORIGIN 'OATH-LDAP' " + "AUXILIARY " + "SUP oathToken " + "MAY ( oathTOTPParams $ oathTOTPLastTimeStep $ oathTOTPTimeStepDrift ) )", + &oc_oathTOTPToken }, + { NULL } +}; + +typedef struct myval { + ber_len_t mv_len; + void *mv_val; +} myval; + +static void +do_hmac( const void *hash, myval *key, myval *data, myval *out ) +{ + TOTP_HMAC_CTX ctx; + unsigned int digestLen; + + HMAC_setup( ctx, key->mv_val, key->mv_len, hash ); + HMAC_crunch( ctx, data->mv_val, data->mv_len ); + HMAC_finish( ctx, out->mv_val, digestLen ); + out->mv_len = digestLen; +} + +#define MAX_DIGITS 8 +static const int DIGITS_POWER[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, +}; + +static const void * +otp_choose_mech( struct berval *oid ) +{ + /* RFC 8018 OIDs */ + const struct berval oid_hmacwithSHA1 = BER_BVC("1.2.840.113549.2.7"); + const struct berval oid_hmacwithSHA224 = BER_BVC("1.2.840.113549.2.8"); + const struct berval oid_hmacwithSHA256 = BER_BVC("1.2.840.113549.2.9"); + const struct berval oid_hmacwithSHA384 = BER_BVC("1.2.840.113549.2.10"); + const struct berval oid_hmacwithSHA512 = BER_BVC("1.2.840.113549.2.11"); + + if ( !ber_bvcmp( &oid_hmacwithSHA1, oid ) ) { + return TOTP_SHA1; + } else if ( !ber_bvcmp( &oid_hmacwithSHA224, oid ) ) { + return TOTP_SHA224; + } else if ( !ber_bvcmp( &oid_hmacwithSHA256, oid ) ) { + return TOTP_SHA256; + } else if ( !ber_bvcmp( &oid_hmacwithSHA384, oid ) ) { + return TOTP_SHA384; + } else if ( !ber_bvcmp( &oid_hmacwithSHA512, oid ) ) { + return TOTP_SHA512; + } + + Debug( LDAP_DEBUG_TRACE, "otp_choose_mech: " + "hmac OID %s unsupported\n", + oid->bv_val ); + return NULL; +} + +static void +generate( + struct berval *bv, + uint64_t tval, + int digits, + struct berval *out, + const void *mech ) +{ + unsigned char digest[TOTP_SHA512_DIGEST_LENGTH]; + myval digval; + myval key, data; + unsigned char msg[8]; + int i, offset, res, otp; + +#if WORDS_BIGENDIAN + *(uint64_t *)msg = tval; +#else + for ( i = 7; i >= 0; i-- ) { + msg[i] = tval & 0xff; + tval >>= 8; + } +#endif + + key.mv_len = bv->bv_len; + key.mv_val = bv->bv_val; + + data.mv_val = msg; + data.mv_len = sizeof(msg); + + digval.mv_val = digest; + digval.mv_len = sizeof(digest); + do_hmac( mech, &key, &data, &digval ); + + offset = digest[digval.mv_len - 1] & 0xf; + res = ( (digest[offset] & 0x7f) << 24 ) | + ( ( digest[offset + 1] & 0xff ) << 16 ) | + ( ( digest[offset + 2] & 0xff ) << 8 ) | + ( digest[offset + 3] & 0xff ); + + otp = res % DIGITS_POWER[digits]; + out->bv_len = snprintf( out->bv_val, out->bv_len, "%0*d", digits, otp ); +} + +static int +otp_bind_response( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_err == LDAP_SUCCESS ) { + /* If the bind succeeded, return our result */ + rs->sr_err = LDAP_INVALID_CREDENTIALS; + } + return SLAP_CB_CONTINUE; +} + +static long +otp_hotp( Operation *op, Entry *token ) +{ + char outbuf[MAX_DIGITS + 1]; + Entry *params = NULL; + Attribute *a; + BerValue *secret, client_otp; + const void *mech; + long last_step = -1, found = -1; + int i, otp_len, window; + + a = attr_find( token->e_attrs, ad_oathSecret ); + secret = &a->a_vals[0]; + + a = attr_find( token->e_attrs, ad_oathHOTPCounter ); + if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_hotp: " + "could not parse oathHOTPCounter value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + + a = attr_find( token->e_attrs, ad_oathHOTPParams ); + if ( !a || + be_entry_get_rw( op, &a->a_nvals[0], oc_oathHOTPParams, NULL, 0, + ¶ms ) ) { + goto done; + } + + a = attr_find( params->e_attrs, ad_oathOTPLength ); + if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_hotp: " + "could not parse oathOTPLength value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) { + /* Client didn't even send the token, fail immediately */ + goto done; + } + + a = attr_find( params->e_attrs, ad_oathHOTPLookahead ); + if ( lutil_atoi( &window, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_hotp: " + "could not parse oathHOTPLookAhead value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + window++; + + a = attr_find( params->e_attrs, ad_oathHMACAlgorithm ); + if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) { + goto done; + } + be_entry_release_r( op, params ); + params = NULL; + + /* We are provided "password" + "OTP", split accordingly */ + client_otp.bv_len = otp_len; + client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len; + + /* If check succeeds, advance the step counter accordingly */ + for ( i = 1; i <= window; i++ ) { + BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) }; + + generate( secret, last_step + i, otp_len, &out, mech ); + if ( !ber_bvcmp( &out, &client_otp ) ) { + found = last_step + i; + /* Would we leak information if we stopped right now? */ + } + } + + if ( found >= 0 ) { + /* OTP check passed, trim the password */ + op->orb_cred.bv_len -= otp_len; + Debug( LDAP_DEBUG_STATS, "%s HOTP token %s no. %ld redeemed\n", + op->o_log_prefix, token->e_name.bv_val, found ); + } + +done: + memset( outbuf, 0, sizeof(outbuf) ); + if ( params ) { + be_entry_release_r( op, params ); + } + return found; +} + +static long +otp_totp( Operation *op, Entry *token, long *drift ) +{ + char outbuf[MAX_DIGITS + 1]; + Entry *params = NULL; + Attribute *a; + BerValue *secret, client_otp; + const void *mech; + long t, last_step = -1, found = -1, window = 0, old_drift; + int i, otp_len, time_step; + + a = attr_find( token->e_attrs, ad_oathSecret ); + secret = &a->a_vals[0]; + + a = attr_find( token->e_attrs, ad_oathTOTPLastTimeStep ); + if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_totp: " + "could not parse oathTOTPLastTimeStep value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + + a = attr_find( token->e_attrs, ad_oathTOTPParams ); + if ( !a || + be_entry_get_rw( op, &a->a_nvals[0], oc_oathTOTPParams, NULL, 0, + ¶ms ) ) { + goto done; + } + + a = attr_find( params->e_attrs, ad_oathTOTPTimeStepPeriod ); + if ( lutil_atoi( &time_step, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_totp: " + "could not parse oathTOTPTimeStepPeriod value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + + a = attr_find( params->e_attrs, ad_oathTOTPTimeStepWindow ); + if ( a && lutil_atol( &window, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_totp: " + "could not parse oathTOTPTimeStepWindow value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + + a = attr_find( params->e_attrs, ad_oathTOTPTimeStepDrift ); + if ( a && lutil_atol( drift, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_totp: " + "could not parse oathTOTPTimeStepDrift value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + old_drift = *drift; + t = op->o_time / time_step + *drift; + + a = attr_find( params->e_attrs, ad_oathOTPLength ); + if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "otp_totp: " + "could not parse oathOTPLength value %s\n", + a->a_vals[0].bv_val ); + goto done; + } + if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) { + /* Client didn't even send the token, fail immediately */ + goto done; + } + + a = attr_find( params->e_attrs, ad_oathHMACAlgorithm ); + if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) { + goto done; + } + be_entry_release_r( op, params ); + params = NULL; + + /* We are provided "password" + "OTP", split accordingly */ + client_otp.bv_len = otp_len; + client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len; + + /* If check succeeds, advance the step counter accordingly */ + /* Negation of A001057 series that enumerates all integers: + * (0, -1, 1, -2, 2, ...) */ + for ( i = 0; i >= -window; i = ( i < 0 ) ? -i : ~i ) { + BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) }; + + if ( t + i <= last_step ) continue; + + generate( secret, t + i, otp_len, &out, mech ); + if ( !ber_bvcmp( &out, &client_otp ) ) { + found = t + i; + *drift = old_drift + i; + /* Would we leak information if we stopped right now? */ + } + } + + /* OTP check passed, trim the password */ + if ( found >= 0 ) { + assert( found > last_step ); + + op->orb_cred.bv_len -= otp_len; + Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with new drift of %ld\n", + op->o_log_prefix, token->e_name.bv_val, *drift ); + } + +done: + memset( outbuf, 0, sizeof(outbuf) ); + if ( params ) { + be_entry_release_r( op, params ); + } + return found; +} + +static int +otp_op_bind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + BerValue totpdn = BER_BVNULL, hotpdn = BER_BVNULL, ndn; + Entry *user = NULL, *token = NULL; + AttributeDescription *ad = NULL, *drift_ad = NULL; + Attribute *a; + long t = -1, drift = 0; + int rc = SLAP_CB_CONTINUE; + + if ( op->oq_bind.rb_method != LDAP_AUTH_SIMPLE ) { + return rc; + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + if ( be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &user ) ) { + goto done; + } + + if ( !is_entry_objectclass_or_sub( user, oc_oathOTPUser ) ) { + be_entry_release_r( op, user ); + goto done; + } + + if ( (a = attr_find( user->e_attrs, ad_oathTOTPToken )) ) { + ber_dupbv_x( &totpdn, &a->a_nvals[0], op->o_tmpmemctx ); + } + + if ( (a = attr_find( user->e_attrs, ad_oathHOTPToken )) ) { + ber_dupbv_x( &hotpdn, &a->a_nvals[0], op->o_tmpmemctx ); + } + be_entry_release_r( op, user ); + + if ( !BER_BVISNULL( &totpdn ) && + be_entry_get_rw( op, &totpdn, oc_oathTOTPToken, ad_oathSecret, 0, + &token ) == LDAP_SUCCESS ) { + ndn = totpdn; + ad = ad_oathTOTPLastTimeStep; + drift_ad = ad_oathTOTPTimeStepDrift; + t = otp_totp( op, token, &drift ); + be_entry_release_r( op, token ); + token = NULL; + } + if ( t < 0 && !BER_BVISNULL( &hotpdn ) && + be_entry_get_rw( op, &hotpdn, oc_oathHOTPToken, ad_oathSecret, 0, + &token ) == LDAP_SUCCESS ) { + ndn = hotpdn; + ad = ad_oathHOTPCounter; + t = otp_hotp( op, token ); + be_entry_release_r( op, token ); + token = NULL; + } + + /* If check succeeds, advance the step counter and drift accordingly */ + if ( t >= 0 ) { + char outbuf[32], drift_buf[32]; + Operation op2; + Opheader oh; + Modifications mod[2], *m = mod; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb = { .sc_response = &slap_null_cb }; + BerValue bv[2], bv_drift[2]; + + bv[0].bv_val = outbuf; + bv[0].bv_len = snprintf( bv[0].bv_val, sizeof(outbuf), "%ld", t ); + BER_BVZERO( &bv[1] ); + + m->sml_numvals = 1; + m->sml_values = bv; + m->sml_nvalues = NULL; + m->sml_desc = ad; + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = SLAP_MOD_INTERNAL; + + if ( drift_ad ) { + m->sml_next = &mod[1]; + + bv_drift[0].bv_val = drift_buf; + bv_drift[0].bv_len = snprintf( + bv_drift[0].bv_val, sizeof(drift_buf), "%ld", drift ); + BER_BVZERO( &bv_drift[1] ); + + m++; + m->sml_numvals = 1; + m->sml_values = bv_drift; + m->sml_nvalues = NULL; + m->sml_desc = drift_ad; + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = SLAP_MOD_INTERNAL; + } + m->sml_next = NULL; + + op2 = *op; + oh = *op->o_hdr; + op2.o_hdr = &oh; + + op2.o_callback = &cb; + + op2.o_tag = LDAP_REQ_MODIFY; + op2.orm_modlist = mod; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + op2.o_req_dn = ndn; + op2.o_req_ndn = ndn; + op2.o_opid = -1; + + op2.o_bd->be_modify( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + rc = LDAP_OTHER; + goto done; + } + } else { + /* Client failed the bind, but we still have to pass it over to the + * backend and fail the Bind later */ + slap_callback *cb; + cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_response = otp_bind_response; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + +done: + if ( !BER_BVISNULL( &hotpdn ) ) { + ber_memfree_x( hotpdn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &totpdn ) ) { + ber_memfree_x( totpdn.bv_val, op->o_tmpmemctx ); + } + op->o_bd->bd_info = (BackendInfo *)on; + return rc; +} + +static slap_overinst otp; + +int +otp_initialize( void ) +{ + ConfigArgs ca; + char *argv[4]; + int i; + + otp.on_bi.bi_type = "otp"; + otp.on_bi.bi_op_bind = otp_op_bind; + + ca.argv = argv; + argv[0] = "otp"; + ca.argv = argv; + ca.argc = 3; + ca.fname = argv[0]; + + argv[3] = NULL; + for ( i = 0; otp_oid[i].name; i++ ) { + argv[1] = otp_oid[i].name; + argv[2] = otp_oid[i].oid; + parse_oidm( &ca, 0, NULL ); + } + + /* schema integration */ + for ( i = 0; otp_at[i].schema; i++ ) { + if ( register_at( otp_at[i].schema, otp_at[i].adp, 0 ) ) { + Debug( LDAP_DEBUG_ANY, "otp_initialize: " + "register_at failed\n" ); + return -1; + } + } + + for ( i = 0; otp_oc[i].schema; i++ ) { + if ( register_oc( otp_oc[i].schema, otp_oc[i].ocp, 0 ) ) { + Debug( LDAP_DEBUG_ANY, "otp_initialize: " + "register_oc failed\n" ); + return -1; + } + } + + return overlay_register( &otp ); +} + +#if SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return otp_initialize(); +} +#endif /* SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_OTP) */ diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c new file mode 100644 index 0000000..8290200 --- /dev/null +++ b/servers/slapd/overlays/overlays.c @@ -0,0 +1,44 @@ +/* overlays.c - Static overlay framework */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Copyright 2003 by Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include "slap.h" + +extern OverlayInit slap_oinfo[]; + +int +overlay_init(void) +{ + int i, rc = 0; + + for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) { + rc = slap_oinfo[i].ov_init(); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "%s overlay setup failed, err %d\n", + slap_oinfo[i].ov_type, rc ); + break; + } + } + + return rc; +} diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c new file mode 100644 index 0000000..423c196 --- /dev/null +++ b/servers/slapd/overlays/pcache.c @@ -0,0 +1,5814 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2003 IBM Corporation. + * Portions Copyright 2003-2009 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Apurva Kumar for inclusion + * in OpenLDAP Software and subsequently rewritten by Howard Chu. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_PROXYCACHE + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/time.h> + +#include "slap.h" +#include "lutil.h" +#include "ldap_rq.h" +#include "ldap_avl.h" + +#include "../back-monitor/back-monitor.h" + +#include "slap-config.h" + +/* + * Control that allows to access the private DB + * instead of the public one + */ +#define PCACHE_CONTROL_PRIVDB "1.3.6.1.4.1.4203.666.11.9.5.1" + +/* + * Extended Operation that allows to remove a query from the cache + */ +#define PCACHE_EXOP_QUERY_DELETE "1.3.6.1.4.1.4203.666.11.9.6.1" + +/* + * Monitoring + */ +#define PCACHE_MONITOR + +/* query cache structs */ +/* query */ + +typedef struct Query_s { + Filter* filter; /* Search Filter */ + struct berval base; /* Search Base */ + int scope; /* Search scope */ +} Query; + +struct query_template_s; + +typedef struct Qbase_s { + TAvlnode *scopes[4]; /* threaded AVL trees of cached queries */ + struct berval base; + int queries; +} Qbase; + +/* struct representing a cached query */ +typedef struct cached_query_s { + Filter *filter; + Filter *first; + Qbase *qbase; + int scope; + struct berval q_uuid; /* query identifier */ + int q_sizelimit; + struct query_template_s *qtemp; /* template of the query */ + time_t expiry_time; /* time till the query is considered invalid */ + time_t refresh_time; /* time till the query is refreshed */ + time_t bindref_time; /* time till the bind is refreshed */ + int bind_refcnt; /* number of bind operation referencing this query */ + unsigned long answerable_cnt; /* how many times it was answerable */ + int refcnt; /* references since last refresh */ + int in_lru; /* query is in LRU list */ + ldap_pvt_thread_mutex_t answerable_cnt_mutex; + struct cached_query_s *next; /* next query in the template */ + struct cached_query_s *prev; /* previous query in the template */ + struct cached_query_s *lru_up; /* previous query in the LRU list */ + struct cached_query_s *lru_down; /* next query in the LRU list */ + ldap_pvt_thread_rdwr_t rwlock; +} CachedQuery; + +/* + * URL representation: + * + * ldap:///<base>??<scope>?<filter>?x-uuid=<uid>,x-template=<template>,x-attrset=<attrset>,x-expiry=<expiry>,x-refresh=<refresh> + * + * <base> ::= CachedQuery.qbase->base + * <scope> ::= CachedQuery.scope + * <filter> ::= filter2bv(CachedQuery.filter) + * <uuid> ::= CachedQuery.q_uuid + * <attrset> ::= CachedQuery.qtemp->attr_set_index + * <expiry> ::= CachedQuery.expiry_time + * <refresh> ::= CachedQuery.refresh_time + * + * quick hack: parse URI, call add_query() and then fix + * CachedQuery.expiry_time and CachedQuery.q_uuid + * + * NOTE: if the <attrset> changes, all stored URLs will be invalidated. + */ + +/* + * Represents a set of projected attributes. + */ + +struct attr_set { + struct query_template_s *templates; + AttributeName* attrs; /* specifies the set */ + unsigned flags; +#define PC_CONFIGURED (0x1) +#define PC_REFERENCED (0x2) +#define PC_GOT_OC (0x4) + int count; /* number of attributes */ +}; + +/* struct representing a query template + * e.g. template string = &(cn=)(mail=) + */ +typedef struct query_template_s { + struct query_template_s *qtnext; + struct query_template_s *qmnext; + + Avlnode* qbase; + CachedQuery* query; /* most recent query cached for the template */ + CachedQuery* query_last; /* oldest query cached for the template */ + ldap_pvt_thread_rdwr_t t_rwlock; /* Rd/wr lock for accessing queries in the template */ + struct berval querystr; /* Filter string corresponding to the QT */ + struct berval bindbase; /* base DN for Bind request */ + struct berval bindfilterstr; /* Filter string for Bind request */ + struct berval bindftemp; /* bind filter template */ + Filter *bindfilter; + AttributeDescription **bindfattrs; /* attrs to substitute in ftemp */ + + int bindnattrs; /* number of bindfattrs */ + int bindscope; + int attr_set_index; /* determines the projected attributes */ + int no_of_queries; /* Total number of queries in the template */ + time_t ttl; /* TTL for the queries of this template */ + time_t negttl; /* TTL for negative results */ + time_t limitttl; /* TTL for sizelimit exceeding results */ + time_t ttr; /* time to refresh */ + time_t bindttr; /* TTR for cached binds */ + struct attr_set t_attrs; /* filter attrs + attr_set */ +} QueryTemplate; + +typedef enum { + PC_IGNORE = 0, + PC_POSITIVE, + PC_NEGATIVE, + PC_SIZELIMIT +} pc_caching_reason_t; + +static const char *pc_caching_reason_str[] = { + "IGNORE", + "POSITIVE", + "NEGATIVE", + "SIZELIMIT", + + NULL +}; + +struct query_manager_s; + +/* prototypes for functions for 1) query containment + * 2) query addition, 3) cache replacement + */ +typedef CachedQuery *(QCfunc)(Operation *op, struct query_manager_s*, + Query*, QueryTemplate*); +typedef CachedQuery *(AddQueryfunc)(Operation *op, struct query_manager_s*, + Query*, QueryTemplate*, pc_caching_reason_t, int wlock); +typedef void (CRfunc)(struct query_manager_s*, struct berval*); + +/* LDAP query cache */ +typedef struct query_manager_s { + struct attr_set* attr_sets; /* possible sets of projected attributes */ + QueryTemplate* templates; /* cacheable templates */ + + CachedQuery* lru_top; /* top and bottom of LRU list */ + CachedQuery* lru_bottom; + + ldap_pvt_thread_mutex_t lru_mutex; /* mutex for accessing LRU list */ + + /* Query cache methods */ + QCfunc *qcfunc; /* Query containment*/ + CRfunc *crfunc; /* cache replacement */ + AddQueryfunc *addfunc; /* add query */ +} query_manager; + +/* LDAP query cache manager */ +typedef struct cache_manager_s { + BackendDB db; /* underlying database */ + unsigned long num_cached_queries; /* total number of cached queries */ + unsigned long max_queries; /* upper bound on # of cached queries */ + int save_queries; /* save cached queries across restarts */ + int check_cacheability; /* check whether a query is cacheable */ + int numattrsets; /* number of attribute sets */ + int cur_entries; /* current number of entries cached */ + int max_entries; /* max number of entries cached */ + int num_entries_limit; /* max # of entries in a cacheable query */ + + char response_cb; /* install the response callback + * at the tail of the callback list */ +#define PCACHE_RESPONSE_CB_HEAD 0 +#define PCACHE_RESPONSE_CB_TAIL 1 + char defer_db_open; /* defer open for online add */ + char cache_binds; /* cache binds or just passthru */ + + time_t cc_period; /* interval between successive consistency checks (sec) */ +#define PCACHE_CC_PAUSED 1 +#define PCACHE_CC_OFFLINE 2 + int cc_paused; + void *cc_arg; + + ldap_pvt_thread_mutex_t cache_mutex; + + query_manager* qm; /* query cache managed by the cache manager */ + +#ifdef PCACHE_MONITOR + void *monitor_cb; + struct berval monitor_ndn; +#endif /* PCACHE_MONITOR */ +} cache_manager; + +#ifdef PCACHE_MONITOR +static int pcache_monitor_db_init( BackendDB *be ); +static int pcache_monitor_db_open( BackendDB *be ); +static int pcache_monitor_db_close( BackendDB *be ); +static int pcache_monitor_db_destroy( BackendDB *be ); +#endif /* PCACHE_MONITOR */ + +static int pcache_debug; + +#ifdef PCACHE_CONTROL_PRIVDB +static int privDB_cid; +#endif /* PCACHE_CONTROL_PRIVDB */ + +static AttributeDescription *ad_queryId, *ad_cachedQueryURL; + +#ifdef PCACHE_MONITOR +static AttributeDescription *ad_numQueries, *ad_numEntries; +static ObjectClass *oc_olmPCache; +#endif /* PCACHE_MONITOR */ + +static struct { + char *name; + char *oid; +} s_oid[] = { + { "PCacheOID", "1.3.6.1.4.1.4203.666.11.9.1" }, + { "PCacheAttributes", "PCacheOID:1" }, + { "PCacheObjectClasses", "PCacheOID:2" }, + + { NULL } +}; + +static struct { + char *desc; + AttributeDescription **adp; +} s_ad[] = { + { "( PCacheAttributes:1 " + "NAME 'pcacheQueryID' " + "DESC 'ID of query the entry belongs to, formatted as a UUID' " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40{64} " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_queryId }, + { "( PCacheAttributes:2 " + "NAME 'pcacheQueryURL' " + "DESC 'URI describing a cached query' " + "EQUALITY caseExactMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_cachedQueryURL }, +#ifdef PCACHE_MONITOR + { "( PCacheAttributes:3 " + "NAME 'pcacheNumQueries' " + "DESC 'Number of cached queries' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_numQueries }, + { "( PCacheAttributes:4 " + "NAME 'pcacheNumEntries' " + "DESC 'Number of cached entries' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_numEntries }, +#endif /* PCACHE_MONITOR */ + + { NULL } +}; + +static struct { + char *desc; + ObjectClass **ocp; +} s_oc[] = { +#ifdef PCACHE_MONITOR + /* augments an existing object, so it must be AUXILIARY */ + { "( PCacheObjectClasses:1 " + "NAME ( 'olmPCache' ) " + "SUP top AUXILIARY " + "MAY ( " + "pcacheQueryURL " + "$ pcacheNumQueries " + "$ pcacheNumEntries " + " ) )", + &oc_olmPCache }, +#endif /* PCACHE_MONITOR */ + + { NULL } +}; + +static int +filter2template( + Operation *op, + Filter *f, + struct berval *fstr ); + +static CachedQuery * +add_query( + Operation *op, + query_manager* qm, + Query* query, + QueryTemplate *templ, + pc_caching_reason_t why, + int wlock); + +static int +remove_query_data( + Operation *op, + struct berval *query_uuid ); + +/* + * Turn a cached query into its URL representation + */ +static int +query2url( Operation *op, CachedQuery *q, struct berval *urlbv, int dolock ) +{ + struct berval bv_scope, + bv_filter; + char attrset_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + expiry_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + refresh_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + answerable_buf[ LDAP_PVT_INTTYPE_CHARS( unsigned long ) ], + *ptr; + ber_len_t attrset_len, + expiry_len, + refresh_len, + answerable_len; + + if ( dolock ) { + ldap_pvt_thread_rdwr_rlock( &q->rwlock ); + } + + ldap_pvt_scope2bv( q->scope, &bv_scope ); + filter2bv_x( op, q->filter, &bv_filter ); + attrset_len = sprintf( attrset_buf, + "%lu", (unsigned long)q->qtemp->attr_set_index ); + expiry_len = sprintf( expiry_buf, + "%lu", (unsigned long)q->expiry_time ); + answerable_len = snprintf( answerable_buf, sizeof( answerable_buf ), + "%lu", q->answerable_cnt ); + if ( q->refresh_time ) + refresh_len = sprintf( refresh_buf, + "%lu", (unsigned long)q->refresh_time ); + else + refresh_len = 0; + + urlbv->bv_len = STRLENOF( "ldap:///" ) + + q->qbase->base.bv_len + + STRLENOF( "??" ) + + bv_scope.bv_len + + STRLENOF( "?" ) + + bv_filter.bv_len + + STRLENOF( "?x-uuid=" ) + + q->q_uuid.bv_len + + STRLENOF( ",x-attrset=" ) + + attrset_len + + STRLENOF( ",x-expiry=" ) + + expiry_len + + STRLENOF( ",x-answerable=" ) + + answerable_len; + if ( refresh_len ) + urlbv->bv_len += STRLENOF( ",x-refresh=" ) + + refresh_len; + + ptr = urlbv->bv_val = ber_memalloc_x( urlbv->bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( ptr, "ldap:///" ); + ptr = lutil_strcopy( ptr, q->qbase->base.bv_val ); + ptr = lutil_strcopy( ptr, "??" ); + ptr = lutil_strcopy( ptr, bv_scope.bv_val ); + ptr = lutil_strcopy( ptr, "?" ); + ptr = lutil_strcopy( ptr, bv_filter.bv_val ); + ptr = lutil_strcopy( ptr, "?x-uuid=" ); + ptr = lutil_strcopy( ptr, q->q_uuid.bv_val ); + ptr = lutil_strcopy( ptr, ",x-attrset=" ); + ptr = lutil_strcopy( ptr, attrset_buf ); + ptr = lutil_strcopy( ptr, ",x-expiry=" ); + ptr = lutil_strcopy( ptr, expiry_buf ); + ptr = lutil_strcopy( ptr, ",x-answerable=" ); + ptr = lutil_strcopy( ptr, answerable_buf ); + if ( refresh_len ) { + ptr = lutil_strcopy( ptr, ",x-refresh=" ); + ptr = lutil_strcopy( ptr, refresh_buf ); + } + + ber_memfree_x( bv_filter.bv_val, op->o_tmpmemctx ); + + if ( dolock ) { + ldap_pvt_thread_rdwr_runlock( &q->rwlock ); + } + + return 0; +} + +/* Find and record the empty filter clauses */ + +static int +ftemp_attrs( struct berval *ftemp, struct berval *template, + AttributeDescription ***ret, const char **text ) +{ + int i; + int attr_cnt=0; + struct berval bv; + char *p1, *p2, *t1; + AttributeDescription *ad; + AttributeDescription **descs = NULL; + char *temp2; + + temp2 = ch_malloc( ftemp->bv_len + 1 ); + p1 = ftemp->bv_val; + t1 = temp2; + + *ret = NULL; + + for (;;) { + while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) + *t1++ = *p1++; + + p2 = strchr( p1, '=' ); + if ( !p2 ) { + if ( !descs ) { + ch_free( temp2 ); + return -1; + } + break; + } + i = p2 - p1; + AC_MEMCPY( t1, p1, i ); + t1 += i; + *t1++ = '='; + + if ( p2[-1] == '<' || p2[-1] == '>' ) p2--; + bv.bv_val = p1; + bv.bv_len = p2 - p1; + ad = NULL; + i = slap_bv2ad( &bv, &ad, text ); + if ( i ) { + ch_free( temp2 ); + ch_free( descs ); + return -1; + } + if ( *p2 == '<' || *p2 == '>' ) p2++; + if ( p2[1] != ')' ) { + p2++; + while ( *p2 != ')' ) p2++; + p1 = p2; + continue; + } + + descs = (AttributeDescription **)ch_realloc(descs, + (attr_cnt + 2)*sizeof(AttributeDescription *)); + + descs[attr_cnt++] = ad; + + p1 = p2+1; + } + *t1 = '\0'; + descs[attr_cnt] = NULL; + *ret = descs; + template->bv_val = temp2; + template->bv_len = t1 - temp2; + return attr_cnt; +} + +static int +template_attrs( char *template, struct attr_set *set, AttributeName **ret, + const char **text ) +{ + int got_oc = 0; + int alluser = 0; + int allop = 0; + int i; + int attr_cnt; + int t_cnt = 0; + struct berval bv; + char *p1, *p2; + AttributeDescription *ad; + AttributeName *attrs; + + p1 = template; + + *ret = NULL; + + attrs = ch_calloc( set->count + 1, sizeof(AttributeName) ); + for ( i=0; i < set->count; i++ ) + attrs[i] = set->attrs[i]; + attr_cnt = i; + alluser = an_find( attrs, slap_bv_all_user_attrs ); + allop = an_find( attrs, slap_bv_all_operational_attrs ); + + for (;;) { + while ( *p1 == '(' || *p1 == '&' || *p1 == '|' || *p1 == ')' ) p1++; + p2 = strchr( p1, '=' ); + if ( !p2 ) + break; + if ( p2[-1] == '<' || p2[-1] == '>' ) p2--; + bv.bv_val = p1; + bv.bv_len = p2 - p1; + ad = NULL; + i = slap_bv2ad( &bv, &ad, text ); + if ( i ) { + ch_free( attrs ); + return -1; + } + t_cnt++; + + if ( ad == slap_schema.si_ad_objectClass ) + got_oc = 1; + + if ( is_at_operational(ad->ad_type)) { + if ( allop ) { + goto bottom; + } + } else if ( alluser ) { + goto bottom; + } + if ( !ad_inlist( ad, attrs )) { + attrs = (AttributeName *)ch_realloc(attrs, + (attr_cnt + 2)*sizeof(AttributeName)); + + attrs[attr_cnt].an_desc = ad; + attrs[attr_cnt].an_name = ad->ad_cname; + attrs[attr_cnt].an_oc = NULL; + attrs[attr_cnt].an_flags = 0; + BER_BVZERO( &attrs[attr_cnt+1].an_name ); + attr_cnt++; + } + +bottom: + p1 = p2+2; + } + if ( !t_cnt ) { + *text = "couldn't parse template"; + ch_free(attrs); + return -1; + } + if ( !got_oc && !( set->flags & PC_GOT_OC )) { + attrs = (AttributeName *)ch_realloc(attrs, + (attr_cnt + 2)*sizeof(AttributeName)); + + ad = slap_schema.si_ad_objectClass; + attrs[attr_cnt].an_desc = ad; + attrs[attr_cnt].an_name = ad->ad_cname; + attrs[attr_cnt].an_oc = NULL; + attrs[attr_cnt].an_flags = 0; + BER_BVZERO( &attrs[attr_cnt+1].an_name ); + attr_cnt++; + } + *ret = attrs; + return attr_cnt; +} + +/* + * Turn an URL representing a formerly cached query into a cached query, + * and try to cache it + */ +static int +url2query( + char *url, + Operation *op, + query_manager *qm ) +{ + Query query = { 0 }; + QueryTemplate *qt; + CachedQuery *cq; + LDAPURLDesc *lud = NULL; + struct berval base, + tempstr = BER_BVNULL, + uuid = BER_BVNULL; + int attrset; + time_t expiry_time; + time_t refresh_time; + unsigned long answerable_cnt; + int i, + got = 0, +#define GOT_UUID 0x1U +#define GOT_ATTRSET 0x2U +#define GOT_EXPIRY 0x4U +#define GOT_ANSWERABLE 0x8U +#define GOT_REFRESH 0x10U +#define GOT_ALL (GOT_UUID|GOT_ATTRSET|GOT_EXPIRY|GOT_ANSWERABLE) + rc = 0; + + rc = ldap_url_parse( url, &lud ); + if ( rc != LDAP_URL_SUCCESS ) { + return -1; + } + + /* non-allowed fields */ + if ( lud->lud_host != NULL ) { + rc = 1; + goto error; + } + + if ( lud->lud_attrs != NULL ) { + rc = 1; + goto error; + } + + /* be pedantic */ + if ( strcmp( lud->lud_scheme, "ldap" ) != 0 ) { + rc = 1; + goto error; + } + + /* required fields */ + if ( lud->lud_dn == NULL || lud->lud_dn[ 0 ] == '\0' ) { + rc = 1; + goto error; + } + + switch ( lud->lud_scope ) { + case LDAP_SCOPE_BASE: + case LDAP_SCOPE_ONELEVEL: + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + break; + + default: + rc = 1; + goto error; + } + + if ( lud->lud_filter == NULL || lud->lud_filter[ 0 ] == '\0' ) { + rc = 1; + goto error; + } + + if ( lud->lud_exts == NULL ) { + rc = 1; + goto error; + } + + for ( i = 0; lud->lud_exts[ i ] != NULL; i++ ) { + if ( strncmp( lud->lud_exts[ i ], "x-uuid=", STRLENOF( "x-uuid=" ) ) == 0 ) { + struct berval tmpUUID; + Syntax *syn_UUID = slap_schema.si_ad_entryUUID->ad_type->sat_syntax; + + if ( got & GOT_UUID ) { + rc = 1; + goto error; + } + + ber_str2bv( &lud->lud_exts[ i ][ STRLENOF( "x-uuid=" ) ], 0, 0, &tmpUUID ); + if ( !BER_BVISEMPTY( &tmpUUID ) ) { + rc = syn_UUID->ssyn_pretty( syn_UUID, &tmpUUID, &uuid, NULL ); + if ( rc != LDAP_SUCCESS ) { + goto error; + } + } + got |= GOT_UUID; + + } else if ( strncmp( lud->lud_exts[ i ], "x-attrset=", STRLENOF( "x-attrset=" ) ) == 0 ) { + if ( got & GOT_ATTRSET ) { + rc = 1; + goto error; + } + + rc = lutil_atoi( &attrset, &lud->lud_exts[ i ][ STRLENOF( "x-attrset=" ) ] ); + if ( rc ) { + goto error; + } + got |= GOT_ATTRSET; + + } else if ( strncmp( lud->lud_exts[ i ], "x-expiry=", STRLENOF( "x-expiry=" ) ) == 0 ) { + unsigned long l; + + if ( got & GOT_EXPIRY ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-expiry=" ) ] ); + if ( rc ) { + goto error; + } + expiry_time = (time_t)l; + got |= GOT_EXPIRY; + + } else if ( strncmp( lud->lud_exts[ i ], "x-answerable=", STRLENOF( "x-answerable=" ) ) == 0 ) { + if ( got & GOT_ANSWERABLE ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &answerable_cnt, &lud->lud_exts[ i ][ STRLENOF( "x-answerable=" ) ] ); + if ( rc ) { + goto error; + } + got |= GOT_ANSWERABLE; + + } else if ( strncmp( lud->lud_exts[ i ], "x-refresh=", STRLENOF( "x-refresh=" ) ) == 0 ) { + unsigned long l; + + if ( got & GOT_REFRESH ) { + rc = 1; + goto error; + } + + rc = lutil_atoul( &l, &lud->lud_exts[ i ][ STRLENOF( "x-refresh=" ) ] ); + if ( rc ) { + goto error; + } + refresh_time = (time_t)l; + got |= GOT_REFRESH; + + } else { + rc = -1; + goto error; + } + } + + if ( got != GOT_ALL ) { + rc = 1; + goto error; + } + + if ( !(got & GOT_REFRESH )) + refresh_time = 0; + + /* ignore expired queries */ + if ( expiry_time <= slap_get_time()) { + Operation op2 = *op; + + memset( &op2.oq_search, 0, sizeof( op2.oq_search ) ); + + (void)remove_query_data( &op2, &uuid ); + + rc = 0; + + } else { + ber_str2bv( lud->lud_dn, 0, 0, &base ); + rc = dnNormalize( 0, NULL, NULL, &base, &query.base, NULL ); + if ( rc != LDAP_SUCCESS ) { + goto error; + } + query.scope = lud->lud_scope; + query.filter = str2filter( lud->lud_filter ); + if ( query.filter == NULL ) { + rc = -1; + goto error; + } + + tempstr.bv_val = ch_malloc( strlen( lud->lud_filter ) + 1 ); + tempstr.bv_len = 0; + if ( filter2template( op, query.filter, &tempstr ) ) { + ch_free( tempstr.bv_val ); + rc = -1; + goto error; + } + + /* check for query containment */ + qt = qm->attr_sets[attrset].templates; + for ( ; qt; qt = qt->qtnext ) { + /* find if template i can potentially answer tempstr */ + if ( bvmatch( &qt->querystr, &tempstr ) ) { + break; + } + } + + if ( qt == NULL ) { + rc = 1; + goto error; + } + + cq = add_query( op, qm, &query, qt, PC_POSITIVE, 0 ); + if ( cq != NULL ) { + cq->expiry_time = expiry_time; + cq->refresh_time = refresh_time; + cq->q_uuid = uuid; + cq->answerable_cnt = answerable_cnt; + cq->refcnt = 0; + + /* it's now into cq->filter */ + BER_BVZERO( &uuid ); + query.filter = NULL; + + } else { + rc = 1; + } + } + +error:; + if ( query.filter != NULL ) filter_free( query.filter ); + if ( !BER_BVISNULL( &tempstr ) ) ch_free( tempstr.bv_val ); + if ( !BER_BVISNULL( &query.base ) ) ch_free( query.base.bv_val ); + if ( !BER_BVISNULL( &uuid ) ) ch_free( uuid.bv_val ); + if ( lud != NULL ) ldap_free_urldesc( lud ); + + return rc; +} + +/* Return 1 for an added entry, else 0 */ +static int +merge_entry( + Operation *op, + Entry *e, + int dup, + struct berval* query_uuid ) +{ + int rc; + Modifications* modlist = NULL; + const char* text = NULL; + Attribute *attr; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + + SlapReply sreply = {REP_RESULT}; + + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + + if ( dup ) + e = entry_dup( e ); + attr = e->e_attrs; + e->e_attrs = NULL; + + /* add queryId attribute */ + attr_merge_one( e, ad_queryId, query_uuid, NULL ); + + /* append the attribute list from the fetched entry */ + e->e_attrs->a_next = attr; + + op->o_tag = LDAP_REQ_ADD; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + + op->ora_e = e; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + rc = op->o_bd->be_add( op, &sreply ); + + if ( rc != LDAP_SUCCESS ) { + if ( rc == LDAP_ALREADY_EXISTS ) { + rs_reinit( &sreply, REP_RESULT ); + slap_entry2mods( e, &modlist, &text, textbuf, textlen ); + modlist->sml_op = LDAP_MOD_ADD; + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = modlist; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd->be_modify( op, &sreply ); + slap_mods_free( modlist, 1 ); + } else if ( rc == LDAP_REFERRAL || + rc == LDAP_NO_SUCH_OBJECT ) { + syncrepl_add_glue( op, e ); + e = NULL; + rc = 1; + } + if ( e ) { + entry_free( e ); + rc = 0; + } + } else { + if ( op->ora_e == e ) + entry_free( e ); + rc = 1; + } + + return rc; +} + +/* Length-ordered sort on normalized DNs */ +static int pcache_dn_cmp( const void *v1, const void *v2 ) +{ + const Qbase *q1 = v1, *q2 = v2; + + int rc = q1->base.bv_len - q2->base.bv_len; + if ( rc == 0 ) + rc = strncmp( q1->base.bv_val, q2->base.bv_val, q1->base.bv_len ); + return rc; +} + +static int lex_bvcmp( struct berval *bv1, struct berval *bv2 ) +{ + int len, dif; + dif = bv1->bv_len - bv2->bv_len; + len = bv1->bv_len; + if ( dif > 0 ) len -= dif; + len = memcmp( bv1->bv_val, bv2->bv_val, len ); + if ( !len ) + len = dif; + return len; +} + +/* compare the current value in each filter */ +static int pcache_filter_cmp( Filter *f1, Filter *f2 ) +{ + int rc, weight1, weight2; + + switch( f1->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + weight1 = 0; + break; + case LDAP_FILTER_PRESENT: + weight1 = 1; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + weight1 = 2; + break; + default: + weight1 = 3; + } + switch( f2->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + weight2 = 0; + break; + case LDAP_FILTER_PRESENT: + weight2 = 1; + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + weight2 = 2; + break; + default: + weight2 = 3; + } + rc = weight1 - weight2; + if ( !rc ) { + switch( weight1 ) { + case 0: + rc = pcache_filter_cmp( f1->f_and, f2->f_and ); + break; + case 1: + break; + case 2: + rc = lex_bvcmp( &f1->f_av_value, &f2->f_av_value ); + break; + case 3: + if ( f1->f_choice == LDAP_FILTER_SUBSTRINGS ) { + rc = 0; + if ( !BER_BVISNULL( &f1->f_sub_initial )) { + if ( !BER_BVISNULL( &f2->f_sub_initial )) { + rc = lex_bvcmp( &f1->f_sub_initial, + &f2->f_sub_initial ); + } else { + rc = 1; + } + } else if ( !BER_BVISNULL( &f2->f_sub_initial )) { + rc = -1; + } + if ( rc ) break; + if ( f1->f_sub_any ) { + if ( f2->f_sub_any ) { + rc = lex_bvcmp( f1->f_sub_any, + f2->f_sub_any ); + } else { + rc = 1; + } + } else if ( f2->f_sub_any ) { + rc = -1; + } + if ( rc ) break; + if ( !BER_BVISNULL( &f1->f_sub_final )) { + if ( !BER_BVISNULL( &f2->f_sub_final )) { + rc = lex_bvcmp( &f1->f_sub_final, + &f2->f_sub_final ); + } else { + rc = 1; + } + } else if ( !BER_BVISNULL( &f2->f_sub_final )) { + rc = -1; + } + } else { + rc = lex_bvcmp( &f1->f_mr_value, + &f2->f_mr_value ); + } + break; + } + while ( !rc ) { + f1 = f1->f_next; + f2 = f2->f_next; + if ( f1 || f2 ) { + if ( !f1 ) + rc = -1; + else if ( !f2 ) + rc = 1; + else { + rc = pcache_filter_cmp( f1, f2 ); + } + } else { + break; + } + } + } + return rc; +} + +/* compare filters in each query */ +static int pcache_query_cmp( const void *v1, const void *v2 ) +{ + const CachedQuery *q1 = v1, *q2 =v2; + return pcache_filter_cmp( q1->filter, q2->filter ); +} + +/* add query on top of LRU list */ +static void +add_query_on_top (query_manager* qm, CachedQuery* qc) +{ + CachedQuery* top = qm->lru_top; + + qc->in_lru = 1; + qm->lru_top = qc; + + if (top) + top->lru_up = qc; + else + qm->lru_bottom = qc; + + qc->lru_down = top; + qc->lru_up = NULL; + Debug( pcache_debug, "Base of added query = %s\n", + qc->qbase->base.bv_val ); +} + +/* remove_query from LRU list */ + +static void +remove_query (query_manager* qm, CachedQuery* qc) +{ + CachedQuery* up; + CachedQuery* down; + + if (!qc || !qc->in_lru) + return; + + qc->in_lru = 0; + up = qc->lru_up; + down = qc->lru_down; + + if (!up) + qm->lru_top = down; + + if (!down) + qm->lru_bottom = up; + + if (down) + down->lru_up = up; + + if (up) + up->lru_down = down; + + qc->lru_up = qc->lru_down = NULL; +} + +/* find and remove string2 from string1 + * from start if position = 1, + * from end if position = 3, + * from anywhere if position = 2 + * string1 is overwritten if position = 2. + */ + +static int +find_and_remove(struct berval* ber1, struct berval* ber2, int position) +{ + int ret=0; + + if ( !ber2->bv_val ) + return 1; + if ( !ber1->bv_val ) + return 0; + + switch( position ) { + case 1: + if ( ber1->bv_len >= ber2->bv_len && !memcmp( ber1->bv_val, + ber2->bv_val, ber2->bv_len )) { + ret = 1; + ber1->bv_val += ber2->bv_len; + ber1->bv_len -= ber2->bv_len; + } + break; + case 2: { + char *temp; + ber1->bv_val[ber1->bv_len] = '\0'; + temp = strstr( ber1->bv_val, ber2->bv_val ); + if ( temp ) { + strcpy( temp, temp+ber2->bv_len ); + ber1->bv_len -= ber2->bv_len; + ret = 1; + } + break; + } + case 3: + if ( ber1->bv_len >= ber2->bv_len && + !memcmp( ber1->bv_val+ber1->bv_len-ber2->bv_len, ber2->bv_val, + ber2->bv_len )) { + ret = 1; + ber1->bv_len -= ber2->bv_len; + } + break; + } + return ret; +} + + +static struct berval* +merge_init_final(Operation *op, struct berval* init, struct berval* any, + struct berval* final) +{ + struct berval* merged, *temp; + int i, any_count, count; + + for (any_count=0; any && any[any_count].bv_val; any_count++) + ; + + count = any_count; + + if (init->bv_val) + count++; + if (final->bv_val) + count++; + + merged = (struct berval*)op->o_tmpalloc( (count+1)*sizeof(struct berval), + op->o_tmpmemctx ); + temp = merged; + + if (init->bv_val) { + ber_dupbv_x( temp, init, op->o_tmpmemctx ); + temp++; + } + + for (i=0; i<any_count; i++) { + ber_dupbv_x( temp, any, op->o_tmpmemctx ); + temp++; any++; + } + + if (final->bv_val){ + ber_dupbv_x( temp, final, op->o_tmpmemctx ); + temp++; + } + BER_BVZERO( temp ); + return merged; +} + +/* Each element in stored must be found in incoming. Incoming is overwritten. + */ +static int +strings_containment(struct berval* stored, struct berval* incoming) +{ + struct berval* element; + int k=0; + int j, rc = 0; + + for ( element=stored; element->bv_val != NULL; element++ ) { + for (j = k; incoming[j].bv_val != NULL; j++) { + if (find_and_remove(&(incoming[j]), element, 2)) { + k = j; + rc = 1; + break; + } + rc = 0; + } + if ( rc ) { + continue; + } else { + return 0; + } + } + return 1; +} + +static int +substr_containment_substr(Operation *op, Filter* stored, Filter* incoming) +{ + int rc = 0; + + struct berval init_incoming; + struct berval final_incoming; + struct berval *remaining_incoming = NULL; + + if ((!(incoming->f_sub_initial.bv_val) && (stored->f_sub_initial.bv_val)) + || (!(incoming->f_sub_final.bv_val) && (stored->f_sub_final.bv_val))) + return 0; + + init_incoming = incoming->f_sub_initial; + final_incoming = incoming->f_sub_final; + + if (find_and_remove(&init_incoming, + &(stored->f_sub_initial), 1) && find_and_remove(&final_incoming, + &(stored->f_sub_final), 3)) + { + if (stored->f_sub_any == NULL) { + rc = 1; + goto final; + } + remaining_incoming = merge_init_final(op, &init_incoming, + incoming->f_sub_any, &final_incoming); + rc = strings_containment(stored->f_sub_any, remaining_incoming); + ber_bvarray_free_x( remaining_incoming, op->o_tmpmemctx ); + } +final: + return rc; +} + +static int +substr_containment_equality(Operation *op, Filter* stored, Filter* incoming) +{ + struct berval incoming_val[2]; + int rc = 0; + + incoming_val[1] = incoming->f_av_value; + + if (find_and_remove(incoming_val+1, + &(stored->f_sub_initial), 1) && find_and_remove(incoming_val+1, + &(stored->f_sub_final), 3)) { + if (stored->f_sub_any == NULL){ + rc = 1; + goto final; + } + ber_dupbv_x( incoming_val, incoming_val+1, op->o_tmpmemctx ); + BER_BVZERO( incoming_val+1 ); + rc = strings_containment(stored->f_sub_any, incoming_val); + op->o_tmpfree( incoming_val[0].bv_val, op->o_tmpmemctx ); + } +final: + return rc; +} + +static Filter * +filter_first( Filter *f ) +{ + while ( f->f_choice == LDAP_FILTER_OR || f->f_choice == LDAP_FILTER_AND ) + f = f->f_and; + return f; +} + +typedef struct fstack { + struct fstack *fs_next; + Filter *fs_fs; + Filter *fs_fi; +} fstack; + +static CachedQuery * +find_filter( Operation *op, TAvlnode *root, Filter *inputf, Filter *first ) +{ + Filter* fs; + Filter* fi; + MatchingRule* mrule = NULL; + int res=0, eqpass= 0; + int ret, rc, dir; + TAvlnode *ptr; + CachedQuery cq, *qc; + fstack *stack = NULL, *fsp; + + cq.filter = inputf; + cq.first = first; + + /* substring matches sort to the end, and we just have to + * walk the entire list. + */ + if ( first->f_choice == LDAP_FILTER_SUBSTRINGS ) { + ptr = ldap_tavl_end( root, 1 ); + dir = TAVL_DIR_LEFT; + } else { + ptr = ldap_tavl_find3( root, &cq, pcache_query_cmp, &ret ); + dir = (first->f_choice == LDAP_FILTER_GE) ? TAVL_DIR_LEFT : + TAVL_DIR_RIGHT; + } + + while (ptr) { + qc = ptr->avl_data; + fi = inputf; + fs = qc->filter; + + /* an incoming substr query can only be satisfied by a cached + * substr query. + */ + if ( first->f_choice == LDAP_FILTER_SUBSTRINGS && + qc->first->f_choice != LDAP_FILTER_SUBSTRINGS ) + break; + + /* an incoming eq query can be satisfied by a cached eq or substr + * query + */ + if ( first->f_choice == LDAP_FILTER_EQUALITY ) { + if ( eqpass == 0 ) { + if ( qc->first->f_choice != LDAP_FILTER_EQUALITY ) { +nextpass: eqpass = 1; + ptr = ldap_tavl_end( root, 1 ); + dir = TAVL_DIR_LEFT; + continue; + } + } else { + if ( qc->first->f_choice != LDAP_FILTER_SUBSTRINGS ) + break; + } + } + do { + res=0; + switch (fs->f_choice) { + case LDAP_FILTER_EQUALITY: + if (fi->f_choice == LDAP_FILTER_EQUALITY) + mrule = fs->f_ava->aa_desc->ad_type->sat_equality; + else + ret = 1; + break; + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + mrule = fs->f_ava->aa_desc->ad_type->sat_ordering; + break; + default: + mrule = NULL; + } + if (mrule) { + const char *text; + rc = value_match(&ret, fs->f_ava->aa_desc, mrule, + SLAP_MR_VALUE_OF_ASSERTION_SYNTAX, + &(fi->f_ava->aa_value), + &(fs->f_ava->aa_value), &text); + if (rc != LDAP_SUCCESS) { + return NULL; + } + if ( fi==first && fi->f_choice==LDAP_FILTER_EQUALITY && ret ) + goto nextpass; + } + switch (fs->f_choice) { + case LDAP_FILTER_OR: + case LDAP_FILTER_AND: + if ( fs->f_next ) { + /* save our stack position */ + fsp = op->o_tmpalloc(sizeof(fstack), op->o_tmpmemctx); + fsp->fs_next = stack; + fsp->fs_fs = fs->f_next; + fsp->fs_fi = fi->f_next; + stack = fsp; + } + fs = fs->f_and; + fi = fi->f_and; + res=1; + break; + case LDAP_FILTER_SUBSTRINGS: + /* check if the equality query can be + * answered with cached substring query */ + if ((fi->f_choice == LDAP_FILTER_EQUALITY) + && substr_containment_equality( op, + fs, fi)) + res=1; + /* check if the substring query can be + * answered with cached substring query */ + if ((fi->f_choice ==LDAP_FILTER_SUBSTRINGS + ) && substr_containment_substr( op, + fs, fi)) + res= 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_PRESENT: + res=1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_EQUALITY: + if (ret == 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_GE: + if (mrule && ret >= 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_LE: + if (mrule && ret <= 0) + res = 1; + fs=fs->f_next; + fi=fi->f_next; + break; + case LDAP_FILTER_NOT: + res=0; + break; + default: + break; + } + if (!fs && !fi && stack) { + /* pop the stack */ + fsp = stack; + stack = fsp->fs_next; + fs = fsp->fs_fs; + fi = fsp->fs_fi; + op->o_tmpfree(fsp, op->o_tmpmemctx); + } + } while((res) && (fi != NULL) && (fs != NULL)); + + if ( res ) + return qc; + ptr = ldap_tavl_next( ptr, dir ); + } + return NULL; +} + +/* check whether query is contained in any of + * the cached queries in template + */ +static CachedQuery * +query_containment(Operation *op, query_manager *qm, + Query *query, + QueryTemplate *templa) +{ + CachedQuery* qc; + int depth = 0, tscope; + Qbase qbase, *qbptr = NULL; + struct berval pdn; + + if (query->filter != NULL) { + Filter *first; + + Debug( pcache_debug, "Lock QC index = %p\n", + (void *) templa ); + qbase.base = query->base; + + first = filter_first( query->filter ); + + ldap_pvt_thread_rdwr_rlock(&templa->t_rwlock); + for( ;; ) { + /* Find the base */ + qbptr = ldap_avl_find( templa->qbase, &qbase, pcache_dn_cmp ); + if ( qbptr ) { + tscope = query->scope; + /* Find a matching scope: + * match at depth 0 OK + * scope is BASE, + * one at depth 1 OK + * subord at depth > 0 OK + * subtree at any depth OK + * scope is ONE, + * subtree or subord at any depth OK + * scope is SUBORD, + * subtree or subord at any depth OK + * scope is SUBTREE, + * subord at depth > 0 OK + * subtree at any depth OK + */ + for ( tscope = 0 ; tscope <= LDAP_SCOPE_CHILDREN; tscope++ ) { + switch ( query->scope ) { + case LDAP_SCOPE_BASE: + if ( tscope == LDAP_SCOPE_BASE && depth ) continue; + if ( tscope == LDAP_SCOPE_ONE && depth != 1) continue; + if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue; + break; + case LDAP_SCOPE_ONE: + if ( tscope == LDAP_SCOPE_BASE ) + tscope = LDAP_SCOPE_ONE; + if ( tscope == LDAP_SCOPE_ONE && depth ) continue; + if ( !depth ) break; + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + break; + case LDAP_SCOPE_SUBTREE: + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + if ( tscope == LDAP_SCOPE_CHILDREN && !depth ) continue; + break; + case LDAP_SCOPE_CHILDREN: + if ( tscope < LDAP_SCOPE_SUBTREE ) + tscope = LDAP_SCOPE_SUBTREE; + break; + } + if ( !qbptr->scopes[tscope] ) continue; + + /* Find filter */ + qc = find_filter( op, qbptr->scopes[tscope], + query->filter, first ); + if ( qc ) { + if ( qc->q_sizelimit ) { + ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock); + return NULL; + } + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + if (qm->lru_top != qc) { + remove_query(qm, qc); + add_query_on_top(qm, qc); + } + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + return qc; + } + } + } + if ( be_issuffix( op->o_bd, &qbase.base )) + break; + /* Up a level */ + dnParent( &qbase.base, &pdn ); + qbase.base = pdn; + depth++; + } + + Debug( pcache_debug, + "Not answerable: Unlock QC index=%p\n", + (void *) templa ); + ldap_pvt_thread_rdwr_runlock(&templa->t_rwlock); + } + return NULL; +} + +static void +free_query (CachedQuery* qc) +{ + free(qc->q_uuid.bv_val); + filter_free(qc->filter); + ldap_pvt_thread_mutex_destroy(&qc->answerable_cnt_mutex); + ldap_pvt_thread_rdwr_destroy( &qc->rwlock ); + memset(qc, 0, sizeof(*qc)); + free(qc); +} + + +/* Add query to query cache, the returned Query is locked for writing */ +static CachedQuery * +add_query( + Operation *op, + query_manager* qm, + Query* query, + QueryTemplate *templ, + pc_caching_reason_t why, + int wlock) +{ + CachedQuery* new_cached_query = (CachedQuery*) ch_malloc(sizeof(CachedQuery)); + Qbase *qbase, qb; + Filter *first; + int rc; + time_t ttl = 0, ttr = 0; + time_t now; + + new_cached_query->qtemp = templ; + BER_BVZERO( &new_cached_query->q_uuid ); + new_cached_query->q_sizelimit = 0; + + now = slap_get_time(); + switch ( why ) { + case PC_POSITIVE: + ttl = templ->ttl; + if ( templ->ttr ) + ttr = now + templ->ttr; + break; + + case PC_NEGATIVE: + ttl = templ->negttl; + break; + + case PC_SIZELIMIT: + ttl = templ->limitttl; + break; + + default: + assert( 0 ); + break; + } + new_cached_query->expiry_time = now + ttl; + new_cached_query->refresh_time = ttr; + new_cached_query->bindref_time = 0; + + new_cached_query->bind_refcnt = 0; + new_cached_query->answerable_cnt = 0; + new_cached_query->refcnt = 1; + ldap_pvt_thread_mutex_init(&new_cached_query->answerable_cnt_mutex); + + new_cached_query->lru_up = NULL; + new_cached_query->lru_down = NULL; + Debug( pcache_debug, "Added query expires at %ld (%s)\n", + (long) new_cached_query->expiry_time, + pc_caching_reason_str[ why ] ); + + new_cached_query->scope = query->scope; + new_cached_query->filter = query->filter; + new_cached_query->first = first = filter_first( query->filter ); + + ldap_pvt_thread_rdwr_init(&new_cached_query->rwlock); + if (wlock) + ldap_pvt_thread_rdwr_wlock(&new_cached_query->rwlock); + + qb.base = query->base; + + /* Adding a query */ + Debug( pcache_debug, "Lock AQ index = %p\n", + (void *) templ ); + ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock); + qbase = ldap_avl_find( templ->qbase, &qb, pcache_dn_cmp ); + if ( !qbase ) { + qbase = ch_calloc( 1, sizeof(Qbase) + qb.base.bv_len + 1 ); + qbase->base.bv_len = qb.base.bv_len; + qbase->base.bv_val = (char *)(qbase+1); + memcpy( qbase->base.bv_val, qb.base.bv_val, qb.base.bv_len ); + qbase->base.bv_val[qbase->base.bv_len] = '\0'; + ldap_avl_insert( &templ->qbase, qbase, pcache_dn_cmp, ldap_avl_dup_error ); + } + new_cached_query->next = templ->query; + new_cached_query->prev = NULL; + new_cached_query->qbase = qbase; + rc = ldap_tavl_insert( &qbase->scopes[query->scope], new_cached_query, + pcache_query_cmp, ldap_avl_dup_error ); + if ( rc == 0 ) { + qbase->queries++; + if (templ->query == NULL) + templ->query_last = new_cached_query; + else + templ->query->prev = new_cached_query; + templ->query = new_cached_query; + templ->no_of_queries++; + } else { + ldap_pvt_thread_mutex_destroy(&new_cached_query->answerable_cnt_mutex); + if (wlock) + ldap_pvt_thread_rdwr_wunlock(&new_cached_query->rwlock); + ldap_pvt_thread_rdwr_destroy( &new_cached_query->rwlock ); + ch_free( new_cached_query ); + new_cached_query = find_filter( op, qbase->scopes[query->scope], + query->filter, first ); + filter_free( query->filter ); + query->filter = NULL; + } + Debug( pcache_debug, "TEMPLATE %p QUERIES++ %d\n", + (void *) templ, templ->no_of_queries ); + + /* Adding on top of LRU list */ + if ( rc == 0 ) { + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + add_query_on_top(qm, new_cached_query); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + } + Debug( pcache_debug, "Unlock AQ index = %p \n", + (void *) templ ); + ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock); + + return rc == 0 ? new_cached_query : NULL; +} + +static void +remove_from_template (CachedQuery* qc, QueryTemplate* template) +{ + if (!qc->prev && !qc->next) { + template->query_last = template->query = NULL; + } else if (qc->prev == NULL) { + qc->next->prev = NULL; + template->query = qc->next; + } else if (qc->next == NULL) { + qc->prev->next = NULL; + template->query_last = qc->prev; + } else { + qc->next->prev = qc->prev; + qc->prev->next = qc->next; + } + ldap_tavl_delete( &qc->qbase->scopes[qc->scope], qc, pcache_query_cmp ); + qc->qbase->queries--; + if ( qc->qbase->queries == 0 ) { + ldap_avl_delete( &template->qbase, qc->qbase, pcache_dn_cmp ); + ch_free( qc->qbase ); + qc->qbase = NULL; + } + + template->no_of_queries--; +} + +/* remove bottom query of LRU list from the query cache */ +/* + * NOTE: slight change in functionality. + * + * - if result->bv_val is NULL, the query at the bottom of the LRU + * is removed + * - otherwise, the query whose UUID is *result is removed + * - if not found, result->bv_val is zeroed + */ +static void +cache_replacement(query_manager* qm, struct berval *result) +{ + CachedQuery* bottom; + QueryTemplate *temp; + + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + if ( BER_BVISNULL( result ) ) { + bottom = qm->lru_bottom; + + if (!bottom) { + Debug ( pcache_debug, + "Cache replacement invoked without " + "any query in LRU list\n" ); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + return; + } + + } else { + for ( bottom = qm->lru_bottom; + bottom != NULL; + bottom = bottom->lru_up ) + { + if ( bvmatch( result, &bottom->q_uuid ) ) { + break; + } + } + + if ( !bottom ) { + Debug ( pcache_debug, + "Could not find query with uuid=\"%s\"" + "in LRU list\n", result->bv_val ); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + BER_BVZERO( result ); + return; + } + } + + temp = bottom->qtemp; + remove_query(qm, bottom); + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + + *result = bottom->q_uuid; + BER_BVZERO( &bottom->q_uuid ); + + Debug( pcache_debug, "Lock CR index = %p\n", (void *) temp ); + ldap_pvt_thread_rdwr_wlock(&temp->t_rwlock); + remove_from_template(bottom, temp); + Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n", + (void *) temp, temp->no_of_queries ); + Debug( pcache_debug, "Unlock CR index = %p\n", (void *) temp ); + ldap_pvt_thread_rdwr_wunlock(&temp->t_rwlock); + free_query(bottom); +} + +struct query_info { + struct query_info *next; + struct berval xdn; + int del; +}; + +static int +remove_func ( + Operation *op, + SlapReply *rs +) +{ + Attribute *attr; + struct query_info *qi; + int count = 0; + + if ( rs->sr_type != REP_SEARCH ) return 0; + + attr = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + if ( attr == NULL ) return 0; + + count = attr->a_numvals; + assert( count > 0 ); + qi = op->o_tmpalloc( sizeof( struct query_info ), op->o_tmpmemctx ); + qi->next = op->o_callback->sc_private; + op->o_callback->sc_private = qi; + ber_dupbv_x( &qi->xdn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + qi->del = ( count == 1 ); + + return 0; +} + +static int +remove_query_data( + Operation *op, + struct berval *query_uuid ) +{ + struct query_info *qi, *qnext; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + Filter filter = {LDAP_FILTER_EQUALITY}; + SlapReply sreply = {REP_RESULT}; + slap_callback cb = { NULL, remove_func, NULL, NULL }; + int deleted = 0; + + op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str), + "(%s=%s)", ad_queryId->ad_cname.bv_val, query_uuid->bv_val); + filter.f_ava = &ava; + filter.f_av_desc = ad_queryId; + filter.f_av_value = *query_uuid; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + op->ors_filter = &filter; + op->ors_filterstr.bv_val = filter_str; + op->ors_filterstr.bv_len = strlen(filter_str); + op->ors_attrs = NULL; + op->ors_attrsonly = 0; + + op->o_bd->be_search( op, &sreply ); + + for ( qi=cb.sc_private; qi; qi=qnext ) { + qnext = qi->next; + + op->o_req_dn = qi->xdn; + op->o_req_ndn = qi->xdn; + rs_reinit( &sreply, REP_RESULT ); + + if ( qi->del ) { + Debug( pcache_debug, "DELETING ENTRY TEMPLATE=%s\n", + query_uuid->bv_val ); + + op->o_tag = LDAP_REQ_DELETE; + + if (op->o_bd->be_delete(op, &sreply) == LDAP_SUCCESS) { + deleted++; + } + + } else { + Modifications mod; + struct berval vals[2]; + + vals[0] = *query_uuid; + vals[1].bv_val = NULL; + vals[1].bv_len = 0; + mod.sml_op = LDAP_MOD_DELETE; + mod.sml_flags = 0; + mod.sml_desc = ad_queryId; + mod.sml_type = ad_queryId->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + Debug( pcache_debug, + "REMOVING TEMP ATTR : TEMPLATE=%s\n", + query_uuid->bv_val ); + + op->orm_modlist = &mod; + + op->o_bd->be_modify( op, &sreply ); + } + op->o_tmpfree( qi->xdn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( qi, op->o_tmpmemctx ); + } + return deleted; +} + +static int +get_attr_set( + AttributeName* attrs, + query_manager* qm, + int num +); + +static int +filter2template( + Operation *op, + Filter *f, + struct berval *fstr ) +{ + AttributeDescription *ad; + int len, ret; + + switch ( f->f_choice ) { + case LDAP_FILTER_EQUALITY: + ad = f->f_av_desc; + len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_GE: + ad = f->f_av_desc; + len = STRLENOF( "(>=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s>=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_LE: + ad = f->f_av_desc; + len = STRLENOF( "(<=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s<=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + len = STRLENOF( "(~=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s~=)", ad->ad_cname.bv_val); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_SUBSTRINGS: + ad = f->f_sub_desc; + len = STRLENOF( "(=)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + len = STRLENOF( "(=*)" ) + ad->ad_cname.bv_len; + ret = snprintf( fstr->bv_val+fstr->bv_len, len + 1, "(%s=*)", ad->ad_cname.bv_val ); + assert( ret == len ); + fstr->bv_len += len; + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + int rc = 0; + fstr->bv_val[fstr->bv_len++] = '('; + switch ( f->f_choice ) { + case LDAP_FILTER_AND: + fstr->bv_val[fstr->bv_len] = '&'; + break; + case LDAP_FILTER_OR: + fstr->bv_val[fstr->bv_len] = '|'; + break; + case LDAP_FILTER_NOT: + fstr->bv_val[fstr->bv_len] = '!'; + break; + } + fstr->bv_len++; + + for ( f = f->f_list; f != NULL; f = f->f_next ) { + rc = filter2template( op, f, fstr ); + if ( rc ) break; + } + fstr->bv_val[fstr->bv_len++] = ')'; + fstr->bv_val[fstr->bv_len] = '\0'; + + return rc; + } + + default: + /* a filter should at least have room for "()", + * an "=" and for a 1-char attr */ + strcpy( fstr->bv_val, "(?=)" ); + fstr->bv_len += STRLENOF("(?=)"); + return -1; + } + + return 0; +} + +#define BI_HASHED 0x01 +#define BI_DIDCB 0x02 +#define BI_LOOKUP 0x04 + +struct search_info; + +typedef struct bindinfo { + cache_manager *bi_cm; + CachedQuery *bi_cq; + QueryTemplate *bi_templ; + struct search_info *bi_si; + int bi_flags; + slap_callback bi_cb; +} bindinfo; + +struct search_info { + slap_overinst *on; + Query query; + QueryTemplate *qtemp; + AttributeName* save_attrs; /* original attributes, saved for response */ + int swap_saved_attrs; + int max; + int over; + int count; + int slimit; + int slimit_exceeded; + pc_caching_reason_t caching_reason; + Entry *head, *tail; + bindinfo *pbi; +}; + +static void +remove_query_and_data( + Operation *op, + cache_manager *cm, + struct berval *uuid ) +{ + query_manager* qm = cm->qm; + + qm->crfunc( qm, uuid ); + if ( !BER_BVISNULL( uuid ) ) { + int return_val; + + Debug( pcache_debug, + "Removing query UUID %s\n", + uuid->bv_val ); + return_val = remove_query_data( op, uuid ); + Debug( pcache_debug, + "QUERY REMOVED, SIZE=%d\n", + return_val ); + ldap_pvt_thread_mutex_lock( &cm->cache_mutex ); + cm->cur_entries -= return_val; + cm->num_cached_queries--; + Debug( pcache_debug, + "STORED QUERIES = %lu\n", + cm->num_cached_queries ); + ldap_pvt_thread_mutex_unlock( &cm->cache_mutex ); + Debug( pcache_debug, + "QUERY REMOVED, CACHE =" + "%d entries\n", + cm->cur_entries ); + } +} + +/* + * Callback used to fetch queryId values based on entryUUID; + * used by pcache_remove_entries_from_cache() + */ +static int +fetch_queryId_cb( Operation *op, SlapReply *rs ) +{ + int rc = 0; + + /* only care about searchEntry responses */ + if ( rs->sr_type != REP_SEARCH ) { + return 0; + } + + /* allow only one response per entryUUID */ + if ( op->o_callback->sc_private != NULL ) { + rc = 1; + + } else { + Attribute *a; + + /* copy all queryId values into callback's private data */ + a = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + if ( a != NULL ) { + BerVarray vals = NULL; + + ber_bvarray_dup_x( &vals, a->a_nvals, op->o_tmpmemctx ); + op->o_callback->sc_private = (void *)vals; + } + } + + /* clear entry if required */ + rs_flush_entry( op, rs, (slap_overinst *) op->o_bd->bd_info ); + + return rc; +} + +/* + * Call that allows to remove a set of entries from the cache, + * by forcing the removal of all the related queries. + */ +int +pcache_remove_entries_from_cache( + Operation *op, + cache_manager *cm, + BerVarray entryUUIDs ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation op2; + slap_callback sc = { 0 }; + Filter f = { 0 }; + char filtbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(entryUUID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + int s, rc; + + if ( op == NULL ) { + void *thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + } else { + op2 = *op; + op = &op2; + } + + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + f.f_choice = LDAP_FILTER_EQUALITY; + f.f_ava = &ava; + ava.aa_desc = slap_schema.si_ad_entryUUID; + op->ors_filter = &f; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + op->o_req_dn = cm->db.be_suffix[ 0 ]; + op->o_req_ndn = cm->db.be_nsuffix[ 0 ]; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + sc.sc_response = fetch_queryId_cb; + op->o_callback = ≻ + + for ( s = 0; !BER_BVISNULL( &entryUUIDs[ s ] ); s++ ) { + BerVarray vals = NULL; + SlapReply rs = { REP_RESULT }; + + op->ors_filterstr.bv_len = snprintf( filtbuf, sizeof( filtbuf ), + "(entryUUID=%s)", entryUUIDs[ s ].bv_val ); + op->ors_filterstr.bv_val = filtbuf; + ava.aa_value = entryUUIDs[ s ]; + + rc = op->o_bd->be_search( op, &rs ); + if ( rc != LDAP_SUCCESS ) { + continue; + } + + vals = (BerVarray)op->o_callback->sc_private; + if ( vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + struct berval val = vals[ i ]; + + remove_query_and_data( op, cm, &val ); + + if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) { + ch_free( val.bv_val ); + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + op->o_callback->sc_private = NULL; + } + } + + return 0; +} + +/* + * Call that allows to remove a query from the cache. + */ +int +pcache_remove_query_from_cache( + Operation *op, + cache_manager *cm, + struct berval *queryid ) +{ + Operation op2 = *op; + + op2.o_bd = &cm->db; + + /* remove the selected query */ + remove_query_and_data( &op2, cm, queryid ); + + return LDAP_SUCCESS; +} + +/* + * Call that allows to remove a set of queries related to an entry + * from the cache; if queryid is not null, the entry must belong to + * the query indicated by queryid. + */ +int +pcache_remove_entry_queries_from_cache( + Operation *op, + cache_manager *cm, + struct berval *ndn, + struct berval *queryid ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation op2; + slap_callback sc = { 0 }; + SlapReply rs = { REP_RESULT }; + Filter f = { 0 }; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + int rc; + + BerVarray vals = NULL; + + if ( op == NULL ) { + void *thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init( &conn, &opbuf, thrctx ); + op = &opbuf.ob_op; + + } else { + op2 = *op; + op = &op2; + } + + memset( &op->oq_search, 0, sizeof( op->oq_search ) ); + op->ors_scope = LDAP_SCOPE_BASE; + op->ors_deref = LDAP_DEREF_NEVER; + if ( queryid == NULL || BER_BVISNULL( queryid ) ) { + BER_BVSTR( &op->ors_filterstr, "(objectClass=*)" ); + f.f_choice = LDAP_FILTER_PRESENT; + f.f_desc = slap_schema.si_ad_objectClass; + + } else { + op->ors_filterstr.bv_len = snprintf( filter_str, + sizeof( filter_str ), "(%s=%s)", + ad_queryId->ad_cname.bv_val, queryid->bv_val ); + f.f_choice = LDAP_FILTER_EQUALITY; + f.f_ava = &ava; + f.f_av_desc = ad_queryId; + f.f_av_value = *queryid; + } + op->ors_filter = &f; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + op->o_req_dn = *ndn; + op->o_req_ndn = *ndn; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + sc.sc_response = fetch_queryId_cb; + op->o_callback = ≻ + + rc = op->o_bd->be_search( op, &rs ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + vals = (BerVarray)op->o_callback->sc_private; + if ( vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + struct berval val = vals[ i ]; + + remove_query_and_data( op, cm, &val ); + + if ( !BER_BVISNULL( &val ) && val.bv_val != vals[ i ].bv_val ) { + ch_free( val.bv_val ); + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + return LDAP_SUCCESS; +} + +static int +cache_entries( + Operation *op, + struct berval *query_uuid ) +{ + struct search_info *si = op->o_callback->sc_private; + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + int return_val = 0; + Entry *e; + struct berval crp_uuid; + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + Operation *op_tmp; + Connection conn = {0}; + OperationBuffer opbuf; + void *thrctx = ldap_pvt_thread_pool_context(); + + query_uuid->bv_len = lutil_uuidstr(uuidbuf, sizeof(uuidbuf)); + ber_str2bv(uuidbuf, query_uuid->bv_len, 1, query_uuid); + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op_tmp = &opbuf.ob_op; + op_tmp->o_bd = &cm->db; + op_tmp->o_dn = cm->db.be_rootdn; + op_tmp->o_ndn = cm->db.be_rootndn; + + Debug( pcache_debug, "UUID for query being added = %s\n", + uuidbuf ); + + for ( e=si->head; e; e=si->head ) { + si->head = e->e_private; + e->e_private = NULL; + while ( cm->cur_entries > (cm->max_entries) ) { + BER_BVZERO( &crp_uuid ); + remove_query_and_data( op_tmp, cm, &crp_uuid ); + } + + return_val = merge_entry(op_tmp, e, 0, query_uuid); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->cur_entries += return_val; + Debug( pcache_debug, + "ENTRY ADDED/MERGED, CACHED ENTRIES=%d\n", + cm->cur_entries ); + return_val = 0; + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + } + + return return_val; +} + +static int +pcache_op_cleanup( Operation *op, SlapReply *rs ) { + slap_callback *cb = op->o_callback; + struct search_info *si = cb->sc_private; + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + + if ( rs->sr_type == REP_RESULT || + op->o_abandon || rs->sr_err == SLAPD_ABANDON ) + { + if ( si->swap_saved_attrs ) { + rs->sr_attrs = si->save_attrs; + op->ors_attrs = si->save_attrs; + } + if ( (op->o_abandon || rs->sr_err == SLAPD_ABANDON) && + si->caching_reason == PC_IGNORE ) + { + filter_free( si->query.filter ); + if ( si->count ) { + /* duplicate query, free it */ + Entry *e; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else if ( si->caching_reason != PC_IGNORE ) { + CachedQuery *qc = qm->addfunc(op, qm, &si->query, + si->qtemp, si->caching_reason, 1 ); + + if ( qc != NULL ) { + switch ( si->caching_reason ) { + case PC_POSITIVE: + cache_entries( op, &qc->q_uuid ); + if ( si->pbi ) { + qc->bind_refcnt++; + si->pbi->bi_cq = qc; + } + break; + + case PC_SIZELIMIT: + qc->q_sizelimit = rs->sr_nentries; + break; + + case PC_NEGATIVE: + break; + + default: + assert( 0 ); + break; + } + ldap_pvt_thread_rdwr_wunlock(&qc->rwlock); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->num_cached_queries++; + Debug( pcache_debug, "STORED QUERIES = %lu\n", + cm->num_cached_queries ); + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + + /* If the consistency checker suspended itself, + * wake it back up + */ + if ( cm->cc_paused == PCACHE_CC_PAUSED ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( cm->cc_paused == PCACHE_CC_PAUSED ) { + cm->cc_paused = 0; + ldap_pvt_runqueue_resched( &slapd_rq, cm->cc_arg, 0 ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + } else if ( si->count ) { + /* duplicate query, free it */ + Entry *e; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else { + filter_free( si->query.filter ); + } + + op->o_callback = op->o_callback->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static int +pcache_response( + Operation *op, + SlapReply *rs ) +{ + struct search_info *si = op->o_callback->sc_private; + + if ( si->swap_saved_attrs ) { + rs->sr_attrs = si->save_attrs; + rs->sr_attr_flags = slap_attr_flags( si->save_attrs ); + op->ors_attrs = si->save_attrs; + } + + if ( rs->sr_type == REP_SEARCH ) { + Entry *e; + + /* don't return more entries than requested by the client */ + if ( si->slimit > 0 && rs->sr_nentries >= si->slimit ) { + si->slimit_exceeded = 1; + } + + /* If we haven't exceeded the limit for this query, + * build a chain of answers to store. If we hit the + * limit, empty the chain and ignore the rest. + */ + if ( !si->over ) { + slap_overinst *on = si->on; + cache_manager *cm = on->on_bi.bi_private; + + /* check if the entry contains undefined + * attributes/objectClasses (ITS#5680) */ + if ( cm->check_cacheability && test_filter( op, rs->sr_entry, si->query.filter ) != LDAP_COMPARE_TRUE ) { + Debug( pcache_debug, "%s: query not cacheable because of schema issues in DN \"%s\"\n", + op->o_log_prefix, rs->sr_entry->e_name.bv_val ); + goto over; + } + + /* check for malformed entries: attrs with no values */ + { + Attribute *a = rs->sr_entry->e_attrs; + for (; a; a=a->a_next) { + if ( !a->a_numvals ) { + Debug( pcache_debug, "%s: query not cacheable because of attrs without values in DN \"%s\" (%s)\n", + op->o_log_prefix, rs->sr_entry->e_name.bv_val, + a->a_desc->ad_cname.bv_val ); + goto over; + } + } + } + + if ( si->count < si->max ) { + si->count++; + e = entry_dup( rs->sr_entry ); + if ( !si->head ) si->head = e; + if ( si->tail ) si->tail->e_private = e; + si->tail = e; + + } else { +over:; + si->over = 1; + si->count = 0; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + si->tail = NULL; + } + } + if ( si->slimit_exceeded ) { + return 0; + } + } else if ( rs->sr_type == REP_RESULT ) { + + if ( si->count ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + si->caching_reason = PC_POSITIVE; + + } else if ( rs->sr_err == LDAP_SIZELIMIT_EXCEEDED + && si->qtemp->limitttl ) + { + Entry *e; + + si->caching_reason = PC_SIZELIMIT; + for (;si->head; si->head=e) { + e = si->head->e_private; + si->head->e_private = NULL; + entry_free(si->head); + } + } + + } else if ( si->qtemp->negttl && !si->count && !si->over && + rs->sr_err == LDAP_SUCCESS ) + { + si->caching_reason = PC_NEGATIVE; + } + + + if ( si->slimit_exceeded ) { + rs->sr_err = LDAP_SIZELIMIT_EXCEEDED; + } + } + + return SLAP_CB_CONTINUE; +} + +/* NOTE: this is a quick workaround to let pcache minimally interact + * with pagedResults. A more articulated solutions would be to + * perform the remote query without control and cache all results, + * performing the pagedResults search only within the client + * and the proxy. This requires pcache to understand pagedResults. */ +static int +pcache_chk_controls( + Operation *op, + SlapReply *rs ) +{ + const char *non = ""; + const char *stripped = ""; + + switch( op->o_pagedresults ) { + case SLAP_CONTROL_NONCRITICAL: + non = "non-"; + stripped = "; stripped"; + /* fallthru */ + + case SLAP_CONTROL_CRITICAL: + Debug( pcache_debug, "%s: " + "%scritical pagedResults control " + "disabled with proxy cache%s.\n", + op->o_log_prefix, non, stripped ); + + slap_remove_control( op, rs, slap_cids.sc_pagedResults, NULL ); + break; + + default: + rs->sr_err = SLAP_CB_CONTINUE; + break; + } + + return rs->sr_err; +} + +static int +pc_setpw( Operation *op, struct berval *pwd, cache_manager *cm ) +{ + struct berval vals[2]; + + { + const char *text = NULL; + BER_BVZERO( &vals[0] ); + slap_passwd_hash( pwd, &vals[0], &text ); + if ( BER_BVISEMPTY( &vals[0] )) { + Debug( pcache_debug, "pc_setpw: hash failed %s\n", + text ); + return LDAP_OTHER; + } + } + + BER_BVZERO( &vals[1] ); + + { + Modifications mod; + SlapReply sr = { REP_RESULT }; + slap_callback cb = { 0, slap_null_cb, 0, 0 }; + int rc; + + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = 0; + mod.sml_desc = slap_schema.si_ad_userPassword; + mod.sml_type = mod.sml_desc->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->o_bd = &cm->db; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_callback = &cb; + Debug( pcache_debug, "pc_setpw: CACHING BIND for %s\n", + op->o_req_dn.bv_val ); + rc = op->o_bd->be_modify( op, &sr ); + ch_free( vals[0].bv_val ); + return rc; + } +} + +typedef struct bindcacheinfo { + slap_overinst *on; + CachedQuery *qc; +} bindcacheinfo; + +static int +pc_bind_save( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_err == LDAP_SUCCESS ) { + bindcacheinfo *bci = op->o_callback->sc_private; + slap_overinst *on = bci->on; + cache_manager *cm = on->on_bi.bi_private; + CachedQuery *qc = bci->qc; + int delete = 0; + + ldap_pvt_thread_rdwr_wlock( &qc->rwlock ); + if ( qc->bind_refcnt-- ) { + Operation op2 = *op; + if ( pc_setpw( &op2, &op->orb_cred, cm ) == LDAP_SUCCESS ) + bci->qc->bindref_time = op->o_time + bci->qc->qtemp->bindttr; + } else { + bci->qc = NULL; + delete = 1; + } + ldap_pvt_thread_rdwr_wunlock( &qc->rwlock ); + if ( delete ) free_query(qc); + } + return SLAP_CB_CONTINUE; +} + +static Filter * +pc_bind_attrs( Operation *op, Entry *e, QueryTemplate *temp, + struct berval *fbv ) +{ + int i, len = 0; + struct berval *vals, pres = BER_BVC("*"); + char *p1, *p2; + Attribute *a; + + vals = op->o_tmpalloc( temp->bindnattrs * sizeof( struct berval ), + op->o_tmpmemctx ); + + for ( i=0; i<temp->bindnattrs; i++ ) { + a = attr_find( e->e_attrs, temp->bindfattrs[i] ); + if ( a && a->a_vals ) { + vals[i] = a->a_vals[0]; + len += a->a_vals[0].bv_len; + } else { + vals[i] = pres; + } + } + fbv->bv_len = len + temp->bindftemp.bv_len; + fbv->bv_val = op->o_tmpalloc( fbv->bv_len + 1, op->o_tmpmemctx ); + + p1 = temp->bindftemp.bv_val; + p2 = fbv->bv_val; + i = 0; + while ( *p1 ) { + *p2++ = *p1; + if ( p1[0] == '=' && p1[1] == ')' ) { + AC_MEMCPY( p2, vals[i].bv_val, vals[i].bv_len ); + p2 += vals[i].bv_len; + i++; + } + p1++; + } + *p2 = '\0'; + op->o_tmpfree( vals, op->o_tmpmemctx ); + + /* FIXME: are we sure str2filter_x can't fail? + * caller needs to check */ + { + Filter *f = str2filter_x( op, fbv->bv_val ); + assert( f != NULL ); + return f; + } +} + +/* Check if the requested entry is from the cache and has a valid + * ttr and password hash + */ +static int +pc_bind_search( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + bindinfo *pbi = op->o_callback->sc_private; + + /* We only care if this is an already cached result and we're + * below the refresh time, or we're offline. + */ + if ( pbi->bi_cq ) { + if (( pbi->bi_cm->cc_paused & PCACHE_CC_OFFLINE ) || + op->o_time < pbi->bi_cq->bindref_time ) { + Attribute *a; + + /* See if a recognized password is hashed here */ + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_userPassword ); + if ( a && a->a_vals[0].bv_val[0] == '{' && + lutil_passwd_scheme( a->a_vals[0].bv_val )) + pbi->bi_flags |= BI_HASHED; + } else { + Debug( pcache_debug, "pc_bind_search: cache is stale, " + "reftime: %ld, current time: %ld\n", + pbi->bi_cq->bindref_time, op->o_time ); + } + } else if ( pbi->bi_si ) { + /* This search result is going into the cache */ + struct berval fbv; + Filter *f; + + filter_free( pbi->bi_si->query.filter ); + f = pc_bind_attrs( op, rs->sr_entry, pbi->bi_templ, &fbv ); + op->o_tmpfree( fbv.bv_val, op->o_tmpmemctx ); + pbi->bi_si->query.filter = filter_dup( f, NULL ); + filter_free_x( op, f, 1 ); + } + } + return 0; +} + +/* We always want pc_bind_search to run after the search handlers */ +static int +pc_bind_resp( Operation *op, SlapReply *rs ) +{ + bindinfo *pbi = op->o_callback->sc_private; + if ( !( pbi->bi_flags & BI_DIDCB )) { + slap_callback *sc = op->o_callback; + while ( sc && sc->sc_response != pcache_response ) + sc = sc->sc_next; + if ( !sc ) + sc = op->o_callback; + pbi->bi_cb.sc_next = sc->sc_next; + sc->sc_next = &pbi->bi_cb; + pbi->bi_flags |= BI_DIDCB; + } + return SLAP_CB_CONTINUE; +} + +#ifdef PCACHE_CONTROL_PRIVDB +static int +pcache_op_privdb( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + slap_callback *save_cb; + slap_op_t type; + + /* skip if control is unset */ + if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_CRITICAL ) { + return SLAP_CB_CONTINUE; + } + + /* The cache DB isn't open yet */ + if ( cm->defer_db_open ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE, + "pcachePrivDB: cacheDB not available" ); + return rs->sr_err; + } + + /* FIXME: might be a little bit exaggerated... */ + if ( !be_isroot( op ) ) { + save_cb = op->o_callback; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "pcachePrivDB: operation not allowed" ); + op->o_callback = save_cb; + + return rs->sr_err; + } + + /* map tag to operation */ + type = slap_req2op( op->o_tag ); + if ( type != SLAP_OP_LAST ) { + BackendInfo *bi = cm->db.bd_info; + int rc; + + /* execute, if possible */ + if ( (&bi->bi_op_bind)[ type ] ) { + Operation op2 = *op; + + op2.o_bd = &cm->db; + + rc = (&bi->bi_op_bind)[ type ]( &op2, rs ); + if ( type == SLAP_OP_BIND && rc == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = cm->db.be_private; + } + + return rs->sr_err; + } + } + + /* otherwise fall back to error */ + save_cb = op->o_callback; + op->o_callback = NULL; + send_ldap_error( op, rs, LDAP_UNWILLING_TO_PERFORM, + "operation not supported with pcachePrivDB control" ); + op->o_callback = save_cb; + + return rs->sr_err; +} +#endif /* PCACHE_CONTROL_PRIVDB */ + +static int +pcache_op_bind( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + QueryTemplate *temp; + Entry *e; + slap_callback cb = { 0 }, *sc; + bindinfo bi = { 0 }; + bindcacheinfo *bci; + Operation op2; + int rc; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) + return pcache_op_privdb( op, rs ); +#endif /* PCACHE_CONTROL_PRIVDB */ + + /* Skip if we're not configured for Binds, or cache DB isn't open yet */ + if ( !cm->cache_binds || cm->defer_db_open ) + return SLAP_CB_CONTINUE; + + /* First find a matching template with Bind info */ + for ( temp=cm->qm->templates; temp; temp=temp->qmnext ) { + if ( temp->bindttr && dnIsSuffix( &op->o_req_ndn, &temp->bindbase )) + break; + } + /* Didn't find a suitable template, just passthru */ + if ( !temp ) + return SLAP_CB_CONTINUE; + + /* See if the entry is already locally cached. If so, we can + * populate the query filter to retrieve the cached query. We + * need to check the bindrefresh time in the query. + */ + op2 = *op; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.o_bd = &cm->db; + e = NULL; + rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS && e ) { + bi.bi_flags |= BI_LOOKUP; + op2.ors_filter = pc_bind_attrs( op, e, temp, &op2.ors_filterstr ); + be_entry_release_r( &op2, e ); + } else { + op2.ors_filter = temp->bindfilter; + op2.ors_filterstr = temp->bindfilterstr; + } + + op2.o_bd = op->o_bd; + op2.o_tag = LDAP_REQ_SEARCH; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_limit = NULL; + op2.ors_attrs = cm->qm->attr_sets[temp->attr_set_index].attrs; + op2.ors_attrsonly = 0; + + /* We want to invoke search at the same level of the stack + * as we're already at... + */ + bi.bi_cm = cm; + bi.bi_templ = temp; + + bi.bi_cb.sc_response = pc_bind_search; + bi.bi_cb.sc_private = &bi; + cb.sc_private = &bi; + cb.sc_response = pc_bind_resp; + op2.o_callback = &cb; + overlay_op_walk( &op2, rs, op_search, on->on_info, on ); + + /* OK, just bind locally */ + if ( bi.bi_flags & BI_HASHED ) { + int delete = 0; + BackendDB *be = op->o_bd; + op->o_bd = &cm->db; + + Debug( pcache_debug, "pcache_op_bind: CACHED BIND for %s\n", + op->o_req_dn.bv_val ); + + if ( op->o_bd->be_bind( op, rs ) == LDAP_SUCCESS ) { + op->o_conn->c_authz_cookie = cm->db.be_private; + } + op->o_bd = be; + ldap_pvt_thread_rdwr_wlock( &bi.bi_cq->rwlock ); + if ( !bi.bi_cq->bind_refcnt-- ) { + delete = 1; + } + ldap_pvt_thread_rdwr_wunlock( &bi.bi_cq->rwlock ); + if ( delete ) free_query( bi.bi_cq ); + return rs->sr_err; + } + + /* We have a cached query to work with */ + if ( bi.bi_cq ) { + sc = op->o_tmpalloc( sizeof(slap_callback) + sizeof(bindcacheinfo), + op->o_tmpmemctx ); + sc->sc_response = pc_bind_save; + sc->sc_cleanup = NULL; + sc->sc_private = sc+1; + sc->sc_writewait = NULL; + bci = sc->sc_private; + sc->sc_next = op->o_callback; + op->o_callback = sc; + bci->on = on; + bci->qc = bi.bi_cq; + } + return SLAP_CB_CONTINUE; +} + +static slap_response refresh_merge; + +static int +pcache_op_search( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + + int i = -1; + + Query query; + QueryTemplate *qtemp = NULL; + bindinfo *pbi = NULL; + + int attr_set = -1; + CachedQuery *answerable = NULL; + int cacheable = 0; + + struct berval tempstr; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) { + return pcache_op_privdb( op, rs ); + } +#endif /* PCACHE_CONTROL_PRIVDB */ + + /* The cache DB isn't open yet */ + if ( cm->defer_db_open ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE, + "pcachePrivDB: cacheDB not available" ); + return rs->sr_err; + } + + /* pickup runtime ACL changes */ + cm->db.be_acl = op->o_bd->be_acl; + + { + /* See if we're processing a Bind request + * or a cache refresh */ + slap_callback *cb = op->o_callback; + + for ( ; cb; cb=cb->sc_next ) { + if ( cb->sc_response == pc_bind_resp ) { + pbi = cb->sc_private; + break; + } + if ( cb->sc_response == refresh_merge ) { + /* This is a refresh, do not search the cache */ + return SLAP_CB_CONTINUE; + } + } + } + + /* FIXME: cannot cache/answer requests with pagedResults control */ + + query.filter = op->ors_filter; + + if ( pbi ) { + query.base = pbi->bi_templ->bindbase; + query.scope = pbi->bi_templ->bindscope; + attr_set = pbi->bi_templ->attr_set_index; + cacheable = 1; + qtemp = pbi->bi_templ; + if ( pbi->bi_flags & BI_LOOKUP ) + answerable = qm->qcfunc(op, qm, &query, qtemp); + + } else { + tempstr.bv_val = op->o_tmpalloc( op->ors_filterstr.bv_len+1, + op->o_tmpmemctx ); + tempstr.bv_len = 0; + if ( filter2template( op, op->ors_filter, &tempstr )) + { + op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; + } + + Debug( pcache_debug, "query template of incoming query = %s\n", + tempstr.bv_val ); + + /* find attr set */ + attr_set = get_attr_set(op->ors_attrs, qm, cm->numattrsets); + + query.base = op->o_req_ndn; + query.scope = op->ors_scope; + + /* check for query containment */ + if (attr_set > -1) { + QueryTemplate *qt = qm->attr_sets[attr_set].templates; + for (; qt; qt = qt->qtnext ) { + /* find if template i can potentially answer tempstr */ + if ( ber_bvstrcasecmp( &qt->querystr, &tempstr ) != 0 ) + continue; + cacheable = 1; + qtemp = qt; + Debug( pcache_debug, "Entering QC, querystr = %s\n", + op->ors_filterstr.bv_val ); + answerable = qm->qcfunc(op, qm, &query, qt); + + /* if != NULL, rlocks qtemp->t_rwlock */ + if (answerable) + break; + } + } + op->o_tmpfree( tempstr.bv_val, op->o_tmpmemctx ); + } + + if (answerable) { + BackendDB *save_bd = op->o_bd; + + ldap_pvt_thread_mutex_lock( &answerable->answerable_cnt_mutex ); + answerable->answerable_cnt++; + /* we only care about refcnts if we're refreshing */ + if ( answerable->refresh_time ) + answerable->refcnt++; + Debug( pcache_debug, "QUERY ANSWERABLE (answered %lu times)\n", + answerable->answerable_cnt ); + ldap_pvt_thread_mutex_unlock( &answerable->answerable_cnt_mutex ); + + ldap_pvt_thread_rdwr_wlock(&answerable->rwlock); + if ( BER_BVISNULL( &answerable->q_uuid )) { + /* No entries cached, just an empty result set */ + i = rs->sr_err = 0; + send_ldap_result( op, rs ); + } else { + /* Let Bind know we used a cached query */ + if ( pbi ) { + answerable->bind_refcnt++; + pbi->bi_cq = answerable; + } + + op->o_bd = &cm->db; + if ( cm->response_cb == PCACHE_RESPONSE_CB_TAIL ) { + slap_callback cb; + /* The cached entry was already processed by any + * other overlays, so don't let it get processed again. + * + * This loop removes over_back_response from the stack. + */ + if ( overlay_callback_after_backover( op, &cb, 0) == 0 ) { + slap_callback **scp; + for ( scp = &op->o_callback; *scp != NULL; + scp = &(*scp)->sc_next ) { + if ( (*scp)->sc_next == &cb ) { + *scp = cb.sc_next; + break; + } + } + } + } + i = cm->db.bd_info->bi_op_search( op, rs ); + } + ldap_pvt_thread_rdwr_wunlock(&answerable->rwlock); + /* locked by qtemp->qcfunc (query_containment) */ + ldap_pvt_thread_rdwr_runlock(&qtemp->t_rwlock); + op->o_bd = save_bd; + return i; + } + + Debug( pcache_debug, "QUERY NOT ANSWERABLE\n" ); + + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + if (cm->num_cached_queries >= cm->max_queries) { + cacheable = 0; + } + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + + if (op->ors_attrsonly) + cacheable = 0; + + if (cacheable) { + slap_callback *cb; + struct search_info *si; + + Debug( pcache_debug, "QUERY CACHEABLE\n" ); + query.filter = filter_dup(op->ors_filter, NULL); + + cb = op->o_tmpalloc( sizeof(*cb) + sizeof(*si), op->o_tmpmemctx ); + cb->sc_response = pcache_response; + cb->sc_cleanup = pcache_op_cleanup; + cb->sc_private = (cb+1); + cb->sc_writewait = 0; + si = cb->sc_private; + si->on = on; + si->query = query; + si->qtemp = qtemp; + si->max = cm->num_entries_limit ; + si->over = 0; + si->count = 0; + si->slimit = 0; + si->slimit_exceeded = 0; + si->caching_reason = PC_IGNORE; + if ( op->ors_slimit > 0 && op->ors_slimit < cm->num_entries_limit ) { + si->slimit = op->ors_slimit; + op->ors_slimit = cm->num_entries_limit; + } + si->head = NULL; + si->tail = NULL; + si->swap_saved_attrs = 1; + si->save_attrs = op->ors_attrs; + si->pbi = pbi; + if ( pbi ) + pbi->bi_si = si; + + op->ors_attrs = qtemp->t_attrs.attrs; + + if ( cm->response_cb == PCACHE_RESPONSE_CB_HEAD ) { + cb->sc_next = op->o_callback; + op->o_callback = cb; + + } else { + slap_callback **pcb; + + /* need to move the callback at the end, in case other + * overlays are present, so that the final entry is + * actually cached */ + cb->sc_next = NULL; + for ( pcb = &op->o_callback; *pcb; pcb = &(*pcb)->sc_next ); + *pcb = cb; + } + + } else { + Debug( pcache_debug, "QUERY NOT CACHEABLE\n" ); + } + + return SLAP_CB_CONTINUE; +} + +static int +get_attr_set( + AttributeName* attrs, + query_manager* qm, + int num ) +{ + int i = 0; + int count = 0; + + if ( attrs ) { + for ( ; attrs[i].an_name.bv_val; i++ ) { + /* only count valid attribute names + * (searches ignore others, this overlay does the same) */ + if ( attrs[i].an_desc ) { + count++; + } + } + } + + /* recognize default or explicit single "*" */ + if ( ! attrs || + ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_all_user_attrs ) ) ) + { + count = 1; + attrs = slap_anlist_all_user_attributes; + + /* recognize implicit (no valid attributes) or explicit single "1.1" */ + } else if ( count == 0 || + ( i == 1 && bvmatch( &attrs[0].an_name, slap_bv_no_attrs ) ) ) + { + count = 0; + attrs = NULL; + } + + for ( i = 0; i < num; i++ ) { + AttributeName *a2; + int found = 1; + + if ( count > qm->attr_sets[i].count ) { + if ( qm->attr_sets[i].count && + bvmatch( &qm->attr_sets[i].attrs[0].an_name, slap_bv_all_user_attrs )) { + break; + } + continue; + } + + if ( !count ) { + if ( !qm->attr_sets[i].count ) { + break; + } + continue; + } + + for ( a2 = attrs; a2->an_name.bv_val; a2++ ) { + if ( !a2->an_desc && !bvmatch( &a2->an_name, slap_bv_all_user_attrs ) ) continue; + + if ( !an_find( qm->attr_sets[i].attrs, &a2->an_name ) ) { + found = 0; + break; + } + } + + if ( found ) { + break; + } + } + + if ( i == num ) { + i = -1; + } + + return i; +} + +/* Refresh a cached query: + * 1: Replay the query on the remote DB and merge each entry into + * the local DB. Remember the DNs of each remote entry. + * 2: Search the local DB for all entries matching this queryID. + * Delete any entry whose DN is not in the list from (1). + */ +typedef struct dnlist { + struct dnlist *next; + struct berval dn; + char delete; +} dnlist; + +typedef struct refresh_info { + dnlist *ri_dns; + dnlist *ri_tail; + dnlist *ri_dels; + BackendDB *ri_be; + CachedQuery *ri_q; +} refresh_info; + +static dnlist *dnl_alloc( Operation *op, struct berval *bvdn ) +{ + dnlist *dn = op->o_tmpalloc( sizeof(dnlist) + bvdn->bv_len + 1, + op->o_tmpmemctx ); + dn->dn.bv_len = bvdn->bv_len; + dn->dn.bv_val = (char *)(dn+1); + AC_MEMCPY( dn->dn.bv_val, bvdn->bv_val, dn->dn.bv_len ); + dn->dn.bv_val[dn->dn.bv_len] = '\0'; + return dn; +} + +static int +refresh_merge( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + refresh_info *ri = op->o_callback->sc_private; + Entry *e; + dnlist *dnl; + slap_callback *ocb; + int rc; + + ocb = op->o_callback; + /* Find local entry, merge */ + op->o_bd = ri->ri_be; + rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ); + if ( rc != LDAP_SUCCESS || e == NULL ) { + /* No local entry, just add it. FIXME: we are not checking + * the cache entry limit here + */ + merge_entry( op, rs->sr_entry, 1, &ri->ri_q->q_uuid ); + } else { + /* Entry exists, update it */ + Entry ne; + Attribute *a, **b; + Modifications *modlist, *mods = NULL; + const char* text = NULL; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + + ne = *e; + b = &ne.e_attrs; + /* Get a copy of only the attrs we requested */ + for ( a=e->e_attrs; a; a=a->a_next ) { + if ( ad_inlist( a->a_desc, rs->sr_attrs )) { + *b = attr_alloc( a->a_desc ); + *(*b) = *a; + /* The actual values still belong to e */ + (*b)->a_flags |= SLAP_ATTR_DONT_FREE_VALS | + SLAP_ATTR_DONT_FREE_DATA; + b = &((*b)->a_next); + } + } + *b = NULL; + slap_entry2mods( rs->sr_entry, &modlist, &text, textbuf, textlen ); + syncrepl_diff_entry( op, ne.e_attrs, rs->sr_entry->e_attrs, + &mods, &modlist, 0 ); + be_entry_release_r( op, e ); + attrs_free( ne.e_attrs ); + slap_mods_free( modlist, 1 ); + /* mods is NULL if there are no changes */ + if ( mods ) { + SlapReply rs2 = { REP_RESULT }; + struct berval dn = op->o_req_dn; + struct berval ndn = op->o_req_ndn; + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = mods; + op->o_req_dn = rs->sr_entry->e_name; + op->o_req_ndn = rs->sr_entry->e_nname; + op->o_callback = &cb; + op->o_bd->be_modify( op, &rs2 ); + rs->sr_err = rs2.sr_err; + rs_assert_done( &rs2 ); + slap_mods_free( mods, 1 ); + op->o_req_dn = dn; + op->o_req_ndn = ndn; + } + } + + /* Add DN to list */ + dnl = dnl_alloc( op, &rs->sr_entry->e_nname ); + dnl->next = NULL; + if ( ri->ri_tail ) { + ri->ri_tail->next = dnl; + } else { + ri->ri_dns = dnl; + } + ri->ri_tail = dnl; + op->o_callback = ocb; + } + return 0; +} + +static int +refresh_purge( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + refresh_info *ri = op->o_callback->sc_private; + dnlist **dn; + int del = 1; + + /* Did the entry exist on the remote? */ + for ( dn=&ri->ri_dns; *dn; dn = &(*dn)->next ) { + if ( dn_match( &(*dn)->dn, &rs->sr_entry->e_nname )) { + dnlist *dnext = (*dn)->next; + op->o_tmpfree( *dn, op->o_tmpmemctx ); + *dn = dnext; + del = 0; + break; + } + } + /* No, so put it on the list to delete */ + if ( del ) { + Attribute *a; + dnlist *dnl = dnl_alloc( op, &rs->sr_entry->e_nname ); + dnl->next = ri->ri_dels; + ri->ri_dels = dnl; + a = attr_find( rs->sr_entry->e_attrs, ad_queryId ); + /* If ours is the only queryId, delete entry */ + dnl->delete = ( a->a_numvals == 1 ); + } + } + return 0; +} + +static int +refresh_query( Operation *op, CachedQuery *query, slap_overinst *on ) +{ + SlapReply rs = {REP_RESULT}; + slap_callback cb = { 0 }; + refresh_info ri = { 0 }; + char filter_str[ LDAP_LUTIL_UUIDSTR_BUFSIZE + STRLENOF( "(pcacheQueryID=)" ) ]; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + Filter filter = {LDAP_FILTER_EQUALITY}; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + dnlist *dn; + int rc; + + ldap_pvt_thread_mutex_lock( &query->answerable_cnt_mutex ); + query->refcnt = 0; + ldap_pvt_thread_mutex_unlock( &query->answerable_cnt_mutex ); + + cb.sc_response = refresh_merge; + cb.sc_private = &ri; + + /* cache DB */ + ri.ri_be = op->o_bd; + ri.ri_q = query; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + op->o_callback = &cb; + op->o_do_not_cache = 1; + + op->o_req_dn = query->qbase->base; + op->o_req_ndn = query->qbase->base; + op->ors_scope = query->scope; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + op->ors_filter = query->filter; + filter2bv_x( op, query->filter, &op->ors_filterstr ); + op->ors_attrs = query->qtemp->t_attrs.attrs; + op->ors_attrsonly = 0; + + op->o_bd = on->on_info->oi_origdb; + rc = op->o_bd->be_search( op, &rs ); + if ( rc ) { + op->o_bd = ri.ri_be; + goto leave; + } + + /* Get the DNs of all entries matching this query */ + cb.sc_response = refresh_purge; + + op->o_bd = ri.ri_be; + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_filterstr.bv_len = snprintf(filter_str, sizeof(filter_str), + "(%s=%s)", ad_queryId->ad_cname.bv_val, query->q_uuid.bv_val); + filter.f_ava = &ava; + filter.f_av_desc = ad_queryId; + filter.f_av_value = query->q_uuid; + attrs[ 0 ].an_desc = ad_queryId; + attrs[ 0 ].an_name = ad_queryId->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + rs_reinit( &rs, REP_RESULT ); + rc = op->o_bd->be_search( op, &rs ); + if ( rc ) goto leave; + + while (( dn = ri.ri_dels )) { + op->o_req_dn = dn->dn; + op->o_req_ndn = dn->dn; + rs_reinit( &rs, REP_RESULT ); + if ( dn->delete ) { + op->o_tag = LDAP_REQ_DELETE; + op->o_bd->be_delete( op, &rs ); + } else { + Modifications mod; + struct berval vals[2]; + + vals[0] = query->q_uuid; + BER_BVZERO( &vals[1] ); + mod.sml_op = LDAP_MOD_DELETE; + mod.sml_flags = 0; + mod.sml_desc = ad_queryId; + mod.sml_type = ad_queryId->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_numvals = 1; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = &mod; + op->o_bd->be_modify( op, &rs ); + } + ri.ri_dels = dn->next; + op->o_tmpfree( dn, op->o_tmpmemctx ); + } + +leave: + /* reset our local heap, we're done with it */ + slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, op->o_threadctx, 1 ); + return rc; +} + +static void* +consistency_check( + void *ctx, + void *arg ) +{ + struct re_s *rtask = arg; + slap_overinst *on = rtask->arg; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + CachedQuery *query, *qprev; + CachedQuery *expires = NULL; + 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; + } + + Debug( pcache_debug, "Lock CR index = %p\n", + (void *) templ ); + ldap_pvt_thread_rdwr_wlock(&templ->t_rwlock); + for ( query=templ->query_last; query; query=qprev ) { + qprev = query->prev; + if ( query->refresh_time && query->refresh_time < op->o_time ) { + /* A refresh will extend the expiry if the query has been + * referenced, but not if it's unreferenced. If the + * expiration has been hit, then skip the refresh since + * we're just going to discard the result anyway. + */ + if ( query->refcnt ) + query->expiry_time = op->o_time + templ->ttl; + if ( query->expiry_time > op->o_time ) { + /* perform actual refresh below */ + continue; + } + } + + if (query->expiry_time < op->o_time) { + int rem = 0; + if ( query != templ->query_last ) + continue; + ldap_pvt_thread_mutex_lock(&qm->lru_mutex); + if (query->in_lru) { + remove_query(qm, query); + rem = 1; + } + ldap_pvt_thread_mutex_unlock(&qm->lru_mutex); + if (!rem) + continue; + remove_from_template(query, templ); + Debug( pcache_debug, "TEMPLATE %p QUERIES-- %d\n", + (void *) templ, templ->no_of_queries ); + query->prev = expires; + expires = query; + query->qtemp = NULL; + } else if ( !templ->ttr && query->expiry_time > ttl ) { + /* We don't need to check for refreshes, and this + * query's expiry is too new, and all subsequent queries + * will be newer yet. So stop looking. + * + * If we have refreshes, then we always have to walk the + * entire query list. + */ + break; + } + } + Debug( pcache_debug, "Unlock CR index = %p\n", + (void *) templ ); + ldap_pvt_thread_rdwr_wunlock(&templ->t_rwlock); + for ( query=expires; query; query=qprev ) { + int rem; + qprev = query->prev; + if ( BER_BVISNULL( &query->q_uuid )) + return_val = 0; + else + return_val = remove_query_data(op, &query->q_uuid); + Debug( pcache_debug, "STALE QUERY REMOVED, SIZE=%d\n", + return_val ); + ldap_pvt_thread_mutex_lock(&cm->cache_mutex); + cm->cur_entries -= return_val; + cm->num_cached_queries--; + Debug( pcache_debug, "STORED QUERIES = %lu\n", + cm->num_cached_queries ); + ldap_pvt_thread_mutex_unlock(&cm->cache_mutex); + Debug( pcache_debug, + "STALE QUERY REMOVED, CACHE =" + "%d entries\n", + cm->cur_entries ); + ldap_pvt_thread_rdwr_wlock( &query->rwlock ); + if ( query->bind_refcnt-- ) { + rem = 0; + } else { + rem = 1; + } + ldap_pvt_thread_rdwr_wunlock( &query->rwlock ); + if ( rem ) free_query(query); + } + + /* handle refreshes that we skipped earlier */ + if ( templ->ttr ) { + ldap_pvt_thread_rdwr_rlock(&templ->t_rwlock); + for ( query=templ->query_last; query; query=qprev ) { + qprev = query->prev; + if ( query->refresh_time && query->refresh_time < op->o_time ) { + /* A refresh will extend the expiry if the query has been + * referenced, but not if it's unreferenced. If the + * expiration has been hit, then skip the refresh since + * we're just going to discard the result anyway. + */ + if ( query->expiry_time > op->o_time ) { + refresh_query( op, query, on ); + query->refresh_time = op->o_time + templ->ttr; + } + } + } + ldap_pvt_thread_rdwr_runlock(&templ->t_rwlock); + } + } + +leave: + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, rtask )) { + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + } + /* If there were no queries, defer processing for a while */ + if ( cm->cc_paused != pause ) + cm->cc_paused = pause; + ldap_pvt_runqueue_resched( &slapd_rq, rtask, pause ); + + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + return NULL; +} + + +#define MAX_ATTR_SETS 500 + +enum { + PC_MAIN = 1, + PC_ATTR, + PC_TEMP, + PC_RESP, + PC_QUERIES, + PC_OFFLINE, + PC_BIND, + PC_PRIVATE_DB +}; + +static ConfigDriver pc_cf_gen; +static ConfigLDAPadd pc_ldadd; +static ConfigCfAdd pc_cfadd; + +static ConfigTable pccfg[] = { + { "pcache", "backend> <max_entries> <numattrsets> <entry limit> " + "<cycle_time", + 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen, + "( OLcfgOvAt:2.1 NAME ( 'olcPcache' 'olcProxyCache' ) " + "DESC 'Proxy Cache basic parameters' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "pcacheAttrset", "index> <attributes...", + 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen, + "( OLcfgOvAt:2.2 NAME ( 'olcPcacheAttrset' 'olcProxyAttrset' ) " + "DESC 'A set of attributes to cache' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcacheTemplate", "filter> <attrset-index> <TTL> <negTTL> " + "<limitTTL> <TTR", + 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen, + "( OLcfgOvAt:2.3 NAME ( 'olcPcacheTemplate' 'olcProxyCacheTemplate' ) " + "DESC 'Filter template, attrset, cache TTL, " + "optional negative TTL, optional sizelimit TTL, " + "optional TTR' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcachePosition", "head|tail(default)", + 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen, + "( OLcfgOvAt:2.4 NAME 'olcPcachePosition' " + "DESC 'Response callback position in overlay stack' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "pcacheMaxQueries", "queries", + 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen, + "( OLcfgOvAt:2.5 NAME ( 'olcPcacheMaxQueries' 'olcProxyCacheQueries' ) " + "DESC 'Maximum number of queries to cache' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "pcachePersist", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries), + "( OLcfgOvAt:2.6 NAME ( 'olcPcachePersist' 'olcProxySaveQueries' ) " + "DESC 'Save cached queries for hot restart' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheValidate", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability), + "( OLcfgOvAt:2.7 NAME ( 'olcPcacheValidate' 'olcProxyCheckCacheability' ) " + "DESC 'Check whether the results of a query are cacheable, e.g. for schema issues' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheOffline", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|PC_OFFLINE, pc_cf_gen, + "( OLcfgOvAt:2.8 NAME 'olcPcacheOffline' " + "DESC 'Set cache to offline mode and disable expiration' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "pcacheBind", "filter> <attrset-index> <TTR> <scope> <base", + 6, 6, 0, ARG_MAGIC|PC_BIND, pc_cf_gen, + "( OLcfgOvAt:2.9 NAME 'olcPcacheBind' " + "DESC 'Parameters for caching Binds' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "pcache-", "private database args", + 1, 0, STRLENOF("pcache-"), ARG_MAGIC|PC_PRIVATE_DB, pc_cf_gen, + NULL, NULL, NULL }, + + /* Legacy keywords */ + { "proxycache", "backend> <max_entries> <numattrsets> <entry limit> " + "<cycle_time", + 6, 6, 0, ARG_MAGIC|ARG_NO_DELETE|PC_MAIN, pc_cf_gen, + NULL, NULL, NULL }, + { "proxyattrset", "index> <attributes...", + 2, 0, 0, ARG_MAGIC|PC_ATTR, pc_cf_gen, + NULL, NULL, NULL }, + { "proxytemplate", "filter> <attrset-index> <TTL> <negTTL", + 4, 7, 0, ARG_MAGIC|PC_TEMP, pc_cf_gen, + NULL, NULL, NULL }, + { "response-callback", "head|tail(default)", + 2, 2, 0, ARG_MAGIC|PC_RESP, pc_cf_gen, + NULL, NULL, NULL }, + { "proxyCacheQueries", "queries", + 2, 2, 0, ARG_INT|ARG_MAGIC|PC_QUERIES, pc_cf_gen, + NULL, NULL, NULL }, + { "proxySaveQueries", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, save_queries), + NULL, NULL, NULL }, + { "proxyCheckCacheability", "TRUE|FALSE", + 2, 2, 0, ARG_ON_OFF|ARG_OFFSET, (void *)offsetof(cache_manager, check_cacheability), + NULL, NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs pcocs[] = { + { "( OLcfgOvOc:2.1 " + "NAME 'olcPcacheConfig' " + "DESC 'ProxyCache configuration' " + "SUP olcOverlayConfig " + "MUST ( olcPcache $ olcPcacheAttrset $ olcPcacheTemplate ) " + "MAY ( olcPcachePosition $ olcPcacheMaxQueries $ olcPcachePersist $ " + "olcPcacheValidate $ olcPcacheOffline $ olcPcacheBind ) )", + Cft_Overlay, pccfg, NULL, pc_cfadd }, + { "( OLcfgOvOc:2.2 " + "NAME 'olcPcacheDatabase' " + "DESC 'Cache database configuration' " + /* co_table is initialized in pcache_initialize */ + "AUXILIARY )", Cft_Misc, NULL, pc_ldadd }, + { NULL, 0, NULL } +}; + +static int pcache_db_open2( slap_overinst *on, ConfigReply *cr ); + +static int +pc_ldadd_cleanup( ConfigArgs *c ) +{ + slap_overinst *on = c->ca_private; + return pcache_db_open2( on, &c->reply ); +} + +static int +pc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + cache_manager *cm; + + if ( p->ce_type != Cft_Overlay || !p->ce_bi || + p->ce_bi->bi_cf_ocs != pcocs ) + return LDAP_CONSTRAINT_VIOLATION; + + on = (slap_overinst *)p->ce_bi; + cm = on->on_bi.bi_private; + ca->be = &cm->db; + /* Defer open if this is an LDAPadd */ + if ( CONFIG_ONLINE_ADD( ca )) + config_push_cleanup( ca, pc_ldadd_cleanup ); + else + cm->defer_db_open = 0; + ca->ca_private = on; + return LDAP_SUCCESS; +} + +static int +pc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca ) +{ + CfEntryInfo *pe = p->e_private; + slap_overinst *on = (slap_overinst *)pe->ce_bi; + cache_manager *cm = on->on_bi.bi_private; + struct berval bv; + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase=" SLAP_X_ORDERED_FMT "%s", + 0, cm->db.bd_info->bi_type ); + if ( bv.bv_len >= sizeof( ca->cr_msg ) ) { + return -1; + } + bv.bv_val = ca->cr_msg; + ca->be = &cm->db; + cm->defer_db_open = 0; + + /* We can only create this entry if the database is table-driven + */ + if ( cm->db.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 ); + return( 1 ); + } + + if ( lutil_atoi( &cm->numattrsets, c->argv[3] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse num attrsets=\"%s\" (arg #3)", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( cm->numattrsets <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( cm->numattrsets > MAX_ATTR_SETS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "numattrsets (arg #3) must be <= %d", MAX_ATTR_SETS ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( !backend_db_init( c->argv[1], &cm->db, -1, NULL )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown backend type (arg #1)" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( lutil_atoi( &cm->max_entries, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse max entries=\"%s\" (arg #2)", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( cm->max_entries <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "max entries (arg #2) must be positive.\n" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( lutil_atoi( &cm->num_entries_limit, c->argv[4] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse entry limit=\"%s\" (arg #4)", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( cm->num_entries_limit <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( cm->num_entries_limit > cm->max_entries ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "entry limit (arg #4) must be less than max entries %d (arg #2)", cm->max_entries ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( lutil_parse_time( c->argv[5], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse period=\"%s\" (arg #5)", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + cm->cc_period = (time_t)t; + Debug( pcache_debug, + "Total # of attribute sets to be cached = %d.\n", + cm->numattrsets ); + qm->attr_sets = ( struct attr_set * )ch_calloc( cm->numattrsets, + sizeof( struct attr_set ) ); + break; + case PC_ATTR: + if ( cm->numattrsets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( lutil_atoi( &num, c->argv[1] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse attrset #=\"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( num < 0 || num >= cm->numattrsets ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "attrset index %d out of bounds (must be %s%d)", + num, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + qm->attr_sets[num].flags |= PC_CONFIGURED; + if ( c->argc == 2 ) { + /* assume "1.1" */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need an explicit attr in attrlist; use \"*\" to indicate all attrs" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + + } else if ( c->argc == 3 ) { + if ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) { + qm->attr_sets[num].count = 1; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES ); + break; + + } else if ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) { + qm->attr_sets[num].count = 1; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 2, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + break; + + } else if ( strcmp( c->argv[2], LDAP_NO_ATTRS ) == 0 ) { + break; + } + /* else: fallthru */ + + } else if ( c->argc == 4 ) { + if ( ( strcmp( c->argv[2], LDAP_ALL_USER_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) + || ( strcmp( c->argv[2], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 && strcmp( c->argv[3], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) ) + { + qm->attr_sets[num].count = 2; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( 3, + sizeof( AttributeName ) ); + BER_BVSTR( &qm->attr_sets[num].attrs[0].an_name, LDAP_ALL_USER_ATTRIBUTES ); + BER_BVSTR( &qm->attr_sets[num].attrs[1].an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + break; + } + /* else: fallthru */ + } + + if ( c->argc > 2 ) { + int all_user = 0, all_op = 0; + + qm->attr_sets[num].count = c->argc - 2; + qm->attr_sets[num].attrs = (AttributeName*)ch_calloc( c->argc - 1, + sizeof( AttributeName ) ); + attr_name = qm->attr_sets[num].attrs; + for ( i = 2; i < c->argc; i++ ) { + attr_name->an_desc = NULL; + if ( strcmp( c->argv[i], LDAP_NO_ATTRS ) == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid attr #%d \"%s\" in attrlist", + i - 2, c->argv[i] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + ch_free( qm->attr_sets[num].attrs ); + qm->attr_sets[num].attrs = NULL; + qm->attr_sets[num].count = 0; + return 1; + } + if ( strcmp( c->argv[i], LDAP_ALL_USER_ATTRIBUTES ) == 0 ) { + all_user = 1; + BER_BVSTR( &attr_name->an_name, LDAP_ALL_USER_ATTRIBUTES ); + } else if ( strcmp( c->argv[i], LDAP_ALL_OPERATIONAL_ATTRIBUTES ) == 0 ) { + all_op = 1; + BER_BVSTR( &attr_name->an_name, LDAP_ALL_OPERATIONAL_ATTRIBUTES ); + } else { + if ( strncasecmp( c->argv[i], "undef:", STRLENOF("undef:") ) == 0 ) { + struct berval bv; + ber_str2bv( c->argv[i] + STRLENOF("undef:"), 0, 0, &bv ); + attr_name->an_desc = slap_bv2tmp_ad( &bv, NULL ); + + } else if ( slap_str2ad( c->argv[i], &attr_name->an_desc, &text ) ) { + strcpy( c->cr_msg, text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + ch_free( qm->attr_sets[num].attrs ); + qm->attr_sets[num].attrs = NULL; + qm->attr_sets[num].count = 0; + return 1; + } + attr_name->an_name = attr_name->an_desc->ad_cname; + } + attr_name->an_oc = NULL; + attr_name->an_flags = 0; + if ( attr_name->an_desc == slap_schema.si_ad_objectClass ) + qm->attr_sets[num].flags |= PC_GOT_OC; + attr_name++; + BER_BVZERO( &attr_name->an_name ); + } + + /* warn if list contains both "*" and "+" */ + if ( i > 4 && all_user && all_op ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "warning: attribute list contains \"*\" and \"+\"" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + } + } + break; + case PC_TEMP: + if ( cm->numattrsets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcache\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( lutil_atoi( &i, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template #=\"%s\"", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( i < 0 || i >= cm->numattrsets || + !(qm->attr_sets[i].flags & PC_CONFIGURED )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "template index %d invalid (%s%d)", + i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + { + AttributeName *attrs; + int cnt; + cnt = template_attrs( c->argv[1], &qm->attr_sets[i], &attrs, &text ); + if ( cnt < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s", + text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + temp = ch_calloc( 1, sizeof( QueryTemplate )); + temp->qmnext = qm->templates; + qm->templates = temp; + temp->t_attrs.attrs = attrs; + temp->t_attrs.count = cnt; + } + ldap_pvt_thread_rdwr_init( &temp->t_rwlock ); + temp->query = temp->query_last = NULL; + if ( lutil_parse_time( c->argv[3], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template ttl=\"%s\"", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); +pc_temp_fail: + ch_free( temp->t_attrs.attrs ); + ch_free( temp ); + return( 1 ); + } + temp->ttl = (time_t)t; + temp->negttl = (time_t)0; + temp->limitttl = (time_t)0; + temp->ttr = (time_t)0; + switch ( c->argc ) { + case 7: + if ( lutil_parse_time( c->argv[6], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template ttr=\"%s\"", + c->argv[6] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + goto pc_temp_fail; + } + temp->ttr = (time_t)t; + /* fallthru */ + + case 6: + if ( lutil_parse_time( c->argv[5], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template sizelimit ttl=\"%s\"", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + goto pc_temp_fail; + } + temp->limitttl = (time_t)t; + /* fallthru */ + + case 5: + if ( lutil_parse_time( c->argv[4], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse template negative ttl=\"%s\"", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + goto pc_temp_fail; + } + temp->negttl = (time_t)t; + break; + } + + temp->no_of_queries = 0; + + ber_str2bv( c->argv[1], 0, 1, &temp->querystr ); + Debug( pcache_debug, "Template:\n" ); + Debug( pcache_debug, " query template: %s\n", + temp->querystr.bv_val ); + temp->attr_set_index = i; + qm->attr_sets[i].flags |= PC_REFERENCED; + temp->qtnext = qm->attr_sets[i].templates; + qm->attr_sets[i].templates = temp; + Debug( pcache_debug, " attributes: \n" ); + if ( ( attrarray = qm->attr_sets[i].attrs ) != NULL ) { + for ( i=0; attrarray[i].an_name.bv_val; i++ ) + Debug( pcache_debug, "\t%s\n", + attrarray[i].an_name.bv_val ); + } + break; + case PC_BIND: + if ( !qm->templates ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "\"pcacheTemplate\" directive not provided yet" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + if ( lutil_atoi( &i, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse Bind index #=\"%s\"", + c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( i < 0 || i >= cm->numattrsets || + !(qm->attr_sets[i].flags & PC_CONFIGURED )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind index %d invalid (%s%d)", + i, cm->numattrsets > 1 ? "0->" : "", cm->numattrsets - 1 ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + { struct berval bv, tempbv; + AttributeDescription **descs; + int ndescs; + ber_str2bv( c->argv[1], 0, 0, &bv ); + ndescs = ftemp_attrs( &bv, &tempbv, &descs, &text ); + if ( ndescs < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unable to parse template: %s", + text ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + for ( temp = qm->templates; temp; temp=temp->qmnext ) { + if ( temp->attr_set_index == i && bvmatch( &tempbv, + &temp->querystr )) + break; + } + ch_free( tempbv.bv_val ); + if ( !temp ) { + ch_free( descs ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), "Bind template %s %d invalid", + c->argv[1], i ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + ber_dupbv( &temp->bindftemp, &bv ); + temp->bindfattrs = descs; + temp->bindnattrs = ndescs; + } + if ( lutil_parse_time( c->argv[3], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bind ttr=\"%s\"", + c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); +pc_bind_fail: + ch_free( temp->bindfattrs ); + temp->bindfattrs = NULL; + ch_free( temp->bindftemp.bv_val ); + BER_BVZERO( &temp->bindftemp ); + return( 1 ); + } + num = ldap_pvt_str2scope( c->argv[4] ); + if ( num < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bind scope=\"%s\"", + c->argv[4] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + goto pc_bind_fail; + } + { + struct berval dn, ndn; + ber_str2bv( c->argv[5], 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid bind baseDN=\"%s\"", + c->argv[5] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + goto pc_bind_fail; + } + if ( temp->bindbase.bv_val ) + ch_free( temp->bindbase.bv_val ); + temp->bindbase = ndn; + } + { + /* convert the template into dummy filter */ + struct berval bv; + char *eq = temp->bindftemp.bv_val, *e2; + Filter *f; + i = 0; + while ((eq = strchr(eq, '=' ))) { + eq++; + if ( eq[0] == ')' ) + i++; + } + bv.bv_len = temp->bindftemp.bv_len + i; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + for ( e2 = bv.bv_val, eq = temp->bindftemp.bv_val; + *eq; eq++ ) { + if ( *eq == '=' ) { + *e2++ = '='; + if ( eq[1] == ')' ) + *e2++ = '*'; + } else { + *e2++ = *eq; + } + } + *e2 = '\0'; + f = str2filter( bv.bv_val ); + if ( !f ) { + ch_free( bv.bv_val ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse bindfilter=\"%s\"", bv.bv_val ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + ch_free( temp->bindbase.bv_val ); + BER_BVZERO( &temp->bindbase ); + goto pc_bind_fail; + } + if ( temp->bindfilter ) + filter_free( temp->bindfilter ); + if ( temp->bindfilterstr.bv_val ) + ch_free( temp->bindfilterstr.bv_val ); + temp->bindfilterstr = bv; + temp->bindfilter = f; + } + temp->bindttr = (time_t)t; + temp->bindscope = num; + cm->cache_binds = 1; + break; + + case PC_RESP: + if ( strcasecmp( c->argv[1], "head" ) == 0 ) { + cm->response_cb = PCACHE_RESPONSE_CB_HEAD; + + } else if ( strcasecmp( c->argv[1], "tail" ) == 0 ) { + cm->response_cb = PCACHE_RESPONSE_CB_TAIL; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "unknown specifier" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + break; + case PC_QUERIES: + if ( c->value_int <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "max queries must be positive" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + cm->max_queries = c->value_int; + break; + case PC_OFFLINE: + if ( c->value_int ) + cm->cc_paused |= PCACHE_CC_OFFLINE; + else + cm->cc_paused &= ~PCACHE_CC_OFFLINE; + break; + case PC_PRIVATE_DB: + if ( cm->db.be_private == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "private database must be defined before setting database specific options" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return( 1 ); + } + + if ( cm->db.bd_info->bi_cf_ocs ) { + ConfigTable *ct; + ConfigArgs c2 = *c; + char *argv0 = c->argv[ 0 ]; + + c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ]; + + ct = config_find_keyword( cm->db.bd_info->bi_cf_ocs->co_table, c ); + if ( ct == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "private database does not recognize specific option '%s'", + c->argv[ 0 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + rc = 1; + + } else { + c->table = cm->db.bd_info->bi_cf_ocs->co_type; + c->be = &cm->db; + c->bi = c->be->bd_info; + + rc = config_add_vals( ct, c ); + + c->bi = c2.bi; + c->be = c2.be; + c->table = c2.table; + } + + c->argv[ 0 ] = argv0; + + } else if ( cm->db.be_config != NULL ) { + char *argv0 = c->argv[ 0 ]; + + c->argv[ 0 ] = &argv0[ STRLENOF( "pcache-" ) ]; + rc = cm->db.be_config( &cm->db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[ 0 ] = argv0; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "no means to set private database specific options" ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + break; + default: + rc = SLAP_CONF_UNKNOWN; + break; + } + + return rc; +} + +static int +pcache_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager* cm = on->on_bi.bi_private; + + /* Something for the cache database? */ + if ( cm->db.bd_info && cm->db.bd_info->bi_db_config ) + return cm->db.bd_info->bi_db_config( &cm->db, fname, lineno, + argc, argv ); + return SLAP_CONF_UNKNOWN; +} + +static int +pcache_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm; + query_manager *qm; + + cm = (cache_manager *)ch_malloc(sizeof(cache_manager)); + on->on_bi.bi_private = cm; + + qm = (query_manager*)ch_malloc(sizeof(query_manager)); + + cm->db = *be; + cm->db.bd_info = NULL; + SLAP_DBFLAGS(&cm->db) |= SLAP_DBFLAG_NO_SCHEMA_CHECK; + cm->db.be_private = NULL; + cm->db.bd_self = &cm->db; + cm->qm = qm; + cm->numattrsets = 0; + cm->num_entries_limit = 5; + cm->num_cached_queries = 0; + cm->max_entries = 0; + cm->cur_entries = 0; + cm->max_queries = 10000; + cm->save_queries = 0; + cm->check_cacheability = 0; + cm->response_cb = PCACHE_RESPONSE_CB_TAIL; + cm->defer_db_open = 1; + cm->cache_binds = 0; + cm->cc_period = 1000; + cm->cc_paused = 0; + cm->cc_arg = NULL; +#ifdef PCACHE_MONITOR + cm->monitor_cb = NULL; +#endif /* PCACHE_MONITOR */ + + qm->attr_sets = NULL; + qm->templates = NULL; + qm->lru_top = NULL; + qm->lru_bottom = NULL; + + qm->qcfunc = query_containment; + qm->crfunc = cache_replacement; + qm->addfunc = add_query; + ldap_pvt_thread_mutex_init(&qm->lru_mutex); + + ldap_pvt_thread_mutex_init(&cm->cache_mutex); + +#ifndef PCACHE_MONITOR + return 0; +#else /* PCACHE_MONITOR */ + return pcache_monitor_db_init( be ); +#endif /* PCACHE_MONITOR */ +} + +static int +pcache_cachedquery_open_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + Attribute *a; + + a = attr_find( rs->sr_entry->e_attrs, ad_cachedQueryURL ); + if ( a != NULL ) { + BerVarray *valsp; + + assert( a->a_nvals != NULL ); + + valsp = op->o_callback->sc_private; + assert( *valsp == NULL ); + + ber_bvarray_dup_x( valsp, a->a_nvals, op->o_tmpmemctx ); + } + } + + return 0; +} + +static int +pcache_cachedquery_count_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + if ( rs->sr_type == REP_SEARCH ) { + int *countp = (int *)op->o_callback->sc_private; + + (*countp)++; + } + + return 0; +} + +static int +pcache_db_open2( + slap_overinst *on, + ConfigReply *cr ) +{ + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + int rc; + + rc = backend_startup_one( &cm->db, cr ); + if ( rc == 0 ) { + cm->defer_db_open = 0; + } + + /* There is no runqueue in TOOL mode */ + if (( slapMode & SLAP_SERVER_MODE ) && rc == 0 ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_insert( &slapd_rq, cm->cc_period, + consistency_check, on, + "pcache_consistency", cm->db.be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + /* Cached database must have the rootdn */ + if ( BER_BVISNULL( &cm->db.be_rootndn ) + || BER_BVISEMPTY( &cm->db.be_rootndn ) ) + { + Debug( LDAP_DEBUG_ANY, "pcache_db_open(): " + "underlying database of type \"%s\"\n" + " serving naming context \"%s\"\n" + " has no \"rootdn\", required by \"pcache\".\n", + on->on_info->oi_orig->bi_type, + cm->db.be_suffix[0].bv_val ); + return 1; + } + + if ( cm->save_queries ) { + void *thrctx = ldap_pvt_thread_pool_context(); + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { 0 }; + SlapReply rs = { REP_RESULT }; + BerVarray vals = NULL; + Filter f = { 0 }, f2 = { 0 }; + AttributeAssertion ava = ATTRIBUTEASSERTION_INIT; + AttributeName attrs[ 2 ] = {{{ 0 }}}; + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + op->o_bd = &cm->db; + + op->o_tag = LDAP_REQ_SEARCH; + op->o_protocol = LDAP_VERSION3; + cb.sc_response = pcache_cachedquery_open_cb; + cb.sc_private = &vals; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + + op->o_dn = cm->db.be_rootdn; + op->o_ndn = cm->db.be_rootndn; + op->o_req_dn = cm->db.be_suffix[ 0 ]; + op->o_req_ndn = cm->db.be_nsuffix[ 0 ]; + + op->ors_scope = LDAP_SCOPE_BASE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_limit = NULL; + ber_str2bv( "(pcacheQueryURL=*)", 0, 0, &op->ors_filterstr ); + f.f_choice = LDAP_FILTER_PRESENT; + f.f_desc = ad_cachedQueryURL; + op->ors_filter = &f; + attrs[ 0 ].an_desc = ad_cachedQueryURL; + attrs[ 0 ].an_name = ad_cachedQueryURL->ad_cname; + op->ors_attrs = attrs; + op->ors_attrsonly = 0; + + rc = op->o_bd->be_search( op, &rs ); + if ( rc == LDAP_SUCCESS && vals != NULL ) { + int i; + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + if ( url2query( vals[ i ].bv_val, op, qm ) == 0 ) { + cm->num_cached_queries++; + } + } + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + /* count cached entries */ + f.f_choice = LDAP_FILTER_NOT; + f.f_not = &f2; + f2.f_choice = LDAP_FILTER_EQUALITY; + f2.f_ava = &ava; + f2.f_av_desc = slap_schema.si_ad_objectClass; + BER_BVSTR( &f2.f_av_value, "glue" ); + ber_str2bv( "(!(objectClass=glue))", 0, 0, &op->ors_filterstr ); + + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_attrs = slap_anlist_no_attrs; + + rs_reinit( &rs, REP_RESULT ); + op->o_callback->sc_response = pcache_cachedquery_count_cb; + op->o_callback->sc_private = &rs.sr_nentries; + + rc = op->o_bd->be_search( op, &rs ); + + cm->cur_entries = rs.sr_nentries; + + /* ignore errors */ + rc = 0; + } + } + return rc; +} + +static int +pcache_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager* qm = cm->qm; + int i, ncf = 0, rf = 0, nrf = 0, rc = 0; + + /* check attr sets */ + for ( i = 0; i < cm->numattrsets; i++) { + if ( !( qm->attr_sets[i].flags & PC_CONFIGURED ) ) { + if ( qm->attr_sets[i].flags & PC_REFERENCED ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d not configured but referenced.\n", i ); + rf++; + + } else { + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, attr set #%d not configured.\n", i ); + } + ncf++; + + } else if ( !( qm->attr_sets[i].flags & PC_REFERENCED ) ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: attr set #%d configured but not referenced.\n", i ); + nrf++; + } + } + + if ( ncf || rf || nrf ) { + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets configured but not referenced.\n", nrf ); + Debug( LDAP_DEBUG_CONFIG, "pcache: warning, %d attr sets not configured.\n", ncf ); + Debug( LDAP_DEBUG_CONFIG, "pcache: %d attr sets not configured but referenced.\n", rf ); + + if ( rf > 0 ) { + return 1; + } + } + + /* need to inherit something from the original database... */ + cm->db.be_def_limit = be->be_def_limit; + cm->db.be_limits = be->be_limits; + cm->db.be_acl = be->be_acl; + cm->db.be_dfltaccess = be->be_dfltaccess; + + if ( SLAP_DBMONITORING( be ) ) { + SLAP_DBFLAGS( &cm->db ) |= SLAP_DBFLAG_MONITORING; + + } else { + SLAP_DBFLAGS( &cm->db ) &= ~SLAP_DBFLAG_MONITORING; + } + + if ( !cm->defer_db_open ) { + rc = pcache_db_open2( on, cr ); + } + +#ifdef PCACHE_MONITOR + if ( rc == LDAP_SUCCESS ) { + rc = pcache_monitor_db_open( be ); + } +#endif /* PCACHE_MONITOR */ + + return rc; +} + +static void +pcache_free_qbase( void *v ) +{ + Qbase *qb = v; + int i; + + for (i=0; i<3; i++) + ldap_tavl_free( qb->scopes[i], NULL ); + ch_free( qb ); +} + +static int +pcache_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + QueryTemplate *tm; + int rc = 0; + + /* stop the thread ... */ + if ( cm->cc_arg ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, cm->cc_arg ) ) { + ldap_pvt_runqueue_stoptask( &slapd_rq, cm->cc_arg ); + } + ldap_pvt_runqueue_remove( &slapd_rq, cm->cc_arg ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + cm->cc_arg = NULL; + } + + if ( cm->save_queries ) { + CachedQuery *qc; + BerVarray vals = NULL; + + void *thrctx; + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { 0 }; + + SlapReply rs = { REP_RESULT }; + Modifications mod = {{ 0 }}; + + thrctx = ldap_pvt_thread_pool_context(); + + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + + mod.sml_numvals = 0; + if ( qm->templates != NULL ) { + for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) { + for ( qc = tm->query; qc; qc = qc->next ) { + struct berval bv; + + if ( query2url( op, qc, &bv, 0 ) == 0 ) { + ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx ); + mod.sml_numvals++; + } + } + } + } + + op->o_bd = &cm->db; + op->o_dn = cm->db.be_rootdn; + op->o_ndn = cm->db.be_rootndn; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_protocol = LDAP_VERSION3; + cb.sc_response = slap_null_cb; + op->o_callback = &cb; + op->o_time = slap_get_time(); + op->o_do_not_cache = 1; + op->o_managedsait = SLAP_CONTROL_CRITICAL; + + op->o_req_dn = op->o_bd->be_suffix[0]; + op->o_req_ndn = op->o_bd->be_nsuffix[0]; + + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = 0; + mod.sml_desc = ad_cachedQueryURL; + mod.sml_type = ad_cachedQueryURL->ad_cname; + mod.sml_values = vals; + mod.sml_nvalues = NULL; + mod.sml_next = NULL; + Debug( pcache_debug, + "%sSETTING CACHED QUERY URLS\n", + vals == NULL ? "RE" : "" ); + + op->orm_modlist = &mod; + + op->o_bd->be_modify( op, &rs ); + + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + /* cleanup stuff inherited from the original database... */ + cm->db.be_limits = NULL; + cm->db.be_acl = NULL; + + if ( cm->db.bd_info->bi_db_close ) { + rc = cm->db.bd_info->bi_db_close( &cm->db, NULL ); + } + +#ifdef PCACHE_MONITOR + if ( rc == LDAP_SUCCESS ) { + rc = pcache_monitor_db_close( be ); + } +#endif /* PCACHE_MONITOR */ + + return rc; +} + +static int +pcache_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + query_manager *qm = cm->qm; + QueryTemplate *tm; + int i; + + if ( cm->db.be_private != NULL ) { + backend_stopdown_one( &cm->db ); + } + + while ( (tm = qm->templates) != NULL ) { + CachedQuery *qc, *qn; + qm->templates = tm->qmnext; + for ( qc = tm->query; qc; qc = qn ) { + qn = qc->next; + free_query( qc ); + } + ldap_avl_free( tm->qbase, pcache_free_qbase ); + free( tm->querystr.bv_val ); + free( tm->bindfattrs ); + free( tm->bindftemp.bv_val ); + free( tm->bindfilterstr.bv_val ); + free( tm->bindbase.bv_val ); + filter_free( tm->bindfilter ); + ldap_pvt_thread_rdwr_destroy( &tm->t_rwlock ); + free( tm->t_attrs.attrs ); + free( tm ); + } + + for ( i = 0; i < cm->numattrsets; i++ ) { + int j; + + /* Account of LDAP_NO_ATTRS */ + if ( !qm->attr_sets[i].count ) continue; + + for ( j = 0; !BER_BVISNULL( &qm->attr_sets[i].attrs[j].an_name ); j++ ) { + if ( qm->attr_sets[i].attrs[j].an_desc && + ( qm->attr_sets[i].attrs[j].an_desc->ad_flags & + SLAP_DESC_TEMPORARY ) ) { + slap_sl_mfuncs.bmf_free( qm->attr_sets[i].attrs[j].an_desc, NULL ); + } + } + free( qm->attr_sets[i].attrs ); + } + free( qm->attr_sets ); + qm->attr_sets = NULL; + + ldap_pvt_thread_mutex_destroy( &qm->lru_mutex ); + ldap_pvt_thread_mutex_destroy( &cm->cache_mutex ); + free( qm ); + free( cm ); + +#ifdef PCACHE_MONITOR + pcache_monitor_db_destroy( be ); +#endif /* PCACHE_MONITOR */ + + return 0; +} + +#ifdef PCACHE_CONTROL_PRIVDB +/* + Control ::= SEQUENCE { + controlType LDAPOID, + criticality BOOLEAN DEFAULT FALSE, + controlValue OCTET STRING OPTIONAL } + + controlType ::= 1.3.6.1.4.1.4203.666.11.9.5.1 + + * criticality must be TRUE; controlValue must be absent. + */ +static int +parse_privdb_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( op->o_ctrlflag[ privDB_cid ] != SLAP_CONTROL_NONE ) { + rs->sr_text = "privateDB control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "privateDB control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( !ctrl->ldctl_iscritical ) { + rs->sr_text = "privateDB control criticality required"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_ctrlflag[ privDB_cid ] = SLAP_CONTROL_CRITICAL; + + return LDAP_SUCCESS; +} + +static char *extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + NULL +}; +#endif /* PCACHE_CONTROL_PRIVDB */ + +static struct berval pcache_exop_MODIFY_PASSWD = BER_BVC( LDAP_EXOP_MODIFY_PASSWD ); +#ifdef PCACHE_EXOP_QUERY_DELETE +static struct berval pcache_exop_QUERY_DELETE = BER_BVC( PCACHE_EXOP_QUERY_DELETE ); + +#define LDAP_TAG_EXOP_QUERY_DELETE_BASE ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 0) +#define LDAP_TAG_EXOP_QUERY_DELETE_DN ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 1) +#define LDAP_TAG_EXOP_QUERY_DELETE_UUID ((LBER_CLASS_CONTEXT|LBER_CONSTRUCTED) + 2) + +/* + ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + requestName [0] LDAPOID, + requestValue [1] OCTET STRING OPTIONAL } + + requestName ::= 1.3.6.1.4.1.4203.666.11.9.6.1 + + requestValue ::= SEQUENCE { CHOICE { + baseDN [0] LDAPDN + entryDN [1] LDAPDN }, + queryID [2] OCTET STRING (SIZE(16)) + -- constrained to UUID } + + * Either baseDN or entryDN must be present, to allow database selection. + * + * 1. if baseDN and queryID are present, then the query corresponding + * to queryID is deleted; + * 2. if baseDN is present and queryID is absent, then all queries + * are deleted; + * 3. if entryDN is present and queryID is absent, then all queries + * corresponding to the queryID values present in entryDN are deleted; + * 4. if entryDN and queryID are present, then all queries + * corresponding to the queryID values present in entryDN are deleted, + * but only if the value of queryID is contained in the entry; + * + * Currently, only 1, 3 and 4 are implemented. 2 can be obtained by either + * recursively deleting the database (ldapdelete -r) with PRIVDB control, + * or by removing the database files. + + ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + COMPONENTS OF LDAPResult, + responseName [10] LDAPOID OPTIONAL, + responseValue [11] OCTET STRING OPTIONAL } + + * responseName and responseValue must be absent. + */ + +/* + * - on success, *tagp is either LDAP_TAG_EXOP_QUERY_DELETE_BASE + * or LDAP_TAG_EXOP_QUERY_DELETE_DN. + * - if ndn != NULL, it is set to the normalized DN in the request + * corresponding to either the baseDN or the entryDN, according + * to *tagp; memory is malloc'ed on the Operation's slab, and must + * be freed by the caller. + * - if uuid != NULL, it is set to point to the normalized UUID; + * memory is malloc'ed on the Operation's slab, and must + * be freed by the caller. + */ +static int +pcache_parse_query_delete( + struct berval *in, + ber_tag_t *tagp, + struct berval *ndn, + struct berval *uuid, + const char **text, + void *ctx ) +{ + int rc = LDAP_SUCCESS; + ber_tag_t tag; + ber_len_t len = -1; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval reqdata = BER_BVNULL; + + *text = NULL; + + if ( ndn ) { + BER_BVZERO( ndn ); + } + + if ( uuid ) { + BER_BVZERO( uuid ); + } + + if ( in == NULL || in->bv_len == 0 ) { + *text = "empty request data field in queryDelete exop"; + return LDAP_PROTOCOL_ERROR; + } + + ber_dupbv_x( &reqdata, in, ctx ); + + /* ber_init2 uses reqdata directly, doesn't allocate new buffers */ + ber_init2( ber, &reqdata, 0 ); + + tag = ber_scanf( ber, "{" /*}*/ ); + + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: decoding error.\n" ); + goto decoding_error; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE + || tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) + { + *tagp = tag; + + if ( ndn != NULL ) { + struct berval dn; + + tag = ber_scanf( ber, "m", &dn ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: DN parse failed.\n" ); + goto decoding_error; + } + + rc = dnNormalize( 0, NULL, NULL, &dn, ndn, ctx ); + if ( rc != LDAP_SUCCESS ) { + *text = "invalid DN in queryDelete exop request data"; + goto done; + } + + } else { + tag = ber_scanf( ber, "x" /* "m" */ ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_UUID ) { + if ( uuid != NULL ) { + struct berval bv; + char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ]; + + tag = ber_scanf( ber, "m", &bv ); + if ( tag == LBER_ERROR ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: UUID parse failed.\n" ); + goto decoding_error; + } + + if ( bv.bv_len != 16 ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: invalid UUID length %lu.\n", + (unsigned long)bv.bv_len ); + goto decoding_error; + } + + rc = lutil_uuidstr_from_normalized( + bv.bv_val, bv.bv_len, + uuidbuf, sizeof( uuidbuf ) ); + if ( rc == -1 ) { + goto decoding_error; + } + ber_str2bv( uuidbuf, rc, 1, uuid ); + rc = LDAP_SUCCESS; + + } else { + tag = ber_skip_tag( ber, &len ); + if ( tag == LBER_DEFAULT ) { + goto decoding_error; + } + + if ( len != 16 ) { + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: invalid UUID length %lu.\n", + (unsigned long)len ); + goto decoding_error; + } + } + + tag = ber_peek_tag( ber, &len ); + } + + if ( tag != LBER_DEFAULT || len != 0 ) { +decoding_error:; + Debug( LDAP_DEBUG_TRACE, + "pcache_parse_query_delete: decoding error\n" ); + rc = LDAP_PROTOCOL_ERROR; + *text = "queryDelete data decoding error"; + +done:; + if ( ndn && !BER_BVISNULL( ndn ) ) { + slap_sl_free( ndn->bv_val, ctx ); + BER_BVZERO( ndn ); + } + + if ( uuid && !BER_BVISNULL( uuid ) ) { + slap_sl_free( uuid->bv_val, ctx ); + BER_BVZERO( uuid ); + } + } + + if ( !BER_BVISNULL( &reqdata ) ) { + ber_memfree_x( reqdata.bv_val, ctx ); + } + + return rc; +} + +static int +pcache_exop_query_delete( + Operation *op, + SlapReply *rs ) +{ + BackendDB *bd = op->o_bd; + + struct berval uuid = BER_BVNULL, + *uuidp = NULL; + char buf[ SLAP_TEXT_BUFLEN ]; + unsigned len; + ber_tag_t tag = LBER_DEFAULT; + + if ( LogTest( LDAP_DEBUG_STATS ) ) { + uuidp = &uuid; + } + + rs->sr_err = pcache_parse_query_delete( op->ore_reqdata, + &tag, &op->o_req_ndn, uuidp, + &rs->sr_text, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + if ( LogTest( LDAP_DEBUG_STATS ) ) { + assert( !BER_BVISNULL( &op->o_req_ndn ) ); + len = snprintf( buf, sizeof( buf ), " dn=\"%s\"", op->o_req_ndn.bv_val ); + + if ( !BER_BVISNULL( &uuid ) && len < sizeof( buf ) ) { + snprintf( &buf[ len ], sizeof( buf ) - len, " pcacheQueryId=\"%s\"", uuid.bv_val ); + } + + Debug( LDAP_DEBUG_STATS, "%s QUERY DELETE%s\n", + op->o_log_prefix, buf ); + } + op->o_req_dn = op->o_req_ndn; + + op->o_bd = select_backend( &op->o_req_ndn, 0 ); + if ( op->o_bd == NULL ) { + send_ldap_error( op, rs, LDAP_NO_SUCH_OBJECT, + "no global superior knowledge" ); + } + rs->sr_err = backend_check_restrictions( op, rs, + (struct berval *)&pcache_exop_QUERY_DELETE ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + + if ( op->o_bd->be_extended == NULL ) { + send_ldap_error( op, rs, LDAP_UNAVAILABLE_CRITICAL_EXTENSION, + "backend does not support extended operations" ); + goto done; + } + + op->o_bd->be_extended( op, rs ); + +done:; + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + op->o_tmpfree( op->o_req_ndn.bv_val, op->o_tmpmemctx ); + BER_BVZERO( &op->o_req_ndn ); + BER_BVZERO( &op->o_req_dn ); + } + + if ( !BER_BVISNULL( &uuid ) ) { + op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx ); + } + + op->o_bd = bd; + + return rs->sr_err; +} +#endif /* PCACHE_EXOP_QUERY_DELETE */ + +static int +pcache_op_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + +#ifdef PCACHE_CONTROL_PRIVDB + if ( op->o_ctrlflag[ privDB_cid ] == SLAP_CONTROL_CRITICAL ) { + return pcache_op_privdb( op, rs ); + } +#endif /* PCACHE_CONTROL_PRIVDB */ + +#ifdef PCACHE_EXOP_QUERY_DELETE + if ( bvmatch( &op->ore_reqoid, &pcache_exop_QUERY_DELETE ) ) { + struct berval uuid = BER_BVNULL; + ber_tag_t tag = LBER_DEFAULT; + + rs->sr_err = pcache_parse_query_delete( op->ore_reqdata, + &tag, NULL, &uuid, &rs->sr_text, op->o_tmpmemctx ); + assert( rs->sr_err == LDAP_SUCCESS ); + + if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_DN ) { + /* remove all queries related to the selected entry */ + rs->sr_err = pcache_remove_entry_queries_from_cache( op, + cm, &op->o_req_ndn, &uuid ); + + } else if ( tag == LDAP_TAG_EXOP_QUERY_DELETE_BASE ) { + if ( !BER_BVISNULL( &uuid ) ) { + /* remove the selected query */ + rs->sr_err = pcache_remove_query_from_cache( op, + cm, &uuid ); + + } else { + /* TODO: remove all queries */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "deletion of all queries not implemented"; + } + } + + op->o_tmpfree( uuid.bv_val, op->o_tmpmemctx ); + return rs->sr_err; + } +#endif /* PCACHE_EXOP_QUERY_DELETE */ + + /* We only care if we're configured for Bind caching */ + if ( bvmatch( &op->ore_reqoid, &pcache_exop_MODIFY_PASSWD ) && + cm->cache_binds ) { + /* See if the local entry exists and has a password. + * It's too much work to find the matching query, so + * we just see if there's a hashed password to update. + */ + Operation op2 = *op; + Entry *e = NULL; + int rc; + int doit = 0; + + op2.o_bd = &cm->db; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + rc = be_entry_get_rw( &op2, &op->o_req_ndn, NULL, + slap_schema.si_ad_userPassword, 0, &e ); + if ( rc == LDAP_SUCCESS && e ) { + /* See if a recognized password is hashed here */ + Attribute *a = attr_find( e->e_attrs, + slap_schema.si_ad_userPassword ); + if ( a && a->a_vals[0].bv_val[0] == '{' && + lutil_passwd_scheme( a->a_vals[0].bv_val )) { + doit = 1; + } + be_entry_release_r( &op2, e ); + } + + if ( doit ) { + rc = overlay_op_walk( op, rs, op_extended, on->on_info, + on->on_next ); + if ( rc == LDAP_SUCCESS ) { + req_pwdexop_s *qpw = &op->oq_pwdexop; + + /* We don't care if it succeeds or not */ + pc_setpw( &op2, &qpw->rs_new, cm ); + } + return rc; + } + } + return SLAP_CB_CONTINUE; +} + +static int +pcache_entry_release( Operation *op, Entry *e, int rw ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + cache_manager *cm = on->on_bi.bi_private; + BackendDB *db = op->o_bd; + int rc; + + op->o_bd = &cm->db; + rc = be_entry_release_rw( op, e, rw ); + op->o_bd = db; + return rc; +} + +#ifdef PCACHE_MONITOR + +static int +pcache_monitor_update( + Operation *op, + SlapReply *rs, + Entry *e, + void *priv ) +{ + cache_manager *cm = (cache_manager *) priv; + query_manager *qm = cm->qm; + + CachedQuery *qc; + BerVarray vals = NULL; + + attr_delete( &e->e_attrs, ad_cachedQueryURL ); + if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( ad_cachedQueryURL, rs->sr_attrs ) ) + && qm->templates != NULL ) + { + QueryTemplate *tm; + + for ( tm = qm->templates; tm != NULL; tm = tm->qmnext ) { + for ( qc = tm->query; qc; qc = qc->next ) { + struct berval bv; + + if ( query2url( op, qc, &bv, 1 ) == 0 ) { + ber_bvarray_add_x( &vals, &bv, op->o_tmpmemctx ); + } + } + } + + + if ( vals != NULL ) { + attr_merge_normalize( e, ad_cachedQueryURL, vals, NULL ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + + { + Attribute *a; + char buf[ SLAP_TEXT_BUFLEN ]; + struct berval bv; + + /* number of cached queries */ + a = attr_find( e->e_attrs, ad_numQueries ); + assert( a != NULL ); + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%lu", cm->num_cached_queries ); + + if ( a->a_nvals != a->a_vals ) { + ber_bvreplace( &a->a_nvals[ 0 ], &bv ); + } + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + + /* number of cached entries */ + a = attr_find( e->e_attrs, ad_numEntries ); + assert( a != NULL ); + + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", cm->cur_entries ); + + if ( a->a_nvals != a->a_vals ) { + ber_bvreplace( &a->a_nvals[ 0 ], &bv ); + } + ber_bvreplace( &a->a_vals[ 0 ], &bv ); + } + + return SLAP_CB_CONTINUE; +} + +static int +pcache_monitor_free( + Entry *e, + void **priv ) +{ + struct berval values[ 2 ]; + Modification mod = { 0 }; + + const char *text; + char textbuf[ SLAP_TEXT_BUFLEN ]; + + int rc; + + /* NOTE: if slap_shutdown != 0, priv might have already been freed */ + *priv = NULL; + + /* Remove objectClass */ + mod.sm_op = LDAP_MOD_DELETE; + mod.sm_desc = slap_schema.si_ad_objectClass; + mod.sm_values = values; + mod.sm_numvals = 1; + values[ 0 ] = oc_olmPCache->soc_cname; + BER_BVZERO( &values[ 1 ] ); + + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_cachedQueryURL; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_numQueries; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + /* remove attrs */ + mod.sm_values = NULL; + mod.sm_desc = ad_numEntries; + mod.sm_numvals = 0; + rc = modify_delete_values( e, &mod, 1, &text, + textbuf, sizeof( textbuf ) ); + /* don't care too much about return code... */ + + return SLAP_CB_CONTINUE; +} + +/* + * call from within pcache_initialize() + */ +static int +pcache_monitor_initialize( void ) +{ + static int pcache_monitor_initialized = 0; + + if ( backend_info( "monitor" ) == NULL ) { + return -1; + } + + if ( pcache_monitor_initialized++ ) { + return 0; + } + + return 0; +} + +static int +pcache_monitor_db_init( BackendDB *be ) +{ + if ( pcache_monitor_initialize() == LDAP_SUCCESS ) { + SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_MONITORING; + } + + return 0; +} + +static int +pcache_monitor_db_open( BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + Attribute *a, *next; + monitor_callback_t *cb = NULL; + int rc = 0; + BackendInfo *mi; + monitor_extra_t *mbe; + + if ( !SLAP_DBMONITORING( be ) ) { + return 0; + } + + mi = backend_info( "monitor" ); + if ( !mi || !mi->bi_extra ) { + SLAP_DBFLAGS( be ) ^= SLAP_DBFLAG_MONITORING; + return 0; + } + mbe = mi->bi_extra; + + /* don't bother if monitor is not configured */ + if ( !mbe->is_configured() ) { + static int warning = 0; + + if ( warning++ == 0 ) { + Debug( LDAP_DEBUG_CONFIG, "pcache_monitor_db_open: " + "monitoring disabled; " + "configure monitor database to enable\n" ); + } + + return 0; + } + + /* alloc as many as required (plus 1 for objectClass) */ + a = attrs_alloc( 1 + 2 ); + if ( a == NULL ) { + rc = 1; + goto cleanup; + } + + a->a_desc = slap_schema.si_ad_objectClass; + attr_valadd( a, &oc_olmPCache->soc_cname, NULL, 1 ); + next = a->a_next; + + { + struct berval bv = BER_BVC( "0" ); + + next->a_desc = ad_numQueries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + + next->a_desc = ad_numEntries; + attr_valadd( next, &bv, NULL, 1 ); + next = next->a_next; + } + + cb = ch_calloc( sizeof( monitor_callback_t ), 1 ); + cb->mc_update = pcache_monitor_update; + cb->mc_free = pcache_monitor_free; + cb->mc_private = (void *)cm; + + /* make sure the database is registered; then add monitor attributes */ + BER_BVZERO( &cm->monitor_ndn ); + rc = mbe->register_overlay( be, on, &cm->monitor_ndn ); + if ( rc == 0 ) { + rc = mbe->register_entry_attrs( &cm->monitor_ndn, a, cb, + NULL, -1, NULL); + } + +cleanup:; + if ( rc != 0 ) { + if ( cb != NULL ) { + ch_free( cb ); + cb = NULL; + } + + if ( a != NULL ) { + attrs_free( a ); + a = NULL; + } + } + + /* store for cleanup */ + cm->monitor_cb = (void *)cb; + + /* we don't need to keep track of the attributes, because + * mdb_monitor_free() takes care of everything */ + if ( a != NULL ) { + attrs_free( a ); + } + + return rc; +} + +static int +pcache_monitor_db_close( BackendDB *be ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + cache_manager *cm = on->on_bi.bi_private; + + if ( !BER_BVISNULL( &cm->monitor_ndn )) { + BackendInfo *mi = backend_info( "monitor" ); + monitor_extra_t *mbe; + + if ( mi && mi->bi_extra ) { + struct berval dummy = BER_BVNULL; + mbe = mi->bi_extra; + mbe->unregister_entry_callback( &cm->monitor_ndn, + (monitor_callback_t *)cm->monitor_cb, + &dummy, 0, &dummy ); + } + } + + return 0; +} + +static int +pcache_monitor_db_destroy( BackendDB *be ) +{ + return 0; +} + +#endif /* PCACHE_MONITOR */ + +static slap_overinst pcache; + +static char *obsolete_names[] = { + "proxycache", + NULL +}; + +#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC */ +int +pcache_initialize() +{ + int i, code; + struct berval debugbv = BER_BVC("pcache"); + ConfigArgs c; + char *argv[ 4 ]; + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + pcocs[1].co_table = olcDatabaseDummy; + + + code = slap_loglevel_get( &debugbv, &pcache_debug ); + if ( code ) { + return code; + } + +#ifdef PCACHE_CONTROL_PRIVDB + code = register_supported_control( PCACHE_CONTROL_PRIVDB, + SLAP_CTRL_BIND|SLAP_CTRL_ACCESS|SLAP_CTRL_HIDE, extops, + parse_privdb_ctrl, &privDB_cid ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: failed to register control %s (%d)\n", + PCACHE_CONTROL_PRIVDB, code ); + return code; + } +#endif /* PCACHE_CONTROL_PRIVDB */ + +#ifdef PCACHE_EXOP_QUERY_DELETE + code = load_extop2( (struct berval *)&pcache_exop_QUERY_DELETE, + SLAP_EXOP_WRITES|SLAP_EXOP_HIDE, pcache_exop_query_delete, + 0 ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: unable to register queryDelete exop: %d.\n", + code ); + return code; + } +#endif /* PCACHE_EXOP_QUERY_DELETE */ + + argv[ 0 ] = "back-mdb monitor"; + c.argv = argv; + c.argc = 3; + c.fname = argv[0]; + + for ( i = 0; s_oid[ i ].name; i++ ) { + c.lineno = i; + argv[ 1 ] = s_oid[ i ].name; + argv[ 2 ] = s_oid[ i ].oid; + + if ( parse_oidm( &c, 0, NULL ) != 0 ) { + Debug( LDAP_DEBUG_ANY, "pcache_initialize: " + "unable to add objectIdentifier \"%s=%s\"\n", + s_oid[ i ].name, s_oid[ i ].oid ); + return 1; + } + } + + for ( i = 0; s_ad[i].desc != NULL; i++ ) { + code = register_at( s_ad[i].desc, s_ad[i].adp, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: register_at #%d failed\n", i ); + return code; + } + (*s_ad[i].adp)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + + for ( i = 0; s_oc[i].desc != NULL; i++ ) { + code = register_oc( s_oc[i].desc, s_oc[i].ocp, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "pcache_initialize: register_oc #%d failed\n", i ); + return code; + } + (*s_oc[i].ocp)->soc_flags |= SLAP_OC_HIDE; + } + + pcache.on_bi.bi_type = "pcache"; + pcache.on_bi.bi_obsolete_names = obsolete_names; + pcache.on_bi.bi_db_init = pcache_db_init; + pcache.on_bi.bi_db_config = pcache_db_config; + pcache.on_bi.bi_db_open = pcache_db_open; + pcache.on_bi.bi_db_close = pcache_db_close; + pcache.on_bi.bi_db_destroy = pcache_db_destroy; + + pcache.on_bi.bi_op_search = pcache_op_search; + pcache.on_bi.bi_op_bind = pcache_op_bind; +#ifdef PCACHE_CONTROL_PRIVDB + pcache.on_bi.bi_op_compare = pcache_op_privdb; + pcache.on_bi.bi_op_modrdn = pcache_op_privdb; + pcache.on_bi.bi_op_modify = pcache_op_privdb; + pcache.on_bi.bi_op_add = pcache_op_privdb; + pcache.on_bi.bi_op_delete = pcache_op_privdb; +#endif /* PCACHE_CONTROL_PRIVDB */ + pcache.on_bi.bi_extended = pcache_op_extended; + + pcache.on_bi.bi_entry_release_rw = pcache_entry_release; + pcache.on_bi.bi_chk_controls = pcache_chk_controls; + + pcache.on_bi.bi_cf_ocs = pcocs; + + code = config_register_schema( pccfg, pcocs ); + if ( code ) return code; + + return overlay_register( &pcache ); +} + +#if SLAPD_OVER_PROXYCACHE == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return pcache_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_PROXYCACHE) */ diff --git a/servers/slapd/overlays/ppolicy.c b/servers/slapd/overlays/ppolicy.c new file mode 100644 index 0000000..a3f2e70 --- /dev/null +++ b/servers/slapd/overlays/ppolicy.c @@ -0,0 +1,3413 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * Portions Copyright 2004-2005 Howard Chu, Symas Corporation. + * Portions Copyright 2004 Hewlett-Packard Company. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was developed by Howard Chu for inclusion in + * OpenLDAP Software, based on prior work by Neil Dunbar (HP). + * This work was sponsored by the Hewlett-Packard Company. + */ + +#include "portable.h" + +/* This file implements "Password Policy for LDAP Directories", + * based on draft behera-ldap-password-policy-09 + */ + +#ifdef SLAPD_OVER_PPOLICY + +#include <ldap.h> +#include "lutil.h" +#include "slap.h" +#ifdef SLAPD_MODULES +#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */ +#include <ltdl.h> +#endif +#include <ac/errno.h> +#include <ac/time.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include "slap-config.h" + +#ifndef MODULE_NAME_SZ +#define MODULE_NAME_SZ 256 +#endif + +#ifndef PPOLICY_DEFAULT_MAXRECORDED_FAILURE +#define PPOLICY_DEFAULT_MAXRECORDED_FAILURE 5 +#endif + +/* Per-instance configuration information */ +typedef struct pp_info { + struct berval def_policy; /* DN of default policy subentry */ + int use_lockout; /* send AccountLocked result? */ + int hash_passwords; /* transparently hash cleartext pwds */ + int forward_updates; /* use frontend for policy state updates */ + int disable_write; + int send_netscape_controls; /* send netscape password controls */ + ldap_pvt_thread_mutex_t pwdFailureTime_mutex; +} pp_info; + +/* Our per-connection info - note, it is not per-instance, it is + * used by all instances + */ +typedef struct pw_conn { + struct berval dn; /* DN of restricted user */ +} pw_conn; + +static pw_conn *pwcons; +static int ppolicy_cid; +static int account_usability_cid; +static int ov_count; + +typedef struct pass_policy { + AttributeDescription *ad; /* attribute to which the policy applies */ + int pwdMinAge; /* minimum time (seconds) until passwd can change */ + int pwdMaxAge; /* time in seconds until pwd will expire after change */ + int pwdMaxIdle; /* number of seconds since last successful bind before + passwd gets locked out */ + int pwdInHistory; /* number of previous passwords kept */ + int pwdCheckQuality; /* 0 = don't check quality, 1 = check if possible, + 2 = check mandatory; fail if not possible */ + int pwdMinLength; /* minimum number of chars in password */ + int pwdMaxLength; /* maximum number of chars in password */ + int pwdExpireWarning; /* number of seconds that warning controls are + sent before a password expires */ + int pwdGraceExpiry; /* number of seconds after expiry grace logins are + valid */ + int pwdGraceAuthNLimit; /* number of times you can log in with an + expired password */ + int pwdLockout; /* 0 = do not lockout passwords, 1 = lock them out */ + int pwdLockoutDuration; /* time in seconds a password is locked out for */ + int pwdMinDelay; /* base bind delay in seconds on failure */ + int pwdMaxDelay; /* maximum bind delay in seconds */ + int pwdMaxFailure; /* number of failed binds allowed before lockout */ + int pwdMaxRecordedFailure; /* number of failed binds to store */ + int pwdFailureCountInterval; /* number of seconds before failure + counts are zeroed */ + int pwdMustChange; /* 0 = users can use admin set password + 1 = users must change password after admin set */ + int pwdAllowUserChange; /* 0 = users cannot change their passwords + 1 = users can change them */ + int pwdSafeModify; /* 0 = old password doesn't need to come + with password change request + 1 = password change must supply existing pwd */ + char pwdCheckModule[MODULE_NAME_SZ]; /* name of module to dynamically + load to check password */ + struct berval pwdCheckModuleArg; /* Optional argument to the password check + module */ +} PassPolicy; + +typedef struct pw_hist { + time_t t; /* timestamp of history entry */ + struct berval pw; /* old password hash */ + struct berval bv; /* text of entire entry */ + struct pw_hist *next; +} pw_hist; + +/* Operational attributes */ +static AttributeDescription *ad_pwdChangedTime, *ad_pwdAccountLockedTime, + *ad_pwdFailureTime, *ad_pwdHistory, *ad_pwdGraceUseTime, *ad_pwdReset, + *ad_pwdPolicySubentry, *ad_pwdStartTime, *ad_pwdEndTime, + *ad_pwdLastSuccess, *ad_pwdAccountTmpLockoutEnd; + +/* Policy attributes */ +static AttributeDescription *ad_pwdMinAge, *ad_pwdMaxAge, *ad_pwdMaxIdle, + *ad_pwdInHistory, *ad_pwdCheckQuality, *ad_pwdMinLength, *ad_pwdMaxLength, + *ad_pwdMaxFailure, *ad_pwdGraceExpiry, *ad_pwdGraceAuthNLimit, + *ad_pwdExpireWarning, *ad_pwdMinDelay, *ad_pwdMaxDelay, + *ad_pwdLockoutDuration, *ad_pwdFailureCountInterval, + *ad_pwdCheckModule, *ad_pwdCheckModuleArg, *ad_pwdLockout, + *ad_pwdMustChange, *ad_pwdAllowUserChange, *ad_pwdSafeModify, + *ad_pwdAttribute, *ad_pwdMaxRecordedFailure; + +static struct schema_info { + char *def; + AttributeDescription **ad; +} pwd_OpSchema[] = { + { "( 1.3.6.1.4.1.42.2.27.8.1.16 " + "NAME ( 'pwdChangedTime' ) " + "DESC 'The time the password was last changed' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdChangedTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.17 " + "NAME ( 'pwdAccountLockedTime' ) " + "DESC 'The time an user account was locked' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " +#if 0 /* FIXME: ITS#9671 until we introduce a separate lockout flag? */ + "NO-USER-MODIFICATION " +#endif + "USAGE directoryOperation )", + &ad_pwdAccountLockedTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.19 " + "NAME ( 'pwdFailureTime' ) " + "DESC 'The timestamps of the last consecutive authentication failures' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdFailureTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.20 " + "NAME ( 'pwdHistory' ) " + "DESC 'The history of users passwords' " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdHistory }, + { "( 1.3.6.1.4.1.42.2.27.8.1.21 " + "NAME ( 'pwdGraceUseTime' ) " + "DESC 'The timestamps of the grace login once the password has expired' " + "EQUALITY generalizedTimeMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdGraceUseTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.22 " + "NAME ( 'pwdReset' ) " + "DESC 'The indication that the password has been reset' " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE " + "USAGE directoryOperation )", + &ad_pwdReset }, + { "( 1.3.6.1.4.1.42.2.27.8.1.23 " + "NAME ( 'pwdPolicySubentry' ) " + "DESC 'The pwdPolicy subentry in effect for this object' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE " +#if 0 /* ITS#9671: until we implement ITS#9343 or similar */ + "NO-USER-MODIFICATION " +#endif + "USAGE directoryOperation )", + &ad_pwdPolicySubentry }, + { "( 1.3.6.1.4.1.42.2.27.8.1.27 " + "NAME ( 'pwdStartTime' ) " + "DESC 'The time the password becomes enabled' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "USAGE directoryOperation )", + &ad_pwdStartTime }, + { "( 1.3.6.1.4.1.42.2.27.8.1.28 " + "NAME ( 'pwdEndTime' ) " + "DESC 'The time the password becomes disabled' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "USAGE directoryOperation )", + &ad_pwdEndTime }, + /* Defined in schema_prep.c now + { "( 1.3.6.1.4.1.42.2.27.8.1.29 " + "NAME ( 'pwdLastSuccess' ) " + "DESC 'The timestamp of the last successful authentication' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdLastSuccess }, + */ + { "( 1.3.6.1.4.1.42.2.27.8.1.33 " + "NAME ( 'pwdAccountTmpLockoutEnd' ) " + "DESC 'Temporary lockout end' " + "EQUALITY generalizedTimeMatch " + "ORDERING generalizedTimeOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 " + "SINGLE-VALUE " + "NO-USER-MODIFICATION " + "USAGE directoryOperation )", + &ad_pwdAccountTmpLockoutEnd }, + + { "( 1.3.6.1.4.1.42.2.27.8.1.1 " + "NAME ( 'pwdAttribute' ) " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )", + &ad_pwdAttribute }, + { "( 1.3.6.1.4.1.42.2.27.8.1.2 " + "NAME ( 'pwdMinAge' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMinAge }, + { "( 1.3.6.1.4.1.42.2.27.8.1.3 " + "NAME ( 'pwdMaxAge' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxAge }, + { "( 1.3.6.1.4.1.42.2.27.8.1.4 " + "NAME ( 'pwdInHistory' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdInHistory }, + { "( 1.3.6.1.4.1.42.2.27.8.1.5 " + "NAME ( 'pwdCheckQuality' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdCheckQuality }, + { "( 1.3.6.1.4.1.42.2.27.8.1.6 " + "NAME ( 'pwdMinLength' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMinLength }, + { "( 1.3.6.1.4.1.42.2.27.8.1.31 " + "NAME ( 'pwdMaxLength' ) " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxLength }, + { "( 1.3.6.1.4.1.42.2.27.8.1.7 " + "NAME ( 'pwdExpireWarning' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdExpireWarning }, + { "( 1.3.6.1.4.1.42.2.27.8.1.8 " + "NAME ( 'pwdGraceAuthNLimit' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdGraceAuthNLimit }, + { "( 1.3.6.1.4.1.42.2.27.8.1.30 " + "NAME ( 'pwdGraceExpiry' ) " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdGraceExpiry }, + { "( 1.3.6.1.4.1.42.2.27.8.1.9 " + "NAME ( 'pwdLockout' ) " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_pwdLockout }, + { "( 1.3.6.1.4.1.42.2.27.8.1.10 " + "NAME ( 'pwdLockoutDuration' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdLockoutDuration }, + { "( 1.3.6.1.4.1.42.2.27.8.1.11 " + "NAME ( 'pwdMaxFailure' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxFailure }, + { "( 1.3.6.1.4.1.42.2.27.8.1.12 " + "NAME ( 'pwdFailureCountInterval' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdFailureCountInterval }, + { "( 1.3.6.1.4.1.42.2.27.8.1.13 " + "NAME ( 'pwdMustChange' ) " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_pwdMustChange }, + { "( 1.3.6.1.4.1.42.2.27.8.1.14 " + "NAME ( 'pwdAllowUserChange' ) " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_pwdAllowUserChange }, + { "( 1.3.6.1.4.1.42.2.27.8.1.15 " + "NAME ( 'pwdSafeModify' ) " + "EQUALITY booleanMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_pwdSafeModify }, + { "( 1.3.6.1.4.1.42.2.27.8.1.24 " + "NAME ( 'pwdMinDelay' ) " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMinDelay }, + { "( 1.3.6.1.4.1.42.2.27.8.1.25 " + "NAME ( 'pwdMaxDelay' ) " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxDelay }, + { "( 1.3.6.1.4.1.42.2.27.8.1.26 " + "NAME ( 'pwdMaxIdle' ) " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxIdle }, + { "( 1.3.6.1.4.1.42.2.27.8.1.32 " + "NAME ( 'pwdMaxRecordedFailure' ) " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_pwdMaxRecordedFailure }, + { "( 1.3.6.1.4.1.4754.1.99.1 " + "NAME ( 'pwdCheckModule' ) " + "EQUALITY caseExactIA5Match " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 " + "DESC 'Loadable module that instantiates check_password() function' " + "SINGLE-VALUE )", + &ad_pwdCheckModule }, + { "( 1.3.6.1.4.1.4754.1.99.2 " + "NAME ( 'pwdCheckModuleArg' ) " + "EQUALITY octetStringMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "DESC 'Argument to pass to check_password() function' " + "SINGLE-VALUE )", + &ad_pwdCheckModuleArg }, + + { NULL, NULL } +}; + +static char *pwd_ocs[] = { + "( 1.3.6.1.4.1.4754.2.99.1 " + "NAME 'pwdPolicyChecker' " + "SUP top " + "AUXILIARY " + "MAY ( pwdCheckModule $ pwdCheckModuleArg ) )" , + "( 1.3.6.1.4.1.42.2.27.8.2.1 " + "NAME 'pwdPolicy' " + "SUP top " + "AUXILIARY " + "MUST ( pwdAttribute ) " + "MAY ( pwdMinAge $ pwdMaxAge $ pwdInHistory $ pwdCheckQuality $ " + "pwdMinLength $ pwdMaxLength $ pwdExpireWarning $ " + "pwdGraceAuthNLimit $ pwdGraceExpiry $ pwdLockout $ " + "pwdLockoutDuration $ pwdMaxFailure $ pwdFailureCountInterval $ " + "pwdMustChange $ pwdAllowUserChange $ pwdSafeModify $ " + "pwdMinDelay $ pwdMaxDelay $ pwdMaxIdle $ " + "pwdMaxRecordedFailure ) )", + NULL +}; + +static ldap_pvt_thread_mutex_t chk_syntax_mutex; + +enum { + PPOLICY_DEFAULT = 1, + PPOLICY_HASH_CLEARTEXT, + PPOLICY_USE_LOCKOUT, + PPOLICY_DISABLE_WRITE, +}; + +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' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_hash_cleartext", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET|PPOLICY_HASH_CLEARTEXT, + (void *)offsetof(pp_info,hash_passwords), + "( OLcfgOvAt:12.2 NAME 'olcPPolicyHashCleartext' " + "DESC 'Hash passwords on add or modify' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_forward_updates", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(pp_info,forward_updates), + "( OLcfgOvAt:12.4 NAME 'olcPPolicyForwardUpdates' " + "DESC 'Allow policy state updates to be forwarded via updateref' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_use_lockout", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET|PPOLICY_USE_LOCKOUT, + (void *)offsetof(pp_info,use_lockout), + "( OLcfgOvAt:12.3 NAME 'olcPPolicyUseLockout' " + "DESC 'Warn clients with AccountLocked' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_disable_write", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET|PPOLICY_DISABLE_WRITE, + (void *)offsetof(pp_info,disable_write), + "( OLcfgOvAt:12.5 NAME 'olcPPolicyDisableWrite' " + "DESC 'Prevent all policy overlay writes' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "ppolicy_send_netscape_controls", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(pp_info,send_netscape_controls), + "( OLcfgOvAt:12.6 NAME 'olcPPolicySendNetscapeControls' " + "DESC 'Send Netscape policy controls' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs ppolicyocs[] = { + { "( OLcfgOvOc:12.1 " + "NAME 'olcPPolicyConfig' " + "DESC 'Password Policy configuration' " + "SUP olcOverlayConfig " + "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ " + "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ " + "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls ) )", + Cft_Overlay, ppolicycfg }, + { NULL, 0, NULL } +}; + +static int +ppolicy_cf_default( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + pp_info *pi = (pp_info *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + assert ( c->type == PPOLICY_DEFAULT ); + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default\n" ); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default emit\n" ); + rc = 0; + if ( !BER_BVISEMPTY( &pi->def_policy )) { + rc = value_add_one( &c->rvalue_vals, + &pi->def_policy ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &pi->def_policy ); + } + break; + case LDAP_MOD_DELETE: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default delete\n" ); + if ( pi->def_policy.bv_val ) { + ber_memfree ( pi->def_policy.bv_val ); + pi->def_policy.bv_val = NULL; + } + pi->def_policy.bv_len = 0; + rc = 0; + break; + case SLAP_CONFIG_ADD: + /* fallthru to LDAP_MOD_ADD */ + case LDAP_MOD_ADD: + Debug(LDAP_DEBUG_TRACE, "==> ppolicy_cf_default add\n" ); + if ( pi->def_policy.bv_val ) { + ber_memfree ( pi->def_policy.bv_val ); + } + pi->def_policy = c->value_ndn; + ber_memfree( c->value_dn.bv_val ); + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + rc = 0; + break; + default: + abort (); + } + + return rc; +} + +static time_t +parse_time( char *atm ) +{ + struct lutil_tm tm; + struct lutil_timet tt; + time_t ret = (time_t)-1; + + if ( lutil_parsetime( atm, &tm ) == 0) { + lutil_tm2time( &tm, &tt ); + ret = tt.tt_sec; + } + return ret; +} + +static int +account_locked( Operation *op, Entry *e, + PassPolicy *pp, Modifications **mod ) +{ + Attribute *la; + + if ( (la = attr_find( e->e_attrs, ad_pwdStartTime )) != NULL ) { + BerVarray vals = la->a_nvals; + time_t then, now = op->o_time; + + /* + * Password has a defined start of validity + */ + if ( vals[0].bv_val != NULL ) { + if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) { + return 1; + } + if ( now < then ) { + return 1; + } + } + } + + if ( (la = attr_find( e->e_attrs, ad_pwdEndTime )) != NULL ) { + BerVarray vals = la->a_nvals; + time_t then, now = op->o_time; + + /* + * Password has a defined end of validity + */ + if ( vals[0].bv_val != NULL ) { + if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) { + return 1; + } + if ( then <= now ) { + return 1; + } + } + } + + if ( !pp->pwdLockout ) + return 0; + + if ( (la = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) { + BerVarray vals = la->a_nvals; + time_t then, now = op->o_time; + + /* + * We have temporarily locked the account after a failure + */ + if ( vals[0].bv_val != NULL ) { + if ( (then = parse_time( vals[0].bv_val )) == (time_t)-1 ) { + return 1; + } + if ( now < then ) { + return 1; + } + } + } + + /* Only check if database maintains lastbind */ + if ( pp->pwdMaxIdle && SLAP_LASTBIND( op->o_bd ) ) { + time_t lastbindtime = (time_t)-1; + + la = attr_find( e->e_attrs, ad_pwdLastSuccess ); + if ( la == NULL ) { + la = attr_find( e->e_attrs, ad_pwdChangedTime ); + } + if ( la != NULL ) { + lastbindtime = parse_time( la->a_nvals[0].bv_val ); + } + + if ( lastbindtime != (time_t)-1 && + op->o_time > lastbindtime + pp->pwdMaxIdle ) { + return 1; + } + } + + if ( (la = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) { + BerVarray vals = la->a_nvals; + + /* + * there is a lockout stamp - we now need to know if it's + * a valid one. + */ + if (vals[0].bv_val != NULL) { + time_t then, now; + Modifications *m; + + if ((then = parse_time( vals[0].bv_val )) == (time_t)0) + return 1; + + now = slap_get_time(); + + /* Still in the future? not yet in effect */ + if (now < then) + return 0; + + if (!pp->pwdLockoutDuration) + return 1; + + if (now < then + pp->pwdLockoutDuration) + return 1; + + if ( mod != NULL ) { + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_type = ad_pwdAccountLockedTime->ad_cname; + m->sml_desc = ad_pwdAccountLockedTime; + m->sml_next = *mod; + *mod = m; + } + } + } + + return 0; +} + +/* IMPLICIT TAGS, all context-specific */ +#define PPOLICY_WARNING 0xa0L /* constructed + 0 */ +#define PPOLICY_ERROR 0x81L /* primitive + 1 */ + +#define PPOLICY_EXPIRE 0x80L /* primitive + 0 */ +#define PPOLICY_GRACE 0x81L /* primitive + 1 */ + +static const char ppolicy_ctrl_oid[] = LDAP_CONTROL_PASSWORDPOLICYRESPONSE; +static const char ppolicy_account_ctrl_oid[] = LDAP_CONTROL_X_ACCOUNT_USABILITY; +static const char ppolicy_pwd_expired_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRED; +static const char ppolicy_pwd_expiring_oid[] = LDAP_CONTROL_X_PASSWORD_EXPIRING; + +static LDAPControl * +create_passcontrol( Operation *op, int exptime, int grace, LDAPPasswordPolicyError err ) +{ + BerElementBuffer berbuf, bb2; + BerElement *ber = (BerElement *) &berbuf, *b2 = (BerElement *) &bb2; + LDAPControl c = { 0 }, *cp; + struct berval bv; + int rc; + + BER_BVZERO( &c.ldctl_value ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_printf( ber, "{" /*}*/ ); + + if ( exptime >= 0 ) { + ber_init2( b2, NULL, LBER_USE_DER ); + ber_printf( b2, "ti", PPOLICY_EXPIRE, exptime ); + rc = ber_flatten2( b2, &bv, 1 ); + (void)ber_free_buf(b2); + if (rc == -1) { + cp = NULL; + goto fail; + } + ber_printf( ber, "tO", PPOLICY_WARNING, &bv ); + ch_free( bv.bv_val ); + } else if ( grace >= 0 ) { + ber_init2( b2, NULL, LBER_USE_DER ); + ber_printf( b2, "ti", PPOLICY_GRACE, grace ); + rc = ber_flatten2( b2, &bv, 1 ); + (void)ber_free_buf(b2); + if (rc == -1) { + cp = NULL; + goto fail; + } + ber_printf( ber, "tO", PPOLICY_WARNING, &bv ); + ch_free( bv.bv_val ); + } + + if (err != PP_noError ) { + ber_printf( ber, "te", PPOLICY_ERROR, err ); + } + ber_printf( ber, /*{*/ "N}" ); + + if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) { + return NULL; + } + cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = (char *)ppolicy_ctrl_oid; + cp->ldctl_iscritical = 0; + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = c.ldctl_value.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len ); +fail: + (void)ber_free_buf(ber); + + return cp; +} + +static LDAPControl * +create_passexpiry( Operation *op, int expired, int warn ) +{ + LDAPControl *cp; + char buf[sizeof("-2147483648")]; + struct berval bv = { .bv_val = buf, .bv_len = sizeof(buf) }; + + bv.bv_len = snprintf( bv.bv_val, bv.bv_len, "%d", warn ); + + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + if ( expired ) { + cp->ldctl_oid = (char *)ppolicy_pwd_expired_oid; + } else { + cp->ldctl_oid = (char *)ppolicy_pwd_expiring_oid; + } + cp->ldctl_iscritical = 0; + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + return cp; +} + +static LDAPControl ** +add_passcontrol( Operation *op, SlapReply *rs, LDAPControl *ctrl ) +{ + LDAPControl **ctrls, **oldctrls = rs->sr_ctrls; + int n; + + n = 0; + if ( oldctrls ) { + for ( ; oldctrls[n]; n++ ) + ; + } + n += 2; + + ctrls = op->o_tmpcalloc( sizeof( LDAPControl * ), n, op->o_tmpmemctx ); + + n = 0; + if ( oldctrls ) { + for ( ; oldctrls[n]; n++ ) { + ctrls[n] = oldctrls[n]; + } + } + ctrls[n] = ctrl; + ctrls[n+1] = NULL; + + rs->sr_ctrls = ctrls; + + return oldctrls; +} + +static void +add_account_control( + Operation *op, + SlapReply *rs, + int available, + int remaining, + LDAPAccountUsabilityMoreInfo *more_info ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + LDAPControl c = { 0 }, *cp = NULL, **ctrls; + int i = 0; + + BER_BVZERO( &c.ldctl_value ); + + ber_init2( ber, NULL, LBER_USE_DER ); + + if ( available ) { + ber_put_int( ber, remaining, LDAP_TAG_X_ACCOUNT_USABILITY_AVAILABLE ); + } else { + assert( more_info != NULL ); + + ber_start_seq( ber, LDAP_TAG_X_ACCOUNT_USABILITY_NOT_AVAILABLE ); + ber_put_boolean( ber, more_info->inactive, LDAP_TAG_X_ACCOUNT_USABILITY_INACTIVE ); + ber_put_boolean( ber, more_info->reset, LDAP_TAG_X_ACCOUNT_USABILITY_RESET ); + ber_put_boolean( ber, more_info->expired, LDAP_TAG_X_ACCOUNT_USABILITY_EXPIRED ); + ber_put_int( ber, more_info->remaining_grace, LDAP_TAG_X_ACCOUNT_USABILITY_REMAINING_GRACE ); + ber_put_int( ber, more_info->seconds_before_unlock, LDAP_TAG_X_ACCOUNT_USABILITY_UNTIL_UNLOCK ); + ber_put_seq( ber ); + } + + if (ber_flatten2( ber, &c.ldctl_value, 0 ) == -1) { + goto fail; + } + + if ( rs->sr_ctrls != NULL ) { + for ( ; rs->sr_ctrls[ i ] != NULL; i++ ) /* Count */; + } + + ctrls = op->o_tmprealloc( rs->sr_ctrls, sizeof(LDAPControl *)*( i + 2 ), op->o_tmpmemctx ); + if ( ctrls == NULL ) { + goto fail; + } + + cp = op->o_tmpalloc( sizeof( LDAPControl ) + c.ldctl_value.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = (char *)ppolicy_account_ctrl_oid; + cp->ldctl_iscritical = 0; + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = c.ldctl_value.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, c.ldctl_value.bv_val, c.ldctl_value.bv_len ); + + ctrls[ i ] = cp; + ctrls[ i + 1 ] = NULL; + rs->sr_ctrls = ctrls; + +fail: + (void)ber_free_buf(ber); +} + +static void +ppolicy_get_default( PassPolicy *pp ) +{ + memset( pp, 0, sizeof(PassPolicy) ); + + pp->ad = slap_schema.si_ad_userPassword; + + /* Users can change their own password by default */ + pp->pwdAllowUserChange = 1; +} + + +static int +ppolicy_get( Operation *op, Entry *e, PassPolicy *pp ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + BackendDB *bd, *bd_orig = op->o_bd; + AttributeDescription *ad = NULL; + Attribute *a; + BerVarray vals; + int rc = LDAP_SUCCESS; + Entry *pe = NULL; +#if 0 + const char *text; +#endif + + ppolicy_get_default( pp ); + + ad = ad_pwdPolicySubentry; + if ( (a = attr_find( e->e_attrs, ad )) == NULL ) { + /* + * entry has no password policy assigned - use default + */ + vals = &pi->def_policy; + if ( !vals->bv_val ) + goto defaultpol; + } else { + vals = a->a_nvals; + if (vals[0].bv_val == NULL) { + Debug( LDAP_DEBUG_ANY, + "ppolicy_get: NULL value for policySubEntry\n" ); + goto defaultpol; + } + } + + op->o_bd = bd = select_backend( vals, 0 ); + if ( op->o_bd == NULL ) { + op->o_bd = bd_orig; + goto defaultpol; + } + + rc = be_entry_get_rw( op, vals, NULL, NULL, 0, &pe ); + op->o_bd = bd_orig; + + if ( rc ) goto defaultpol; + +#if 0 /* Only worry about userPassword for now */ + if ((a = attr_find( pe->e_attrs, ad_pwdAttribute ))) + slap_bv2ad( &a->a_vals[0], &pp->ad, &text ); +#endif + + ad = ad_pwdMinAge; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMinAge, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxAge; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxAge, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxIdle; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxIdle, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdInHistory; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdInHistory, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdCheckQuality; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdCheckQuality, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMinLength; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMinLength, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxLength; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxLength, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxFailure; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxFailure, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxRecordedFailure; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxRecordedFailure, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdGraceExpiry; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdGraceExpiry, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdGraceAuthNLimit; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdGraceAuthNLimit, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdExpireWarning; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdExpireWarning, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdFailureCountInterval; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdFailureCountInterval, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdLockoutDuration; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdLockoutDuration, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMinDelay; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMinDelay, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdMaxDelay; + if ( (a = attr_find( pe->e_attrs, ad )) + && lutil_atoi( &pp->pwdMaxDelay, a->a_vals[0].bv_val ) != 0 ) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto defaultpol; + } + + ad = ad_pwdCheckModule; + if ( (a = attr_find( pe->e_attrs, ad )) ) { + strncpy( pp->pwdCheckModule, a->a_vals[0].bv_val, + sizeof(pp->pwdCheckModule) ); + pp->pwdCheckModule[sizeof(pp->pwdCheckModule)-1] = '\0'; + } + + ad = ad_pwdCheckModuleArg; + if ( (a = attr_find( pe->e_attrs, ad )) ) { + ber_dupbv_x( &pp->pwdCheckModuleArg, &a->a_vals[0], op->o_tmpmemctx ); + } + + ad = ad_pwdLockout; + if ( (a = attr_find( pe->e_attrs, ad )) ) + pp->pwdLockout = bvmatch( &a->a_nvals[0], &slap_true_bv ); + + ad = ad_pwdMustChange; + if ( (a = attr_find( pe->e_attrs, ad )) ) + pp->pwdMustChange = bvmatch( &a->a_nvals[0], &slap_true_bv ); + + ad = ad_pwdAllowUserChange; + if ( (a = attr_find( pe->e_attrs, ad )) ) + pp->pwdAllowUserChange = bvmatch( &a->a_nvals[0], &slap_true_bv ); + + ad = ad_pwdSafeModify; + if ( (a = attr_find( pe->e_attrs, ad )) ) + pp->pwdSafeModify = bvmatch( &a->a_nvals[0], &slap_true_bv ); + + if ( pp->pwdMaxRecordedFailure < pp->pwdMaxFailure ) + pp->pwdMaxRecordedFailure = pp->pwdMaxFailure; + + if ( !pp->pwdMaxRecordedFailure && pp->pwdMinDelay ) + pp->pwdMaxRecordedFailure = PPOLICY_DEFAULT_MAXRECORDED_FAILURE; + + if ( pp->pwdMinDelay && !pp->pwdMaxDelay ) { + Debug( LDAP_DEBUG_ANY, "ppolicy_get: " + "pwdMinDelay was set but pwdMaxDelay wasn't, assuming they " + "are equal\n" ); + pp->pwdMaxDelay = pp->pwdMinDelay; + } + + op->o_bd = bd; + be_entry_release_r( op, pe ); + op->o_bd = bd_orig; + + return LDAP_SUCCESS; + +defaultpol: + if ( pe ) { + op->o_bd = bd; + be_entry_release_r( op, pe ); + op->o_bd = bd_orig; + } + + if ( rc && !BER_BVISNULL( vals ) ) { + Debug( LDAP_DEBUG_ANY, "ppolicy_get: " + "policy subentry %s missing or invalid at '%s', " + "no policy will be applied!\n", + vals->bv_val, ad ? ad->ad_cname.bv_val : "" ); + } else { + Debug( LDAP_DEBUG_TRACE, + "ppolicy_get: using default policy\n" ); + } + + ppolicy_get_default( pp ); + + return -1; +} + +static int +password_scheme( struct berval *cred, struct berval *sch ) +{ + int e; + + assert( cred != NULL ); + + if (sch) { + sch->bv_val = NULL; + sch->bv_len = 0; + } + + if ((cred->bv_len == 0) || (cred->bv_val == NULL) || + (cred->bv_val[0] != '{')) return LDAP_OTHER; + + for(e = 1; cred->bv_val[e] && cred->bv_val[e] != '}'; e++); + if (cred->bv_val[e]) { + int rc; + rc = lutil_passwd_scheme( cred->bv_val ); + if (rc) { + if (sch) { + sch->bv_val = cred->bv_val; + sch->bv_len = e; + } + return LDAP_SUCCESS; + } + } + return LDAP_OTHER; +} + +static int +check_password_quality( struct berval *cred, 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; + } + + if ( pp->pwdMaxLength && cred->bv_len > pp->pwdMaxLength ) { + rc = LDAP_CONSTRAINT_VIOLATION; + if ( err ) *err = PP_passwordTooLong; + return rc; + } + + /* + * We need to know if the password is already hashed - if so + * what scheme is it. The reason being that the "hash" of + * {cleartext} still allows us to check the password. + */ + rc = password_scheme( cred, &sch ); + if (rc == LDAP_SUCCESS) { + if ((sch.bv_val) && (strncasecmp( sch.bv_val, "{cleartext}", + sch.bv_len ) == 0)) { + /* + * We can check the cleartext "hash" + */ + ptr = cred->bv_val + sch.bv_len; + } else { + /* everything else, we can't check */ + if (pp->pwdCheckQuality == 2) { + rc = LDAP_CONSTRAINT_VIOLATION; + if (err) *err = PP_insufficientPasswordQuality; + return rc; + } + /* + * We can't check the syntax of the password, but it's not + * mandatory (according to the policy), so we return success. + */ + + return LDAP_SUCCESS; + } + } + + rc = LDAP_SUCCESS; + + if (pp->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 ); + 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, struct berval *arg ); + + 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 ); + ok = LDAP_OTHER; + } else { + struct berval *arg = NULL; + if ( !BER_BVISNULL( &pp->pwdCheckModuleArg ) ) { + arg = &pp->pwdCheckModuleArg; + } + + ldap_pvt_thread_mutex_lock( &chk_syntax_mutex ); + ok = prog( ptr, txt, e, arg ); + ldap_pvt_thread_mutex_unlock( &chk_syntax_mutex ); + if (ok != LDAP_SUCCESS) { + Debug(LDAP_DEBUG_ANY, + "check_password_quality: module error: (%s) %s.[%d]\n", + pp->pwdCheckModule, *txt ? *txt : "", ok ); + } + } + + lt_dlclose( mod ); + } +#else + Debug(LDAP_DEBUG_ANY, "check_password_quality: external modules not " + "supported. pwdCheckModule ignored.\n" ); +#endif /* SLAPD_MODULES */ + } + + + if (ok != LDAP_SUCCESS) { + rc = LDAP_CONSTRAINT_VIOLATION; + if (err) *err = PP_insufficientPasswordQuality; + } + + return rc; +} + +static int +parse_pwdhistory( struct berval *bv, char **oid, time_t *oldtime, struct berval *oldpw ) +{ + char *ptr; + struct berval nv, npw; + ber_len_t i, j; + + assert (bv && (bv->bv_len > 0) && (bv->bv_val) && oldtime && oldpw ); + + if ( oid ) { + *oid = 0; + } + *oldtime = (time_t)-1; + BER_BVZERO( oldpw ); + + ber_dupbv( &nv, bv ); + + /* first get the time field */ + for ( i = 0; (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + ptr = nv.bv_val; + *oldtime = parse_time( ptr ); + if (*oldtime == (time_t)-1) { + goto exit_failure; + } + + /* get the OID field */ + for (ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + if ( oid ) { + *oid = ber_strdup( ptr ); + } + + /* get the length field */ + for ( ptr = &(nv.bv_val[i]); (i < nv.bv_len) && (nv.bv_val[i] != '#'); i++ ) + ; + if ( i == nv.bv_len ) { + goto exit_failure; /* couldn't locate the '#' separator */ + } + nv.bv_val[i++] = '\0'; /* terminate the string & move to next field */ + oldpw->bv_len = strtol( ptr, NULL, 10 ); + if (errno == ERANGE) { + goto exit_failure; + } + + /* lastly, get the octets of the string */ + for ( j = i, ptr = &(nv.bv_val[i]); i < nv.bv_len; i++ ) + ; + if ( i - j != oldpw->bv_len) { + goto exit_failure; /* length is wrong */ + } + + npw.bv_val = ptr; + npw.bv_len = oldpw->bv_len; + ber_dupbv( oldpw, &npw ); + ber_memfree( nv.bv_val ); + + return LDAP_SUCCESS; + +exit_failure:; + if ( oid && *oid ) { + ber_memfree(*oid); + *oid = NULL; + } + if ( oldpw->bv_val ) { + ber_memfree( oldpw->bv_val); + BER_BVZERO( oldpw ); + } + ber_memfree( nv.bv_val ); + + return LDAP_OTHER; +} + +static void +add_to_pwd_history( pw_hist **l, time_t t, + struct berval *oldpw, struct berval *bv ) +{ + pw_hist *p, *p1, *p2; + + if (!l) return; + + p = ch_malloc( sizeof( pw_hist )); + p->pw = *oldpw; + ber_dupbv( &p->bv, bv ); + p->t = t; + p->next = NULL; + + if (*l == NULL) { + /* degenerate case */ + *l = p; + return; + } + /* + * advance p1 and p2 such that p1 is the node before the + * new one, and p2 is the node after it + */ + for (p1 = NULL, p2 = *l; p2 && p2->t <= t; p1 = p2, p2=p2->next ); + p->next = p2; + if (p1 == NULL) { *l = p; return; } + p1->next = p; +} + +#ifndef MAX_PWD_HISTORY_SZ +#define MAX_PWD_HISTORY_SZ 1024 +#endif /* MAX_PWD_HISTORY_SZ */ + +static void +make_pwd_history_value( char *timebuf, struct berval *bv, Attribute *pa ) +{ + char str[ MAX_PWD_HISTORY_SZ ]; + int nlen; + + snprintf( str, MAX_PWD_HISTORY_SZ, + "%s#%s#%lu#", timebuf, + pa->a_desc->ad_type->sat_syntax->ssyn_oid, + (unsigned long) pa->a_nvals[0].bv_len ); + str[MAX_PWD_HISTORY_SZ-1] = 0; + nlen = strlen(str); + + /* + * We have to assume that the string is a string of octets, + * not readable characters. In reality, yes, it probably is + * a readable (ie, base64) string, but we can't count on that + * Hence, while the first 3 fields of the password history + * are definitely readable (a timestamp, an OID and an integer + * length), the remaining octets of the actual password + * are deemed to be binary data. + */ + AC_MEMCPY( str + nlen, pa->a_nvals[0].bv_val, pa->a_nvals[0].bv_len ); + nlen += pa->a_nvals[0].bv_len; + bv->bv_val = ch_malloc( nlen + 1 ); + AC_MEMCPY( bv->bv_val, str, nlen ); + bv->bv_val[nlen] = '\0'; + bv->bv_len = nlen; +} + +static void +free_pwd_history_list( pw_hist **l ) +{ + pw_hist *p; + + if (!l) return; + p = *l; + while (p) { + pw_hist *pp = p->next; + + free(p->pw.bv_val); + free(p->bv.bv_val); + free(p); + p = pp; + } + *l = NULL; +} + +typedef struct ppbind { + pp_info *pi; + BackendDB *be; + int send_ctrl; + int set_restrict; + LDAPControl **oldctrls; + Modifications *mod; + LDAPPasswordPolicyError pErr; + PassPolicy pp; +} ppbind; + +static void +ctrls_cleanup( Operation *op, SlapReply *rs, LDAPControl **oldctrls ) +{ + int n; + + assert( rs->sr_ctrls != NULL ); + assert( rs->sr_ctrls[0] != NULL ); + + for ( n = 0; rs->sr_ctrls[n]; n++ ) { + if ( rs->sr_ctrls[n]->ldctl_oid == ppolicy_ctrl_oid || + rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expired_oid || + rs->sr_ctrls[n]->ldctl_oid == ppolicy_pwd_expiring_oid ) { + op->o_tmpfree( rs->sr_ctrls[n], op->o_tmpmemctx ); + rs->sr_ctrls[n] = (LDAPControl *)(-1); + break; + } + } + + if ( rs->sr_ctrls[n] == NULL ) { + /* missed? */ + } + + op->o_tmpfree( rs->sr_ctrls, op->o_tmpmemctx ); + + rs->sr_ctrls = oldctrls; +} + +static int +ppolicy_ctrls_cleanup( Operation *op, SlapReply *rs ) +{ + ppbind *ppb = op->o_callback->sc_private; + if ( ppb->send_ctrl ) { + ctrls_cleanup( op, rs, ppb->oldctrls ); + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_bind_response( Operation *op, SlapReply *rs ) +{ + ppbind *ppb = op->o_callback->sc_private; + pp_info *pi = ppb->pi; + Modifications *mod = ppb->mod, *m; + int pwExpired = 0; + int ngut = -1, warn = -1, fc = 0, age, rc; + Attribute *a; + time_t now, pwtime = (time_t)-1; + struct lutil_tm now_tm; + struct lutil_timet now_usec; + char nowstr[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + char nowstr_usec[ LDAP_LUTIL_GENTIME_BUFSIZE+8 ]; + struct berval timestamp, timestamp_usec; + BackendDB *be = op->o_bd; + LDAPControl *ctrl = NULL; + Entry *e; + + ldap_pvt_thread_mutex_lock( &pi->pwdFailureTime_mutex ); + /* If we already know it's locked, just get on with it */ + if ( ppb->pErr != PP_noError ) { + goto locked; + } + + op->o_bd = ppb->be; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + op->o_bd = be; + + if ( rc != LDAP_SUCCESS ) { + ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex ); + return SLAP_CB_CONTINUE; + } + + /* ITS#7089 Skip lockout checks/modifications if password attribute missing */ + if ( attr_find( e->e_attrs, ppb->pp.ad ) == NULL ) { + goto done; + } + + ldap_pvt_gettime(&now_tm); /* stored for later consideration */ + lutil_tm2time(&now_tm, &now_usec); + now = now_usec.tt_sec; + timestamp.bv_val = nowstr; + timestamp.bv_len = sizeof(nowstr); + slap_timestamp( &now, ×tamp ); + + /* Separate timestamp for pwdFailureTime with microsecond granularity */ + strcpy(nowstr_usec, nowstr); + timestamp_usec.bv_val = nowstr_usec; + timestamp_usec.bv_len = timestamp.bv_len; + snprintf( timestamp_usec.bv_val + timestamp_usec.bv_len-1, sizeof(".123456Z"), ".%06dZ", now_usec.tt_nsec / 1000 ); + timestamp_usec.bv_len += STRLENOF(".123456"); + + if ( rs->sr_err == LDAP_INVALID_CREDENTIALS && ppb->pp.pwdMaxRecordedFailure ) { + int i = 0; + + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + + ber_dupbv( &m->sml_values[0], ×tamp_usec ); + ber_dupbv( &m->sml_nvalues[0], ×tamp_usec ); + m->sml_next = mod; + mod = m; + + /* + * Count the pwdFailureTimes - if it's + * greater than the policy pwdMaxFailure, + * then lock the account. + */ + if ((a = attr_find( e->e_attrs, ad_pwdFailureTime )) != NULL) { + for(i=0; a->a_nvals[i].bv_val; i++) { + + /* + * If the interval is 0, then failures + * stay on the record until explicitly + * reset by successful authentication. + */ + if (ppb->pp.pwdFailureCountInterval == 0) { + fc++; + } else if (now <= + parse_time(a->a_nvals[i].bv_val) + + ppb->pp.pwdFailureCountInterval) { + + fc++; + } + /* + * We only count those failures + * which are not due to expire. + */ + } + /* Do we have too many timestamps? If so, delete some values. + * We don't bother to sort the values here. OpenLDAP keeps the + * values in order by default. Fundamentally, relying on the + * information here is wrong anyway; monitoring systems should + * be tracking Bind failures in syslog, not here. + */ + if (a->a_numvals >= ppb->pp.pwdMaxRecordedFailure) { + int j = ppb->pp.pwdMaxRecordedFailure-1; + /* If more than 2x, cheaper to perform a Replace */ + if (a->a_numvals >= 2 * ppb->pp.pwdMaxRecordedFailure) { + struct berval v, nv; + + /* Change the mod we constructed above */ + m->sml_op = LDAP_MOD_REPLACE; + m->sml_numvals = ppb->pp.pwdMaxRecordedFailure; + v = m->sml_values[0]; + nv = m->sml_nvalues[0]; + ch_free(m->sml_values); + ch_free(m->sml_nvalues); + m->sml_values = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), ppb->pp.pwdMaxRecordedFailure+1 ); + for (i=0; i<j; i++) { + ber_dupbv(&m->sml_values[i], &a->a_vals[a->a_numvals-j+i]); + ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[a->a_numvals-j+i]); + } + m->sml_values[i] = v; + m->sml_nvalues[i] = nv; + } else { + /* else just delete some */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_numvals = a->a_numvals - j; + m->sml_values = ch_calloc( sizeof(struct berval), m->sml_numvals+1 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), m->sml_numvals+1 ); + for (i=0; i<m->sml_numvals; i++) { + ber_dupbv(&m->sml_values[i], &a->a_vals[i]); + ber_dupbv(&m->sml_nvalues[i], &a->a_nvals[i]); + } + m->sml_next = mod; + mod = m; + } + } + } + + if ((ppb->pp.pwdMaxFailure > 0) && + (fc >= ppb->pp.pwdMaxFailure - 1)) { + + /* + * We subtract 1 from the failure max + * because the new failure entry hasn't + * made it to the entry yet. + */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = 0; + m->sml_type = ad_pwdAccountLockedTime->ad_cname; + m->sml_desc = ad_pwdAccountLockedTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + ber_dupbv( &m->sml_values[0], ×tamp ); + ber_dupbv( &m->sml_nvalues[0], ×tamp ); + m->sml_next = mod; + mod = m; + } else if ( ppb->pp.pwdMinDelay ) { + int waittime = ppb->pp.pwdMinDelay << fc; + time_t wait_end; + struct berval lockout_stamp; + + if ( waittime > ppb->pp.pwdMaxDelay ) { + waittime = ppb->pp.pwdMaxDelay; + } + wait_end = now + waittime; + + slap_timestamp( &wait_end, &lockout_stamp ); + + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = 0; + m->sml_type = ad_pwdAccountTmpLockoutEnd->ad_cname; + m->sml_desc = ad_pwdAccountTmpLockoutEnd; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + ber_dupbv( &m->sml_values[0], &lockout_stamp ); + ber_dupbv( &m->sml_nvalues[0], &lockout_stamp ); + m->sml_next = mod; + mod = m; + } + } else if ( rs->sr_err == LDAP_SUCCESS ) { + if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) + pwtime = parse_time( a->a_nvals[0].bv_val ); + + /* delete all pwdFailureTimes */ + if ( attr_find( e->e_attrs, ad_pwdFailureTime )) { + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_type = ad_pwdFailureTime->ad_cname; + m->sml_desc = ad_pwdFailureTime; + m->sml_next = mod; + mod = m; + } + + /* + * check to see if the password must be changed + */ + if ( ppb->pp.pwdMustChange && + (a = attr_find( e->e_attrs, ad_pwdReset )) && + bvmatch( &a->a_nvals[0], &slap_true_bv ) ) + { + /* + * need to inject client controls here to give + * more information. For the moment, we ensure + * that we are disallowed from doing anything + * other than change password. + */ + if ( ppb->set_restrict ) { + ber_dupbv( &pwcons[op->o_conn->c_conn_idx].dn, + &op->o_conn->c_ndn ); + } + + ppb->pErr = PP_changeAfterReset; + + } else { + /* + * the password does not need to be changed, so + * we now check whether the password has expired. + * + * We can skip this bit if passwords don't age in + * the policy. Also, if there was no pwdChangedTime + * attribute in the entry, the password never expires. + */ + if (ppb->pp.pwdMaxAge == 0) goto grace; + + if (pwtime != (time_t)-1) { + /* + * Check: was the last change time of + * the password older than the maximum age + * allowed. (Ignore case 2 from I-D, it's just silly.) + */ + if (now - pwtime > ppb->pp.pwdMaxAge ) pwExpired = 1; + } + } + +grace: + if (!pwExpired) goto check_expiring_password; + + if ( ppb->pp.pwdGraceExpiry && + now - pwtime > ppb->pp.pwdMaxAge + ppb->pp.pwdGraceExpiry ) { + /* Grace logins have expired now */ + ngut = 0; + } else if ((a = attr_find( e->e_attrs, ad_pwdGraceUseTime )) == NULL) { + ngut = ppb->pp.pwdGraceAuthNLimit; + } else { + for(ngut=0; a->a_nvals[ngut].bv_val; ngut++); + ngut = ppb->pp.pwdGraceAuthNLimit - ngut; + } + + /* + * ngut is the number of remaining grace logins + */ + Debug( LDAP_DEBUG_ANY, + "ppolicy_bind: Entry %s has an expired password: %d grace logins\n", + e->e_name.bv_val, ngut ); + + ngut--; + + if (ngut < 0) { + ppb->pErr = PP_passwordExpired; + rs->sr_err = LDAP_INVALID_CREDENTIALS; + goto done; + } + + /* + * Add a grace user time to the entry + */ + m = ch_calloc( sizeof(Modifications), 1 ); + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_type = ad_pwdGraceUseTime->ad_cname; + m->sml_desc = ad_pwdGraceUseTime; + m->sml_numvals = 1; + m->sml_values = ch_calloc( sizeof(struct berval), 2 ); + m->sml_nvalues = ch_calloc( sizeof(struct berval), 2 ); + ber_dupbv( &m->sml_values[0], ×tamp_usec ); + ber_dupbv( &m->sml_nvalues[0], ×tamp_usec ); + m->sml_next = mod; + mod = m; + +check_expiring_password: + /* + * Now we need to check to see + * if it is about to expire, and if so, should the user + * be warned about it in the password policy control. + * + * If the password has expired, and we're in the grace period, then + * we don't need to do this bit. Similarly, if we don't have password + * aging, then there's no need to do this bit either. + * + * If pwdtime is -1 there is no password Change Time attribute on the + * entry so we skip the expiry check. + * + */ + if ((ppb->pp.pwdMaxAge < 1) || (pwExpired) || (ppb->pp.pwdExpireWarning < 1) || + (pwtime == -1)) + goto done; + + age = (int)(now - pwtime); + + /* + * We know that there is a password Change Time attribute - if + * there wasn't, then the pwdExpired value would be true, unless + * there is no password aging - and if there is no password aging, + * then this section isn't called anyway - you can't have an + * expiring password if there's no limit to expire. + */ + if (ppb->pp.pwdMaxAge - age < ppb->pp.pwdExpireWarning ) { + /* + * Set the warning value. + */ + warn = ppb->pp.pwdMaxAge - age; /* seconds left until expiry */ + if (warn < 0) warn = 0; /* something weird here - why is pwExpired not set? */ + + Debug( LDAP_DEBUG_TRACE, + "ppolicy_bind: Setting warning for password expiry for %s = %d seconds\n", + op->o_req_dn.bv_val, warn ); + } + } + +done: + op->o_bd = ppb->be; + be_entry_release_r( op, e ); + op->o_bd = be; + +locked: + if ( mod && !pi->disable_write ) { + Operation op2 = *op; + SlapReply r2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + LDAPControl c, *ca[2]; + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_callback = &cb; + op2.orm_modlist = mod; + op2.orm_no_opattrs = 0; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + /* If this server is a shadow and forward_updates is true, + * use the frontend to perform this modify. That will trigger + * the update referral, which can then be forwarded by the + * chain overlay. Obviously the updateref and chain overlay + * must be configured appropriately for this to be useful. + */ + if ( SLAP_SHADOW( op->o_bd ) && pi->forward_updates ) { + op2.o_bd = frontendDB; + + /* Must use Relax control since these are no-user-mod */ + op2.o_relax = SLAP_CONTROL_CRITICAL; + op2.o_ctrls = ca; + ca[0] = &c; + ca[1] = NULL; + BER_BVZERO( &c.ldctl_value ); + c.ldctl_iscritical = 1; + c.ldctl_oid = LDAP_CONTROL_RELAX; + } else { + /* If not forwarding, don't update opattrs and don't replicate */ + if ( SLAP_SINGLE_SHADOW( op->o_bd )) { + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + } + op2.o_bd = ppb->be; + } + rc = op2.o_bd->be_modify( &op2, &r2 ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s ppolicy_bind_response: " + "ppolicy state change failed with rc=%d text=%s\n", + op->o_log_prefix, rc, r2.sr_text ); + } + } + if ( mod ) { + slap_mods_free( mod, 1 ); + } + + if ( ppb->send_ctrl ) { + + /* Do we really want to tell that the account is locked? */ + if ( ppb->pErr == PP_accountLocked && !pi->use_lockout ) { + ppb->pErr = PP_noError; + } + ctrl = create_passcontrol( op, warn, ngut, ppb->pErr ); + } else if ( pi->send_netscape_controls ) { + if ( ppb->pErr != PP_noError || pwExpired ) { + ctrl = create_passexpiry( op, 1, 0 ); + } else if ( warn > 0 ) { + ctrl = create_passexpiry( op, 0, warn ); + } + } + if ( ctrl ) { + ppb->oldctrls = add_passcontrol( op, rs, ctrl ); + op->o_callback->sc_cleanup = ppolicy_ctrls_cleanup; + } + ldap_pvt_thread_mutex_unlock( &pi->pwdFailureTime_mutex ); + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_bind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + /* Reset lockout status on all Bind requests */ + if ( !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + + /* Root bypasses policy */ + if ( !be_isroot_dn( op->o_bd, &op->o_req_ndn )) { + Entry *e; + int rc; + ppbind *ppb; + slap_callback *cb; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + + if ( rc != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback), + 1, op->o_tmpmemctx ); + ppb = (ppbind *)(cb+1); + ppb->pi = on->on_bi.bi_private; + ppb->be = op->o_bd->bd_self; + ppb->pErr = PP_noError; + ppb->set_restrict = 1; + + /* Setup a callback so we can munge the result */ + + cb->sc_response = ppolicy_bind_response; + cb->sc_private = ppb; + overlay_callback_after_backover( op, cb, 1 ); + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + ppb->send_ctrl = 1; + } + + op->o_bd->bd_info = (BackendInfo *)on; + + if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) { + rc = account_locked( op, e, &ppb->pp, &ppb->mod ); + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + + if ( rc ) { + ppb->pErr = PP_accountLocked; + send_ldap_error( op, rs, LDAP_INVALID_CREDENTIALS, NULL ); + return rs->sr_err; + } + + } + + return SLAP_CB_CONTINUE; +} + +/* Reset the restricted info for the next session on this connection */ +static int +ppolicy_connection_destroy( BackendDB *bd, Connection *conn ) +{ + if ( pwcons && !BER_BVISEMPTY( &pwcons[conn->c_conn_idx].dn )) { + ch_free( pwcons[conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[conn->c_conn_idx].dn ); + } + return SLAP_CB_CONTINUE; +} + +/* Check if this connection is restricted */ +static int +ppolicy_restrict( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + int send_ctrl = 0; + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + send_ctrl = 1; + } + + if ( op->o_conn && !BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + LDAPControl **oldctrls; + /* if the current authcDN doesn't match the one we recorded, + * then an intervening Bind has succeeded and the restriction + * no longer applies. (ITS#4516) + */ + if ( !dn_match( &op->o_conn->c_ndn, + &pwcons[op->o_conn->c_conn_idx].dn )) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + return SLAP_CB_CONTINUE; + } + + Debug( LDAP_DEBUG_TRACE, + "connection restricted to password changing only\n" ); + if ( send_ctrl ) { + LDAPControl *ctrl = NULL; + ctrl = create_passcontrol( op, -1, -1, PP_changeAfterReset ); + oldctrls = add_passcontrol( op, rs, ctrl ); + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_INSUFFICIENT_ACCESS, + "Operations are restricted to bind/unbind/abandon/StartTLS/modify password" ); + if ( send_ctrl ) { + ctrls_cleanup( op, rs, oldctrls ); + } + return rs->sr_err; + } + + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_account_usability_entry_cb( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = op->o_callback->sc_private; + BackendInfo *bi = op->o_bd->bd_info; + LDAPControl *ctrl = NULL; + PassPolicy pp; + Attribute *a; + Entry *e = NULL; + time_t pwtime = 0, seconds_until_expiry = -1, now = op->o_time; + int isExpired = 0, grace = -1; + + if ( rs->sr_type != REP_SEARCH ) { + return SLAP_CB_CONTINUE; + } + + if ( be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &e ) != LDAP_SUCCESS ) { + goto done; + } + + op->o_bd->bd_info = (BackendInfo *)on; + + if ( ppolicy_get( op, e, &pp ) != LDAP_SUCCESS ) { + /* TODO: If there is no policy, should we check if */ + goto done; + } + + if ( !access_allowed( op, e, pp.ad, NULL, ACL_COMPARE, NULL ) ) { + goto done; + } + + if ( attr_find( e->e_attrs, pp.ad ) == NULL ) { + goto done; + } + + if ((a = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) { + pwtime = parse_time( a->a_nvals[0].bv_val ); + } + + if ( pp.pwdMaxAge && pwtime ) { + seconds_until_expiry = pwtime + pp.pwdMaxAge - now; + if ( seconds_until_expiry <= 0 ) isExpired = 1; + if ( pp.pwdGraceAuthNLimit ) { + if ( !pp.pwdGraceExpiry || seconds_until_expiry + pp.pwdGraceExpiry > 0 ) { + grace = pp.pwdGraceAuthNLimit; + if ( attr_find( e->e_attrs, ad_pwdGraceUseTime ) ) { + grace -= a->a_numvals; + } + } + } + } + if ( !isExpired && pp.pwdMaxIdle && (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) ) { + time_t lastbindtime = pwtime; + + if ( (a = attr_find( e->e_attrs, ad_pwdLastSuccess )) != NULL ) { + lastbindtime = parse_time( a->a_nvals[0].bv_val ); + } + + if ( lastbindtime ) { + int remaining_idle = lastbindtime + pp.pwdMaxIdle - now; + if ( remaining_idle <= 0 ) { + isExpired = 1; + } else if ( seconds_until_expiry == -1 || remaining_idle < seconds_until_expiry ) { + seconds_until_expiry = remaining_idle; + } + } + } + + if ( isExpired || account_locked( op, e, &pp, NULL ) ) { + LDAPAccountUsabilityMoreInfo more_info = { 0, 0, 0, -1, -1 }; + time_t then, lockoutEnd = 0; + + if ( isExpired ) more_info.remaining_grace = grace; + + if ( (a = attr_find( e->e_attrs, ad_pwdAccountLockedTime )) != NULL ) { + then = parse_time( a->a_vals[0].bv_val ); + if ( then == 0 ) + lockoutEnd = -1; + + /* Still in the future? not yet in effect */ + if ( now < then ) + then = 0; + + if ( !pp.pwdLockoutDuration ) + lockoutEnd = -1; + + if ( now < then + pp.pwdLockoutDuration ) + lockoutEnd = then + pp.pwdLockoutDuration; + } + + if ( (a = attr_find( e->e_attrs, ad_pwdAccountTmpLockoutEnd )) != NULL ) { + then = parse_time( a->a_vals[0].bv_val ); + if ( lockoutEnd != -1 && then > lockoutEnd ) + lockoutEnd = then; + } + + if ( lockoutEnd > now ) { + more_info.inactive = 1; + more_info.seconds_before_unlock = lockoutEnd - now; + } + + if ( pp.pwdMustChange && + (a = attr_find( e->e_attrs, ad_pwdReset )) && + bvmatch( &a->a_nvals[0], &slap_true_bv ) ) + { + more_info.reset = 1; + } + + add_account_control( op, rs, 0, -1, &more_info ); + } else { + add_account_control( op, rs, 1, seconds_until_expiry, NULL ); + } + +done: + op->o_bd->bd_info = bi; + if ( e ) { + be_entry_release_r( op, e ); + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_search( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + int rc = ppolicy_restrict( op, rs ); + + if ( rc != SLAP_CB_CONTINUE ) { + return rc; + } + + if ( op->o_ctrlflag[account_usability_cid] ) { + slap_callback *cb; + + cb = op->o_tmpcalloc( sizeof(slap_callback), 1, op->o_tmpmemctx ); + + cb->sc_response = ppolicy_account_usability_entry_cb; + cb->sc_private = on; + overlay_callback_after_backover( op, cb, 1 ); + } + + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_compare_response( + Operation *op, + SlapReply *rs ) +{ + /* map compare responses to bind responses */ + if ( rs->sr_err == LDAP_COMPARE_TRUE ) + rs->sr_err = LDAP_SUCCESS; + else if ( rs->sr_err == LDAP_COMPARE_FALSE ) + rs->sr_err = LDAP_INVALID_CREDENTIALS; + + ppolicy_bind_response( op, rs ); + + /* map back to compare */ + if ( rs->sr_err == LDAP_SUCCESS ) + rs->sr_err = LDAP_COMPARE_TRUE; + else if ( rs->sr_err == LDAP_INVALID_CREDENTIALS ) + rs->sr_err = LDAP_COMPARE_FALSE; + + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_compare( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE ) + return rs->sr_err; + + /* Did we receive a password policy request control? + * Are we testing the userPassword? + */ + if ( op->o_ctrlflag[ppolicy_cid] && + op->orc_ava->aa_desc == slap_schema.si_ad_userPassword ) { + Entry *e; + int rc; + ppbind *ppb; + slap_callback *cb; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + + if ( rc != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback), + 1, op->o_tmpmemctx ); + ppb = (ppbind *)(cb+1); + ppb->pi = on->on_bi.bi_private; + ppb->be = op->o_bd->bd_self; + ppb->pErr = PP_noError; + ppb->send_ctrl = 1; + /* failures here don't lockout the connection */ + ppb->set_restrict = 0; + + /* Setup a callback so we can munge the result */ + + cb->sc_response = ppolicy_compare_response; + cb->sc_private = ppb; + overlay_callback_after_backover( op, cb, 1 ); + + op->o_bd->bd_info = (BackendInfo *)on; + + if ( ppolicy_get( op, e, &ppb->pp ) == LDAP_SUCCESS ) { + rc = account_locked( op, e, &ppb->pp, &ppb->mod ); + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + + if ( rc ) { + ppb->pErr = PP_accountLocked; + send_ldap_error( op, rs, LDAP_COMPARE_FALSE, NULL ); + return rs->sr_err; + } + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_add( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + PassPolicy pp; + Attribute *pa; + const char *txt; + int is_pwdadmin = 0; + + if ( ppolicy_restrict( op, rs ) != SLAP_CB_CONTINUE ) + return rs->sr_err; + + /* If this is a replica, assume the provider checked everything */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) + return SLAP_CB_CONTINUE; + + ppolicy_get( op, op->ora_e, &pp ); + + if ( access_allowed( op, op->ora_e, pp.ad, NULL, ACL_MANAGE, NULL ) ) { + is_pwdadmin = 1; + } + + /* Check for password in entry */ + if ( (pa = attr_find( op->oq_add.rs_e->e_attrs, pp.ad )) ) { + assert( pa->a_vals != NULL ); + assert( !BER_BVISNULL( &pa->a_vals[ 0 ] ) ); + + if ( !BER_BVISNULL( &pa->a_vals[ 1 ] ) ) { + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, "Password policy only allows one password value" ); + return rs->sr_err; + } + + /* + * new entry contains a password - if we're not the password admin + * then we need to check that the password fits in with the + * security policy for the new entry. + */ + + if ( pp.pwdCheckQuality > 0 && !is_pwdadmin ) { + struct berval *bv = &(pa->a_vals[0]); + int rc, send_ctrl = 0; + LDAPPasswordPolicyError pErr = PP_noError; + char *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 ) && + !attr_find( op->ora_e->e_attrs, ad_pwdChangedTime ) ) { + struct berval timestamp; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + time_t now = slap_get_time(); + + timestamp.bv_val = timebuf; + timestamp.bv_len = sizeof(timebuf); + slap_timestamp( &now, ×tamp ); + + attr_merge_one( op->ora_e, ad_pwdChangedTime, ×tamp, ×tamp ); + } + } + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_mod_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + op->o_callback = sc->sc_next; + if ( rs->sr_err == LDAP_SUCCESS ) { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + op->o_tmpfree( sc, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_text_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + + if ( rs->sr_text == sc->sc_private ) { + rs->sr_text = NULL; + } + free( sc->sc_private ); + + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + return SLAP_CB_CONTINUE; +} + +static int +ppolicy_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + pp_info *pi = on->on_bi.bi_private; + int i, rc, mod_pw_only, pwmod = 0, pwmop = -1, deladd, + hsize = 0, hskip; + PassPolicy pp; + Modifications *mods = NULL, *modtail = NULL, + *ml, *delmod, *addmod; + Attribute *pa, *ha, at; + const char *txt; + pw_hist *tl = NULL, *p; + int zapReset, send_ctrl = 0, free_txt = 0; + Entry *e; + struct berval newpw = BER_BVNULL, oldpw = BER_BVNULL, + *bv, cr[2]; + LDAPPasswordPolicyError pErr = PP_noError; + LDAPControl *ctrl = NULL; + LDAPControl **oldctrls = NULL; + int is_pwdexop = 0, is_pwdadmin = 0; + int got_del_grace = 0, got_del_lock = 0, got_pw = 0, got_del_fail = 0, + got_del_success = 0; + int got_changed = 0, got_history = 0; + int have_policy = 0; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + if ( pi->disable_write ) return SLAP_CB_CONTINUE; + + /* If this is a replica, we may need to tweak some of the + * provider's modifications. Otherwise, just pass it through. + */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) { + Modifications **prev; + Attribute *a_grace, *a_lock, *a_fail, *a_success; + + a_grace = attr_find( e->e_attrs, ad_pwdGraceUseTime ); + a_lock = attr_find( e->e_attrs, ad_pwdAccountLockedTime ); + a_fail = attr_find( e->e_attrs, ad_pwdFailureTime ); + a_success = attr_find( e->e_attrs, ad_pwdLastSuccess ); + + for( prev = &op->orm_modlist, ml = *prev; ml; ml = *prev ) { + + if ( ml->sml_desc == slap_schema.si_ad_userPassword ) + got_pw = 1; + + /* If we're deleting an attr that didn't exist, + * drop this delete op + */ + if ( ml->sml_op == LDAP_MOD_DELETE || + ml->sml_op == SLAP_MOD_SOFTDEL ) { + int drop = 0; + + if ( ml->sml_desc == ad_pwdGraceUseTime ) { + if ( !a_grace || got_del_grace ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_grace = 1; + } + } else + if ( ml->sml_desc == ad_pwdAccountLockedTime ) { + if ( !a_lock || got_del_lock ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_lock = 1; + } + } else + if ( ml->sml_desc == ad_pwdFailureTime ) { + if ( !a_fail || got_del_fail ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_fail = 1; + } + } + if ( ml->sml_desc == ad_pwdLastSuccess ) { + if ( !a_success || got_del_success ) { + drop = ml->sml_op == LDAP_MOD_DELETE; + } else { + got_del_success = 1; + } + } + if ( drop ) { + *prev = ml->sml_next; + ml->sml_next = NULL; + slap_mods_free( ml, 1 ); + continue; + } + } + prev = &ml->sml_next; + } + + /* If we're resetting the password, make sure grace, accountlock, + * success, and failure also get removed. + */ + if ( got_pw ) { + if ( a_grace && !got_del_grace ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdGraceUseTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + prev = &ml->sml_next; + } + if ( a_lock && !got_del_lock ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdAccountLockedTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } + if ( a_fail && !got_del_fail ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdFailureTime; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } + if ( a_success && !got_del_success ) { + ml = (Modifications *) ch_malloc( sizeof( Modifications ) ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_type.bv_val = NULL; + ml->sml_desc = ad_pwdLastSuccess; + ml->sml_numvals = 0; + ml->sml_values = NULL; + ml->sml_nvalues = NULL; + ml->sml_next = NULL; + *prev = ml; + } + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + return SLAP_CB_CONTINUE; + } + + /* Did we receive a password policy request control? */ + if ( op->o_ctrlflag[ppolicy_cid] ) { + send_ctrl = 1; + } + + /* See if this is a pwdModify exop. If so, we can + * access the plaintext passwords from that request. + */ + { + slap_callback *sc; + + for ( sc = op->o_callback; sc; sc=sc->sc_next ) { + if ( sc->sc_response == slap_null_cb && + sc->sc_private ) { + req_pwdexop_s *qpw = sc->sc_private; + newpw = qpw->rs_new; + oldpw = qpw->rs_old; + is_pwdexop = 1; + break; + } + } + } + + /* ppolicy_hash_cleartext depends on pwmod being determined first */ + if ( ppolicy_get( op, e, &pp ) == LDAP_SUCCESS ) { + have_policy = 1; + } + + if ( access_allowed( op, e, pp.ad, NULL, ACL_MANAGE, NULL ) ) { + is_pwdadmin = 1; + } + + for ( ml = op->orm_modlist, + pwmod = 0, mod_pw_only = 1, + deladd = 0, delmod = NULL, + addmod = NULL, + zapReset = 1; + ml != NULL; modtail = ml, ml = ml->sml_next ) + { + if ( ml->sml_desc == pp.ad ) { + pwmod = 1; + pwmop = ml->sml_op; + if ((deladd == 0) && (ml->sml_op == LDAP_MOD_DELETE) && + (ml->sml_values) && !BER_BVISNULL( &ml->sml_values[0] )) + { + deladd = 1; + delmod = ml; + } + + if ((ml->sml_op == LDAP_MOD_ADD) || + (ml->sml_op == LDAP_MOD_REPLACE)) + { + if ( ml->sml_values && !BER_BVISNULL( &ml->sml_values[0] )) { + if ( deladd == 1 ) + deladd = 2; + + /* FIXME: there's no easy way to ensure + * that add does not cause multiple + * userPassword values; one way (that + * would be consistent with the single + * password constraint) would be to turn + * add into replace); another would be + * to disallow add. + * + * Let's check at least that a single value + * is being added + */ + if ( addmod || !BER_BVISNULL( &ml->sml_values[ 1 ] ) ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password policy only allows one password value"; + goto return_results; + } + + addmod = ml; + } else { + /* replace can have no values, add cannot */ + assert( ml->sml_op == LDAP_MOD_REPLACE ); + } + } + + } else if ( !(ml->sml_flags & SLAP_MOD_INTERNAL) && !is_at_operational( ml->sml_desc->ad_type ) ) { + mod_pw_only = 0; + /* modifying something other than password */ + } + + /* + * If there is a request to explicitly add a pwdReset + * attribute, then we suppress the normal behaviour on + * password change, which is to remove the pwdReset + * attribute. + * + * This enables an administrator to assign a new password + * and place a "must reset" flag on the entry, which will + * stay until the user explicitly changes his/her password. + */ + if (ml->sml_desc == ad_pwdReset ) { + if ((ml->sml_op == LDAP_MOD_ADD) || + (ml->sml_op == LDAP_MOD_REPLACE)) + zapReset = 0; + } + if ( ml->sml_op == LDAP_MOD_DELETE ) { + if ( ml->sml_desc == ad_pwdGraceUseTime ) { + got_del_grace = 1; + } else if ( ml->sml_desc == ad_pwdAccountLockedTime ) { + got_del_lock = 1; + } else if ( ml->sml_desc == ad_pwdFailureTime ) { + got_del_fail = 1; + } else if ( ml->sml_desc == ad_pwdLastSuccess ) { + got_del_success = 1; + } + } + if ( ml->sml_desc == ad_pwdChangedTime ) { + got_changed = 1; + } else if (ml->sml_desc == ad_pwdHistory ) { + got_history = 1; + } + } + + if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn ) && !mod_pw_only ) { + if ( dn_match( &op->o_conn->c_ndn, + &pwcons[op->o_conn->c_conn_idx].dn )) { + Debug( LDAP_DEBUG_TRACE, + "connection restricted to password changing only\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "Operations are restricted to bind/unbind/abandon/StartTLS/modify password"; + pErr = PP_changeAfterReset; + goto return_results; + } else { + ch_free( pwcons[op->o_conn->c_conn_idx].dn.bv_val ); + BER_BVZERO( &pwcons[op->o_conn->c_conn_idx].dn ); + } + } + + /* + * if we have a "safe password modify policy", then we need to check if we're doing + * a delete (with the old password), followed by an add (with the new password). + * + * If we got just a delete with nothing else, just let it go. We also skip all the checks if + * the root user is bound. Root can do anything, including avoid the policies. + */ + + if (!have_policy || !pwmod) goto do_modify; + + /* + * Build the password history list in ascending time order + * We need this, even if the user is root, in order to maintain + * the pwdHistory operational attributes properly. + */ + if (addmod && pp.pwdInHistory > 0 && (ha = attr_find( e->e_attrs, ad_pwdHistory ))) { + struct berval oldpw; + time_t oldtime; + + for(i=0; ha->a_nvals[i].bv_val; i++) { + rc = parse_pwdhistory( &(ha->a_nvals[i]), NULL, + &oldtime, &oldpw ); + + if (rc != LDAP_SUCCESS) continue; /* invalid history entry */ + + if (oldpw.bv_val) { + add_to_pwd_history( &tl, oldtime, &oldpw, + &(ha->a_nvals[i]) ); + oldpw.bv_val = NULL; + oldpw.bv_len = 0; + } + } + for(p=tl; p; p=p->next, hsize++); /* count history size */ + } + + if (is_pwdadmin) goto do_modify; + + /* NOTE: according to draft-behera-ldap-password-policy + * pwdAllowUserChange == FALSE must only prevent pwd changes + * by the user the pwd belongs to (ITS#7021) */ + if (!pp.pwdAllowUserChange && dn_match(&op->o_req_ndn, &op->o_ndn)) { + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "User alteration of password is not allowed"; + pErr = PP_passwordModNotAllowed; + goto return_results; + } + + /* Just deleting? */ + if (!addmod) { + /* skip everything else */ + pwmod = 0; + goto do_modify; + } + + /* This is a pwdModify exop that provided the old pw. + * We need to create a Delete mod for this old pw and + * let the matching value get found later + */ + if (pp.pwdSafeModify && oldpw.bv_val ) { + ml = (Modifications *)ch_calloc( sizeof( Modifications ), 1 ); + ml->sml_op = LDAP_MOD_DELETE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_desc = pp.ad; + ml->sml_type = pp.ad->ad_cname; + ml->sml_numvals = 1; + ml->sml_values = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) ); + ber_dupbv( &ml->sml_values[0], &oldpw ); + BER_BVZERO( &ml->sml_values[1] ); + ml->sml_next = op->orm_modlist; + op->orm_modlist = ml; + delmod = ml; + deladd = 2; + } + + if (pp.pwdSafeModify && deladd != 2) { + Debug( LDAP_DEBUG_TRACE, + "change password must use DELETE followed by ADD/REPLACE\n" ); + rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "Must supply old password to be changed as well as new one"; + pErr = PP_mustSupplyOldPassword; + goto return_results; + } + + /* Check age, but only if pwdReset is not TRUE */ + pa = attr_find( e->e_attrs, ad_pwdReset ); + if ((!pa || !bvmatch( &pa->a_nvals[0], &slap_true_bv )) && + pp.pwdMinAge > 0) { + time_t pwtime = (time_t)-1, now; + int age; + + if ((pa = attr_find( e->e_attrs, ad_pwdChangedTime )) != NULL) + pwtime = parse_time( pa->a_nvals[0].bv_val ); + now = slap_get_time(); + age = (int)(now - pwtime); + if ((pwtime != (time_t)-1) && (age < pp.pwdMinAge)) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is too young to change"; + pErr = PP_passwordTooYoung; + goto return_results; + } + } + + /* pa is used in password history check below, be sure it's set */ + if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL && delmod) { + /* + * we have a password to check + */ + bv = oldpw.bv_val ? &oldpw : delmod->sml_values; + /* FIXME: no access checking? */ + rc = slap_passwd_check( op, NULL, pa, bv, &txt ); + if (rc != LDAP_SUCCESS) { + Debug( LDAP_DEBUG_TRACE, + "old password check failed: %s\n", txt ); + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Must supply correct old password to change to new one"; + pErr = PP_mustSupplyOldPassword; + goto return_results; + + } else { + int i; + + /* + * replace the delete value with the (possibly hashed) + * value which is currently in the password. + */ + for ( i = 0; !BER_BVISNULL( &delmod->sml_values[i] ); i++ ) { + free( delmod->sml_values[i].bv_val ); + BER_BVZERO( &delmod->sml_values[i] ); + } + free( delmod->sml_values ); + delmod->sml_values = ch_calloc( sizeof(struct berval), 2 ); + BER_BVZERO( &delmod->sml_values[1] ); + ber_dupbv( &(delmod->sml_values[0]), &(pa->a_nvals[0]) ); + } + } + + bv = newpw.bv_val ? &newpw : &addmod->sml_values[0]; + if (pp.pwdCheckQuality > 0) { + + rc = check_password_quality( bv, &pp, &pErr, e, (char **)&txt ); + if (rc != LDAP_SUCCESS) { + rs->sr_err = rc; + if ( txt ) { + rs->sr_text = txt; + free_txt = 1; + } else { + rs->sr_text = "Password fails quality checking policy"; + } + goto return_results; + } + } + + /* If pwdInHistory is zero, passwords may be reused */ + if (pa && pp.pwdInHistory > 0) { + /* + * Last check - the password history. + */ + /* FIXME: no access checking? */ + if (slap_passwd_check( op, NULL, pa, bv, &txt ) == LDAP_SUCCESS) { + /* + * This is bad - it means that the user is attempting + * to set the password to the same as the old one. + */ + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is not being changed from existing value"; + pErr = PP_passwordInHistory; + goto return_results; + } + + /* We need this when reduce pwdInHistory */ + hskip = hsize - pp.pwdInHistory; + + /* + * Iterate through the password history, and fail on any + * password matches. + */ + at = *pa; + at.a_vals = cr; + cr[1].bv_val = NULL; + for(p=tl; p; p=p->next) { + if(hskip > 0){ + hskip--; + continue; + } + cr[0] = p->pw; + /* FIXME: no access checking? */ + rc = slap_passwd_check( op, NULL, &at, bv, &txt ); + + if (rc != LDAP_SUCCESS) continue; + + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + rs->sr_text = "Password is in history of old passwords"; + pErr = PP_passwordInHistory; + goto return_results; + } + } + +do_modify: + if (pwmod) { + struct berval timestamp; + char timebuf[ LDAP_LUTIL_GENTIME_BUFSIZE ]; + time_t now = slap_get_time(); + + /* If the conn is restricted, set a callback to clear it + * if the pwmod succeeds + */ + if (!BER_BVISEMPTY( &pwcons[op->o_conn->c_conn_idx].dn )) { + slap_callback *sc = op->o_tmpcalloc( 1, sizeof( slap_callback ), + op->o_tmpmemctx ); + sc->sc_next = op->o_callback; + /* Must use sc_response to insure we reset on success, before + * the client sees the response. Must use sc_cleanup to insure + * that it gets cleaned up if sc_response is not called. + */ + sc->sc_response = ppolicy_mod_cb; + sc->sc_cleanup = ppolicy_mod_cb; + op->o_callback = sc; + } + + /* + * keep the necessary pwd.. operational attributes + * up to date. + */ + + if (!got_changed) { + timestamp.bv_val = timebuf; + timestamp.bv_len = sizeof(timebuf); + slap_timestamp( &now, ×tamp ); + + mods = NULL; + if (pwmop != LDAP_MOD_DELETE) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_REPLACE; + mods->sml_numvals = 1; + mods->sml_values = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + mods->sml_nvalues = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + + ber_dupbv( &mods->sml_values[0], ×tamp ); + ber_dupbv( &mods->sml_nvalues[0], ×tamp ); + } else if (attr_find(e->e_attrs, ad_pwdChangedTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + } + if (mods) { + mods->sml_desc = ad_pwdChangedTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + } + + if (!got_del_grace && attr_find(e->e_attrs, ad_pwdGraceUseTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdGraceUseTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_del_lock && attr_find(e->e_attrs, ad_pwdAccountLockedTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdAccountLockedTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdFailureTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if ( zapReset ) { + /* + * ITS#7084 Is this a modification by the password + * administrator? Then force a reset if configured. + * Otherwise clear it. + */ + if ( pp.pwdMustChange && is_pwdadmin ) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_REPLACE; + mods->sml_desc = ad_pwdReset; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_numvals = 1; + mods->sml_values = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + mods->sml_nvalues = (BerVarray) ch_calloc( sizeof( struct berval ), 2 ); + + ber_dupbv( &mods->sml_values[0], (struct berval *)&slap_true_bv ); + ber_dupbv( &mods->sml_nvalues[0], (struct berval *)&slap_true_bv ); + + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } else if ( attr_find( e->e_attrs, ad_pwdReset ) ) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdReset; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + } + + /* TODO: do we remove pwdLastSuccess or set it to 'now'? */ + if (!got_del_success && attr_find(e->e_attrs, ad_pwdLastSuccess )){ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdLastSuccess; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + /* Delete all pwdInHistory attribute */ + if (!got_history && pp.pwdInHistory == 0 && + attr_find(e->e_attrs, ad_pwdHistory )){ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdHistory; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + + if (!got_history && pp.pwdInHistory > 0){ + if (hsize >= pp.pwdInHistory) { + /* + * We use the >= operator, since we are going to add + * the existing password attribute value into the + * history - thus the cardinality of history values is + * about to rise by one. + * + * If this would push it over the limit of history + * values (remembering - the password policy could have + * changed since the password was last altered), we must + * delete at least 1 value from the pwdHistory list. + * + * In fact, we delete '(#pwdHistory attrs - max pwd + * history length) + 1' values, starting with the oldest. + * This is easily evaluated, since the linked list is + * created in ascending time order. + */ + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_desc = ad_pwdHistory; + mods->sml_numvals = hsize - pp.pwdInHistory + 1; + mods->sml_values = ch_calloc( sizeof( struct berval ), + hsize - pp.pwdInHistory + 2 ); + BER_BVZERO( &mods->sml_values[ hsize - pp.pwdInHistory + 1 ] ); + for(i=0,p=tl; i < (hsize - pp.pwdInHistory + 1); i++, p=p->next) { + BER_BVZERO( &mods->sml_values[i] ); + ber_dupbv( &(mods->sml_values[i]), &p->bv ); + } + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + free_pwd_history_list( &tl ); + + /* + * Now add the existing password into the history list. + * This will be executed even if the operation is to delete + * the password entirely. + * + * This isn't in the spec explicitly, but it seems to make + * sense that the password history list is the list of all + * previous passwords - even if they were deleted. Thus, if + * someone tries to add a historical password at some future + * point, it will fail. + */ + if ((pa = attr_find( e->e_attrs, pp.ad )) != NULL) { + mods = (Modifications *) ch_malloc( sizeof( Modifications ) ); + mods->sml_op = LDAP_MOD_ADD; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_type.bv_val = NULL; + mods->sml_desc = ad_pwdHistory; + mods->sml_nvalues = NULL; + mods->sml_numvals = 1; + mods->sml_values = ch_calloc( sizeof( struct berval ), 2 ); + mods->sml_values[ 1 ].bv_val = NULL; + mods->sml_values[ 1 ].bv_len = 0; + make_pwd_history_value( timebuf, &mods->sml_values[0], pa ); + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + + } else { + Debug( LDAP_DEBUG_TRACE, + "ppolicy_modify: password attr lookup failed\n" ); + } + } + + /* + * Controversial bit here. If the new password isn't hashed + * (ie, is cleartext), we probably should hash it according + * to the default hash. The reason for this is that we want + * to use the policy if possible, but if we hash the password + * before, then we're going to run into trouble when it + * comes time to check the password. + * + * Now, the right thing to do is to use the extended password + * modify operation, but not all software can do this, + * therefore it makes sense to hash the new password, now + * we know it passes the policy requirements. + * + * Of course, if the password is already hashed, then we + * leave it alone. + */ + + if ((pi->hash_passwords) && (addmod) && !newpw.bv_val && + (password_scheme( &(addmod->sml_values[0]), NULL ) != LDAP_SUCCESS)) + { + struct berval hpw, bv; + + slap_passwd_hash( &(addmod->sml_values[0]), &hpw, &txt ); + if (hpw.bv_val == NULL) { + /* + * hashing didn't work. Emit an error. + */ + rs->sr_err = LDAP_OTHER; + rs->sr_text = txt; + goto return_results; + } + bv = addmod->sml_values[0]; + /* clear and discard the clear password */ + memset(bv.bv_val, 0, bv.bv_len); + ber_memfree(bv.bv_val); + addmod->sml_values[0] = hpw; + } + } else { + /* ITS#8762 Make sure we drop pwdFailureTime if unlocking */ + if (got_del_lock && !got_del_fail && attr_find(e->e_attrs, ad_pwdFailureTime )) { + mods = (Modifications *) ch_calloc( sizeof( Modifications ), 1 ); + mods->sml_op = LDAP_MOD_DELETE; + mods->sml_desc = ad_pwdFailureTime; + mods->sml_flags = SLAP_MOD_INTERNAL; + mods->sml_next = NULL; + modtail->sml_next = mods; + modtail = mods; + } + } + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + return SLAP_CB_CONTINUE; + +return_results: + free_pwd_history_list( &tl ); + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, e ); + if ( send_ctrl ) { + ctrl = create_passcontrol( op, -1, -1, pErr ); + oldctrls = add_passcontrol( op, rs, ctrl ); + } + send_ldap_result( op, rs ); + if ( free_txt ) { + if ( is_pwdexop ) { + slap_callback *cb; + cb = op->o_tmpcalloc( sizeof(ppbind)+sizeof(slap_callback), + 1, op->o_tmpmemctx ); + + /* Setup a callback so we can free the text when sent */ + cb->sc_cleanup = ppolicy_text_cleanup; + cb->sc_private = (void *)txt; + overlay_callback_after_backover( op, cb, 1 ); + } else { + if ( rs->sr_text == txt ) { + rs->sr_text = NULL; + } + free( (char *)txt ); + } + } + if ( send_ctrl ) { + if ( is_pwdexop ) { + if ( rs->sr_flags & REP_CTRLS_MUSTBEFREED ) { + op->o_tmpfree( oldctrls, op->o_tmpmemctx ); + } + oldctrls = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + + } else { + ctrls_cleanup( op, rs, oldctrls ); + } + } + return rs->sr_err; +} + +static int +ppolicy_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "passwordPolicyRequest control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + op->o_ctrlflag[ppolicy_cid] = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int +ppolicy_au_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + if ( !BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "account usability control value not absent"; + return LDAP_PROTOCOL_ERROR; + } + op->o_ctrlflag[account_usability_cid] = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + return LDAP_SUCCESS; +} + +static int +attrPretty( + Syntax *syntax, + struct berval *val, + struct berval *out, + void *ctx ) +{ + AttributeDescription *ad = NULL; + const char *err; + int code; + + code = slap_bv2ad( val, &ad, &err ); + if ( !code ) { + ber_dupbv_x( out, &ad->ad_type->sat_cname, ctx ); + } + return code; +} + +static int +attrNormalize( + slap_mask_t use, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *out, + void *ctx ) +{ + AttributeDescription *ad = NULL; + const char *err; + int code; + + code = slap_bv2ad( val, &ad, &err ); + if ( !code ) { + ber_str2bv_x( ad->ad_type->sat_oid, 0, 1, out, ctx ); + } + return code; +} + +static int +ppolicy_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + pp_info *pi; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + /* do not allow slapo-ppolicy to be global by now (ITS#5858) */ + if ( cr ){ + snprintf( cr->msg, sizeof(cr->msg), + "slapo-ppolicy cannot be global" ); + Debug( LDAP_DEBUG_ANY, "%s\n", cr->msg ); + } + return 1; + } + + pi = on->on_bi.bi_private = ch_calloc( sizeof(pp_info), 1 ); + + if ( !pwcons ) { + /* accommodate for c_conn_idx == -1 */ + pwcons = ch_calloc( sizeof(pw_conn), dtblsize + 1 ); + pwcons++; + } + + ov_count++; + + ldap_pvt_thread_mutex_init( &pi->pwdFailureTime_mutex ); + + return 0; +} + +static int +ppolicy_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + int rc; + + if ( (rc = overlay_register_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY )) != LDAP_SUCCESS ) { + return rc; + } + return overlay_register_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST ); +} + +static int +ppolicy_db_close( + BackendDB *be, + ConfigReply *cr +) +{ +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_PASSWORDPOLICYREQUEST ); + overlay_unregister_control( be, LDAP_CONTROL_X_ACCOUNT_USABILITY ); +#endif /* SLAP_CONFIG_DELETE */ + + return 0; +} + +static int +ppolicy_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + pp_info *pi = on->on_bi.bi_private; + + on->on_bi.bi_private = NULL; + ldap_pvt_thread_mutex_destroy( &pi->pwdFailureTime_mutex ); + free( pi->def_policy.bv_val ); + free( pi ); + + ov_count--; + if ( ov_count <=0 && pwcons ) { + pw_conn *pwc = pwcons; + pwcons = NULL; + pwc--; + ch_free( pwc ); + } + return 0; +} + +static char *extops[] = { + LDAP_EXOP_MODIFY_PASSWD, + NULL +}; + +static slap_overinst ppolicy; + +int ppolicy_initialize() +{ + int i, code; + + for (i=0; pwd_OpSchema[i].def; i++) { + code = register_at( pwd_OpSchema[i].def, pwd_OpSchema[i].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "ppolicy_initialize: register_at failed\n" ); + return code; + } + /* Allow Manager to set these as needed */ + if ( is_at_no_user_mod( (*pwd_OpSchema[i].ad)->ad_type )) { + (*pwd_OpSchema[i].ad)->ad_type->sat_flags |= + SLAP_AT_MANAGEABLE; + } + } + ad_pwdLastSuccess = slap_schema.si_ad_pwdLastSuccess; + { + Syntax *syn; + MatchingRule *mr; + + syn = ch_malloc( sizeof( Syntax )); + *syn = *ad_pwdAttribute->ad_type->sat_syntax; + syn->ssyn_pretty = attrPretty; + ad_pwdAttribute->ad_type->sat_syntax = syn; + + mr = ch_malloc( sizeof( MatchingRule )); + *mr = *ad_pwdAttribute->ad_type->sat_equality; + mr->smr_normalize = attrNormalize; + ad_pwdAttribute->ad_type->sat_equality = mr; + } + + for (i=0; pwd_ocs[i]; i++) { + code = register_oc( pwd_ocs[i], NULL, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, "ppolicy_initialize: " + "register_oc failed\n" ); + return code; + } + } + + code = register_supported_control( LDAP_CONTROL_PASSWORDPOLICYREQUEST, + SLAP_CTRL_ADD|SLAP_CTRL_BIND|SLAP_CTRL_MODIFY, extops, + ppolicy_parseCtrl, &ppolicy_cid ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code ); + return code; + } + + code = register_supported_control( LDAP_CONTROL_X_ACCOUNT_USABILITY, + SLAP_CTRL_SEARCH, NULL, + ppolicy_au_parseCtrl, &account_usability_cid ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code ); + return code; + } + + /* We don't expect to receive these controls, only send them */ + code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRED, + 0, NULL, NULL, NULL ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code ); + return code; + } + + code = register_supported_control( LDAP_CONTROL_X_PASSWORD_EXPIRING, + 0, NULL, NULL, NULL ); + if ( code != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", code ); + return code; + } + + ldap_pvt_thread_mutex_init( &chk_syntax_mutex ); + + ppolicy.on_bi.bi_type = "ppolicy"; + ppolicy.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + ppolicy.on_bi.bi_db_init = ppolicy_db_init; + ppolicy.on_bi.bi_db_open = ppolicy_db_open; + ppolicy.on_bi.bi_db_close = ppolicy_db_close; + ppolicy.on_bi.bi_db_destroy = ppolicy_db_destroy; + + ppolicy.on_bi.bi_op_add = ppolicy_add; + ppolicy.on_bi.bi_op_bind = ppolicy_bind; + ppolicy.on_bi.bi_op_compare = ppolicy_compare; + ppolicy.on_bi.bi_op_delete = ppolicy_restrict; + ppolicy.on_bi.bi_op_modify = ppolicy_modify; + ppolicy.on_bi.bi_op_search = ppolicy_search; + ppolicy.on_bi.bi_connection_destroy = ppolicy_connection_destroy; + + ppolicy.on_bi.bi_cf_ocs = ppolicyocs; + code = config_register_schema( ppolicycfg, ppolicyocs ); + if ( code ) return code; + + return overlay_register( &ppolicy ); +} + +#if SLAPD_OVER_PPOLICY == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return ppolicy_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_PPOLICY) */ diff --git a/servers/slapd/overlays/refint.c b/servers/slapd/overlays/refint.c new file mode 100644 index 0000000..75d0360 --- /dev/null +++ b/servers/slapd/overlays/refint.c @@ -0,0 +1,1097 @@ +/* refint.c - referential integrity module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * Portions Copyright 2004 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +/* This module maintains referential integrity for a set of + * DN-valued attributes by searching for all references to a given + * DN whenever the DN is changed or its entry is deleted, and making + * the appropriate update. + * + * Updates are performed using the database rootdn in a separate task + * to allow the original operation to complete immediately. + */ + +#ifdef SLAPD_OVER_REFINT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" +#include "ldap_rq.h" + +static slap_overinst refint; + +/* The DN to use in the ModifiersName for all refint updates */ +static BerValue refint_dn = BER_BVC("cn=Referential Integrity Overlay"); +static BerValue refint_ndn = BER_BVC("cn=referential integrity overlay"); + +typedef struct refint_attrs_s { + struct refint_attrs_s *next; + AttributeDescription *attr; + BerVarray old_vals; + BerVarray old_nvals; + BerVarray new_vals; + BerVarray new_nvals; + int ra_numvals; + int dont_empty; +} refint_attrs; + +typedef struct dependents_s { + struct dependents_s *next; + BerValue dn; /* target dn */ + BerValue ndn; + refint_attrs *attrs; +} dependent_data; + +typedef struct refint_q { + struct refint_q *next; + struct refint_data_s *rdata; + dependent_data *attrs; /* entries and attrs returned from callback */ + BackendDB *db; + BerValue olddn; + BerValue oldndn; + BerValue newdn; + BerValue newndn; + int do_sub; +} refint_q; + +typedef struct refint_data_s { + struct refint_attrs_s *attrs; /* list of known attrs */ + BerValue dn; /* basedn in parent, */ + BerValue nothing; /* the nothing value, if needed */ + BerValue nnothing; /* normalized nothingness */ + BerValue refint_dn; /* modifier's name */ + BerValue refint_ndn; /* normalized modifier's name */ + struct re_s *qtask; + refint_q *qhead; + refint_q *qtail; + BackendDB *db; + ldap_pvt_thread_mutex_t qmutex; +} refint_data; + +typedef struct refint_pre_s { + slap_overinst *on; + int do_sub; +} refint_pre; + +#define RUNQ_INTERVAL 36000 /* a long time */ + +static MatchingRule *mr_dnSubtreeMatch; + +enum { + REFINT_ATTRS = 1, + REFINT_NOTHING, + REFINT_MODIFIERSNAME +}; + +static ConfigDriver refint_cf_gen; + +static ConfigTable refintcfg[] = { + { "refint_attributes", "attribute...", 2, 0, 0, + ARG_MAGIC|REFINT_ATTRS, refint_cf_gen, + "( OLcfgOvAt:11.1 NAME 'olcRefintAttribute' " + "DESC 'Attributes for referential integrity' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "refint_nothing", "string", 2, 2, 0, + ARG_DN|ARG_QUOTE|ARG_MAGIC|REFINT_NOTHING, refint_cf_gen, + "( OLcfgOvAt:11.2 NAME 'olcRefintNothing' " + "DESC 'Replacement DN to supply when needed' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "refint_modifiersName", "DN", 2, 2, 0, + ARG_DN|ARG_QUOTE|ARG_MAGIC|REFINT_MODIFIERSNAME, refint_cf_gen, + "( OLcfgOvAt:11.3 NAME 'olcRefintModifiersName' " + "DESC 'The DN to use as modifiersName' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs refintocs[] = { + { "( OLcfgOvOc:11.1 " + "NAME 'olcRefintConfig' " + "DESC 'Referential integrity configuration' " + "SUP olcOverlayConfig " + "MAY ( olcRefintAttribute " + "$ olcRefintNothing " + "$ olcRefintModifiersName " + ") )", + Cft_Overlay, refintcfg }, + { NULL, 0, NULL } +}; + +static int +refint_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + refint_data *dd = (refint_data *)on->on_bi.bi_private; + refint_attrs *ip, *pip, **pipp = NULL; + AttributeDescription *ad; + const char *text; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch ( c->type ) { + case REFINT_ATTRS: + ip = dd->attrs; + while ( ip ) { + value_add_one( &c->rvalue_vals, + &ip->attr->ad_cname ); + ip = ip->next; + } + rc = 0; + break; + case REFINT_NOTHING: + if ( !BER_BVISEMPTY( &dd->nothing )) { + rc = value_add_one( &c->rvalue_vals, + &dd->nothing ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &dd->nnothing ); + return rc; + } + rc = 0; + break; + case REFINT_MODIFIERSNAME: + if ( !BER_BVISEMPTY( &dd->refint_dn )) { + rc = value_add_one( &c->rvalue_vals, + &dd->refint_dn ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, + &dd->refint_ndn ); + return rc; + } + rc = 0; + break; + default: + abort (); + } + break; + case LDAP_MOD_DELETE: + switch ( c->type ) { + case REFINT_ATTRS: + pipp = &dd->attrs; + if ( c->valx < 0 ) { + ip = *pipp; + *pipp = NULL; + while ( ip ) { + pip = ip; + ip = ip->next; + ch_free ( pip ); + } + } else { + /* delete from linked list */ + for ( i=0; i < c->valx; ++i ) { + pipp = &(*pipp)->next; + } + ip = *pipp; + *pipp = (*pipp)->next; + + /* AttributeDescriptions are global so + * shouldn't be freed here... */ + ch_free ( ip ); + } + rc = 0; + break; + case REFINT_NOTHING: + ch_free( dd->nothing.bv_val ); + ch_free( dd->nnothing.bv_val ); + BER_BVZERO( &dd->nothing ); + BER_BVZERO( &dd->nnothing ); + rc = 0; + break; + case REFINT_MODIFIERSNAME: + ch_free( dd->refint_dn.bv_val ); + ch_free( dd->refint_ndn.bv_val ); + BER_BVZERO( &dd->refint_dn ); + BER_BVZERO( &dd->refint_ndn ); + rc = 0; + break; + default: + abort (); + } + break; + case SLAP_CONFIG_ADD: + /* fallthru to LDAP_MOD_ADD */ + case LDAP_MOD_ADD: + switch ( c->type ) { + case REFINT_ATTRS: + rc = 0; + if ( c->op != SLAP_CONFIG_ADD && c->argc > 2 ) { + /* We wouldn't know how to delete these values later */ + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "Supplying multiple names in a single %s value is " + "unsupported and will be disallowed in a future version\n", + c->argv[0] ); + } + + for ( i=1; i < c->argc; ++i ) { + ad = NULL; + if ( slap_str2ad ( c->argv[i], &ad, &text ) + == LDAP_SUCCESS) { + ip = ch_malloc ( + sizeof ( refint_attrs ) ); + ip->attr = ad; + + for ( pipp = &dd->attrs; *pipp; pipp = &(*pipp)->next ) + /* Get to the end */ ; + ip->next = *pipp; + *pipp = ip; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s", c->argv[0], c->argv[i], text ); + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + } + break; + case REFINT_NOTHING: + if ( !BER_BVISNULL( &c->value_ndn )) { + ch_free ( dd->nothing.bv_val ); + ch_free ( dd->nnothing.bv_val ); + dd->nothing = c->value_dn; + dd->nnothing = c->value_ndn; + rc = 0; + } else { + rc = ARG_BAD_CONF; + } + break; + case REFINT_MODIFIERSNAME: + if ( !BER_BVISNULL( &c->value_ndn )) { + ch_free( dd->refint_dn.bv_val ); + ch_free( dd->refint_ndn.bv_val ); + dd->refint_dn = c->value_dn; + dd->refint_ndn = c->value_ndn; + rc = 0; + } else { + rc = ARG_BAD_CONF; + } + break; + default: + abort (); + } + break; + default: + abort (); + } + + return rc; +} + +/* +** allocate new refint_data; +** store in on_bi.bi_private; +** +*/ + +static int +refint_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + refint_data *id = ch_calloc(1,sizeof(refint_data)); + + on->on_bi.bi_private = id; + ldap_pvt_thread_mutex_init( &id->qmutex ); + return(0); +} + +static int +refint_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + + if ( on->on_bi.bi_private ) { + refint_data *id = on->on_bi.bi_private; + refint_attrs *ii, *ij; + + on->on_bi.bi_private = NULL; + ldap_pvt_thread_mutex_destroy( &id->qmutex ); + + for(ii = id->attrs; ii; ii = ij) { + ij = ii->next; + ch_free(ii); + } + + ch_free( id->nothing.bv_val ); + BER_BVZERO( &id->nothing ); + ch_free( id->nnothing.bv_val ); + BER_BVZERO( &id->nnothing ); + + ch_free( id ); + } + return(0); +} + +/* +** initialize, copy basedn if not already set +** +*/ + +static int +refint_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + refint_data *id = on->on_bi.bi_private; + + if ( BER_BVISNULL( &id->dn )) { + if ( BER_BVISNULL( &be->be_nsuffix[0] )) + return -1; + ber_dupbv( &id->dn, &be->be_nsuffix[0] ); + } + if ( BER_BVISNULL( &id->refint_dn ) ) { + ber_dupbv( &id->refint_dn, &refint_dn ); + ber_dupbv( &id->refint_ndn, &refint_ndn ); + } + + /* + ** find the backend that matches our configured basedn; + ** make sure it exists and has search and modify methods; + ** + */ + + if ( on->on_info->oi_origdb != frontendDB ) { + BackendDB *db = select_backend(&id->dn, 1); + + if ( db ) { + BackendInfo *bi; + if ( db == be ) + bi = on->on_info->oi_orig; + else + bi = db->bd_info; + if ( !bi->bi_op_search || !bi->bi_op_modify ) { + Debug( LDAP_DEBUG_CONFIG, + "refint_response: backend missing search and/or modify\n" ); + return -1; + } + id->db = db; + } else { + Debug( LDAP_DEBUG_CONFIG, + "refint_response: no backend for our baseDN %s??\n", + id->dn.bv_val ); + return -1; + } + } + return(0); +} + + +/* +** free our basedn; +** free our refintdn +** +*/ + +static int +refint_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + refint_data *id = on->on_bi.bi_private; + + ch_free( id->dn.bv_val ); + BER_BVZERO( &id->dn ); + ch_free( id->refint_dn.bv_val ); + BER_BVZERO( &id->refint_dn ); + ch_free( id->refint_ndn.bv_val ); + BER_BVZERO( &id->refint_ndn ); + + return(0); +} + +/* +** search callback +** generates a list of Attributes from search results +*/ + +static int +refint_search_cb( + Operation *op, + SlapReply *rs +) +{ + Attribute *a; + BerVarray b = NULL; + refint_q *rq = op->o_callback->sc_private; + refint_data *dd = rq->rdata; + refint_attrs *ia, *da = dd->attrs, *na; + dependent_data *ip; + int i; + + Debug(LDAP_DEBUG_TRACE, "refint_search_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "NOTHING" ); + + if (rs->sr_type != REP_SEARCH || !rs->sr_entry) return(0); + + /* + ** foreach configured attribute type: + ** if this attr exists in the search result, + ** and it has a value matching the target: + ** allocate an attr; + ** save/build DNs of any subordinate matches; + ** handle special case: found exact + subordinate match; + ** handle olcRefintNothing; + ** + */ + + ip = op->o_tmpalloc(sizeof(dependent_data), op->o_tmpmemctx ); + ber_dupbv_x( &ip->dn, &rs->sr_entry->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &ip->ndn, &rs->sr_entry->e_nname, op->o_tmpmemctx ); + ip->next = rq->attrs; + rq->attrs = ip; + ip->attrs = NULL; + for(ia = da; ia; ia = ia->next) { + if ( (a = attr_find(rs->sr_entry->e_attrs, ia->attr) ) ) { + int exact = -1, is_exact; + + na = NULL; + + /* Are we doing subtree matching or simple equality? */ + if ( rq->do_sub ) { + for(i = 0, b = a->a_nvals; b[i].bv_val; i++) { + if(dnIsSuffix(&b[i], &rq->oldndn)) { + is_exact = b[i].bv_len == rq->oldndn.bv_len; + + /* Paranoia: skip buggy duplicate exact match, + * it would break ra_numvals + */ + if ( is_exact && exact >= 0 ) + continue; + + /* first match? create structure */ + if ( na == NULL ) { + na = op->o_tmpcalloc( 1, + sizeof( refint_attrs ), + op->o_tmpmemctx ); + na->next = ip->attrs; + ip->attrs = na; + na->attr = ia->attr; + } + + na->ra_numvals++; + + if ( is_exact ) { + /* Exact match: refint_repair will deduce the DNs */ + exact = i; + + } else { + /* Subordinate match */ + struct berval newsub, newdn, olddn, oldndn; + + /* Save old DN */ + ber_dupbv_x( &olddn, &a->a_vals[i], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_vals, &olddn, op->o_tmpmemctx ); + + ber_dupbv_x( &oldndn, &a->a_nvals[i], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_nvals, &oldndn, op->o_tmpmemctx ); + + if ( BER_BVISEMPTY( &rq->newdn ) ) + continue; + + /* Rename subordinate match: Build new DN */ + newsub = a->a_vals[i]; + newsub.bv_len -= rq->olddn.bv_len + 1; + build_new_dn( &newdn, &rq->newdn, &newsub, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_vals, &newdn, op->o_tmpmemctx ); + + newsub = a->a_nvals[i]; + newsub.bv_len -= rq->oldndn.bv_len + 1; + build_new_dn( &newdn, &rq->newndn, &newsub, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_nvals, &newdn, op->o_tmpmemctx ); + } + } + } + + /* If we got both subordinate and exact match, + * refint_repair won't special-case the exact match */ + if ( exact >= 0 && na->old_vals ) { + struct berval dn; + + ber_dupbv_x( &dn, &a->a_vals[exact], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_vals, &dn, op->o_tmpmemctx ); + ber_dupbv_x( &dn, &a->a_nvals[exact], op->o_tmpmemctx ); + ber_bvarray_add_x( &na->old_nvals, &dn, op->o_tmpmemctx ); + + if ( !BER_BVISEMPTY( &rq->newdn ) ) { + ber_dupbv_x( &dn, &rq->newdn, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_vals, &dn, op->o_tmpmemctx ); + ber_dupbv_x( &dn, &rq->newndn, op->o_tmpmemctx ); + ber_bvarray_add_x( &na->new_nvals, &dn, op->o_tmpmemctx ); + } + } + } else { + /* entry has no children, just equality matching */ + is_exact = attr_valfind( a, + SLAP_MR_EQUALITY|SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH| + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, &rq->oldndn, &i, NULL ); + if ( is_exact == LDAP_SUCCESS ) { + na = op->o_tmpcalloc( 1, + sizeof( refint_attrs ), + op->o_tmpmemctx ); + na->next = ip->attrs; + ip->attrs = na; + na->attr = ia->attr; + na->ra_numvals = 1; + } + } + + /* Deleting/replacing all values and a nothing DN is configured? */ + if ( na && na->ra_numvals == a->a_numvals && !BER_BVISNULL(&dd->nothing) ) + na->dont_empty = 1; + + Debug( LDAP_DEBUG_TRACE, "refint_search_cb: %s: %s (#%d)\n", + a->a_desc->ad_cname.bv_val, rq->olddn.bv_val, i ); + } + } + + return(0); +} + +static int +refint_repair( + Operation *op, + refint_data *id, + refint_q *rq ) +{ + dependent_data *dp; + SlapReply rs = {REP_RESULT}; + Operation op2; + unsigned long opid; + int rc; + int cache; + + op->o_callback->sc_response = refint_search_cb; + op->o_req_dn = op->o_bd->be_suffix[ 0 ]; + op->o_req_ndn = op->o_bd->be_nsuffix[ 0 ]; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + cache = op->o_do_not_cache; + op->o_do_not_cache = 1; + + /* search */ + rc = op->o_bd->be_search( op, &rs ); + op->o_do_not_cache = cache; + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: search failed: %d\n", + rc ); + return rc; + } + + /* safety? paranoid just in case */ + if ( op->o_callback->sc_private == NULL ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: callback wiped out sc_private?!\n" ); + return 0; + } + + /* Set up the Modify requests */ + op->o_callback->sc_response = &slap_null_cb; + + /* + * [our search callback builds a list of attrs] + * foreach attr: + * make sure its dn has a backend; + * build Modification* chain; + * call the backend modify function; + * + */ + + opid = op->o_opid; + op2 = *op; + for ( dp = rq->attrs; dp; dp = dp->next ) { + SlapReply rs2 = {REP_RESULT}; + refint_attrs *ra; + Modifications *m; + + if ( dp->attrs == NULL ) continue; /* TODO: Is this needed? */ + + op2.o_bd = select_backend( &dp->ndn, 1 ); + if ( !op2.o_bd ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: no backend for DN %s!\n", + dp->dn.bv_val ); + continue; + } + op2.o_tag = LDAP_REQ_MODIFY; + op2.orm_modlist = NULL; + op2.o_req_dn = dp->dn; + op2.o_req_ndn = dp->ndn; + /* Internal ops, never replicate these */ + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + op2.o_opid = 0; + + /* Set our ModifiersName */ + if ( SLAP_LASTMOD( op->o_bd ) ) { + m = op2.o_tmpalloc( sizeof(Modifications) + + 4*sizeof(BerValue), op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_REPLACE; + m->sml_flags = SLAP_MOD_INTERNAL; + m->sml_desc = slap_schema.si_ad_modifiersName; + m->sml_type = m->sml_desc->ad_cname; + m->sml_numvals = 1; + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + m->sml_values[0] = id->refint_dn; + m->sml_nvalues[0] = id->refint_ndn; + } + + for ( ra = dp->attrs; ra; ra = ra->next ) { + size_t len; + + /* Add values */ + if ( ra->dont_empty || !BER_BVISEMPTY( &rq->newdn ) ) { + len = sizeof(Modifications); + + if ( ra->new_vals == NULL ) { + len += 4*sizeof(BerValue); + } + + m = op2.o_tmpalloc( len, op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_ADD; + m->sml_flags = 0; + m->sml_desc = ra->attr; + m->sml_type = ra->attr->ad_cname; + if ( ra->new_vals == NULL ) { + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + m->sml_numvals = 1; + if ( BER_BVISEMPTY( &rq->newdn ) ) { + m->sml_values[0] = id->nothing; + m->sml_nvalues[0] = id->nnothing; + } else { + m->sml_values[0] = rq->newdn; + m->sml_nvalues[0] = rq->newndn; + } + } else { + m->sml_values = ra->new_vals; + m->sml_nvalues = ra->new_nvals; + m->sml_numvals = ra->ra_numvals; + } + } + + /* Delete values */ + len = sizeof(Modifications); + if ( ra->old_vals == NULL ) { + len += 4*sizeof(BerValue); + } + m = op2.o_tmpalloc( len, op2.o_tmpmemctx ); + m->sml_next = op2.orm_modlist; + op2.orm_modlist = m; + m->sml_op = LDAP_MOD_DELETE; + m->sml_flags = 0; + m->sml_desc = ra->attr; + m->sml_type = ra->attr->ad_cname; + if ( ra->old_vals == NULL ) { + m->sml_numvals = 1; + m->sml_values = (BerVarray)(m+1); + m->sml_nvalues = m->sml_values+2; + m->sml_values[0] = rq->olddn; + m->sml_nvalues[0] = rq->oldndn; + BER_BVZERO( &m->sml_values[1] ); + BER_BVZERO( &m->sml_nvalues[1] ); + } else { + m->sml_values = ra->old_vals; + m->sml_nvalues = ra->old_nvals; + m->sml_numvals = ra->ra_numvals; + } + } + + op2.o_dn = op2.o_bd->be_rootdn; + op2.o_ndn = op2.o_bd->be_rootndn; + rc = op2.o_bd->be_modify( &op2, &rs2 ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, + "refint_repair: dependent modify failed: %d\n", + rs2.sr_err ); + } + + while ( ( m = op2.orm_modlist ) ) { + op2.orm_modlist = m->sml_next; + op2.o_tmpfree( m, op2.o_tmpmemctx ); + } + } + op2.o_opid = opid; + + return 0; +} + +static void * +refint_qtask( void *ctx, void *arg ) +{ + struct re_s *rtask = arg; + refint_data *id = rtask->arg; + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + slap_callback cb = { NULL, NULL, NULL, NULL }; + Filter ftop, *fptr; + refint_q *rq; + refint_attrs *ip; + int pausing = 0, rc = 0; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + /* + ** build a search filter for all configured attributes; + ** populate our Operation; + ** pass our data (attr list, dn) to backend via sc_private; + ** call the backend search function; + ** nb: (|(one=thing)) is valid, but do smart formatting anyway; + ** nb: 16 is arbitrarily a dozen or so extra bytes; + ** + */ + + ftop.f_choice = LDAP_FILTER_OR; + ftop.f_next = NULL; + ftop.f_or = NULL; + op->ors_filter = &ftop; + for(ip = id->attrs; ip; ip = ip->next) { + /* this filter can be either EQUALITY or EXT */ + fptr = op->o_tmpcalloc( sizeof(Filter) + sizeof(MatchingRuleAssertion), + 1, op->o_tmpmemctx ); + fptr->f_mra = (MatchingRuleAssertion *)(fptr+1); + fptr->f_mr_rule = mr_dnSubtreeMatch; + fptr->f_mr_rule_text = mr_dnSubtreeMatch->smr_bvoid; + fptr->f_mr_desc = ip->attr; + fptr->f_mr_dnattrs = 0; + fptr->f_next = ftop.f_or; + ftop.f_or = fptr; + } + + for (;;) { + dependent_data *dp, *dp_next; + refint_attrs *ra, *ra_next; + + if ( ldap_pvt_thread_pool_pausing( &connection_pool ) > 0 ) { + pausing = 1; + break; + } + + /* Dequeue an op */ + ldap_pvt_thread_mutex_lock( &id->qmutex ); + rq = id->qhead; + if ( rq ) { + id->qhead = rq->next; + if ( !id->qhead ) + id->qtail = NULL; + } + ldap_pvt_thread_mutex_unlock( &id->qmutex ); + if ( !rq ) + break; + + for (fptr = ftop.f_or; fptr; fptr = fptr->f_next ) { + fptr->f_mr_value = rq->oldndn; + /* Use (attr:dnSubtreeMatch:=value) to catch subtree rename + * and subtree delete where supported */ + if (rq->do_sub) + fptr->f_choice = LDAP_FILTER_EXT; + else + fptr->f_choice = LDAP_FILTER_EQUALITY; + } + + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + + /* callback gets the searched dn instead */ + cb.sc_private = rq; + cb.sc_response = refint_search_cb; + op->o_callback = &cb; + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_limit = NULL; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_tlimit = SLAP_NO_LIMIT; + + /* no attrs! */ + op->ors_attrs = slap_anlist_no_attrs; + + slap_op_time( &op->o_time, &op->o_tincr ); + + if ( rq->db != NULL ) { + op->o_bd = rq->db; + rc = refint_repair( op, id, rq ); + + } else { + BackendDB *be; + + LDAP_STAILQ_FOREACH( be, &backendDB, be_next ) { + /* we may want to skip cn=config */ + if ( be == LDAP_STAILQ_FIRST(&backendDB) ) { + continue; + } + + if ( be->be_search && be->be_modify ) { + op->o_bd = be; + rc = refint_repair( op, id, rq ); + } + } + } + + for ( dp = rq->attrs; dp; dp = dp_next ) { + dp_next = dp->next; + for ( ra = dp->attrs; ra; ra = ra_next ) { + ra_next = ra->next; + ber_bvarray_free_x( ra->new_nvals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->new_vals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->old_nvals, op->o_tmpmemctx ); + ber_bvarray_free_x( ra->old_vals, op->o_tmpmemctx ); + op->o_tmpfree( ra, op->o_tmpmemctx ); + } + op->o_tmpfree( dp->ndn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( dp->dn.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( dp, op->o_tmpmemctx ); + } + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + if ( rc == LDAP_BUSY ) { + pausing = 1; + /* re-queue this op */ + ldap_pvt_thread_mutex_lock( &id->qmutex ); + rq->next = id->qhead; + id->qhead = rq; + if ( !id->qtail ) + id->qtail = rq; + ldap_pvt_thread_mutex_unlock( &id->qmutex ); + break; + } + + if ( !BER_BVISNULL( &rq->newndn )) { + ch_free( rq->newndn.bv_val ); + ch_free( rq->newdn.bv_val ); + } + ch_free( rq->oldndn.bv_val ); + ch_free( rq->olddn.bv_val ); + ch_free( rq ); + } + + /* free filter */ + for ( fptr = ftop.f_or; fptr; ) { + Filter *f_next = fptr->f_next; + op->o_tmpfree( fptr, op->o_tmpmemctx ); + fptr = f_next; + } + + /* wait until we get explicitly scheduled again */ + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, id->qtask ); + if ( pausing ) { + /* try to run again as soon as the pause is done */ + id->qtask->interval.tv_sec = 0; + ldap_pvt_runqueue_resched( &slapd_rq, id->qtask, 0 ); + id->qtask->interval.tv_sec = RUNQ_INTERVAL; + } else { + ldap_pvt_runqueue_resched( &slapd_rq,id->qtask, 1 ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +/* +** refint_response +** search for matching records and modify them +*/ + +static int +refint_response( + Operation *op, + SlapReply *rs +) +{ + refint_pre *rp; + slap_overinst *on; + refint_data *id; + 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" ); + return 1; + } + + /* statically declared just after the #includes at top */ + refint.on_bi.bi_type = "refint"; + refint.on_bi.bi_db_init = refint_db_init; + refint.on_bi.bi_db_destroy = refint_db_destroy; + refint.on_bi.bi_db_open = refint_open; + refint.on_bi.bi_db_close = refint_close; + refint.on_bi.bi_op_delete = refint_preop; + refint.on_bi.bi_op_modrdn = refint_preop; + + refint.on_bi.bi_cf_ocs = refintocs; + rc = config_register_schema ( refintcfg, refintocs ); + if ( rc ) return rc; + + return(overlay_register(&refint)); +} + +#if SLAPD_OVER_REFINT == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return refint_initialize(); +} +#endif + +#endif /* SLAPD_OVER_REFINT */ diff --git a/servers/slapd/overlays/remoteauth.c b/servers/slapd/overlays/remoteauth.c new file mode 100644 index 0000000..87397a1 --- /dev/null +++ b/servers/slapd/overlays/remoteauth.c @@ -0,0 +1,996 @@ +/* $OpenLDAP$ */ +/* remoteauth.c - Overlay to delegate bind processing to a remote server */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * Portions Copyright 2017-2021 OndÅ™ej KuznÃk, Symas Corporation. + * Portions Copyright 2004-2017 Howard Chu, Symas Corporation. + * Portions Copyright 2004 Hewlett-Packard Company. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <ldap.h> +#if SLAPD_MODULES +#define LIBLTDL_DLL_IMPORT /* Win32: don't re-export libltdl's symbols */ +#include <ltdl.h> +#endif +#include <ac/errno.h> +#include <ac/time.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +#ifndef UP_STR +#define UP_STR "userPassword" +#endif /* UP_STR */ + +#ifndef LDAP_PREFIX +#define LDAP_PREFIX "ldap://" +#endif /* LDAP_PREFIX */ + +#ifndef FILE_PREFIX +#define FILE_PREFIX "file://" +#endif /* LDAP_PREFIX */ + +typedef struct _ad_info { + struct _ad_info *next; + char *domain; + char *realm; +} ad_info; + +typedef struct _ad_pin { + struct _ad_pin *next; + char *hostname; + char *pin; +} ad_pin; + +typedef struct _ad_private { + char *dn; + AttributeDescription *dn_ad; + char *domain_attr; + AttributeDescription *domain_ad; + + AttributeDescription *up_ad; + ad_info *mappings; + + char *default_realm; + char *default_domain; + + int up_set; + int retry_count; + int store_on_success; + + ad_pin *pins; + slap_bindconf ad_tls; +} ad_private; + +enum { + REMOTE_AUTH_MAPPING = 1, + REMOTE_AUTH_DN_ATTRIBUTE, + REMOTE_AUTH_DOMAIN_ATTRIBUTE, + REMOTE_AUTH_DEFAULT_DOMAIN, + REMOTE_AUTH_DEFAULT_REALM, + REMOTE_AUTH_CACERT_DIR, + REMOTE_AUTH_CACERT_FILE, + REMOTE_AUTH_VALIDATE_CERTS, + REMOTE_AUTH_RETRY_COUNT, + REMOTE_AUTH_TLS, + REMOTE_AUTH_TLS_PIN, + REMOTE_AUTH_STORE_ON_SUCCESS, +}; + +static ConfigDriver remoteauth_cf_gen; + +static ConfigTable remoteauthcfg[] = { + { "remoteauth_mapping", "mapping between domain and realm", 2, 3, 0, + ARG_MAGIC|REMOTE_AUTH_MAPPING, + remoteauth_cf_gen, + "( OLcfgOvAt:24.1 NAME 'olcRemoteAuthMapping' " + "DESC 'Mapping from domain name to server' " + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, + { "remoteauth_dn_attribute", "Attribute to use as AD bind DN", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DN_ATTRIBUTE, + remoteauth_cf_gen, + "( OLcfgOvAt:24.2 NAME 'olcRemoteAuthDNAttribute' " + "DESC 'Attribute in entry to use as bind DN for AD' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_domain_attribute", "Attribute to use as domain determinant", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DOMAIN_ATTRIBUTE, + remoteauth_cf_gen, + "( OLcfgOvAt:24.3 NAME 'olcRemoteAuthDomainAttribute' " + "DESC 'Attribute in entry to determine windows domain' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_default_domain", "Default Windows domain", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DEFAULT_DOMAIN, + remoteauth_cf_gen, + "( OLcfgOvAt:24.4 NAME 'olcRemoteAuthDefaultDomain' " + "DESC 'Default Windows domain to use' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_default_realm", "Default AD realm", 2, 2, 0, + ARG_MAGIC|REMOTE_AUTH_DEFAULT_REALM, + remoteauth_cf_gen, + "( OLcfgOvAt:24.5 NAME 'olcRemoteAuthDefaultRealm' " + "DESC 'Default AD realm to use' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_store", "on|off", 1, 2, 0, + ARG_OFFSET|ARG_ON_OFF|REMOTE_AUTH_STORE_ON_SUCCESS, + (void *)offsetof(ad_private, store_on_success), + "( OLcfgOvAt:24.6 NAME 'olcRemoteAuthStore' " + "DESC 'Store password locally on success' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_retry_count", "integer", 2, 2, 0, + ARG_OFFSET|ARG_UINT|REMOTE_AUTH_RETRY_COUNT, + (void *)offsetof(ad_private, retry_count), + "( OLcfgOvAt:24.7 NAME 'olcRemoteAuthRetryCount' " + "DESC 'Number of retries attempted' " + "SYNTAX OMsInteger SINGLE-VALUE )", + NULL, { .v_uint = 3 } + }, + { "remoteauth_tls", "tls settings", 2, 0, 0, + ARG_MAGIC|REMOTE_AUTH_TLS, + remoteauth_cf_gen, + "( OLcfgOvAt:24.8 NAME 'olcRemoteAuthTLS' " + "DESC 'StartTLS settings' " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL + }, + { "remoteauth_tls_peerkey_hash", "mapping between hostnames and their public key hash", 3, 3, 0, + ARG_MAGIC|REMOTE_AUTH_TLS_PIN, + remoteauth_cf_gen, + "( OLcfgOvAt:24.9 NAME 'olcRemoteAuthTLSPeerkeyHash' " + "DESC 'StartTLS hostname to public key pin mapping file' " + "SYNTAX OMsDirectoryString )", + NULL, NULL + }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED, NULL } +}; + +static ConfigOCs remoteauthocs[] = { + { "( OLcfgOvOc:24.1 " + "NAME 'olcRemoteAuthCfg' " + "DESC 'Remote Directory passthough authentication configuration' " + "SUP olcOverlayConfig " + "MUST olcRemoteAuthTLS " + "MAY ( olcRemoteAuthMapping $ olcRemoteAuthDNAttribute $ " + " olcRemoteAuthDomainAttribute $ olcRemoteAuthDefaultDomain $ " + " olcRemoteAuthDefaultRealm $ olcRemoteAuthStore $ " + " olcRemoteAuthRetryCount $ olcRemoteAuthTLSPeerkeyHash ) )", + Cft_Overlay, remoteauthcfg }, + { NULL, 0, NULL } +}; + +static int +remoteauth_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + ad_private *ad = (ad_private *)on->on_bi.bi_private; + struct berval bv; + int i, rc = 0; + ad_info *map; + const char *text = NULL; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + for ( map = ad->mappings; map; map = map->next ) { + char *str; + + str = ch_malloc( strlen( map->domain ) + + strlen( map->realm ) + 2 ); + sprintf( str, "%s %s", map->domain, map->realm ); + ber_str2bv( str, strlen( str ), 1, &bv ); + ch_free( str ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, &bv ); + if ( rc ) return rc; + } + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( ad->dn ) + value_add_one( &c->rvalue_vals, &ad->dn_ad->ad_cname ); + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( ad->domain_attr ) + value_add_one( + &c->rvalue_vals, &ad->domain_ad->ad_cname ); + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ber_str2bv( ad->default_domain, 0, 1, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ber_str2bv( ad->default_realm, 0, 1, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + case REMOTE_AUTH_TLS: + bindconf_tls_unparse( &ad->ad_tls, &bv ); + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + value_add_one( &c->rvalue_vals, &bv ); + break; + case REMOTE_AUTH_TLS_PIN: { + ad_pin *pin = ad->pins; + for ( pin = ad->pins; pin; pin = pin->next ) { + bv.bv_val = ch_malloc( strlen( pin->hostname ) + + strlen( pin->pin ) + 2 ); + bv.bv_len = sprintf( + bv.bv_val, "%s %s", pin->hostname, pin->pin ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc ) return rc; + rc = value_add_one( &c->rvalue_nvals, &bv ); + if ( rc ) return rc; + } + } break; + + default: + abort(); + } + break; + case LDAP_MOD_DELETE: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + if ( c->valx < 0 ) { + /* delete all mappings */ + while ( ad->mappings ) { + map = ad->mappings; + ad->mappings = ad->mappings->next; + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } + } else { + /* delete a specific mapping indicated by 'valx'*/ + ad_info *pmap = NULL; + + for ( map = ad->mappings, i = 0; + ( map ) && ( i < c->valx ); + pmap = map, map = map->next, i++ ) + ; + + if ( pmap ) { + pmap->next = map->next; + map->next = NULL; + + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } else if ( ad->mappings ) { + /* delete the first item in the list */ + map = ad->mappings; + ad->mappings = map->next; + ch_free( map->domain ); + ch_free( map->realm ); + ch_free( map ); + } + } + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( ad->dn ) { + ch_free( ad->dn ); + ad->dn = NULL; /* Don't free AttributeDescription */ + } + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( ad->domain_attr ) { + ch_free( ad->domain_attr ); + /* Don't free AttributeDescription */ + ad->domain_attr = NULL; + } + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ch_free( ad->default_domain ); + ad->default_domain = NULL; + } + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ch_free( ad->default_realm ); + ad->default_realm = NULL; + } + break; + case REMOTE_AUTH_TLS: + /* MUST + SINGLE-VALUE -> this is a replace */ + bindconf_free( &ad->ad_tls ); + break; + case REMOTE_AUTH_TLS_PIN: + while ( ad->pins ) { + ad_pin *pin = ad->pins; + ad->pins = ad->pins->next; + ch_free( pin->hostname ); + ch_free( pin->pin ); + ch_free( pin ); + } + break; + /* ARG_OFFSET */ + case REMOTE_AUTH_STORE_ON_SUCCESS: + case REMOTE_AUTH_RETRY_COUNT: + abort(); + break; + default: + abort(); + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch ( c->type ) { + case REMOTE_AUTH_MAPPING: + /* add mapping to head of list */ + map = ch_malloc( sizeof(ad_info) ); + map->domain = ber_strdup( c->argv[1] ); + map->realm = ber_strdup( c->argv[2] ); + map->next = ad->mappings; + ad->mappings = map; + + break; + case REMOTE_AUTH_DN_ATTRIBUTE: + if ( slap_str2ad( c->argv[1], &ad->dn_ad, &text ) == + LDAP_SUCCESS ) { + ad->dn = ber_strdup( ad->dn_ad->ad_cname.bv_val ); + } else { + strncpy( c->cr_msg, text, sizeof(c->cr_msg) ); + c->cr_msg[sizeof(c->cr_msg) - 1] = '\0'; + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + break; + case REMOTE_AUTH_DOMAIN_ATTRIBUTE: + if ( slap_str2ad( c->argv[1], &ad->domain_ad, &text ) == + LDAP_SUCCESS ) { + ad->domain_attr = + ber_strdup( ad->domain_ad->ad_cname.bv_val ); + } else { + strncpy( c->cr_msg, text, sizeof(c->cr_msg) ); + c->cr_msg[sizeof(c->cr_msg) - 1] = '\0'; + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + break; + case REMOTE_AUTH_DEFAULT_DOMAIN: + if ( ad->default_domain ) { + ch_free( ad->default_domain ); + ad->default_domain = NULL; + } + ad->default_domain = ber_strdup( c->argv[1] ); + break; + case REMOTE_AUTH_DEFAULT_REALM: + if ( ad->default_realm ) { + ch_free( ad->default_realm ); + ad->default_realm = NULL; + } + ad->default_realm = ber_strdup( c->argv[1] ); + break; + case REMOTE_AUTH_TLS: + for ( i=1; i < c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &ad->ad_tls ) ) { + rc = 1; + break; + } + } + bindconf_tls_defaults( &ad->ad_tls ); + break; + case REMOTE_AUTH_TLS_PIN: { + ad_pin *pin = ch_calloc( 1, sizeof(ad_pin) ); + + pin->hostname = ber_strdup( c->argv[1] ); + pin->pin = ber_strdup( c->argv[2] ); + pin->next = ad->pins; + ad->pins = pin; + } break; + /* ARG_OFFSET */ + case REMOTE_AUTH_STORE_ON_SUCCESS: + case REMOTE_AUTH_RETRY_COUNT: + abort(); + break; + default: + abort(); + } + break; + default: + abort(); + } + + return rc; +} + +static char * +get_realm( + const char *domain, + ad_info *mappings, + const char *default_realm, + int *isfile ) +{ + ad_info *ai; + char *dom = NULL, *ch, *ret = NULL; + + if ( isfile ) *isfile = 0; + + if ( !domain ) { + ret = default_realm ? ch_strdup( default_realm ) : NULL; + goto exit; + } + + /* munge any DOMAIN\user or DOMAIN:user values into just DOMAIN */ + + ch = strchr( domain, '\\' ); + if ( !ch ) ch = strchr( domain, ':' ); + + if ( ch ) { + dom = ch_malloc( ch - domain + 1 ); + strncpy( dom, domain, ch - domain ); + dom[ch - domain] = '\0'; + } else { + dom = ch_strdup( domain ); + } + + for ( ai = mappings; ai; ai = ai->next ) + if ( strcasecmp( ai->domain, dom ) == 0 ) { + ret = ch_strdup( ai->realm ); + break; + } + + if ( !ai ) + ret = default_realm ? ch_strdup( default_realm ) : + NULL; /* no mapping found */ +exit: + if ( dom ) ch_free( dom ); + if ( ret && + ( strncasecmp( ret, FILE_PREFIX, strlen( FILE_PREFIX ) ) == 0 ) ) { + char *p; + + p = ret; + ret = ch_strdup( p + strlen( FILE_PREFIX ) ); + ch_free( p ); + if ( isfile ) *isfile = 1; + } + + return ret; +} + +static char * +get_ldap_url( const char *realm, int isfile ) +{ + char *ldap_url = NULL; + FILE *fp; + + if ( !realm ) return NULL; + + if ( !isfile ) { + if ( strstr( realm, "://" ) ) { + return ch_strdup( realm ); + } + + ldap_url = ch_malloc( 1 + strlen( LDAP_PREFIX ) + strlen( realm ) ); + sprintf( ldap_url, "%s%s", LDAP_PREFIX, realm ); + return ldap_url; + } + + fp = fopen( realm, "r" ); + if ( !fp ) { + char ebuf[128]; + int saved_errno = errno; + Debug( LDAP_DEBUG_TRACE, "remoteauth: " + "Unable to open realm file (%s)\n", + sock_errstr( saved_errno, ebuf, sizeof(ebuf) ) ); + return NULL; + } + /* + * Read each line in the file and return a URL of the form + * "ldap://<line1> ldap://<line2> ... ldap://<lineN>" + * which can be passed to ldap_initialize. + */ + while ( !feof( fp ) ) { + char line[512], *p; + + p = fgets( line, sizeof(line), fp ); + if ( !p ) continue; + + /* terminate line at first whitespace */ + for ( p = line; *p; p++ ) + if ( isspace( *p ) ) { + *p = '\0'; + break; + } + + if ( ldap_url ) { + char *nu; + + nu = ch_malloc( strlen( ldap_url ) + 2 + strlen( LDAP_PREFIX ) + + strlen( line ) ); + + if ( strstr( line, "://" ) ) { + sprintf( nu, "%s %s", ldap_url, line ); + } else { + sprintf( nu, "%s %s%s", ldap_url, LDAP_PREFIX, line ); + } + ch_free( ldap_url ); + ldap_url = nu; + } else { + ldap_url = ch_malloc( 1 + strlen( line ) + strlen( LDAP_PREFIX ) ); + if ( strstr( line, "://" ) ) { + strcpy( ldap_url, line ); + } else { + sprintf( ldap_url, "%s%s", LDAP_PREFIX, line ); + } + } + } + + fclose( fp ); + + return ldap_url; +} + +static void +trace_remoteauth_parameters( ad_private *ap ) +{ + ad_info *pad_info; + struct berval bv; + + if ( !ap ) return; + + Debug( LDAP_DEBUG_TRACE, "remoteauth_dn_attribute: %s\n", + ap->dn ? ap->dn : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_domain_attribute: %s\n", + ap->domain_attr ? ap->domain_attr : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_default_realm: %s\n", + ap->default_realm ? ap->default_realm : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_default_domain: %s\n", + ap->default_domain ? ap->default_domain : "NULL" ); + + Debug( LDAP_DEBUG_TRACE, "remoteauth_retry_count: %d\n", ap->retry_count ); + + bindconf_tls_unparse( &ap->ad_tls, &bv ); + Debug( LDAP_DEBUG_TRACE, "remoteauth_tls:%s\n", bv.bv_val ); + ch_free( bv.bv_val ); + + pad_info = ap->mappings; + while ( pad_info ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_mappings(%s,%s)\n", + pad_info->domain ? pad_info->domain : "NULL", + pad_info->realm ? pad_info->realm : "NULL" ); + pad_info = pad_info->next; + } + + return; +} + +static int +remoteauth_conn_cb( + LDAP *ld, + Sockbuf *sb, + LDAPURLDesc *srv, + struct sockaddr *addr, + struct ldap_conncb *ctx ) +{ + ad_private *ap = ctx->lc_arg; + ad_pin *pin = NULL; + char *host; + + host = srv->lud_host; + if ( !host || !*host ) { + host = "localhost"; + } + + for ( pin = ap->pins; pin; pin = pin->next ) { + if ( !strcasecmp( host, pin->hostname ) ) break; + } + + if ( pin ) { + int rc = ldap_set_option( ld, LDAP_OPT_X_TLS_PEERKEY_HASH, pin->pin ); + if ( rc == LDAP_SUCCESS ) { + return 0; + } + + Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: " + "TLS Peerkey hash could not be set to '%s': %d\n", + pin->pin, rc ); + } else { + Debug( LDAP_DEBUG_TRACE, "remoteauth_conn_cb: " + "No TLS Peerkey hash found for host '%s'\n", + host ); + } + + return -1; +} + +static void +remoteauth_conn_delcb( LDAP *ld, Sockbuf *sb, struct ldap_conncb *ctx ) +{ + return; +} + +static int +remoteauth_bind( Operation *op, SlapReply *rs ) +{ + Entry *e; + int rc; + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + ad_private *ap = (ad_private *)on->on_bi.bi_private; + Attribute *a_dom, *a_dn; + struct ldap_conncb ad_conncb = { .lc_add = remoteauth_conn_cb, + .lc_del = remoteauth_conn_delcb, + .lc_arg = ap }; + struct berval dn = { 0 }; + char *dom_val, *realm = NULL; + char *ldap_url = NULL; + LDAP *ld = NULL; + int protocol = LDAP_VERSION3, isfile = 0; + int tries = 0; + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + trace_remoteauth_parameters( ap ); + } + + if ( op->orb_method != LDAP_AUTH_SIMPLE ) + return SLAP_CB_CONTINUE; /* only do password auth */ + + /* Can't handle root via this mechanism */ + if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) return SLAP_CB_CONTINUE; + + if ( !ap->up_set ) { + const char *txt = NULL; + + if ( slap_str2ad( UP_STR, &ap->up_ad, &txt ) ) + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "userPassword attr undefined: %s\n", + txt ? txt : "" ); + ap->up_set = 1; + } + + if ( !ap->up_ad ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "password attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* userPassword not defined */ + } + + if ( !ap->dn ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "remote DN attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* no mapped DN attribute */ + } + + if ( !ap->domain_attr ) { + Debug( LDAP_DEBUG_TRACE, "remoteauth_bind: " + "domain attribute not configured\n" ); + return SLAP_CB_CONTINUE; /* no way to know domain */ + } + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( rc != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + rc = SLAP_CB_CONTINUE; + /* if userPassword is defined in entry, skip to the end */ + if ( attr_find( e->e_attrs, ap->up_ad ) ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "user has a password, skipping\n", + op->o_log_prefix ); + goto exit; + } + + a_dom = attr_find( e->e_attrs, ap->domain_ad ); + if ( !a_dom ) + dom_val = ap->default_domain; + else { + dom_val = a_dom->a_vals[0].bv_val; + } + + if ( !dom_val ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "user has no domain nor do we have a default, skipping\n", + op->o_log_prefix ); + goto exit; /* user has no domain */ + } + + realm = get_realm( dom_val, ap->mappings, ap->default_realm, &isfile ); + if ( !realm ) goto exit; + + a_dn = attr_find( e->e_attrs, ap->dn_ad ); + if ( !a_dn ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "no remote DN found on user\n", + op->o_log_prefix ); + goto exit; /* user has no DN for the other directory */ + } + + ber_dupbv_x( &dn, a_dn->a_vals, op->o_tmpmemctx ); + be_entry_release_r( op, e ); + e = NULL; + + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "(realm, dn) = (%s, %s)\n", + op->o_log_prefix, realm, dn.bv_val ); + + ldap_url = get_ldap_url( realm, isfile ); + if ( !ldap_url ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "No LDAP URL obtained\n", + op->o_log_prefix ); + goto exit; + } + +retry: + rc = ldap_initialize( &ld, ldap_url ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "Cannot initialize %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; /* user has no DN for the other directory */ + } + + ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &protocol ); + +#ifdef HAVE_TLS + rc = bindconf_tls_set( &ap->ad_tls, ld ); + if ( rc ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "bindconf_tls_set failed\n", + op->o_log_prefix ); + goto exit; + } + + if ( ap->pins ) { + if ( (rc = ldap_set_option( ld, LDAP_OPT_CONNECT_CB, &ad_conncb )) != + LDAP_SUCCESS ) { + goto exit; + } + } + + if ( (rc = ldap_connect( ld )) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "Cannot connect to %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; + } + + if ( ap->ad_tls.sb_tls && !ldap_tls_inplace( ld ) ) { + if ( (rc = ldap_start_tls_s( ld, NULL, NULL )) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "LDAP TLS failed %s: %s\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ) ); + goto exit; + } + } + +#endif /* HAVE_TLS */ + + rc = ldap_sasl_bind_s( ld, dn.bv_val, LDAP_SASL_SIMPLE, + &op->oq_bind.rb_cred, NULL, NULL, NULL ); + if ( rc == LDAP_SUCCESS ) { + if ( ap->store_on_success ) { + const char *txt; + + Operation op2 = *op; + SlapReply r2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Modifications m = {}; + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_callback = &cb; + op2.orm_modlist = &m; + op2.orm_no_opattrs = 0; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + m.sml_op = LDAP_MOD_ADD; + m.sml_flags = 0; + m.sml_next = NULL; + m.sml_type = ap->up_ad->ad_cname; + m.sml_desc = ap->up_ad; + m.sml_numvals = 1; + m.sml_values = op->o_tmpcalloc( + sizeof(struct berval), 2, op->o_tmpmemctx ); + + slap_passwd_hash( &op->oq_bind.rb_cred, &m.sml_values[0], &txt ); + if ( m.sml_values[0].bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: " + "password hashing for '%s' failed, storing password in " + "plain text\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + ber_dupbv( &m.sml_values[0], &op->oq_bind.rb_cred ); + } + + /* + * If this server is a shadow use the frontend to perform this + * modify. That will trigger the update referral, which can then be + * forwarded by the chain overlay. Obviously the updateref and + * chain overlay must be configured appropriately for this to be + * useful. + */ + if ( SLAP_SHADOW(op->o_bd) ) { + op2.o_bd = frontendDB; + } else { + op2.o_bd->bd_info = (BackendInfo *)on->on_info; + } + + if ( op2.o_bd->be_modify( &op2, &r2 ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s remoteauth_bind: " + "attempt to store password in entry '%s' failed, " + "ignoring\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + } + ch_free( m.sml_values[0].bv_val ); + } + goto exit; + } + + if ( rc == LDAP_INVALID_CREDENTIALS ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "ldap_sasl_bind_s (%s) failed: invalid credentials\n", + op->o_log_prefix, ldap_url ); + goto exit; + } + + if ( tries < ap->retry_count ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "ldap_sasl_bind_s failed %s: %s (try #%d)\n", + op->o_log_prefix, ldap_url, ldap_err2string( rc ), tries ); + if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL ); + tries++; + goto retry; + } else + goto exit; + +exit: + if ( dn.bv_val ) { + op->o_tmpfree( dn.bv_val, op->o_tmpmemctx ); + } + if ( e ) { + be_entry_release_r( op, e ); + } + if ( ld ) ldap_unbind_ext_s( ld, NULL, NULL ); + if ( ldap_url ) ch_free( ldap_url ); + if ( realm ) ch_free( realm ); + if ( rc == SLAP_CB_CONTINUE ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "continue\n", op->o_log_prefix ); + return rc; + } else { + /* for rc == 0, frontend sends result */ + if ( rc ) { + if ( rc > 0 ) { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "failed\n", op->o_log_prefix ); + send_ldap_error( op, rs, rc, "remoteauth_bind failed" ); + } else { + Debug( LDAP_DEBUG_TRACE, "%s remoteauth_bind: " + "operations error\n", op->o_log_prefix ); + send_ldap_error( op, rs, LDAP_OPERATIONS_ERROR, + "remoteauth_bind operations error" ); + } + } + + return rs->sr_err; + } +} + +static int +remoteauth_db_init( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ad_private *ap; + + if ( SLAP_ISGLOBALOVERLAY(be) ) { + Debug( LDAP_DEBUG_ANY, "remoteauth_db_init: " + "remoteauth overlay must be instantiated within a " + "database.\n" ); + return 1; + } + + ap = ch_calloc( 1, sizeof(ad_private) ); + + ap->dn = NULL; + ap->dn_ad = NULL; + ap->domain_attr = NULL; + ap->domain_ad = NULL; + + ap->up_ad = NULL; + ap->mappings = NULL; + + ap->default_realm = NULL; + ap->default_domain = NULL; + + ap->pins = NULL; + + ap->up_set = 0; + ap->retry_count = 3; + + on->on_bi.bi_private = ap; + + return LDAP_SUCCESS; +} + +static int +remoteauth_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + ad_private *ap = (ad_private *)on->on_bi.bi_private; + ad_info *ai = ap->mappings; + + while ( ai ) { + if ( ai->domain ) ch_free( ai->domain ); + if ( ai->realm ) ch_free( ai->realm ); + ai = ai->next; + } + + if ( ap->dn ) ch_free( ap->dn ); + if ( ap->default_domain ) ch_free( ap->default_domain ); + if ( ap->default_realm ) ch_free( ap->default_realm ); + + bindconf_free( &ap->ad_tls ); + + ch_free( ap ); + + return 0; +} + +static slap_overinst remoteauth; + +int +remoteauth_initialize( void ) +{ + int rc; + + remoteauth.on_bi.bi_type = "remoteauth"; + remoteauth.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + + remoteauth.on_bi.bi_cf_ocs = remoteauthocs; + rc = config_register_schema( remoteauthcfg, remoteauthocs ); + if ( rc ) return rc; + + remoteauth.on_bi.bi_db_init = remoteauth_db_init; + remoteauth.on_bi.bi_db_destroy = remoteauth_db_destroy; + remoteauth.on_bi.bi_op_bind = remoteauth_bind; + + return overlay_register( &remoteauth ); +} + +#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return remoteauth_initialize(); +} +#endif diff --git a/servers/slapd/overlays/retcode.c b/servers/slapd/overlays/retcode.c new file mode 100644 index 0000000..ac57146 --- /dev/null +++ b/servers/slapd/overlays/retcode.c @@ -0,0 +1,1578 @@ +/* retcode.c - customizable response for client testing purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions Copyright 2005 Pierangelo Masarati <ando@sys-net.it> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RETCODE + +#include <stdio.h> + +#include <ac/unistd.h> +#include <ac/string.h> +#include <ac/ctype.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" +#include "ldif.h" + +static slap_overinst retcode; + +static AttributeDescription *ad_errCode; +static AttributeDescription *ad_errText; +static AttributeDescription *ad_errOp; +static AttributeDescription *ad_errSleepTime; +static AttributeDescription *ad_errMatchedDN; +static AttributeDescription *ad_errUnsolicitedOID; +static AttributeDescription *ad_errUnsolicitedData; +static AttributeDescription *ad_errDisconnect; + +static ObjectClass *oc_errAbsObject; +static ObjectClass *oc_errObject; +static ObjectClass *oc_errAuxObject; + +typedef enum retcode_op_e { + SN_DG_OP_NONE = 0x0000, + SN_DG_OP_ADD = 0x0001, + SN_DG_OP_BIND = 0x0002, + SN_DG_OP_COMPARE = 0x0004, + SN_DG_OP_DELETE = 0x0008, + SN_DG_OP_MODIFY = 0x0010, + SN_DG_OP_RENAME = 0x0020, + SN_DG_OP_SEARCH = 0x0040, + SN_DG_EXTENDED = 0x0080, + SN_DG_OP_AUTH = SN_DG_OP_BIND, + SN_DG_OP_READ = (SN_DG_OP_COMPARE|SN_DG_OP_SEARCH), + SN_DG_OP_WRITE = (SN_DG_OP_ADD|SN_DG_OP_DELETE|SN_DG_OP_MODIFY|SN_DG_OP_RENAME), + SN_DG_OP_ALL = (SN_DG_OP_AUTH|SN_DG_OP_READ|SN_DG_OP_WRITE|SN_DG_EXTENDED) +} retcode_op_e; + +typedef struct retcode_item_t { + struct berval rdi_line; + struct berval rdi_dn; + struct berval rdi_ndn; + struct berval rdi_text; + struct berval rdi_matched; + int rdi_err; + BerVarray rdi_ref; + int rdi_sleeptime; + Entry rdi_e; + slap_mask_t rdi_mask; + struct berval rdi_unsolicited_oid; + struct berval rdi_unsolicited_data; + + unsigned rdi_flags; +#define RDI_PRE_DISCONNECT (0x1U) +#define RDI_POST_DISCONNECT (0x2U) + + struct retcode_item_t *rdi_next; +} retcode_item_t; + +typedef struct retcode_t { + struct berval rd_pdn; + struct berval rd_npdn; + + int rd_sleep; + + retcode_item_t *rd_item; + + int rd_indir; +#define RETCODE_FINDIR 0x01 +#define RETCODE_INDIR( rd ) ( (rd)->rd_indir ) +} retcode_t; + +static int +retcode_entry_response( Operation *op, SlapReply *rs, BackendInfo *bi, Entry *e ); + +static unsigned int +retcode_sleep( int s ) +{ + unsigned int r = 0; + + /* sleep as required */ + if ( s < 0 ) { +#if 0 /* use high-order bits for better randomness (Numerical Recipes in "C") */ + r = rand() % (-s); +#endif + r = ((double)(-s))*rand()/(RAND_MAX + 1.0); + } else if ( s > 0 ) { + r = (unsigned int)s; + } + if ( r ) { + sleep( r ); + } + + return r; +} + +static int +retcode_cleanup_cb( Operation *op, SlapReply *rs ) +{ + rs->sr_matched = NULL; + rs->sr_text = NULL; + + if ( rs->sr_ref != NULL ) { + ber_bvarray_free( rs->sr_ref ); + rs->sr_ref = NULL; + } + + ch_free( op->o_callback ); + op->o_callback = NULL; + + return SLAP_CB_CONTINUE; +} + +static int +retcode_send_onelevel( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + retcode_item_t *rdi; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = rdi->rdi_next ) { + if ( op->o_abandon ) { + return rs->sr_err = SLAPD_ABANDON; + } + + rs->sr_err = test_filter( op, &rdi->rdi_e, op->ors_filter ); + if ( rs->sr_err == LDAP_COMPARE_TRUE ) { + /* safe default */ + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_ctrls = NULL; + rs->sr_flags = 0; + rs->sr_err = LDAP_SUCCESS; + rs->sr_entry = &rdi->rdi_e; + + rs->sr_err = send_search_entry( op, rs ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + + switch ( rs->sr_err ) { + case LDAP_UNAVAILABLE: /* connection closed */ + rs->sr_err = LDAP_OTHER; + /* fallthru */ + case LDAP_SIZELIMIT_EXCEEDED: + goto done; + } + } + rs->sr_err = LDAP_SUCCESS; + } + +done:; + + send_ldap_result( op, rs ); + + return rs->sr_err; +} + +static int +retcode_op_add( Operation *op, SlapReply *rs ) +{ + return retcode_entry_response( op, rs, NULL, op->ora_e ); +} + +typedef struct retcode_cb_t { + BackendInfo *rdc_info; + unsigned rdc_flags; + ber_tag_t rdc_tag; + AttributeName *rdc_attrs; +} retcode_cb_t; + +static int +retcode_cb_response( Operation *op, SlapReply *rs ) +{ + retcode_cb_t *rdc = (retcode_cb_t *)op->o_callback->sc_private; + + op->o_tag = rdc->rdc_tag; + if ( rs->sr_type == REP_SEARCH ) { + ber_tag_t o_tag = op->o_tag; + int rc; + + if ( op->o_tag == LDAP_REQ_SEARCH ) { + rs->sr_attrs = rdc->rdc_attrs; + } + rc = retcode_entry_response( op, rs, rdc->rdc_info, rs->sr_entry ); + op->o_tag = o_tag; + + return rc; + } + + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + case LDAP_NO_SUCH_OBJECT: + /* in case of noSuchObject, stop the internal search + * for in-directory error stuff */ + if ( !op->o_abandon ) { + rdc->rdc_flags = SLAP_CB_CONTINUE; + } + return 0; + } + + return SLAP_CB_CONTINUE; +} + +static int +retcode_op_internal( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + + Operation op2 = *op; + BackendDB db = *op->o_bd; + slap_callback sc = { 0 }; + retcode_cb_t rdc; + + int rc; + + op2.o_tag = LDAP_REQ_SEARCH; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_tlimit = SLAP_NO_LIMIT; + op2.ors_slimit = SLAP_NO_LIMIT; + op2.ors_limit = NULL; + op2.ors_attrsonly = 0; + op2.ors_attrs = slap_anlist_all_attributes; + + ber_str2bv_x( "(objectClass=errAbsObject)", + STRLENOF( "(objectClass=errAbsObject)" ), + 1, &op2.ors_filterstr, op2.o_tmpmemctx ); + op2.ors_filter = str2filter_x( &op2, op2.ors_filterstr.bv_val ); + + /* errAbsObject is defined by this overlay! */ + assert( op2.ors_filter != NULL ); + + db.bd_info = on->on_info->oi_orig; + op2.o_bd = &db; + + rdc.rdc_info = on->on_info->oi_orig; + rdc.rdc_flags = RETCODE_FINDIR; + if ( op->o_tag == LDAP_REQ_SEARCH ) { + rdc.rdc_attrs = op->ors_attrs; + } + rdc.rdc_tag = op->o_tag; + sc.sc_response = retcode_cb_response; + sc.sc_private = &rdc; + op2.o_callback = ≻ + + 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|ARG_QUOTE|RC_PARENT, rc_cf_gen, + "( OLcfgOvAt:20.1 NAME 'olcRetcodeParent' " + "DESC '' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "retcode-item", "rdn> <retcode> <...", + 3, 0, 0, ARG_MAGIC|RC_ITEM, rc_cf_gen, + "( OLcfgOvAt:20.2 NAME 'olcRetcodeItem' " + "DESC '' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", NULL, NULL }, + { "retcode-indir", "on|off", + 1, 2, 0, ARG_OFFSET|ARG_ON_OFF, + (void *)offsetof(retcode_t, rd_indir), + "( OLcfgOvAt:20.3 NAME 'olcRetcodeInDir' " + "DESC '' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + + { "retcode-sleep", "sleeptime", + 2, 2, 0, ARG_OFFSET|ARG_INT, + (void *)offsetof(retcode_t, rd_sleep), + "( OLcfgOvAt:20.4 NAME 'olcRetcodeSleep' " + "DESC '' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs rcocs[] = { + { "( OLcfgOvOc:20.1 " + "NAME 'olcRetcodeConfig' " + "DESC 'Retcode configuration' " + "SUP olcOverlayConfig " + "MAY ( olcRetcodeParent " + "$ olcRetcodeItem " + "$ olcRetcodeInDir " + "$ olcRetcodeSleep " + ") )", + Cft_Overlay, rccfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int +rc_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch( c->type ) { + case RC_PARENT: + if ( !BER_BVISEMPTY( &rd->rd_pdn )) { + rc = value_add_one( &c->rvalue_vals, + &rd->rd_pdn ); + if ( rc == 0 ) { + rc = value_add_one( &c->rvalue_nvals, + &rd->rd_npdn ); + } + return rc; + } + rc = 0; + break; + + case RC_ITEM: { + retcode_item_t *rdi; + int i; + + for ( rdi = rd->rd_item, i = 0; rdi; rdi = rdi->rdi_next, i++ ) { + char buf[4096]; + struct berval bv; + char *ptr; + + bv.bv_len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len += rdi->rdi_line.bv_len; + ptr = bv.bv_val = ch_malloc( bv.bv_len + 1 ); + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, rdi->rdi_line.bv_val, rdi->rdi_line.bv_len ); + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + rc = 0; + } break; + + default: + assert( 0 ); + break; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case RC_PARENT: + if ( rd->rd_pdn.bv_val ) { + ber_memfree ( rd->rd_pdn.bv_val ); + rc = 0; + } + if ( rd->rd_npdn.bv_val ) { + ber_memfree ( rd->rd_npdn.bv_val ); + } + break; + + case RC_ITEM: + if ( c->valx == -1 ) { + retcode_item_t *rdi, *next; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) { + next = rdi->rdi_next; + retcode_item_destroy( rdi ); + } + + } else { + retcode_item_t **rdip, *rdi; + int i; + + for ( rdip = &rd->rd_item, i = 0; i <= c->valx && *rdip; i++, rdip = &(*rdip)->rdi_next ) + ; + if ( *rdip == NULL ) { + return 1; + } + rdi = *rdip; + *rdip = rdi->rdi_next; + + retcode_item_destroy( rdi ); + } + rc = 0; + break; + + default: + assert( 0 ); + break; + } + return rc; /* FIXME */ + } + + switch( c->type ) { + case RC_PARENT: + if ( rd->rd_pdn.bv_val ) { + ber_memfree ( rd->rd_pdn.bv_val ); + } + if ( rd->rd_npdn.bv_val ) { + ber_memfree ( rd->rd_npdn.bv_val ); + } + rd->rd_pdn = c->value_dn; + rd->rd_npdn = c->value_ndn; + rc = 0; + break; + + case RC_ITEM: { + retcode_item_t rdi = { BER_BVNULL }, **rdip; + struct berval bv, rdn, nrdn; + char *next = NULL; + int i; + + if ( c->argc < 3 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"retcode-item <RDN> <retcode> [<text>]\": " + "missing args" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + ber_str2bv( c->argv[ 1 ], 0, 0, &bv ); + + rc = dnPrettyNormal( NULL, &bv, &rdn, &nrdn, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to normalize RDN \"%s\": %d", + c->argv[ 1 ], rc ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + if ( !dnIsOneLevelRDN( &nrdn ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "value \"%s\" is not a RDN", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + if ( BER_BVISNULL( &rd->rd_npdn ) ) { + /* FIXME: we use the database suffix */ + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "either \"retcode-parent\" " + "or \"suffix\" must be defined" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + ber_dupbv( &rd->rd_pdn, &c->be->be_suffix[ 0 ] ); + ber_dupbv( &rd->rd_npdn, &c->be->be_nsuffix[ 0 ] ); + } + + build_new_dn( &rdi.rdi_dn, &rd->rd_pdn, &rdn, NULL ); + build_new_dn( &rdi.rdi_ndn, &rd->rd_npdn, &nrdn, NULL ); + + ch_free( rdn.bv_val ); + ch_free( nrdn.bv_val ); + + rdi.rdi_err = strtol( c->argv[ 2 ], &next, 0 ); + if ( next == c->argv[ 2 ] || next[ 0 ] != '\0' ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse return code \"%s\"", + c->argv[ 2 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + rdi.rdi_mask = SN_DG_OP_ALL; + + if ( c->argc > 3 ) { + for ( i = 3; i < c->argc; i++ ) { + if ( strncasecmp( c->argv[ i ], "op=", STRLENOF( "op=" ) ) == 0 ) + { + char **ops; + int j; + + ops = ldap_str2charray( &c->argv[ i ][ STRLENOF( "op=" ) ], "," ); + assert( ops != NULL ); + + rdi.rdi_mask = SN_DG_OP_NONE; + + for ( j = 0; ops[ j ] != NULL; j++ ) { + if ( strcasecmp( ops[ j ], "add" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_ADD; + + } else if ( strcasecmp( ops[ j ], "bind" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_BIND; + + } else if ( strcasecmp( ops[ j ], "compare" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_COMPARE; + + } else if ( strcasecmp( ops[ j ], "delete" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_DELETE; + + } else if ( strcasecmp( ops[ j ], "modify" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_MODIFY; + + } else if ( strcasecmp( ops[ j ], "rename" ) == 0 + || strcasecmp( ops[ j ], "modrdn" ) == 0 ) + { + rdi.rdi_mask |= SN_DG_OP_RENAME; + + } else if ( strcasecmp( ops[ j ], "search" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_SEARCH; + + } else if ( strcasecmp( ops[ j ], "extended" ) == 0 ) { + rdi.rdi_mask |= SN_DG_EXTENDED; + + } else if ( strcasecmp( ops[ j ], "auth" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_AUTH; + + } else if ( strcasecmp( ops[ j ], "read" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_READ; + + } else if ( strcasecmp( ops[ j ], "write" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_WRITE; + + } else if ( strcasecmp( ops[ j ], "all" ) == 0 ) { + rdi.rdi_mask |= SN_DG_OP_ALL; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown op \"%s\"", + ops[ j ] ); + ldap_charray_free( ops ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + } + + ldap_charray_free( ops ); + + } else if ( strncasecmp( c->argv[ i ], "text=", STRLENOF( "text=" ) ) == 0 ) + { + if ( !BER_BVISNULL( &rdi.rdi_text ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"text\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + ber_str2bv( &c->argv[ i ][ STRLENOF( "text=" ) ], 0, 1, &rdi.rdi_text ); + + } else if ( strncasecmp( c->argv[ i ], "matched=", STRLENOF( "matched=" ) ) == 0 ) + { + struct berval dn; + + if ( !BER_BVISNULL( &rdi.rdi_matched ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"matched\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + ber_str2bv( &c->argv[ i ][ STRLENOF( "matched=" ) ], 0, 0, &dn ); + if ( dnPretty( NULL, &dn, &rdi.rdi_matched, NULL ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to prettify matched DN \"%s\"", + &c->argv[ i ][ STRLENOF( "matched=" ) ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + } else if ( strncasecmp( c->argv[ i ], "ref=", STRLENOF( "ref=" ) ) == 0 ) + { + char **refs; + int j; + + if ( rdi.rdi_ref != NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"ref\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + if ( rdi.rdi_err != LDAP_REFERRAL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "providing \"ref\" " + "along with a non-referral " + "resultCode may cause slapd failures " + "related to internal checks" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + } + + refs = ldap_str2charray( &c->argv[ i ][ STRLENOF( "ref=" ) ], " " ); + assert( refs != NULL ); + + for ( j = 0; refs[ j ] != NULL; j++ ) { + struct berval bv; + + ber_str2bv( refs[ j ], 0, 1, &bv ); + ber_bvarray_add( &rdi.rdi_ref, &bv ); + } + + ldap_charray_free( refs ); + + } else if ( strncasecmp( c->argv[ i ], "sleeptime=", STRLENOF( "sleeptime=" ) ) == 0 ) + { + if ( rdi.rdi_sleeptime != 0 ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"sleeptime\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + if ( lutil_atoi( &rdi.rdi_sleeptime, &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse \"sleeptime=%s\"", + &c->argv[ i ][ STRLENOF( "sleeptime=" ) ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + } else if ( strncasecmp( c->argv[ i ], "unsolicited=", STRLENOF( "unsolicited=" ) ) == 0 ) + { + char *data; + + if ( !BER_BVISNULL( &rdi.rdi_unsolicited_oid ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"unsolicited\" already provided" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + data = strchr( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], ':' ); + if ( data != NULL ) { + struct berval oid; + + if ( ldif_parse_line2( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], + &oid, &rdi.rdi_unsolicited_data, NULL ) ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unable to parse \"unsolicited\"" ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + ber_dupbv( &rdi.rdi_unsolicited_oid, &oid ); + + } else { + ber_str2bv( &c->argv[ i ][ STRLENOF( "unsolicited=" ) ], 0, 1, + &rdi.rdi_unsolicited_oid ); + } + + } else if ( strncasecmp( c->argv[ i ], "flags=", STRLENOF( "flags=" ) ) == 0 ) + { + char *arg = &c->argv[ i ][ STRLENOF( "flags=" ) ]; + if ( strcasecmp( arg, "disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_PRE_DISCONNECT; + + } else if ( strcasecmp( arg, "pre-disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_PRE_DISCONNECT; + + } else if ( strcasecmp( arg, "post-disconnect" ) == 0 ) { + rdi.rdi_flags |= RDI_POST_DISCONNECT; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown flag \"%s\"", arg ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "unknown option \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: retcode: %s\n", + c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + } + } + + rdi.rdi_line.bv_len = 2*(c->argc - 1) + c->argc - 2; + for ( i = 1; i < c->argc; i++ ) { + rdi.rdi_line.bv_len += strlen( c->argv[ i ] ); + } + next = rdi.rdi_line.bv_val = ch_malloc( rdi.rdi_line.bv_len + 1 ); + + for ( i = 1; i < c->argc; i++ ) { + *next++ = '"'; + next = lutil_strcopy( next, c->argv[ i ] ); + *next++ = '"'; + *next++ = ' '; + } + *--next = '\0'; + + /* We're marked X-ORDERED 'VALUES', valx might be valid */ + for ( i = 0, rdip = &rd->rd_item; + *rdip && (c->valx < 0 || i < c->valx); + rdip = &(*rdip)->rdi_next, i++ ) + /* go to position */ ; + + + rdi.rdi_next = *rdip; + *rdip = ( retcode_item_t * )ch_malloc( sizeof( retcode_item_t ) ); + *(*rdip) = rdi; + + rc = 0; + } break; + + default: + rc = SLAP_CONF_UNKNOWN; + break; + } + + return rc; +} + +static int +retcode_db_open( BackendDB *be, ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + retcode_item_t *rdi; + + for ( rdi = rd->rd_item; rdi; rdi = rdi->rdi_next ) { + LDAPRDN rdn = NULL; + int rc, j; + char* p; + struct berval val[ 3 ]; + char buf[ SLAP_TEXT_BUFLEN ]; + + /* DN */ + rdi->rdi_e.e_name = rdi->rdi_dn; + rdi->rdi_e.e_nname = rdi->rdi_ndn; + + /* objectClass */ + val[ 0 ] = oc_errObject->soc_cname; + val[ 1 ] = slap_schema.si_oc_extensibleObject->soc_cname; + BER_BVZERO( &val[ 2 ] ); + + attr_merge( &rdi->rdi_e, slap_schema.si_ad_objectClass, val, NULL ); + + /* RDN avas */ + rc = ldap_bv2rdn( &rdi->rdi_dn, &rdn, (char **) &p, + LDAP_DN_FORMAT_LDAP ); + + assert( rc == LDAP_SUCCESS ); + + for ( j = 0; rdn[ j ]; j++ ) { + LDAPAVA *ava = rdn[ j ]; + AttributeDescription *ad = NULL; + const char *text; + + rc = slap_bv2ad( &ava->la_attr, &ad, &text ); + assert( rc == LDAP_SUCCESS ); + + attr_merge_normalize_one( &rdi->rdi_e, ad, + &ava->la_value, NULL ); + } + + ldap_rdnfree( rdn ); + + /* error code */ + snprintf( buf, sizeof( buf ), "%d", rdi->rdi_err ); + ber_str2bv( buf, 0, 0, &val[ 0 ] ); + + attr_merge_one( &rdi->rdi_e, ad_errCode, &val[ 0 ], NULL ); + + if ( rdi->rdi_ref != NULL ) { + attr_merge_normalize( &rdi->rdi_e, slap_schema.si_ad_ref, + rdi->rdi_ref, NULL ); + } + + /* text */ + if ( !BER_BVISNULL( &rdi->rdi_text ) ) { + val[ 0 ] = rdi->rdi_text; + + attr_merge_normalize_one( &rdi->rdi_e, ad_errText, &val[ 0 ], NULL ); + } + + /* matched */ + if ( !BER_BVISNULL( &rdi->rdi_matched ) ) { + val[ 0 ] = rdi->rdi_matched; + + attr_merge_normalize_one( &rdi->rdi_e, ad_errMatchedDN, &val[ 0 ], NULL ); + } + + /* sleep time */ + if ( rdi->rdi_sleeptime ) { + snprintf( buf, sizeof( buf ), "%d", rdi->rdi_sleeptime ); + ber_str2bv( buf, 0, 0, &val[ 0 ] ); + + attr_merge_one( &rdi->rdi_e, ad_errSleepTime, &val[ 0 ], NULL ); + } + + /* operations */ + if ( rdi->rdi_mask & SN_DG_OP_ADD ) { + BER_BVSTR( &val[ 0 ], "add" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_BIND ) { + BER_BVSTR( &val[ 0 ], "bind" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_COMPARE ) { + BER_BVSTR( &val[ 0 ], "compare" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_DELETE ) { + BER_BVSTR( &val[ 0 ], "delete" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_EXTENDED ) { + BER_BVSTR( &val[ 0 ], "extended" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_MODIFY ) { + BER_BVSTR( &val[ 0 ], "modify" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_RENAME ) { + BER_BVSTR( &val[ 0 ], "rename" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + + if ( rdi->rdi_mask & SN_DG_OP_SEARCH ) { + BER_BVSTR( &val[ 0 ], "search" ); + attr_merge_normalize_one( &rdi->rdi_e, ad_errOp, &val[ 0 ], NULL ); + } + } + + return 0; +} + +static int +retcode_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + retcode_t *rd = (retcode_t *)on->on_bi.bi_private; + + if ( rd ) { + retcode_item_t *rdi, *next; + + for ( rdi = rd->rd_item; rdi != NULL; rdi = next ) { + next = rdi->rdi_next; + retcode_item_destroy( rdi ); + } + + if ( !BER_BVISNULL( &rd->rd_pdn ) ) { + ber_memfree( rd->rd_pdn.bv_val ); + } + + if ( !BER_BVISNULL( &rd->rd_npdn ) ) { + ber_memfree( rd->rd_npdn.bv_val ); + } + + ber_memfree( rd ); + } + + return 0; +} + +#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */ +int +retcode_initialize( void ) +{ + int i, code; + + static struct { + char *desc; + AttributeDescription **ad; + } retcode_at[] = { + { "( 1.3.6.1.4.1.4203.666.11.4.1.1 " + "NAME ( 'errCode' ) " + "DESC 'LDAP error code' " + "EQUALITY integerMatch " + "ORDERING integerOrderingMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_errCode }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.2 " + "NAME ( 'errOp' ) " + "DESC 'Operations the errObject applies to' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )", + &ad_errOp}, + { "( 1.3.6.1.4.1.4203.666.11.4.1.3 " + "NAME ( 'errText' ) " + "DESC 'LDAP error textual description' " + "EQUALITY caseIgnoreMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 " + "SINGLE-VALUE )", + &ad_errText }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.4 " + "NAME ( 'errSleepTime' ) " + "DESC 'Time to wait before returning the error' " + "EQUALITY integerMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 " + "SINGLE-VALUE )", + &ad_errSleepTime }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.5 " + "NAME ( 'errMatchedDN' ) " + "DESC 'Value to be returned as matched DN' " + "EQUALITY distinguishedNameMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 " + "SINGLE-VALUE )", + &ad_errMatchedDN }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.6 " + "NAME ( 'errUnsolicitedOID' ) " + "DESC 'OID to be returned within unsolicited response' " + "EQUALITY objectIdentifierMatch " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 " + "SINGLE-VALUE )", + &ad_errUnsolicitedOID }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.7 " + "NAME ( 'errUnsolicitedData' ) " + "DESC 'Data to be returned within unsolicited response' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 " + "SINGLE-VALUE )", + &ad_errUnsolicitedData }, + { "( 1.3.6.1.4.1.4203.666.11.4.1.8 " + "NAME ( 'errDisconnect' ) " + "DESC 'Disconnect without notice' " + "SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 " + "SINGLE-VALUE )", + &ad_errDisconnect }, + { NULL } + }; + + static struct { + char *desc; + ObjectClass **oc; + } retcode_oc[] = { + { "( 1.3.6.1.4.1.4203.666.11.4.3.0 " + "NAME ( 'errAbsObject' ) " + "SUP top ABSTRACT " + "MUST ( errCode ) " + "MAY ( " + "cn " + "$ description " + "$ errOp " + "$ errText " + "$ errSleepTime " + "$ errMatchedDN " + "$ errUnsolicitedOID " + "$ errUnsolicitedData " + "$ errDisconnect " + ") )", + &oc_errAbsObject }, + { "( 1.3.6.1.4.1.4203.666.11.4.3.1 " + "NAME ( 'errObject' ) " + "SUP errAbsObject STRUCTURAL " + ")", + &oc_errObject }, + { "( 1.3.6.1.4.1.4203.666.11.4.3.2 " + "NAME ( 'errAuxObject' ) " + "SUP errAbsObject AUXILIARY " + ")", + &oc_errAuxObject }, + { NULL } + }; + + + for ( i = 0; retcode_at[ i ].desc != NULL; i++ ) { + code = register_at( retcode_at[ i ].desc, retcode_at[ i ].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "retcode: register_at failed\n" ); + return code; + } + + (*retcode_at[ i ].ad)->ad_type->sat_flags |= SLAP_AT_HIDE; + } + + for ( i = 0; retcode_oc[ i ].desc != NULL; i++ ) { + code = register_oc( retcode_oc[ i ].desc, retcode_oc[ i ].oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "retcode: register_oc failed\n" ); + return code; + } + + (*retcode_oc[ i ].oc)->soc_flags |= SLAP_OC_HIDE; + } + + retcode.on_bi.bi_type = "retcode"; + + retcode.on_bi.bi_db_init = retcode_db_init; + retcode.on_bi.bi_db_open = retcode_db_open; + retcode.on_bi.bi_db_destroy = retcode_db_destroy; + + retcode.on_bi.bi_op_add = retcode_op_func; + retcode.on_bi.bi_op_bind = retcode_op_func; + retcode.on_bi.bi_op_compare = retcode_op_func; + retcode.on_bi.bi_op_delete = retcode_op_func; + retcode.on_bi.bi_op_modify = retcode_op_func; + retcode.on_bi.bi_op_modrdn = retcode_op_func; + retcode.on_bi.bi_op_search = retcode_op_func; + + retcode.on_bi.bi_extended = retcode_op_func; + + retcode.on_response = retcode_response; + + retcode.on_bi.bi_cf_ocs = rcocs; + + code = config_register_schema( rccfg, rcocs ); + if ( code ) { + return code; + } + + return overlay_register( &retcode ); +} + +#if SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return retcode_initialize(); +} +#endif /* SLAPD_OVER_RETCODE == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_RETCODE */ diff --git a/servers/slapd/overlays/rwm.c b/servers/slapd/overlays/rwm.c new file mode 100644 index 0000000..8023ba5 --- /dev/null +++ b/servers/slapd/overlays/rwm.c @@ -0,0 +1,2723 @@ +/* rwm.c - rewrite/remap operations */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2022 The OpenLDAP Foundation. + * Portions Copyright 2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" +#include "rwm.h" + +typedef struct rwm_op_state { + ber_tag_t r_tag; + struct berval ro_dn; + struct berval ro_ndn; + struct berval r_dn; + struct berval r_ndn; + struct berval rx_dn; + struct berval rx_ndn; + AttributeName *mapped_attrs; + OpRequest o_request; +} rwm_op_state; + +typedef struct rwm_op_cb { + slap_callback cb; + rwm_op_state ros; +} rwm_op_cb; + +static int +rwm_db_destroy( BackendDB *be, ConfigReply *cr ); + +static int +rwm_send_entry( Operation *op, SlapReply *rs ); + +static void +rwm_op_rollback( Operation *op, SlapReply *rs, rwm_op_state *ros ) +{ + /* in case of successful extended operation cleanup + * gets called *after* (ITS#6632); this hack counts + * on others to cleanup our o_req_dn/o_req_ndn, + * while we cleanup theirs. */ + if ( ros->r_tag == LDAP_REQ_EXTENDED && rs->sr_err == LDAP_SUCCESS ) { + if ( !BER_BVISNULL( &ros->rx_dn ) ) { + ch_free( ros->rx_dn.bv_val ); + } + if ( !BER_BVISNULL( &ros->rx_ndn ) ) { + ch_free( ros->rx_ndn.bv_val ); + } + + } else { + if ( !BER_BVISNULL( &ros->ro_dn ) ) { + op->o_req_dn = ros->ro_dn; + } + if ( !BER_BVISNULL( &ros->ro_ndn ) ) { + op->o_req_ndn = ros->ro_ndn; + } + + if ( !BER_BVISNULL( &ros->r_dn ) + && ros->r_dn.bv_val != ros->ro_dn.bv_val ) + { + assert( ros->r_dn.bv_val != ros->r_ndn.bv_val ); + ch_free( ros->r_dn.bv_val ); + } + + if ( !BER_BVISNULL( &ros->r_ndn ) + && ros->r_ndn.bv_val != ros->ro_ndn.bv_val ) + { + ch_free( ros->r_ndn.bv_val ); + } + } + + BER_BVZERO( &ros->r_dn ); + BER_BVZERO( &ros->r_ndn ); + BER_BVZERO( &ros->ro_dn ); + BER_BVZERO( &ros->ro_ndn ); + BER_BVZERO( &ros->rx_dn ); + BER_BVZERO( &ros->rx_ndn ); + + switch( ros->r_tag ) { + case LDAP_REQ_COMPARE: + if ( op->orc_ava->aa_value.bv_val != ros->orc_ava->aa_value.bv_val ) + op->o_tmpfree( op->orc_ava->aa_value.bv_val, op->o_tmpmemctx ); + op->orc_ava = ros->orc_ava; + break; + case LDAP_REQ_MODIFY: + slap_mods_free( op->orm_modlist, 1 ); + op->orm_modlist = ros->orm_modlist; + break; + case LDAP_REQ_MODRDN: + if ( op->orr_newSup != ros->orr_newSup ) { + if ( op->orr_newSup ) { + ch_free( op->orr_newSup->bv_val ); + ch_free( op->orr_nnewSup->bv_val ); + op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx ); + } + op->orr_newSup = ros->orr_newSup; + op->orr_nnewSup = ros->orr_nnewSup; + } + if ( op->orr_newrdn.bv_val != ros->orr_newrdn.bv_val ) { + ch_free( op->orr_newrdn.bv_val ); + ch_free( op->orr_nnewrdn.bv_val ); + op->orr_newrdn = ros->orr_newrdn; + op->orr_nnewrdn = ros->orr_nnewrdn; + } + break; + case LDAP_REQ_SEARCH: + op->o_tmpfree( ros->mapped_attrs, op->o_tmpmemctx ); + op->ors_attrs = ros->ors_attrs; + if ( op->ors_filter != ros->ors_filter ) { + filter_free_x( op, op->ors_filter, 1 ); + op->ors_filter = ros->ors_filter; + } + if ( op->ors_filterstr.bv_val != ros->ors_filterstr.bv_val ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + op->ors_filterstr = ros->ors_filterstr; + } + break; + case LDAP_REQ_EXTENDED: + if ( op->ore_reqdata != ros->ore_reqdata ) { + ber_bvfree( op->ore_reqdata ); + op->ore_reqdata = ros->ore_reqdata; + } + break; + case LDAP_REQ_BIND: + if ( rs->sr_err == LDAP_SUCCESS ) { +#if 0 + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + /* too late, c_mutex released */ + Debug( LDAP_DEBUG_ANY, "*** DN: \"%s\" => \"%s\"\n", + op->o_conn->c_ndn.bv_val, + op->o_req_ndn.bv_val ); + ber_bvreplace( &op->o_conn->c_ndn, + &op->o_req_ndn ); + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); +#endif + } + break; + default: break; + } +} + +static int +rwm_op_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = op->o_callback; + rwm_op_state *ros = cb->sc_private; + + if ( rs->sr_type == REP_RESULT || rs->sr_type == REP_EXTENDED || + op->o_abandon || rs->sr_err == SLAPD_ABANDON ) + { + rwm_op_rollback( op, rs, ros ); + + op->o_callback = op->o_callback->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static rwm_op_cb * +rwm_callback_get( Operation *op ) +{ + rwm_op_cb *roc; + + roc = op->o_tmpcalloc( 1, sizeof( struct rwm_op_cb ), op->o_tmpmemctx ); + roc->cb.sc_cleanup = rwm_op_cleanup; + roc->cb.sc_response = NULL; + roc->cb.sc_next = op->o_callback; + roc->cb.sc_private = &roc->ros; + roc->ros.r_tag = op->o_tag; + roc->ros.ro_dn = op->o_req_dn; + roc->ros.ro_ndn = op->o_req_ndn; + BER_BVZERO( &roc->ros.r_dn ); + BER_BVZERO( &roc->ros.r_ndn ); + BER_BVZERO( &roc->ros.rx_dn ); + BER_BVZERO( &roc->ros.rx_ndn ); + roc->ros.mapped_attrs = NULL; + roc->ros.o_request = op->o_request; + + return roc; +} + + +static int +rwm_op_dn_massage( Operation *op, SlapReply *rs, void *cookie, + rwm_op_state *ros ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + int rc = 0; + dncookie dc; + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + /* NOTE: in those cases where only the ndn is available, + * and the caller sets op->o_req_dn = op->o_req_ndn, + * only rewrite the op->o_req_ndn and use it as + * op->o_req_dn as well */ + ndn = op->o_req_ndn; + if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) { + dn = op->o_req_dn; + rc = rwm_dn_massage_pretty_normalize( &dc, &op->o_req_dn, &dn, &ndn ); + } else { + rc = rwm_dn_massage_normalize( &dc, &op->o_req_ndn, &ndn ); + } + + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val && dn.bv_val == op->o_req_dn.bv_val ) + || ndn.bv_val == op->o_req_ndn.bv_val ) + { + return LDAP_SUCCESS; + } + + if ( op->o_req_dn.bv_val != op->o_req_ndn.bv_val ) { + op->o_req_dn = dn; + assert( BER_BVISNULL( &ros->r_dn ) ); + ros->r_dn = dn; + } else { + op->o_req_dn = ndn; + } + op->o_req_ndn = ndn; + assert( BER_BVISNULL( &ros->r_ndn ) ); + ros->r_ndn = ndn; + + if ( ros->r_tag == LDAP_REQ_EXTENDED ) { + ros->rx_dn = ros->r_dn; + ros->rx_ndn = ros->r_ndn; + } + + return LDAP_SUCCESS; +} + +static int +rwm_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc, + i; + Attribute **ap = NULL; + char *olddn = op->o_req_dn.bv_val; + int isupdate; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "addDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "addDN massage error" ); + return -1; + } + + if ( olddn != op->o_req_dn.bv_val ) { + ber_bvreplace( &op->ora_e->e_name, &op->o_req_dn ); + ber_bvreplace( &op->ora_e->e_nname, &op->o_req_ndn ); + } + + /* Count number of attributes in entry */ + isupdate = be_shadow_update( op ); + for ( i = 0, ap = &op->oq_add.rs_e->e_attrs; *ap; ) { + Attribute *a; + + if ( (*ap)->a_desc == slap_schema.si_ad_objectClass || + (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + int j, last; + + last = (*ap)->a_numvals - 1; + for ( j = 0; !BER_BVISNULL( &(*ap)->a_vals[ j ] ); j++ ) { + struct ldapmapping *mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_oc, &(*ap)->a_vals[ j ], + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + /* FIXME: we allow to remove objectClasses as well; + * if the resulting entry is inconsistent, that's + * the relayed database's business... + */ + ch_free( (*ap)->a_vals[ j ].bv_val ); + if ( last > j ) { + (*ap)->a_vals[ j ] = (*ap)->a_vals[ last ]; + } + BER_BVZERO( &(*ap)->a_vals[ last ] ); + (*ap)->a_numvals--; + last--; + j--; + } + + } else { + ch_free( (*ap)->a_vals[ j ].bv_val ); + ber_dupbv( &(*ap)->a_vals[ j ], &mapping->m_dst ); + } + } + + } else if ( !isupdate && !get_relax( op ) && (*ap)->a_desc->ad_type->sat_no_user_mod ) + { + goto next_attr; + + } else { + struct ldapmapping *mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_at, &(*ap)->a_desc->ad_cname, + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + goto cleanup_attr; + } + } + + if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + /* + * FIXME: rewrite could fail; in this case + * the operation should give up, right? + */ + rc = rwm_dnattr_rewrite( op, rs, "addAttrDN", + (*ap)->a_vals, + (*ap)->a_nvals ? &(*ap)->a_nvals : NULL ); + if ( rc ) { + goto cleanup_attr; + } + + } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) { + rc = rwm_referral_rewrite( op, rs, "referralAttrDN", + (*ap)->a_vals, + (*ap)->a_nvals ? &(*ap)->a_nvals : NULL ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + } + + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + (*ap)->a_desc = mapping->m_dst_ad; + } + } + +next_attr:; + ap = &(*ap)->a_next; + continue; + +cleanup_attr:; + /* FIXME: leaking attribute/values? */ + a = *ap; + + *ap = (*ap)->a_next; + attr_free( a ); + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_conn_init( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + ( void )rewrite_session_init( rwmap->rwm_rw, conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_conn_destroy( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + ( void )rewrite_session_delete( rwmap->rwm_rw, conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_bind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "bindDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "bindDN massage error" ); + return -1; + } + + overlay_callback_after_backover( op, &roc->cb, 1 ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_unbind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + rewrite_session_delete( rwmap->rwm_rw, op->o_conn ); + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_compare( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + struct berval mapped_vals[2] = { BER_BVNULL, BER_BVNULL }; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "compareDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "compareDN massage error" ); + return -1; + } + + /* if the attribute is an objectClass, try to remap its value */ + if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass + || op->orc_ava->aa_desc == slap_schema.si_ad_structuralObjectClass ) + { + rwm_map( &rwmap->rwm_oc, &op->orc_ava->aa_value, + &mapped_vals[0], RWM_MAP ); + if ( BER_BVISNULL( &mapped_vals[0] ) || BER_BVISEMPTY( &mapped_vals[0] ) ) + { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_OTHER, "compare objectClass map error" ); + return -1; + + } else if ( mapped_vals[0].bv_val != op->orc_ava->aa_value.bv_val ) { + ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0], + op->o_tmpmemctx ); + } + + } else { + struct ldapmapping *mapping = NULL; + AttributeDescription *ad = op->orc_ava->aa_desc; + + ( void )rwm_mapping( &rwmap->rwm_at, &op->orc_ava->aa_desc->ad_cname, + &mapping, RWM_MAP ); + if ( mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, LDAP_OTHER, "compare attributeType map error" ); + return -1; + } + + } else { + assert( mapping->m_dst_ad != NULL ); + ad = mapping->m_dst_ad; + } + + if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + struct berval *mapped_valsp[2]; + + mapped_valsp[0] = &mapped_vals[0]; + mapped_valsp[1] = &mapped_vals[1]; + + mapped_vals[0] = op->orc_ava->aa_value; + + rc = rwm_dnattr_rewrite( op, rs, "compareAttrDN", NULL, mapped_valsp ); + + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "compareAttrDN massage error" ); + return -1; + } + + if ( mapped_vals[ 0 ].bv_val != op->orc_ava->aa_value.bv_val ) { + /* NOTE: if we get here, rwm_dnattr_rewrite() + * already freed the old value, so now + * it's invalid */ + ber_dupbv_x( &op->orc_ava->aa_value, &mapped_vals[0], + op->o_tmpmemctx ); + ber_memfree_x( mapped_vals[ 0 ].bv_val, NULL ); + } + } + op->orc_ava->aa_desc = ad; + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "deleteDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "deleteDN massage error" ); + return -1; + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int isupdate; + Modifications **mlp; + int rc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "modifyDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "modifyDN massage error" ); + return -1; + } + + isupdate = be_shadow_update( op ); + for ( mlp = &op->orm_modlist; *mlp; ) { + int is_oc = 0; + Modifications *ml = *mlp; + struct ldapmapping *mapping = NULL; + + /* ml points to a temporary mod until needs duplication */ + if ( ml->sml_desc == slap_schema.si_ad_objectClass + || ml->sml_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + + } else if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + ml = ch_malloc( sizeof( Modifications ) ); + *ml = **mlp; + if ( (*mlp)->sml_values ) { + ber_bvarray_dup_x( &ml->sml_values, (*mlp)->sml_values, NULL ); + if ( (*mlp)->sml_nvalues ) { + ber_bvarray_dup_x( &ml->sml_nvalues, (*mlp)->sml_nvalues, NULL ); + } + } + *mlp = ml; + goto next_mod; + + } else { + int drop_missing; + + drop_missing = rwm_mapping( &rwmap->rwm_at, + &ml->sml_desc->ad_cname, + &mapping, RWM_MAP ); + if ( drop_missing || ( mapping != NULL && BER_BVISNULL( &mapping->m_dst ) ) ) + { + goto skip_mod; + } + } + + /* duplicate the modlist */ + ml = ch_malloc( sizeof( Modifications )); + *ml = **mlp; + *mlp = ml; + + if ( ml->sml_values != NULL ) { + int i, num; + struct berval *bva; + + for ( num = 0; !BER_BVISNULL( &ml->sml_values[ num ] ); num++ ) + /* count values */ ; + + bva = ch_malloc( (num+1) * sizeof( struct berval )); + for (i=0; i<num; i++) + ber_dupbv( &bva[i], &ml->sml_values[i] ); + BER_BVZERO( &bva[i] ); + ml->sml_values = bva; + + if ( ml->sml_nvalues ) { + bva = ch_malloc( (num+1) * sizeof( struct berval )); + for (i=0; i<num; i++) + ber_dupbv( &bva[i], &ml->sml_nvalues[i] ); + BER_BVZERO( &bva[i] ); + ml->sml_nvalues = bva; + } + + if ( is_oc ) { + int last, j; + + last = num-1; + + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) { + struct ldapmapping *oc_mapping = NULL; + + ( void )rwm_mapping( &rwmap->rwm_oc, &ml->sml_values[ j ], + &oc_mapping, RWM_MAP ); + if ( oc_mapping == NULL ) { + if ( rwmap->rwm_at.drop_missing ) { + /* FIXME: we allow to remove objectClasses as well; + * if the resulting entry is inconsistent, that's + * the relayed database's business... + */ + if ( last > j ) { + ch_free( ml->sml_values[ j ].bv_val ); + ml->sml_values[ j ] = ml->sml_values[ last ]; + } + BER_BVZERO( &ml->sml_values[ last ] ); + last--; + j--; + } + + } else { + ch_free( ml->sml_values[ j ].bv_val ); + ber_dupbv( &ml->sml_values[ j ], &oc_mapping->m_dst ); + } + } + + } else { + if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + rc = rwm_dnattr_rewrite( op, rs, "modifyAttrDN", + ml->sml_values, + ml->sml_nvalues ? &ml->sml_nvalues : NULL ); + + } else if ( ml->sml_desc == slap_schema.si_ad_ref ) { + rc = rwm_referral_rewrite( op, rs, + "referralAttrDN", + ml->sml_values, + ml->sml_nvalues ? &ml->sml_nvalues : NULL ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_mod; + } + } + + if ( rc != LDAP_SUCCESS ) { + goto cleanup_mod; + } + } + } + +next_mod:; + if ( mapping != NULL ) { + /* use new attribute description */ + assert( mapping->m_dst_ad != NULL ); + ml->sml_desc = mapping->m_dst_ad; + } + + mlp = &ml->sml_next; + continue; + +skip_mod:; + *mlp = (*mlp)->sml_next; + continue; + +cleanup_mod:; + ml = *mlp; + *mlp = (*mlp)->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static int +rwm_op_modrdn( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + dncookie dc; + + rwm_op_cb *roc = rwm_callback_get( op ); + + if ( op->orr_newSup ) { + struct berval nnewSup = BER_BVNULL; + struct berval newSup = BER_BVNULL; + + /* + * Rewrite the new superior, if defined and required + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "newSuperiorDN"; + newSup = *op->orr_newSup; + nnewSup = *op->orr_nnewSup; + rc = rwm_dn_massage_pretty_normalize( &dc, op->orr_newSup, &newSup, &nnewSup ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "newSuperiorDN massage error" ); + return -1; + } + + if ( op->orr_newSup->bv_val != newSup.bv_val ) { + op->orr_newSup = op->o_tmpalloc( sizeof( struct berval ), + op->o_tmpmemctx ); + op->orr_nnewSup = op->o_tmpalloc( sizeof( struct berval ), + op->o_tmpmemctx ); + *op->orr_newSup = newSup; + *op->orr_nnewSup = nnewSup; + } + } + + /* + * Rewrite the newRDN, if needed + */ + { + struct berval newrdn = BER_BVNULL; + struct berval nnewrdn = BER_BVNULL; + + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "newRDN"; + newrdn = op->orr_newrdn; + nnewrdn = op->orr_nnewrdn; + rc = rwm_dn_massage_pretty_normalize( &dc, &op->orr_newrdn, &newrdn, &nnewrdn ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "newRDN massage error" ); + goto err; + } + + if ( op->orr_newrdn.bv_val != newrdn.bv_val ) { + op->orr_newrdn = newrdn; + op->orr_nnewrdn = nnewrdn; + } + } + + /* + * Rewrite the dn, if needed + */ + rc = rwm_op_dn_massage( op, rs, "renameDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "renameDN massage error" ); + goto err; + } + + op->o_callback = &roc->cb; + + rc = SLAP_CB_CONTINUE; + + if ( 0 ) { +err:; + if ( op->orr_newSup != roc->ros.orr_newSup ) { + ch_free( op->orr_newSup->bv_val ); + ch_free( op->orr_nnewSup->bv_val ); + op->o_tmpfree( op->orr_newSup, op->o_tmpmemctx ); + op->o_tmpfree( op->orr_nnewSup, op->o_tmpmemctx ); + op->orr_newSup = roc->ros.orr_newSup; + op->orr_nnewSup = roc->ros.orr_nnewSup; + } + + if ( op->orr_newrdn.bv_val != roc->ros.orr_newrdn.bv_val ) { + ch_free( op->orr_newrdn.bv_val ); + ch_free( op->orr_nnewrdn.bv_val ); + op->orr_newrdn = roc->ros.orr_newrdn; + op->orr_nnewrdn = roc->ros.orr_nnewrdn; + } + } + + return rc; +} + + +static int +rwm_swap_attrs( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = op->o_callback; + rwm_op_state *ros = cb->sc_private; + + rs->sr_attrs = ros->ors_attrs; + + /* other overlays might have touched op->ors_attrs, + * so we restore the original version here, otherwise + * attribute-mapping might fail */ + op->ors_attrs = ros->mapped_attrs; + + return SLAP_CB_CONTINUE; +} + +/* + * NOTE: this implementation of get/release entry is probably far from + * optimal. The rationale consists in intercepting the request directed + * to the underlying database, in order to rewrite/remap the request, + * perform it using the modified data, duplicate the resulting entry + * and finally free it when release is called. + * This implies that subsequent overlays are not called, as the request + * is directly shunted to the underlying database. + */ +static int +rwm_entry_release_rw( Operation *op, Entry *e, int rw ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + + /* can't be ours */ + if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* just free entry if (probably) ours */ + if ( e->e_private == NULL && BER_BVISNULL( &e->e_bv ) ) { + entry_free( e ); + return LDAP_SUCCESS; + } + + return SLAP_CB_CONTINUE; +} + +static int +rwm_entry_get_rw( Operation *op, struct berval *ndn, + ObjectClass *oc, AttributeDescription *at, int rw, Entry **ep ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + BackendDB db; + Operation op2; + SlapReply rs = { REP_SEARCH }; + + rwm_op_state ros = { 0 }; + struct berval mndn = BER_BVNULL; + + if ( ((BackendInfo *)on->on_info->oi_orig)->bi_entry_get_rw == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* massage DN */ + op2.o_tag = LDAP_REQ_SEARCH; + op2 = *op; + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + rc = rwm_op_dn_massage( &op2, &rs, "searchDN", &ros ); + if ( rc != LDAP_SUCCESS ) { + return LDAP_OTHER; + } + + mndn = BER_BVISNULL( &ros.r_ndn ) ? *ndn : ros.r_ndn; + + /* map attribute & objectClass */ + if ( at != NULL ) { + } + + if ( oc != NULL ) { + } + + /* fetch entry */ + db = *op->o_bd; + op2.o_bd = &db; + op2.o_bd->bd_info = (BackendInfo *)on->on_info->oi_orig; + op2.ors_attrs = slap_anlist_all_attributes; + rc = op2.o_bd->bd_info->bi_entry_get_rw( &op2, &mndn, oc, at, rw, ep ); + if ( rc == LDAP_SUCCESS && *ep != NULL ) { + /* we assume be_entry_release() needs to be called */ + rs.sr_flags = REP_ENTRY_MUSTRELEASE; + rs.sr_entry = *ep; + + /* duplicate & release */ + op2.o_bd->bd_info = (BackendInfo *)on; + rc = rwm_send_entry( &op2, &rs ); + RS_ASSERT( rs.sr_flags & REP_ENTRY_MUSTFLUSH ); + if ( rc == SLAP_CB_CONTINUE ) { + *ep = rs.sr_entry; + rc = LDAP_SUCCESS; + } else { + assert( rc != LDAP_SUCCESS && rs.sr_entry == *ep ); + *ep = NULL; + op2.o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( &op2, rs.sr_entry ); + op2.o_bd->bd_info = (BackendInfo *)on; + } + } + + if ( !BER_BVISNULL( &ros.r_ndn) && ros.r_ndn.bv_val != ndn->bv_val ) { + op->o_tmpfree( ros.r_ndn.bv_val, op->o_tmpmemctx ); + } + + return rc; +} + +static int +rwm_op_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + dncookie dc; + + struct berval fstr = BER_BVNULL; + Filter *f = NULL; + + AttributeName *an = NULL; + + char *text = NULL; + + rwm_op_cb *roc = rwm_callback_get( op ); + + rc = rewrite_session_var_set( rwmap->rwm_rw, op->o_conn, + "searchFilter", op->ors_filterstr.bv_val ); + if ( rc == LDAP_SUCCESS ) + rc = rwm_op_dn_massage( op, rs, "searchDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + text = "searchDN massage error"; + goto error_return; + } + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "searchFilterAttrDN"; + + rc = rwm_filter_map_rewrite( op, &dc, op->ors_filter, &fstr ); + if ( rc != LDAP_SUCCESS ) { + text = "searchFilter/searchFilterAttrDN massage error"; + goto error_return; + } + + f = str2filter_x( op, fstr.bv_val ); + + if ( f == NULL ) { + text = "massaged filter parse error"; + goto error_return; + } + + op->ors_filter = f; + op->ors_filterstr = fstr; + + rc = rwm_map_attrnames( op, &rwmap->rwm_at, &rwmap->rwm_oc, + op->ors_attrs, &an, RWM_MAP ); + if ( rc != LDAP_SUCCESS ) { + text = "attribute list mapping error"; + goto error_return; + } + + op->ors_attrs = an; + /* store the mapped Attributes for later usage, in + * the case that other overlays change op->ors_attrs */ + roc->ros.mapped_attrs = an; + roc->cb.sc_response = rwm_swap_attrs; + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; + +error_return:; + if ( an != NULL ) { + ch_free( an ); + } + + if ( f != NULL ) { + filter_free_x( op, f, 1 ); + } + + if ( !BER_BVISNULL( &fstr ) ) { + op->o_tmpfree( fstr.bv_val, op->o_tmpmemctx ); + } + + rwm_op_rollback( op, rs, &roc->ros ); + op->oq_search = roc->ros.oq_search; + op->o_tmpfree( roc, op->o_tmpmemctx ); + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, text ); + + return -1; + +} + +static int +rwm_exop_passwd( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + rwm_op_cb *roc; + + struct berval id = BER_BVNULL, + pwold = BER_BVNULL, + pwnew = BER_BVNULL; + BerElement *ber = NULL; + + if ( !BER_BVISNULL( &op->o_req_ndn ) ) { + return LDAP_SUCCESS; + } + + if ( !SLAP_ISGLOBALOVERLAY( op->o_bd ) ) { + rs->sr_err = LDAP_OTHER; + return rs->sr_err; + } + + rs->sr_err = slap_passwd_parse( op->ore_reqdata, &id, + &pwold, &pwnew, &rs->sr_text ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + if ( !BER_BVISNULL( &id ) ) { + char idNul = id.bv_val[id.bv_len]; + id.bv_val[id.bv_len] = '\0'; + rs->sr_err = dnPrettyNormal( NULL, &id, &op->o_req_dn, + &op->o_req_ndn, op->o_tmpmemctx ); + id.bv_val[id.bv_len] = idNul; + if ( rs->sr_err != LDAP_SUCCESS ) { + rs->sr_text = "Invalid DN"; + return rs->sr_err; + } + + } else { + ber_dupbv_x( &op->o_req_dn, &op->o_dn, op->o_tmpmemctx ); + ber_dupbv_x( &op->o_req_ndn, &op->o_ndn, op->o_tmpmemctx ); + } + + roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "extendedDN massage error" ); + return -1; + } + + ber = ber_alloc_t( LBER_USE_DER ); + if ( !ber ) { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "No memory"; + return rs->sr_err; + } + ber_printf( ber, "{" ); + if ( !BER_BVISNULL( &id )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, + &op->o_req_dn ); + } + if ( !BER_BVISNULL( &pwold )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, &pwold ); + } + if ( !BER_BVISNULL( &pwnew )) { + ber_printf( ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &pwnew ); + } + ber_printf( ber, "N}" ); + ber_flatten( ber, &op->ore_reqdata ); + ber_free( ber, 1 ); + + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static struct exop { + struct berval oid; + BI_op_extended *extended; +} exop_table[] = { + { BER_BVC(LDAP_EXOP_MODIFY_PASSWD), rwm_exop_passwd }, + { BER_BVNULL, NULL } +}; + +static int +rwm_extended( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + rwm_op_cb *roc; + + int i; + + for ( i = 0; exop_table[i].extended != NULL; i++ ) { + if ( bvmatch( &exop_table[i].oid, &op->oq_extended.rs_reqoid ) ) + { + rc = exop_table[i].extended( op, rs ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case SLAP_CB_CONTINUE: + case SLAPD_ABANDON: + return rc; + + default: + send_ldap_result( op, rs ); + return rc; + } + break; + } + } + + roc = rwm_callback_get( op ); + + rc = rwm_op_dn_massage( op, rs, "extendedDN", &roc->ros ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "extendedDN massage error" ); + return -1; + } + + /* TODO: rewrite/map extended data ? ... */ + op->o_callback = &roc->cb; + + return SLAP_CB_CONTINUE; +} + +static void +rwm_matched( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval dn, mdn; + dncookie dc; + int rc; + + if ( rs->sr_matched == NULL ) { + return; + } + + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "matchedDN"; + ber_str2bv( rs->sr_matched, 0, 0, &dn ); + mdn = dn; + rc = rwm_dn_massage_pretty( &dc, &dn, &mdn ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + rs->sr_text = "Rewrite error"; + + } else if ( mdn.bv_val != dn.bv_val ) { + if ( rs->sr_flags & REP_MATCHED_MUSTBEFREED ) { + ch_free( (void *)rs->sr_matched ); + + } else { + rs->sr_flags |= REP_MATCHED_MUSTBEFREED; + } + rs->sr_matched = mdn.bv_val; + } +} + +static int +rwm_attrs( Operation *op, SlapReply *rs, Attribute** a_first, int stripEntryDN ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + dncookie dc; + int rc; + Attribute **ap; + int isupdate; + int check_duplicate_attrs = 0; + + /* + * Rewrite the dn attrs, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + + /* FIXME: in principle, one could map an attribute + * on top of another, which already exists. + * As such, in the end there might exist more than + * one instance of an attribute. + * We should at least check if this occurs, and issue + * an error (because multiple instances of attrs in + * response are not valid), or merge the values (what + * about duplicate values?) */ + isupdate = be_shadow_update( op ); + for ( ap = a_first; *ap; ) { + struct ldapmapping *mapping = NULL; + int drop_missing; + int last = -1; + Attribute *a; + + if ( ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ) && + op->ors_attrs != NULL && + !SLAP_USERATTRS( rs->sr_attr_flags ) && + !ad_inlist( (*ap)->a_desc, op->ors_attrs ) ) + { + goto cleanup_attr; + } + + drop_missing = rwm_mapping( &rwmap->rwm_at, + &(*ap)->a_desc->ad_cname, &mapping, RWM_REMAP ); + if ( drop_missing || ( mapping != NULL && BER_BVISEMPTY( &mapping->m_dst ) ) ) + { + goto cleanup_attr; + } + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + + /* try to normalize mapped Attributes if the original + * AttributeType was not normalized */ + if ( (!(*ap)->a_desc->ad_type->sat_equality || + !(*ap)->a_desc->ad_type->sat_equality->smr_normalize) && + mapping->m_dst_ad->ad_type->sat_equality && + mapping->m_dst_ad->ad_type->sat_equality->smr_normalize ) + { + if ((rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS)) + { + int i = 0; + + last = (*ap)->a_numvals; + if ( last ) + { + (*ap)->a_nvals = ch_malloc( (last+1) * sizeof(struct berval) ); + + for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[i]); i++ ) { + int rc; + /* + * check that each value is valid per syntax + * and pretty if appropriate + */ + rc = mapping->m_dst_ad->ad_type->sat_equality->smr_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + mapping->m_dst_ad->ad_type->sat_syntax, + mapping->m_dst_ad->ad_type->sat_equality, + &(*ap)->a_vals[i], &(*ap)->a_nvals[i], + NULL ); + + if ( rc != LDAP_SUCCESS ) { + /* FIXME: this is wrong, putting a non-normalized value + * into nvals. But when a proxy sends us bogus data, + * we still need to give it to the client, even if it + * violates the syntax. I.e., we don't want to silently + * drop things and trigger an apparent data loss. + */ + ber_dupbv( &(*ap)->a_nvals[i], &(*ap)->a_vals[i] ); + } + } + BER_BVZERO( &(*ap)->a_nvals[i] ); + } + + } else { + assert( (*ap)->a_nvals == (*ap)->a_vals ); + (*ap)->a_nvals = NULL; + ber_bvarray_dup_x( &(*ap)->a_nvals, (*ap)->a_vals, NULL ); + } + } + + /* rewrite the attribute description */ + (*ap)->a_desc = mapping->m_dst_ad; + + /* will need to check for duplicate attrs */ + check_duplicate_attrs++; + } + + if ( (*ap)->a_desc == slap_schema.si_ad_entryDN ) { + if ( stripEntryDN ) { + /* will be generated by frontend */ + goto cleanup_attr; + } + + } else if ( !isupdate + && !get_relax( op ) + && (*ap)->a_desc->ad_type->sat_no_user_mod + && (*ap)->a_desc->ad_type != slap_schema.si_at_undefined ) + { + goto next_attr; + } + + if ( last == -1 ) { /* not yet counted */ + last = (*ap)->a_numvals; + } + + if ( last == 0 ) { + /* empty? leave it in place because of attrsonly and vlv */ + goto next_attr; + } + last--; + + if ( (*ap)->a_desc == slap_schema.si_ad_objectClass + || (*ap)->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + struct berval *bv; + + for ( bv = (*ap)->a_vals; !BER_BVISNULL( bv ); bv++ ) { + struct berval mapped; + + rwm_map( &rwmap->rwm_oc, &bv[0], &mapped, RWM_REMAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { +remove_oc:; + ch_free( bv[0].bv_val ); + BER_BVZERO( &bv[0] ); + if ( &(*ap)->a_vals[last] > &bv[0] ) { + bv[0] = (*ap)->a_vals[last]; + BER_BVZERO( &(*ap)->a_vals[last] ); + } + last--; + bv--; + + } else if ( mapped.bv_val != bv[0].bv_val + && ber_bvstrcasecmp( &mapped, &bv[0] ) != 0 ) + { + int i; + + for ( i = 0; !BER_BVISNULL( &(*ap)->a_vals[ i ] ); i++ ) { + if ( &(*ap)->a_vals[ i ] == bv ) { + continue; + } + + if ( ber_bvstrcasecmp( &mapped, &(*ap)->a_vals[ i ] ) == 0 ) { + break; + } + } + + if ( !BER_BVISNULL( &(*ap)->a_vals[ i ] ) ) { + goto remove_oc; + } + + /* + * FIXME: after LBER_FREEing + * the value is replaced by + * ch_alloc'ed memory + */ + ber_bvreplace( &bv[0], &mapped ); + + /* FIXME: will need to check + * if the structuralObjectClass + * changed */ + } + } + + /* + * It is necessary to try to rewrite attributes with + * dn syntax because they might be used in ACLs as + * members of groups; since ACLs are applied to the + * rewritten stuff, no dn-based subject clause could + * be used at the ldap backend side (see + * http://www.OpenLDAP.org/faq/data/cache/452.html) + * The problem can be overcome by moving the dn-based + * ACLs to the target directory server, and letting + * everything pass thru the ldap backend. */ + /* FIXME: handle distinguishedName-like syntaxes, like + * nameAndOptionalUID */ + } else if ( (*ap)->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_src_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + dc.ctx = "searchAttrDN"; + rc = rwm_dnattr_result_rewrite( &dc, (*ap)->a_vals, (*ap)->a_nvals ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + + } else if ( (*ap)->a_desc == slap_schema.si_ad_ref ) { + dc.ctx = "searchAttrDN"; + rc = rwm_referral_result_rewrite( &dc, (*ap)->a_vals ); + if ( rc != LDAP_SUCCESS ) { + goto cleanup_attr; + } + } + + +next_attr:; + ap = &(*ap)->a_next; + continue; + +cleanup_attr:; + a = *ap; + *ap = (*ap)->a_next; + + attr_free( a ); + } + + /* only check if some mapping occurred */ + if ( check_duplicate_attrs ) { + for ( ap = a_first; *ap != NULL; ap = &(*ap)->a_next ) { + Attribute **tap; + + for ( tap = &(*ap)->a_next; *tap != NULL; ) { + if ( (*tap)->a_desc == (*ap)->a_desc ) { + Entry e = { 0 }; + Modification mod = { 0 }; + const char *text = NULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + Attribute *next = (*tap)->a_next; + + BER_BVSTR( &e.e_name, "" ); + BER_BVSTR( &e.e_nname, "" ); + e.e_attrs = *ap; + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = (*ap)->a_desc; + mod.sm_type = mod.sm_desc->ad_cname; + mod.sm_numvals = (*tap)->a_numvals; + mod.sm_values = (*tap)->a_vals; + if ( (*tap)->a_nvals != (*tap)->a_vals ) { + mod.sm_nvalues = (*tap)->a_nvals; + } + + (void)modify_add_values( &e, &mod, + /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + + /* should not insert new attrs! */ + assert( e.e_attrs == *ap ); + + attr_free( *tap ); + *tap = next; + + } else { + tap = &(*tap)->a_next; + } + } + } + } + + return 0; +} + +/* Should return SLAP_CB_CONTINUE or failure, never LDAP_SUCCESS. */ +static int +rwm_send_entry( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + Entry *e = NULL; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + dncookie dc; + int rc; + + assert( rs->sr_entry != NULL ); + + /* + * Rewrite the dn of the result, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + dc.ctx = "searchEntryDN"; + + e = rs->sr_entry; + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + /* FIXME: all we need to duplicate are: + * - dn + * - ndn + * - attributes that are requested + * - no values if attrsonly is set + */ + e = entry_dup( e ); + if ( e == NULL ) { + rc = LDAP_NO_MEMORY; + goto fail; + } + } else if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) { + /* ITS#6423: REP_ENTRY_MUSTRELEASE incompatible + * with REP_ENTRY_MODIFIABLE */ + RS_ASSERT( 0 ); + rc = 1; + goto fail; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + */ + dn = e->e_name; + ndn = e->e_nname; + rc = rwm_dn_massage_pretty_normalize( &dc, &e->e_name, &dn, &ndn ); + if ( rc != LDAP_SUCCESS ) { + rc = 1; + goto fail; + } + + if ( e->e_name.bv_val != dn.bv_val ) { + ch_free( e->e_name.bv_val ); + ch_free( e->e_nname.bv_val ); + + e->e_name = dn; + e->e_nname = ndn; + } + + /* TODO: map entry attribute types, objectclasses + * and dn-valued attribute values */ + + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + (void)rwm_attrs( op, rs, &e->e_attrs, 1 ); + + if ( e != rs->sr_entry ) { + /* Reimplementing rs_replace_entry(), I suppose to + * bypass our own dubious rwm_entry_release_rw() */ + if ( rs->sr_flags & REP_ENTRY_MUSTRELEASE ) { + rs->sr_flags ^= REP_ENTRY_MUSTRELEASE; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, rs->sr_entry ); + op->o_bd->bd_info = (BackendInfo *)on; + } else if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) { + entry_free( rs->sr_entry ); + } + rs->sr_entry = e; + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + + return SLAP_CB_CONTINUE; + +fail:; + if ( e != NULL && e != rs->sr_entry ) { + if ( e->e_name.bv_val == dn.bv_val ) { + BER_BVZERO( &e->e_name ); + } + + if ( e->e_nname.bv_val == ndn.bv_val ) { + BER_BVZERO( &e->e_nname ); + } + + entry_free( e ); + } + + if ( !BER_BVISNULL( &dn ) ) { + ch_free( dn.bv_val ); + } + + if ( !BER_BVISNULL( &ndn ) ) { + ch_free( ndn.bv_val ); + } + + return rc; +} + +static int +rwm_operational( Operation *op, SlapReply *rs ) +{ + /* FIXME: the entries are in the remote mapping form; + * so we need to select those attributes we are willing + * to return, and remap them accordingly */ + if ( rs->sr_operational_attrs ) { + rwm_attrs( op, rs, &rs->sr_operational_attrs, 1 ); + } + + return SLAP_CB_CONTINUE; +} + +#if 0 +/* don't use this; it cannot be reverted, and leaves op->o_req_dn + * rewritten for subsequent operations; fine for plain suffixmassage, + * but destroys everything else */ +static int +rwm_chk_referrals( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + int rc; + + rc = rwm_op_dn_massage( op, rs, "referralCheckDN" ); + if ( rc != LDAP_SUCCESS ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + send_ldap_error( op, rs, rc, "referralCheckDN massage error" ); + return -1; + } + + return SLAP_CB_CONTINUE; +} +#endif + +static int +rwm_rw_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + return rewrite_parse( rwmap->rwm_rw, + fname, lineno, argc, argv ); + + return 0; +} + +static int +rwm_suffixmassage_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + struct berval bvnc, nvnc, pvnc, brnc, nrnc, prnc; + int massaged; + int rc; + + /* + * syntax: + * + * suffixmassage [<suffix>] <massaged suffix> + * + * the [<suffix>] field must be defined as a valid suffix + * for the current database; + * the <massaged suffix> shouldn't have already been + * defined as a valid suffix for the current server + */ + if ( argc == 2 ) { + if ( be->be_suffix == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: " + " \"suffixMassage [<suffix>]" + " <massaged suffix>\" without " + "<suffix> part requires database " + "suffix be defined first.\n", + fname, lineno ); + return 1; + } + bvnc = be->be_suffix[ 0 ]; + massaged = 1; + + } else if ( argc == 3 ) { + ber_str2bv( argv[ 1 ], 0, 0, &bvnc ); + massaged = 2; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is" + " \"suffixMassage [<suffix>]" + " <massaged suffix>\"\n", + fname, lineno ); + return 1; + } + + if ( dnPrettyNormal( NULL, &bvnc, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n", + fname, lineno, bvnc.bv_val ); + return 1; + } + + ber_str2bv( argv[ massaged ], 0, 0, &brnc ); + if ( dnPrettyNormal( NULL, &brnc, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: suffix DN %s is invalid\n", + fname, lineno, brnc.bv_val ); + free( nvnc.bv_val ); + free( pvnc.bv_val ); + return 1; + } + + /* + * The suffix massaging is emulated + * by means of the rewrite capabilities + */ + rc = rwm_suffix_massage_config( rwmap->rwm_rw, + &pvnc, &nvnc, &prnc, &nrnc ); + free( nvnc.bv_val ); + free( pvnc.bv_val ); + free( nrnc.bv_val ); + free( prnc.bv_val ); + + return rc; +} + +static int +rwm_m_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + /* objectclass/attribute mapping */ + return rwm_map_config( &rwmap->rwm_oc, + &rwmap->rwm_at, + fname, lineno, argc, argv ); +} + +static int +rwm_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc; + + if ( op->o_tag == LDAP_REQ_SEARCH && rs->sr_type == REP_SEARCH ) { + return rwm_send_entry( op, rs ); + } + + switch( op->o_tag ) { + case LDAP_REQ_SEARCH: + case LDAP_REQ_BIND: + case LDAP_REQ_ADD: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODRDN: + case LDAP_REQ_MODIFY: + case LDAP_REQ_COMPARE: + case LDAP_REQ_EXTENDED: + if ( rs->sr_ref ) { + dncookie dc; + + /* + * Rewrite the dn of the referrals, if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = NULL; + dc.ctx = "referralDN"; + rc = rwm_referral_result_rewrite( &dc, rs->sr_ref ); + /* FIXME: impossible, so far */ + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + break; + } + } + + rwm_matched( op, rs ); + break; + } + + return SLAP_CB_CONTINUE; +} + +static int +rwm_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int rc = 0; + char *argv0 = NULL; + + if ( strncasecmp( argv[ 0 ], "rwm-", STRLENOF( "rwm-" ) ) == 0 ) { + argv0 = argv[ 0 ]; + argv[ 0 ] = &argv0[ STRLENOF( "rwm-" ) ]; + } + + if ( strncasecmp( argv[0], "rewrite", STRLENOF("rewrite") ) == 0 ) { + rc = rwm_rw_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "map" ) == 0 ) { + rc = rwm_m_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( be, fname, lineno, argc, argv ); + + } else if ( strcasecmp( argv[0], "t-f-support" ) == 0 ) { + if ( argc != 2 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"t-f-support {no|yes|discover}\" needs 1 argument.\n", + fname, lineno ); + return( 1 ); + } + + if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) { + rwmap->rwm_flags &= ~(RWM_F_SUPPORT_T_F_MASK2); + + } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) { + rwmap->rwm_flags |= RWM_F_SUPPORT_T_F; + + /* TODO: not implemented yet */ + } else if ( strcasecmp( argv[ 1 ], "discover" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"discover\" not supported yet " + "in \"t-f-support {no|yes|discover}\".\n", + fname, lineno ); + return( 1 ); +#if 0 + rwmap->rwm_flags |= RWM_F_SUPPORT_T_F_DISCOVER; +#endif + + } else { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: unknown value \"%s\" for \"t-f-support {no|yes|discover}\".\n", + fname, lineno, argv[ 1 ] ); + return 1; + } + + } else if ( strcasecmp( argv[0], "normalize-mapped-attrs" ) == 0 ) { + if ( argc !=2 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: \"normalize-mapped-attrs {no|yes}\" needs 1 argument.\n", + fname, lineno ); + return( 1 ); + } + + if ( strcasecmp( argv[ 1 ], "no" ) == 0 ) { + rwmap->rwm_flags &= ~(RWM_F_NORMALIZE_MAPPED_ATTRS); + + } else if ( strcasecmp( argv[ 1 ], "yes" ) == 0 ) { + rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS; + } + + } else { + rc = SLAP_CONF_UNKNOWN; + } + + if ( argv0 ) { + argv[ 0 ] = argv0; + } + + return rc; +} + +/* + * dynamic configuration... + */ + +enum { + /* rewrite */ + RWM_CF_REWRITE = 1, + + /* map */ + RWM_CF_MAP, + RWM_CF_T_F_SUPPORT, + RWM_CF_NORMALIZE_MAPPED, + RWM_CF_DROP_UNREQUESTED, + + RWM_CF_LAST +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "true" ), RWM_F_SUPPORT_T_F }, + { BER_BVC( "yes" ), RWM_F_SUPPORT_T_F }, + { BER_BVC( "discover" ), RWM_F_SUPPORT_T_F_DISCOVER }, + { BER_BVC( "false" ), RWM_F_NONE }, + { BER_BVC( "no" ), RWM_F_NONE }, + { BER_BVNULL, 0 } +}; + +static ConfigDriver rwm_cf_gen; + +static ConfigTable rwmcfg[] = { + { "rwm-rewrite", "rewrite", + 2, 0, STRLENOF("rwm-rewrite"), + ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen, + "( OLcfgOvAt:16.1 NAME 'olcRwmRewrite' " + "DESC 'Rewrites strings' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "rwm-suffixmassage", "[virtual]> <real", + 2, 3, 0, ARG_MAGIC|RWM_CF_REWRITE, rwm_cf_gen, + NULL, NULL, NULL }, + + { "rwm-t-f-support", "true|false|discover", + 2, 2, 0, ARG_MAGIC|RWM_CF_T_F_SUPPORT, rwm_cf_gen, + "( OLcfgOvAt:16.2 NAME 'olcRwmTFSupport' " + "DESC 'Absolute filters support' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rwm-map", "{objectClass|attribute}", + 2, 4, 0, ARG_MAGIC|RWM_CF_MAP, rwm_cf_gen, + "( OLcfgOvAt:16.3 NAME 'olcRwmMap' " + "DESC 'maps attributes/objectClasses' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "rwm-normalize-mapped-attrs", "true|false", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_NORMALIZE_MAPPED, rwm_cf_gen, + "( OLcfgOvAt:16.4 NAME 'olcRwmNormalizeMapped' " + "DESC 'Normalize mapped attributes/objectClasses' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rwm-drop-unrequested-attrs", "true|false", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|RWM_CF_DROP_UNREQUESTED, rwm_cf_gen, + "( OLcfgOvAt:16.5 NAME 'olcRwmDropUnrequested' " + "DESC 'Drop unrequested attributes' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs rwmocs[] = { + { "( OLcfgOvOc:16.1 " + "NAME 'olcRwmConfig' " + "DESC 'Rewrite/remap configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcRwmRewrite $ " + "olcRwmTFSupport $ " + "olcRwmMap $ " + "olcRwmNormalizeMapped $ " + "olcRwmDropUnrequested" + ") )", + Cft_Overlay, rwmcfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int +rwm_bva_add( + BerVarray *bva, + int idx, + char **argv ) +{ + char *line; + struct berval bv; + + line = ldap_charray2str( argv, "\" \"" ); + if ( line != NULL ) { + int len = strlen( argv[ 0 ] ); + + ber_str2bv( line, 0, 0, &bv ); + AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ], + bv.bv_len - ( len + 1 ) ); + bv.bv_val[ bv.bv_len - 1 ] = '"'; + + if ( idx == -1 ) { + ber_bvarray_add( bva, &bv ); + + } else { + (*bva)[ idx ] = bv; + } + + return 0; + } + + return -1; +} + +static int +rwm_bva_rewrite_add( + struct ldaprwmap *rwmap, + int idx, + char **argv ) +{ + return rwm_bva_add( &rwmap->rwm_bva_rewrite, idx, argv ); +} + +#ifdef unused +static int +rwm_bva_map_add( + struct ldaprwmap *rwmap, + int idx, + char **argv ) +{ + return rwm_bva_add( &rwmap->rwm_bva_map, idx, argv ); +} +#endif /* unused */ + +static int +rwm_info_init( struct rewrite_info ** rwm_rw ) +{ + char *rargv[ 3 ]; + + *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + if ( *rwm_rw == NULL ) { + return -1; + } + + /* this rewriteContext by default must be null; + * rules can be added if required */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchFilter"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 2, 2, rargv ); + + return 0; +} + +static int +rwm_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + BackendDB db; + char *argv0; + int idx0 = 0; + int rc = 0; + + db = *c->be; + db.bd_info = c->bi; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( rwmap->rwm_bva_rewrite == NULL ) { + rc = 1; + + } else { + rc = slap_bv_x_ordered_unparse( rwmap->rwm_bva_rewrite, &c->rvalue_vals ); + } + break; + + case RWM_CF_T_F_SUPPORT: + enum_to_verb( t_f_mode, (rwmap->rwm_flags & RWM_F_SUPPORT_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case RWM_CF_MAP: + if ( rwmap->rwm_bva_map == NULL ) { + rc = 1; + + } else { + slap_bv_x_ordered_unparse( rwmap->rwm_bva_map, &c->rvalue_vals ); + if ( !c->rvalue_vals ) { + rc = 1; + } + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + c->value_int = ( rwmap->rwm_flags & RWM_F_NORMALIZE_MAPPED_ATTRS ); + break; + + case RWM_CF_DROP_UNREQUESTED: + c->value_int = ( rwmap->rwm_flags & RWM_F_DROP_UNREQUESTED_ATTRS ); + break; + + default: + assert( 0 ); + rc = 1; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( c->valx >= 0 ) { + int i; + + for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + /* count'em */ ; + + if ( c->valx >= i ) { + rc = 1; + break; + } + + ber_memfree( rwmap->rwm_bva_rewrite[ c->valx ].bv_val ); + for ( i = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i + 1 ] ); i++ ) + { + rwmap->rwm_bva_rewrite[ i ] = rwmap->rwm_bva_rewrite[ i + 1 ]; + } + BER_BVZERO( &rwmap->rwm_bva_rewrite[ i ] ); + + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + rc = rwm_info_init( &rwmap->rwm_rw ); + + for ( i = 0; !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + } else if ( rwmap->rwm_rw != NULL ) { + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + ber_bvarray_free( rwmap->rwm_bva_rewrite ); + rwmap->rwm_bva_rewrite = NULL; + + rc = rwm_info_init( &rwmap->rwm_rw ); + } + break; + + case RWM_CF_T_F_SUPPORT: + rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2; + break; + + case RWM_CF_MAP: + if ( c->valx >= 0 ) { + struct ldapmap rwm_oc = rwmap->rwm_oc; + struct ldapmap rwm_at = rwmap->rwm_at; + char *argv[5]; + int cnt = 0; + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( c->valx >= cnt ) { + rc = 1; + break; + } + + memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) ); + memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) ); + + /* re-parse all mappings except the one + * that needs to be eliminated */ + argv[0] = "map"; + for ( cnt = 0; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + ConfigArgs ca = { 0 }; + + if ( cnt == c->valx ) { + continue; + } + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + rwmap->rwm_oc = rwm_oc; + rwmap->rwm_at = rwm_at; + break; + } + } + + /* in case of success, destroy the old mapping + * and eliminate the deleted one */ + if ( rc == 0 ) { + ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwm_at.map, rwm_mapping_free ); + + ber_memfree( rwmap->rwm_bva_map[ c->valx ].bv_val ); + for ( cnt = c->valx; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + rwmap->rwm_bva_map[ cnt ] = rwmap->rwm_bva_map[ cnt + 1 ]; + } + } + + } else { + ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + + rwmap->rwm_oc.remap = NULL; + rwmap->rwm_oc.map = NULL; + rwmap->rwm_at.remap = NULL; + rwmap->rwm_at.map = NULL; + + ber_bvarray_free( rwmap->rwm_bva_map ); + rwmap->rwm_bva_map = NULL; + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS; + break; + + case RWM_CF_DROP_UNREQUESTED: + rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS; + break; + + default: + return 1; + } + return rc; + } + + if ( strncasecmp( c->argv[ 0 ], "olcRwm", STRLENOF( "olcRwm" ) ) == 0 ) { + idx0 = 1; + } + + switch ( c->type ) { + case RWM_CF_REWRITE: + if ( c->valx >= 0 ) { + struct rewrite_info *rwm_rw = rwmap->rwm_rw; + int i, last; + + for ( last = 0; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ last ] ); last++ ) + /* count'em */ ; + + if ( c->valx > last ) { + c->valx = last; + } + + rwmap->rwm_rw = NULL; + rc = rwm_info_init( &rwmap->rwm_rw ); + + for ( i = 0; i < c->valx; i++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + argv0 = c->argv[ idx0 ]; + if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) { + return 1; + } + c->argv[ idx0 ] += STRLENOF( "rwm-" ); + if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + } + c->argv[ idx0 ] = argv0; + if ( rc != 0 ) { + rewrite_info_delete( &rwmap->rwm_rw ); + assert( rwmap->rwm_rw == NULL ); + + rwmap->rwm_rw = rwm_rw; + return 1; + } + + for ( i = c->valx; rwmap->rwm_bva_rewrite && !BER_BVISNULL( &rwmap->rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv0 = ca.argv[ 0 ]; + ca.argv[ 0 ] += STRLENOF( "rwm-" ); + + if ( strcasecmp( ca.argv[ 0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + ca.argc, ca.argv ); + } + + ca.argv[ 0 ] = argv0; + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + rwmap->rwm_bva_rewrite = ch_realloc( rwmap->rwm_bva_rewrite, + ( last + 2 )*sizeof( struct berval ) ); + BER_BVZERO( &rwmap->rwm_bva_rewrite[last+1] ); + + for ( i = last - 1; i >= c->valx; i-- ) + { + rwmap->rwm_bva_rewrite[ i + 1 ] = rwmap->rwm_bva_rewrite[ i ]; + } + + rwm_bva_rewrite_add( rwmap, c->valx, &c->argv[ idx0 ] ); + + rewrite_info_delete( &rwm_rw ); + assert( rwm_rw == NULL ); + + break; + } + + argv0 = c->argv[ idx0 ]; + if ( strncasecmp( argv0, "rwm-", STRLENOF( "rwm-" ) ) != 0 ) { + return 1; + } + c->argv[ idx0 ] += STRLENOF( "rwm-" ); + if ( strcasecmp( c->argv[ idx0 ], "suffixmassage" ) == 0 ) { + rc = rwm_suffixmassage_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + + } else { + rc = rwm_rw_config( &db, c->fname, c->lineno, + c->argc - idx0, &c->argv[ idx0 ] ); + } + c->argv[ idx0 ] = argv0; + if ( rc ) { + return 1; + + } else { + rwm_bva_rewrite_add( rwmap, -1, &c->argv[ idx0 ] ); + } + break; + + case RWM_CF_T_F_SUPPORT: + rc = verb_to_mask( c->argv[ 1 ], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[ rc ].word ) ) { + return 1; + } + + rwmap->rwm_flags &= ~RWM_F_SUPPORT_T_F_MASK2; + rwmap->rwm_flags |= t_f_mode[ rc ].mask; + rc = 0; + break; + + case RWM_CF_MAP: + if ( c->valx >= 0 ) { + struct ldapmap rwm_oc = rwmap->rwm_oc; + struct ldapmap rwm_at = rwmap->rwm_at; + char *argv[5]; + int cnt = 0; + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( c->valx >= cnt ) { + c->valx = cnt; + } + + memset( &rwmap->rwm_oc, 0, sizeof( rwmap->rwm_oc ) ); + memset( &rwmap->rwm_at, 0, sizeof( rwmap->rwm_at ) ); + + /* re-parse all mappings, including the one + * that needs to be added */ + argv[0] = "map"; + for ( cnt = 0; cnt < c->valx; cnt++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto rwmmap_fail; + } + } + + argv0 = c->argv[0]; + c->argv[0] = "map"; + rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[0] = argv0; + if ( rc ) { + goto rwmmap_fail; + } + + if ( rwmap->rwm_bva_map ) { + for ( ; !BER_BVISNULL( &rwmap->rwm_bva_map[ cnt ] ); cnt++ ) { + ConfigArgs ca = { 0 }; + + ca.line = rwmap->rwm_bva_map[ cnt ].bv_val; + ca.argc = 0; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + rc = rwm_m_config( &db, c->fname, c->lineno, ca.argc + 1, argv ); + + ch_free( ca.tline ); + ch_free( ca.argv ); + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto rwmmap_fail; + } + } + } + + /* in case of success, destroy the old mapping + * and add the new one */ + if ( rc == 0 ) { + BerVarray tmp; + struct berval bv, *bvp = &bv; + + if ( rwm_bva_add( &bvp, 0, &c->argv[ idx0 ] ) ) { + rc = 1; + goto rwmmap_fail; + } + + tmp = ber_memrealloc( rwmap->rwm_bva_map, + sizeof( struct berval )*( cnt + 2 ) ); + if ( tmp == NULL ) { + ber_memfree( bv.bv_val ); + rc = 1; + goto rwmmap_fail; + } + rwmap->rwm_bva_map = tmp; + BER_BVZERO( &rwmap->rwm_bva_map[ cnt + 1 ] ); + + ldap_avl_free( rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwm_at.map, rwm_mapping_free ); + + for ( ; cnt-- > c->valx; ) { + rwmap->rwm_bva_map[ cnt + 1 ] = rwmap->rwm_bva_map[ cnt ]; + } + rwmap->rwm_bva_map[ c->valx ] = bv; + + } else { +rwmmap_fail:; + ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + rwmap->rwm_oc = rwm_oc; + rwmap->rwm_at = rwm_at; + } + + break; + } + + argv0 = c->argv[ 0 ]; + c->argv[ 0 ] += STRLENOF( "rwm-" ); + rc = rwm_m_config( &db, c->fname, c->lineno, c->argc, c->argv ); + c->argv[ 0 ] = argv0; + if ( rc ) { + return 1; + + } else { + char *line; + struct berval bv; + + line = ldap_charray2str( &c->argv[ 1 ], " " ); + if ( line != NULL ) { + ber_str2bv( line, 0, 0, &bv ); + ber_bvarray_add( &rwmap->rwm_bva_map, &bv ); + } + } + break; + + case RWM_CF_NORMALIZE_MAPPED: + if ( c->value_int ) { + rwmap->rwm_flags |= RWM_F_NORMALIZE_MAPPED_ATTRS; + } else { + rwmap->rwm_flags &= ~RWM_F_NORMALIZE_MAPPED_ATTRS; + } + break; + + case RWM_CF_DROP_UNREQUESTED: + if ( c->value_int ) { + rwmap->rwm_flags |= RWM_F_DROP_UNREQUESTED_ATTRS; + } else { + rwmap->rwm_flags &= ~RWM_F_DROP_UNREQUESTED_ATTRS; + } + break; + + default: + assert( 0 ); + return 1; + } + + return rc; +} + +static int +rwm_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + struct ldaprwmap *rwmap; + int rc = 0; + + rwmap = (struct ldaprwmap *)ch_calloc( 1, sizeof( struct ldaprwmap ) ); + + /* default */ + rwmap->rwm_flags = RWM_F_DROP_UNREQUESTED_ATTRS; + + rc = rwm_info_init( &rwmap->rwm_rw ); + + on->on_bi.bi_private = (void *)rwmap; + + if ( rc ) { + (void)rwm_db_destroy( be, NULL ); + } + + return rc; +} + +static int +rwm_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + int rc = 0; + + if ( on->on_bi.bi_private ) { + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + if ( rwmap->rwm_rw ) { + rewrite_info_delete( &rwmap->rwm_rw ); + if ( rwmap->rwm_bva_rewrite ) + ber_bvarray_free( rwmap->rwm_bva_rewrite ); + } + + ldap_avl_free( rwmap->rwm_oc.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_oc.map, rwm_mapping_free ); + ldap_avl_free( rwmap->rwm_at.remap, rwm_mapping_dst_free ); + ldap_avl_free( rwmap->rwm_at.map, rwm_mapping_free ); + ber_bvarray_free( rwmap->rwm_bva_map ); + + ch_free( rwmap ); + } + + return rc; +} + +static slap_overinst rwm = { { NULL } }; + +#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */ +int +rwm_initialize( void ) +{ + int rc; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( RWM_CF_LAST ); + + memset( &rwm, 0, sizeof( slap_overinst ) ); + + rwm.on_bi.bi_type = "rwm"; + rwm.on_bi.bi_flags = + SLAPO_BFLAG_SINGLE | + 0; + + rwm.on_bi.bi_db_init = rwm_db_init; + rwm.on_bi.bi_db_config = rwm_db_config; + rwm.on_bi.bi_db_destroy = rwm_db_destroy; + + rwm.on_bi.bi_op_bind = rwm_op_bind; + rwm.on_bi.bi_op_search = rwm_op_search; + rwm.on_bi.bi_op_compare = rwm_op_compare; + rwm.on_bi.bi_op_modify = rwm_op_modify; + rwm.on_bi.bi_op_modrdn = rwm_op_modrdn; + rwm.on_bi.bi_op_add = rwm_op_add; + rwm.on_bi.bi_op_delete = rwm_op_delete; + rwm.on_bi.bi_op_unbind = rwm_op_unbind; + rwm.on_bi.bi_extended = rwm_extended; +#if 1 /* TODO */ + rwm.on_bi.bi_entry_release_rw = rwm_entry_release_rw; + rwm.on_bi.bi_entry_get_rw = rwm_entry_get_rw; +#endif + + rwm.on_bi.bi_operational = rwm_operational; + rwm.on_bi.bi_chk_referrals = 0 /* rwm_chk_referrals */ ; + + rwm.on_bi.bi_connection_init = rwm_conn_init; + rwm.on_bi.bi_connection_destroy = rwm_conn_destroy; + + rwm.on_response = rwm_response; + + rwm.on_bi.bi_cf_ocs = rwmocs; + + rc = config_register_schema( rwmcfg, rwmocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &rwm ); +} + +#if SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return rwm_initialize(); +} +#endif /* SLAPD_OVER_RWM == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwm.h b/servers/slapd/overlays/rwm.h new file mode 100644 index 0000000..6753737 --- /dev/null +++ b/servers/slapd/overlays/rwm.h @@ -0,0 +1,183 @@ +/* rwm.h - dn rewrite/attribute mapping header file */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef RWM_H +#define RWM_H + +/* String rewrite library */ +#include "rewrite.h" + +LDAP_BEGIN_DECL + +/* define to enable referral DN massage by default */ +#undef RWM_REFERRAL_REWRITE + +struct ldapmap { + int drop_missing; + + Avlnode *map; + Avlnode *remap; +}; + +struct ldapmapping { + int m_flags; +#define RWMMAP_F_NONE 0x00 +#define RWMMAP_F_IS_OC 0x01 +#define RWMMAP_F_FREE_SRC 0x10 +#define RWMMAP_F_FREE_DST 0x20 + struct berval m_src; + union { + AttributeDescription *m_s_ad; + ObjectClass *m_s_oc; + } m_src_ref; +#define m_src_ad m_src_ref.m_s_ad +#define m_src_oc m_src_ref.m_s_oc + struct berval m_dst; + union { + AttributeDescription *m_d_ad; + ObjectClass *m_d_oc; + } m_dst_ref; +#define m_dst_ad m_dst_ref.m_d_ad +#define m_dst_oc m_dst_ref.m_d_oc +}; + +struct ldaprwmap { + /* + * DN rewriting + */ + struct rewrite_info *rwm_rw; + BerVarray rwm_bva_rewrite; + + /* + * Attribute/objectClass mapping + */ + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + BerVarray rwm_bva_map; + +#define RWM_F_NONE (0x0000U) +#define RWM_F_NORMALIZE_MAPPED_ATTRS (0x0001U) +#define RWM_F_DROP_UNREQUESTED_ATTRS (0x0002U) +#define RWM_F_SUPPORT_T_F (0x4000U) +#define RWM_F_SUPPORT_T_F_DISCOVER (0x8000U) +#define RWM_F_SUPPORT_T_F_MASK (RWM_F_SUPPORT_T_F) +#define RWM_F_SUPPORT_T_F_MASK2 (RWM_F_SUPPORT_T_F|RWM_F_SUPPORT_T_F_DISCOVER) + unsigned rwm_flags; +}; + +/* Whatever context ldap_back_dn_massage needs... */ +typedef struct dncookie { + struct ldaprwmap *rwmap; + + Connection *conn; + char *ctx; + SlapReply *rs; +} dncookie; + +int rwm_dn_massage( dncookie *dc, struct berval *in, struct berval *dn ); +int rwm_dn_massage_pretty( dncookie *dc, struct berval *in, struct berval *pdn ); +int rwm_dn_massage_normalize( dncookie *dc, struct berval *in, struct berval *ndn ); +int rwm_dn_massage_pretty_normalize( dncookie *dc, struct berval *in, struct berval *pdn, struct berval *ndn ); + +/* attributeType/objectClass mapping */ +int rwm_mapping_cmp (const void *, const void *); +int rwm_mapping_dup (void *, void *); + +int rwm_map_init ( struct ldapmap *lm, struct ldapmapping ** ); +void rwm_map ( struct ldapmap *map, struct berval *s, struct berval *m, + int remap ); +int rwm_mapping ( struct ldapmap *map, struct berval *s, + struct ldapmapping **m, int remap ); +#define RWM_MAP 0 +#define RWM_REMAP 1 +char * +rwm_map_filter( + struct ldapmap *at_map, + struct ldapmap *oc_map, + struct berval *f ); + +#if 0 /* unused! */ +int +rwm_map_attrs( + struct ldapmap *at_map, + AttributeName *a, + int remap, + char ***mapped_attrs ); +#endif + +int +rwm_map_attrnames( + Operation *op, + struct ldapmap *at_map, + struct ldapmap *oc_map, + AttributeName *an, + AttributeName **anp, + int remap ); + +extern void rwm_mapping_dst_free ( void *mapping ); + +extern void rwm_mapping_free ( void *mapping ); + +extern int rwm_map_config( + struct ldapmap *oc_map, + struct ldapmap *at_map, + const char *fname, + int lineno, + int argc, + char **argv ); + +extern int +rwm_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ); + +/* suffix massaging by means of librewrite */ +extern int +rwm_suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc); +extern int +rwm_dnattr_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ); +extern int +rwm_referral_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ); +extern int rwm_dnattr_result_rewrite( dncookie *dc, BerVarray a_vals, BerVarray a_nvals ); +extern int rwm_referral_result_rewrite( dncookie *dc, BerVarray a_vals ); + +LDAP_END_DECL + +#endif /* RWM_H */ diff --git a/servers/slapd/overlays/rwmconf.c b/servers/slapd/overlays/rwmconf.c new file mode 100644 index 0000000..a1a9f36 --- /dev/null +++ b/servers/slapd/overlays/rwmconf.c @@ -0,0 +1,413 @@ +/* rwmconf.c - rewrite/map configuration file routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" +#include "lutil.h" + +int +rwm_map_config( + struct ldapmap *oc_map, + struct ldapmap *at_map, + const char *fname, + int lineno, + int argc, + char **argv ) +{ + struct ldapmap *map; + struct ldapmapping *mapping; + char *src, *dst; + int is_oc = 0; + int rc = 0; + + if ( argc < 3 || argc > 4 ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: syntax is \"map {objectclass | attribute} [<local> | *] {<foreign> | *}\"\n", + fname, lineno ); + return 1; + } + + if ( strcasecmp( argv[1], "objectclass" ) == 0 ) { + map = oc_map; + is_oc = 1; + + } else if ( strcasecmp( argv[1], "attribute" ) == 0 ) { + map = at_map; + + } else { + Debug( LDAP_DEBUG_ANY, "%s: line %d: syntax is " + "\"map {objectclass | attribute} [<local> | *] " + "{<foreign> | *}\"\n", + fname, lineno ); + return 1; + } + + if ( !is_oc && map->map == NULL ) { + /* only init if required */ + if ( rwm_map_init( map, &mapping ) != LDAP_SUCCESS ) { + return 1; + } + } + + if ( strcmp( argv[2], "*" ) == 0 ) { + if ( argc < 4 || strcmp( argv[3], "*" ) == 0 ) { + map->drop_missing = ( argc < 4 ); + goto success_return; + } + src = dst = argv[3]; + + } else if ( argc < 4 ) { + src = ""; + dst = argv[2]; + + } else { + src = argv[2]; + dst = ( strcmp( argv[3], "*" ) == 0 ? src : argv[3] ); + } + + if ( ( map == at_map ) + && ( strcasecmp( src, "objectclass" ) == 0 + || strcasecmp( dst, "objectclass" ) == 0 ) ) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: objectclass attribute cannot be mapped\n", + fname, lineno ); + return 1; + } + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof(struct ldapmapping) ); + if ( mapping == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: out of memory\n", + fname, lineno ); + return 1; + } + ber_str2bv( src, 0, 1, &mapping[0].m_src ); + ber_str2bv( dst, 0, 1, &mapping[0].m_dst ); + mapping[1].m_src = mapping[0].m_dst; + mapping[1].m_dst = mapping[0].m_src; + + mapping[0].m_flags = RWMMAP_F_NONE; + mapping[1].m_flags = RWMMAP_F_NONE; + + /* + * schema check + */ + if ( is_oc ) { + if ( src[0] != '\0' ) { + mapping[0].m_src_oc = oc_bvfind( &mapping[0].m_src ); + if ( mapping[0].m_src_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, source objectClass '%s' " + "should be defined in schema\n", + fname, lineno, src ); + + /* + * FIXME: this should become an err + */ + mapping[0].m_src_oc = ch_malloc( sizeof( ObjectClass ) ); + memset( mapping[0].m_src_oc, 0, sizeof( ObjectClass ) ); + mapping[0].m_src_oc->soc_cname = mapping[0].m_src; + mapping[0].m_flags |= RWMMAP_F_FREE_SRC; + } + mapping[1].m_dst_oc = mapping[0].m_src_oc; + } + + mapping[0].m_dst_oc = oc_bvfind( &mapping[0].m_dst ); + if ( mapping[0].m_dst_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, destination objectClass '%s' " + "is not defined in schema\n", + fname, lineno, dst ); + + mapping[0].m_dst_oc = oc_bvfind_undef( &mapping[0].m_dst ); + if ( mapping[0].m_dst_oc == NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: line %d: unable to mimic destination objectClass '%s'\n", + fname, lineno, dst ); + goto error_return; + } + } + mapping[1].m_src_oc = mapping[0].m_dst_oc; + + mapping[0].m_flags |= RWMMAP_F_IS_OC; + mapping[1].m_flags |= RWMMAP_F_IS_OC; + + } else { + int rc; + const char *text = NULL; + + if ( src[0] != '\0' ) { + rc = slap_bv2ad( &mapping[0].m_src, + &mapping[0].m_src_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, source attributeType '%s' " + "should be defined in schema\n", + fname, lineno, src ); + + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[0].m_src, + &mapping[0].m_src_ad, &text, + SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: line %d: source attributeType '%s': %d (%s)\n", + fname, lineno, src, rc, + text ? text : "null" ); + goto error_return; + } + + } + mapping[1].m_dst_ad = mapping[0].m_src_ad; + } + + rc = slap_bv2ad( &mapping[0].m_dst, &mapping[0].m_dst_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: warning, destination attributeType '%s' " + "is not defined in schema\n", + fname, lineno, dst ); + + rc = slap_bv2undef_ad( &mapping[0].m_dst, + &mapping[0].m_dst_ad, &text, + SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: line %d: destination attributeType '%s': %d (%s)\n", + fname, lineno, dst, rc, + text ? text : "null" ); + goto error_return; + } + } + mapping[1].m_src_ad = mapping[0].m_dst_ad; + } + + if ( ( src[0] != '\0' && ldap_avl_find( map->map, (caddr_t)mapping, rwm_mapping_cmp ) != NULL) + || ldap_avl_find( map->remap, (caddr_t)&mapping[1], rwm_mapping_cmp ) != NULL) + { + Debug( LDAP_DEBUG_ANY, + "%s: line %d: duplicate mapping found.\n", + fname, lineno ); + /* FIXME: free stuff */ + goto error_return; + } + + if ( src[0] != '\0' ) { + ldap_avl_insert( &map->map, (caddr_t)&mapping[0], + rwm_mapping_cmp, rwm_mapping_dup ); + } + ldap_avl_insert( &map->remap, (caddr_t)&mapping[1], + rwm_mapping_cmp, rwm_mapping_dup ); + +success_return:; + return rc; + +error_return:; + if ( mapping ) { + rwm_mapping_free( mapping ); + } + + return 1; +} + +static char * +rwm_suffix_massage_regexize( const char *s ) +{ + char *res, *ptr; + const char *p, *r; + int i; + + if ( s[0] == '\0' ) { + return ch_strdup( "^(.+)$" ); + } + + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1, i++ ) + ; + + res = ch_calloc( sizeof( char ), strlen( s ) + + STRLENOF( "((.+),)?" ) + + STRLENOF( "[ ]?" ) * i + + STRLENOF( "$" ) + 1 ); + + ptr = lutil_strcopy( res, "((.+),)?" ); + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1 , i++ ) { + ptr = lutil_strncopy( ptr, p, r - p + 1 ); + ptr = lutil_strcopy( ptr, "[ ]?" ); + + if ( r[ 1 ] == ' ' ) { + r++; + } + } + ptr = lutil_strcopy( ptr, p ); + ptr[0] = '$'; + ptr[1] = '\0'; + + return res; +} + +static char * +rwm_suffix_massage_patternize( const char *s, const char *p ) +{ + ber_len_t len; + char *res, *ptr; + + len = strlen( p ); + + if ( s[ 0 ] == '\0' ) { + len++; + } + + res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 ); + if ( res == NULL ) { + return NULL; + } + + ptr = lutil_strcopy( res, ( p[0] == '\0' ? "%2" : "%1" ) ); + if ( s[ 0 ] == '\0' ) { + ptr[ 0 ] = ','; + ptr++; + } + lutil_strcopy( ptr, p ); + + return res; +} + +int +rwm_suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc +) +{ + char *rargv[ 5 ]; + int line = 0; + + rargv[ 0 ] = "rewriteEngine"; + rargv[ 1 ] = "on"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = rwm_suffix_massage_regexize( pvnc->bv_val ); + rargv[ 2 ] = rwm_suffix_massage_patternize( pvnc->bv_val, prnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( pvnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = prnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchEntryDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = rwm_suffix_massage_regexize( prnc->bv_val ); + rargv[ 2 ] = rwm_suffix_massage_patternize( prnc->bv_val, pvnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( prnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = pvnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "matchedDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + +#ifdef RWM_REFERRAL_REWRITE + /* FIXME: we don't want this on by default, do we? */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); +#else /* ! RWM_REFERRAL_REWRITE */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralAttrDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); +#endif /* ! RWM_REFERRAL_REWRITE */ + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchAttrDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + return 0; +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwmdn.c b/servers/slapd/overlays/rwmdn.c new file mode 100644 index 0000000..c67e3cf --- /dev/null +++ b/servers/slapd/overlays/rwmdn.c @@ -0,0 +1,215 @@ +/* rwmdn.c - massages dns */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" + +/* FIXME: after rewriting, we should also remap attributes ... */ + +/* + * massages "in" and normalizes it into "ndn" + * + * "ndn" may be untouched if no massaging occurred and its value was not null + */ +int +rwm_dn_massage_normalize( + dncookie *dc, + struct berval *in, + struct berval *ndn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage and normalize a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( ndn ) ) { + return rc; + } + + rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" and prettifies it into "pdn" + * + * "pdn" may be untouched if no massaging occurred and its value was not null + */ +int +rwm_dn_massage_pretty( + dncookie *dc, + struct berval *in, + struct berval *pdn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage and pretty a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) { + return rc; + } + + rc = dnPretty( NULL, &mdn, pdn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" and prettifies and normalizes it into "pdn" and "ndn" + * + * "pdn" may be untouched if no massaging occurred and its value was not null; + * "ndn" may be untouched if no massaging occurred and its value was not null; + * if no massage occurred and "ndn" value was not null, it is filled + * with the normalized value of "pdn", much like ndn = dnNormalize( pdn ) + */ +int +rwm_dn_massage_pretty_normalize( + dncookie *dc, + struct berval *in, + struct berval *pdn, + struct berval *ndn ) +{ + int rc; + struct berval mdn = BER_BVNULL; + + /* massage, pretty and normalize a DN */ + rc = rwm_dn_massage( dc, in, &mdn ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( mdn.bv_val == in->bv_val && !BER_BVISNULL( pdn ) ) { + if ( BER_BVISNULL( ndn ) ) { + rc = dnNormalize( 0, NULL, NULL, &mdn, ndn, NULL ); + } + return rc; + } + + rc = dnPrettyNormal( NULL, &mdn, pdn, ndn, NULL ); + + if ( mdn.bv_val != in->bv_val ) { + ch_free( mdn.bv_val ); + } + + return rc; +} + +/* + * massages "in" into "dn" + * + * "dn" may contain the value of "in" if no massage occurred + */ +int +rwm_dn_massage( + dncookie *dc, + struct berval *in, + struct berval *dn +) +{ + int rc = 0; + struct berval mdn; + static char *dmy = ""; + char *in_val; + + assert( dc != NULL ); + assert( in != NULL ); + assert( dn != NULL ); + + /* protect from NULL berval */ + in_val = in->bv_val ? in->bv_val : dmy; + + rc = rewrite_session( dc->rwmap->rwm_rw, dc->ctx, + in_val, dc->conn, &mdn.bv_val ); + switch ( rc ) { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( &mdn ) && mdn.bv_val != in_val ) { + mdn.bv_len = strlen( mdn.bv_val ); + *dn = mdn; + } else { + dn->bv_len = in->bv_len; + dn->bv_val = in_val; + } + rc = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + dc->ctx, in_val, dn->bv_val ); + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + dc->rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_OTHER; + dc->rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( mdn.bv_val == dmy ) { + BER_BVZERO( &mdn ); + } + + if ( dn->bv_val == dmy ) { + BER_BVZERO( dn ); + } + + return rc; +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/rwmmap.c b/servers/slapd/overlays/rwmmap.c new file mode 100644 index 0000000..74ffd05 --- /dev/null +++ b/servers/slapd/overlays/rwmmap.c @@ -0,0 +1,1347 @@ +/* rwmmap.c - rewrite/mapping routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2022 The OpenLDAP Foundation. + * Portions Copyright 1999-2003 Howard Chu. + * Portions Copyright 2000-2003 Pierangelo Masarati. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_RWM + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "rwm.h" + +#undef ldap_debug /* silence a warning in ldap-int.h */ +#include "../../../libraries/libldap/ldap-int.h" + +int +rwm_mapping_cmp( const void *c1, const void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->m_src.bv_len - map2->m_src.bv_len; + + if ( rc ) { + return rc; + } + + return strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ); +} + +int +rwm_mapping_dup( void *c1, void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->m_src.bv_len - map2->m_src.bv_len; + + if ( rc ) { + return 0; + } + + return ( ( strcasecmp( map1->m_src.bv_val, map2->m_src.bv_val ) == 0 ) ? -1 : 0 ); +} + +int +rwm_map_init( struct ldapmap *lm, struct ldapmapping **m ) +{ + struct ldapmapping *mapping; + const char *text; + int rc; + + assert( m != NULL ); + + *m = NULL; + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof( struct ldapmapping ) ); + if ( mapping == NULL ) { + return LDAP_NO_MEMORY; + } + + /* NOTE: this is needed to make sure that + * rwm-map attribute * + * does not filter out all attributes including objectClass */ + rc = slap_str2ad( "objectClass", &mapping[0].m_src_ad, &text ); + if ( rc != LDAP_SUCCESS ) { + ch_free( mapping ); + return rc; + } + + mapping[0].m_dst_ad = mapping[0].m_src_ad; + ber_dupbv( &mapping[0].m_src, &mapping[0].m_src_ad->ad_cname ); + ber_dupbv( &mapping[0].m_dst, &mapping[0].m_src ); + + mapping[1].m_src = mapping[0].m_src; + mapping[1].m_dst = mapping[0].m_dst; + mapping[1].m_src_ad = mapping[0].m_src_ad; + mapping[1].m_dst_ad = mapping[1].m_src_ad; + + ldap_avl_insert( &lm->map, (caddr_t)&mapping[0], + rwm_mapping_cmp, rwm_mapping_dup ); + ldap_avl_insert( &lm->remap, (caddr_t)&mapping[1], + rwm_mapping_cmp, rwm_mapping_dup ); + + *m = mapping; + + return rc; +} + +int +rwm_mapping( struct ldapmap *map, struct berval *s, struct ldapmapping **m, int remap ) +{ + Avlnode *tree; + struct ldapmapping fmapping; + + if ( map == NULL ) { + return 0; + } + + assert( m != NULL ); + + /* let special attrnames slip through (ITS#5760) */ + if ( bvmatch( s, slap_bv_no_attrs ) + || bvmatch( s, slap_bv_all_user_attrs ) + || bvmatch( s, slap_bv_all_operational_attrs ) ) + { + *m = NULL; + return 0; + } + + if ( remap == RWM_REMAP ) { + tree = map->remap; + + } else { + tree = map->map; + } + + fmapping.m_src = *s; + *m = (struct ldapmapping *)ldap_avl_find( tree, (caddr_t)&fmapping, + rwm_mapping_cmp ); + + if ( *m == NULL ) { + return map->drop_missing; + } + + return 0; +} + +void +rwm_map( struct ldapmap *map, struct berval *s, struct berval *bv, int remap ) +{ + struct ldapmapping *mapping; + + /* map->map may be NULL when mapping is configured, + * but map->remap can't */ + if ( map->remap == NULL ) { + *bv = *s; + return; + } + + BER_BVZERO( bv ); + ( void )rwm_mapping( map, s, &mapping, remap ); + if ( mapping != NULL ) { + if ( !BER_BVISNULL( &mapping->m_dst ) ) { + *bv = mapping->m_dst; + } + return; + } + + if ( !map->drop_missing ) { + *bv = *s; + } +} + +/* + * Map attribute names in place + */ +int +rwm_map_attrnames( + Operation *op, + struct ldapmap *at_map, + struct ldapmap *oc_map, + AttributeName *an, + AttributeName **anp, + int remap ) +{ + int i, j, x; + + assert( anp != NULL ); + + *anp = NULL; + + if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) { + return LDAP_SUCCESS; + } + + i = 0; + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) + /* just count */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* just count */ ; + } + + assert( i > 0 || x > 0 ); + *anp = op->o_tmpcalloc( ( i + x + 1 ), sizeof( AttributeName ), + op->o_tmpmemctx ); + if ( *anp == NULL ) { + return LDAP_NO_MEMORY; + } + + j = 0; + if ( an != NULL ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + struct ldapmapping *m; + int at_drop_missing = 0, + oc_drop_missing = 0; + + if ( an[i].an_desc ) { + if ( !at_map ) { + /* FIXME: better leave as is? */ + continue; + } + + at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap ); + if ( at_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) { + continue; + } + + if ( !m ) { + (*anp)[j] = an[i]; + j++; + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_desc = m->m_dst_ad; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_desc = m->m_src_ad; + + } + + j++; + continue; + + } else if ( an[i].an_oc ) { + if ( !oc_map ) { + /* FIXME: better leave as is? */ + continue; + } + + oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap ); + + if ( oc_drop_missing || ( m && BER_BVISNULL( &m->m_dst ) ) ) { + continue; + } + + if ( !m ) { + (*anp)[j] = an[i]; + j++; + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_oc = m->m_dst_oc; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_oc = m->m_src_oc; + } + + } else { + at_drop_missing = rwm_mapping( at_map, &an[i].an_name, &m, remap ); + + if ( at_drop_missing || !m ) { + oc_drop_missing = rwm_mapping( oc_map, &an[i].an_name, &m, remap ); + + /* if both at_map and oc_map required to drop missing, + * then do it */ + if ( oc_drop_missing && at_drop_missing ) { + continue; + } + + /* if no oc_map mapping was found and at_map required + * to drop missing, then do it; otherwise, at_map wins + * and an is considered an attr and is left unchanged */ + if ( !m ) { + if ( at_drop_missing ) { + continue; + } + (*anp)[j] = an[i]; + j++; + continue; + } + + if ( BER_BVISNULL( &m->m_dst ) ) { + continue; + } + + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_oc = m->m_dst_oc; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_oc = m->m_src_oc; + } + j++; + continue; + } + + if ( !BER_BVISNULL( &m->m_dst ) ) { + (*anp)[j] = an[i]; + if ( remap == RWM_MAP ) { + (*anp)[j].an_name = m->m_dst; + (*anp)[j].an_desc = m->m_dst_ad; + } else { + (*anp)[j].an_name = m->m_src; + (*anp)[j].an_desc = m->m_src_ad; + } + j++; + continue; + } + } + } + } + + if ( op->o_bd->be_extra_anlist != NULL ) { + /* we assume be_extra_anlist are already mapped */ + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) { + BER_BVZERO( &(*anp)[j].an_name ); + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, *anp ) ) + { + continue; + } + + (*anp)[j] = op->o_bd->be_extra_anlist[x]; + j++; + } + } + + if ( j == 0 && ( i != 0 || x != 0 ) ) { + memset( &(*anp)[0], 0, sizeof( AttributeName ) ); + (*anp)[0].an_name = *slap_bv_no_attrs; + j = 1; + } + memset( &(*anp)[j], 0, sizeof( AttributeName ) ); + + return LDAP_SUCCESS; +} + +#if 0 /* unused! */ +int +rwm_map_attrs( + struct ldapmap *at_map, + AttributeName *an, + int remap, + char ***mapped_attrs ) +{ + int i, j; + char **na; + + if ( an == NULL ) { + *mapped_attrs = NULL; + return LDAP_SUCCESS; + } + + for ( i = 0; !BER_BVISNULL( &an[ i ].an_name ); i++ ) + /* count'em */ ; + + na = (char **)ch_calloc( i + 1, sizeof( char * ) ); + if ( na == NULL ) { + *mapped_attrs = NULL; + return LDAP_NO_MEMORY; + } + + for ( i = j = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + struct ldapmapping *mapping; + + if ( rwm_mapping( at_map, &an[i].an_name, &mapping, remap ) ) { + continue; + } + + if ( !mapping ) { + na[ j++ ] = an[ i ].an_name.bv_val; + + } else if ( !BER_BVISNULL( &mapping->m_dst ) ) { + na[ j++ ] = mapping->m_dst.bv_val; + } + } + + if ( j == 0 && i != 0 ) { + na[ j++ ] = LDAP_NO_ATTRS; + } + + na[ j ] = NULL; + + *mapped_attrs = na; + + return LDAP_SUCCESS; +} +#endif + +static int +map_attr_value( + dncookie *dc, + AttributeDescription **adp, + struct berval *mapped_attr, + struct berval *value, + struct berval *mapped_value, + int remap, + void *memctx ) +{ + struct berval vtmp = BER_BVNULL; + int freeval = 0; + AttributeDescription *ad = *adp; + struct ldapmapping *mapping = NULL; + + rwm_mapping( &dc->rwmap->rwm_at, &ad->ad_cname, &mapping, remap ); + if ( mapping == NULL ) { + if ( dc->rwmap->rwm_at.drop_missing ) { + return -1; + } + + *mapped_attr = ad->ad_cname; + + } else { + *mapped_attr = mapping->m_dst; + } + + if ( value != NULL ) { + assert( mapped_value != NULL ); + + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName + || ( mapping != NULL && mapping->m_dst_ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) ) + { + dncookie fdc = *dc; + int rc; + + fdc.ctx = "searchFilterAttrDN"; + + vtmp = *value; + rc = rwm_dn_massage_normalize( &fdc, value, &vtmp ); + switch ( rc ) { + case LDAP_SUCCESS: + if ( vtmp.bv_val != value->bv_val ) { + freeval = 1; + } + break; + + case LDAP_UNWILLING_TO_PERFORM: + case LDAP_OTHER: + default: + return -1; + } + + } else if ( ad->ad_type->sat_equality && + ( ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) ) + { + if ( ad->ad_type->sat_equality->smr_normalize( + (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX), + NULL, NULL, value, &vtmp, memctx ) ) + { + return -1; + } + freeval = 2; + + } else if ( ad == slap_schema.si_ad_objectClass + || ad == slap_schema.si_ad_structuralObjectClass ) + { + rwm_map( &dc->rwmap->rwm_oc, value, &vtmp, remap ); + if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) { + vtmp = *value; + } + + } else { + vtmp = *value; + } + + filter_escape_value_x( &vtmp, mapped_value, memctx ); + + switch ( freeval ) { + case 1: + ch_free( vtmp.bv_val ); + break; + + case 2: + ber_memfree_x( vtmp.bv_val, memctx ); + break; + } + } + + if ( mapping != NULL ) { + assert( mapping->m_dst_ad != NULL ); + *adp = mapping->m_dst_ad; + } + + return 0; +} + +static int +rwm_int_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ) +{ + int i; + Filter *p, ftmp; + AttributeDescription *ad; + struct berval atmp, + vtmp, + *tmp; + static struct berval + /* better than nothing... */ + ber_bvfalse = BER_BVC( "(!(objectClass=*))" ), + ber_bvtf_false = BER_BVC( "(|)" ), + /* better than nothing... */ + ber_bvtrue = BER_BVC( "(objectClass=*)" ), + ber_bvtf_true = BER_BVC( "(&)" ), +#if 0 + /* no longer needed; preserved for completeness */ + ber_bvundefined = BER_BVC( "(?=undefined)" ), +#endif + ber_bverror = BER_BVC( "(?=error)" ), + ber_bvunknown = BER_BVC( "(?=unknown)" ), + ber_bvnone = BER_BVC( "(?=none)" ); + ber_len_t len; + + assert( fstr != NULL ); + BER_BVZERO( fstr ); + + if ( f == NULL ) { + ber_dupbv_x( fstr, &ber_bvnone, op->o_tmpmemctx ); + return LDAP_OTHER; + } + +#if 0 + /* ITS#6814: give the caller a chance to use undefined filters */ + if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) { + goto computed; + } +#endif + + switch ( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_GE: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(>=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_LE: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(<=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_APPROX: + ad = f->f_av_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_av_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + STRLENOF( "(~=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + ad = f->f_sub_desc; + if ( map_attr_value( dc, &ad, &atmp, + NULL, NULL, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + /* cannot be a DN ... */ + + fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_initial, &vtmp, op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3, + /* "(attr=" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) { + len = fstr->bv_len; + filter_escape_value_x( &f->f_sub_any[i], &vtmp, + op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len + 1; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init]*[any*]" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_final, &vtmp, op->o_tmpmemctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init*][any*]" */ "%s)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + ad = f->f_desc; + if ( map_attr_value( dc, &ad, &atmp, + NULL, NULL, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + STRLENOF( "(=*)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + fstr->bv_len = STRLENOF( "(%)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 128, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)", + f->f_choice == LDAP_FILTER_AND ? '&' : + f->f_choice == LDAP_FILTER_OR ? '|' : '!' ); + + for ( p = f->f_list; p != NULL; p = p->f_next ) { + int rc; + + len = fstr->bv_len; + + rc = rwm_int_filter_map_rewrite( op, dc, p, &vtmp ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, + op->o_tmpmemctx ); + + snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2, + /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" ); + + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + } + + break; + + case LDAP_FILTER_EXT: { + if ( f->f_mr_desc ) { + ad = f->f_mr_desc; + if ( map_attr_value( dc, &ad, &atmp, + &f->f_mr_value, &vtmp, RWM_MAP, op->o_tmpmemctx ) ) + { + goto computed; + } + + } else { + BER_BVSTR( &atmp, "" ); + filter_escape_value_x( &f->f_mr_value, &vtmp, op->o_tmpmemctx ); + } + + + fstr->bv_len = atmp.bv_len + + ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) + + ( f->f_mr_rule_text.bv_len ? f->f_mr_rule_text.bv_len + 1 : 0 ) + + vtmp.bv_len + STRLENOF( "(:=)" ); + fstr->bv_val = op->o_tmpalloc( fstr->bv_len + 1, op->o_tmpmemctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)", + atmp.bv_val, + f->f_mr_dnattrs ? ":dn" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "", + vtmp.bv_len ? vtmp.bv_val : "" ); + op->o_tmpfree( vtmp.bv_val, op->o_tmpmemctx ); + break; + } + + case -1: +computed:; + f = &ftmp; + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = SLAPD_COMPARE_UNDEFINED; + /* fallthru */ + + case SLAPD_FILTER_COMPUTED: + switch ( f->f_result ) { + case LDAP_COMPARE_FALSE: + /* FIXME: treat UNDEFINED as FALSE */ + case SLAPD_COMPARE_UNDEFINED: + if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) { + tmp = &ber_bvtf_false; + break; + } + tmp = &ber_bvfalse; + break; + + case LDAP_COMPARE_TRUE: + if ( dc->rwmap->rwm_flags & RWM_F_SUPPORT_T_F ) { + tmp = &ber_bvtf_true; + break; + } + tmp = &ber_bvtrue; + break; + + default: + tmp = &ber_bverror; + break; + } + + ber_dupbv_x( fstr, tmp, op->o_tmpmemctx ); + break; + + default: + ber_dupbv_x( fstr, &ber_bvunknown, op->o_tmpmemctx ); + break; + } + + return LDAP_SUCCESS; +} + +int +rwm_filter_map_rewrite( + Operation *op, + dncookie *dc, + Filter *f, + struct berval *fstr ) +{ + int rc; + dncookie fdc; + struct berval ftmp; + + rc = rwm_int_filter_map_rewrite( op, dc, f, fstr ); + + if ( rc != 0 ) { + return rc; + } + + fdc = *dc; + ftmp = *fstr; + + fdc.ctx = "searchFilter"; + + switch ( rewrite_session( fdc.rwmap->rwm_rw, fdc.ctx, + ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : "" ), + fdc.conn, &fstr->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( fstr ) ) { + fstr->bv_len = strlen( fstr->bv_val ); + + } else { + *fstr = ftmp; + } + + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + fdc.ctx, ftmp.bv_val, fstr->bv_val ); + if ( fstr->bv_val != ftmp.bv_val ) { + ber_bvreplace_x( &ftmp, fstr, op->o_tmpmemctx ); + ch_free( fstr->bv_val ); + *fstr = ftmp; + } + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + fdc.rs->sr_text = "Operation not allowed"; + } + op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx ); + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_OTHER; + fdc.rs->sr_text = "Rewrite error"; + } + op->o_tmpfree( ftmp.bv_val, op->o_tmpmemctx ); + rc = LDAP_OTHER; + break; + } + + return rc; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +rwm_referral_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int i, last; + + dncookie dc; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + + assert( a_vals != NULL ); + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + if ( pa_nvals != NULL ) { + if ( *pa_nvals == NULL ) { + *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) ); + memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) ); + } + } + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval olddn = BER_BVNULL, + oldval; + int rc; + LDAPURLDesc *ludp; + + oldval = a_vals[i]; + rc = ldap_url_parse( oldval.bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ber_dupbv( &(*pa_nvals)[i], &oldval ); + } + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + dn = olddn; + if ( pa_nvals ) { + ndn = olddn; + rc = rwm_dn_massage_pretty_normalize( &dc, &olddn, + &dn, &ndn ); + } else { + rc = rwm_dn_massage_pretty( &dc, &olddn, &dn ); + } + + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if (last > i ) { + a_vals[i] = a_vals[last]; + if ( pa_nvals ) { + (*pa_nvals)[i] = (*pa_nvals)[last]; + } + } + BER_BVZERO( &a_vals[last] ); + if ( pa_nvals ) { + BER_BVZERO( &(*pa_nvals)[last] ); + } + last--; + break; + + case LDAP_SUCCESS: + if ( !BER_BVISNULL( &dn ) && dn.bv_val != olddn.bv_val ) { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + ludp->lud_dn = olddn.bv_val; + ch_free( dn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ber_str2bv( newurl, 0, 1, &a_vals[i] ); + ber_memfree( newurl ); + + if ( pa_nvals ) { + ludp->lud_dn = ndn.bv_val; + newurl = ldap_url_desc2str( ludp ); + ludp->lud_dn = olddn.bv_val; + ch_free( ndn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + ch_free( a_vals[i].bv_val ); + a_vals[i] = oldval; + break; + } + + if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ch_free( (*pa_nvals)[i].bv_val ); + } + ber_str2bv( newurl, 0, 1, &(*pa_nvals)[i] ); + ber_memfree( newurl ); + } + + ch_free( oldval.bv_val ); + ludp->lud_dn = olddn.bv_val; + } + break; + + default: + /* leave attr untouched if massage failed */ + if ( pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ber_dupbv( &(*pa_nvals)[i], &a_vals[i] ); + } + break; + } + ldap_free_urldesc( ludp ); + } + + return 0; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +rwm_dnattr_rewrite( + Operation *op, + SlapReply *rs, + void *cookie, + BerVarray a_vals, + BerVarray *pa_nvals ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct ldaprwmap *rwmap = + (struct ldaprwmap *)on->on_bi.bi_private; + + int i, last; + + dncookie dc; + struct berval dn = BER_BVNULL, + ndn = BER_BVNULL; + BerVarray in; + + if ( a_vals ) { + in = a_vals; + + } else { + if ( pa_nvals == NULL || *pa_nvals == NULL ) { + return LDAP_OTHER; + } + in = *pa_nvals; + } + + /* + * Rewrite the dn if needed + */ + dc.rwmap = rwmap; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = (char *)cookie; + + for ( last = 0; !BER_BVISNULL( &in[last] ); last++ ); + last--; + if ( pa_nvals != NULL ) { + if ( *pa_nvals == NULL ) { + *pa_nvals = ch_malloc( ( last + 2 ) * sizeof(struct berval) ); + memset( *pa_nvals, 0, ( last + 2 ) * sizeof(struct berval) ); + } + } + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + int rc; + + if ( a_vals ) { + dn = in[i]; + if ( pa_nvals ) { + ndn = (*pa_nvals)[i]; + rc = rwm_dn_massage_pretty_normalize( &dc, &in[i], &dn, &ndn ); + } else { + rc = rwm_dn_massage_pretty( &dc, &in[i], &dn ); + } + } else { + ndn = in[i]; + rc = rwm_dn_massage_normalize( &dc, &in[i], &ndn ); + } + + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( in[i].bv_val ); + if (last > i ) { + in[i] = in[last]; + if ( a_vals && pa_nvals ) { + (*pa_nvals)[i] = (*pa_nvals)[last]; + } + } + BER_BVZERO( &in[last] ); + if ( a_vals && pa_nvals ) { + BER_BVZERO( &(*pa_nvals)[last] ); + } + last--; + break; + + case LDAP_SUCCESS: + if ( a_vals ) { + if ( !BER_BVISNULL( &dn ) && dn.bv_val != a_vals[i].bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = dn; + + if ( pa_nvals ) { + if ( !BER_BVISNULL( &(*pa_nvals)[i] ) ) { + ch_free( (*pa_nvals)[i].bv_val ); + } + (*pa_nvals)[i] = ndn; + } + } + + } else { + if ( !BER_BVISNULL( &ndn ) && ndn.bv_val != (*pa_nvals)[i].bv_val ) { + ch_free( (*pa_nvals)[i].bv_val ); + (*pa_nvals)[i] = ndn; + } + } + break; + + default: + /* leave attr untouched if massage failed */ + if ( a_vals && pa_nvals && BER_BVISNULL( &(*pa_nvals)[i] ) ) { + dnNormalize( 0, NULL, NULL, &a_vals[i], &(*pa_nvals)[i], NULL ); + } + break; + } + } + + return 0; +} + +int +rwm_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals ) +{ + int i, last; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ); + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval dn, + olddn = BER_BVNULL; + int rc; + LDAPURLDesc *ludp; + + rc = ldap_url_parse( a_vals[i].bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + dn = olddn; + rc = rwm_dn_massage_pretty( dc, &olddn, &dn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + i--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ch_free( a_vals[i].bv_val ); + ber_str2bv( newurl, 0, 1, &a_vals[i] ); + ber_memfree( newurl ); + ludp->lud_dn = olddn.bv_val; + } + break; + } + + ldap_free_urldesc( ludp ); + } + + return 0; +} + +int +rwm_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals, + BerVarray a_nvals ) +{ + int i, last; + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ); + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + struct berval pdn, ndn = BER_BVNULL; + int rc; + + pdn = a_vals[i]; + rc = rwm_dn_massage_pretty_normalize( dc, &a_vals[i], &pdn, &ndn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + assert( a_vals[i].bv_val != a_nvals[i].bv_val ); + ch_free( a_vals[i].bv_val ); + ch_free( a_nvals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + a_nvals[i] = a_nvals[last]; + } + BER_BVZERO( &a_vals[last] ); + BER_BVZERO( &a_nvals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &pdn ) && a_vals[i].bv_val != pdn.bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = pdn; + } + if ( !BER_BVISNULL( &ndn ) && a_nvals[i].bv_val != ndn.bv_val ) { + ch_free( a_nvals[i].bv_val ); + a_nvals[i] = ndn; + } + break; + } + } + + return 0; +} + +void +rwm_mapping_dst_free( void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( BER_BVISEMPTY( &mapping[0].m_dst ) ) { + rwm_mapping_free( &mapping[ -1 ] ); + } +} + +void +rwm_mapping_free( void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( !BER_BVISNULL( &mapping[0].m_src ) ) { + ch_free( mapping[0].m_src.bv_val ); + } + + if ( mapping[0].m_flags & RWMMAP_F_FREE_SRC ) { + if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) { + if ( mapping[0].m_src_oc ) { + ch_free( mapping[0].m_src_oc ); + } + + } else { + if ( mapping[0].m_src_ad ) { + ch_free( mapping[0].m_src_ad ); + } + } + } + + if ( !BER_BVISNULL( &mapping[0].m_dst ) ) { + ch_free( mapping[0].m_dst.bv_val ); + } + + if ( mapping[0].m_flags & RWMMAP_F_FREE_DST ) { + if ( mapping[0].m_flags & RWMMAP_F_IS_OC ) { + if ( mapping[0].m_dst_oc ) { + ch_free( mapping[0].m_dst_oc ); + } + + } else { + if ( mapping[0].m_dst_ad ) { + ch_free( mapping[0].m_dst_ad ); + } + } + } + + ch_free( mapping ); + +} + +#endif /* SLAPD_OVER_RWM */ diff --git a/servers/slapd/overlays/seqmod.c b/servers/slapd/overlays/seqmod.c new file mode 100644 index 0000000..503d6a6 --- /dev/null +++ b/servers/slapd/overlays/seqmod.c @@ -0,0 +1,207 @@ +/* seqmod.c - sequenced modifies */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SEQMOD + +#include "slap.h" +#include "slap-config.h" + +/* This overlay serializes concurrent attempts to modify a single entry */ + +typedef struct modtarget { + struct modtarget *mt_next; + struct modtarget *mt_tail; + Operation *mt_op; +} modtarget; + +typedef struct seqmod_info { + Avlnode *sm_mods; /* entries being modified */ + ldap_pvt_thread_mutex_t sm_mutex; +} seqmod_info; + +static int +sm_avl_cmp( const void *c1, const void *c2 ) +{ + const modtarget *m1, *m2; + int rc; + + m1 = c1; m2 = c2; + rc = m1->mt_op->o_req_ndn.bv_len - m2->mt_op->o_req_ndn.bv_len; + + if ( rc ) return rc; + return ber_bvcmp( &m1->mt_op->o_req_ndn, &m2->mt_op->o_req_ndn ); +} + +static int +seqmod_op_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + seqmod_info *sm = sc->sc_private; + modtarget *mt, mtdummy; + Avlnode *av; + + mtdummy.mt_op = op; + /* This op is done, remove it */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + av = ldap_avl_find2( sm->sm_mods, &mtdummy, sm_avl_cmp ); + assert(av != NULL); + + mt = av->avl_data; + + /* If there are more, promote the next one */ + if ( mt->mt_next ) { + av->avl_data = mt->mt_next; + mt->mt_next->mt_tail = mt->mt_tail; + } else { + ldap_avl_delete( &sm->sm_mods, mt, sm_avl_cmp ); + } + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + return 0; +} + +static int +seqmod_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + seqmod_info *sm = on->on_bi.bi_private; + modtarget *mt; + Avlnode *av; + slap_callback *cb; + + cb = op->o_tmpcalloc( 1, sizeof(slap_callback) + sizeof(modtarget), + op->o_tmpmemctx ); + mt = (modtarget *)(cb+1); + mt->mt_next = NULL; + mt->mt_tail = mt; + mt->mt_op = op; + + /* See if we're already modifying this entry - don't allow + * near-simultaneous mods of the same entry + */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + av = ldap_avl_find2( sm->sm_mods, mt, sm_avl_cmp ); + if ( av ) { + modtarget *mtp = av->avl_data; + mtp->mt_tail->mt_next = mt; + mtp->mt_tail = mt; + /* Wait for this op to get to head of list */ + while ( mtp != mt ) { + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + ldap_pvt_thread_yield(); + /* Let it finish - should use a condition + * variable here... */ + ldap_pvt_thread_mutex_lock( &sm->sm_mutex ); + mtp = av->avl_data; + } + } else { + /* Record that we're modifying this now */ + ldap_avl_insert( &sm->sm_mods, mt, sm_avl_cmp, ldap_avl_dup_error ); + } + ldap_pvt_thread_mutex_unlock( &sm->sm_mutex ); + + cb->sc_cleanup = seqmod_op_cleanup; + cb->sc_private = sm; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + return SLAP_CB_CONTINUE; +} + +static int +seqmod_op_extended( + Operation *op, + SlapReply *rs +) +{ + if ( exop_is_write( op )) return seqmod_op_mod( op, rs ); + else return SLAP_CB_CONTINUE; +} + +static int +seqmod_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + seqmod_info *sm; + + sm = ch_calloc(1, sizeof(seqmod_info)); + on->on_bi.bi_private = sm; + + ldap_pvt_thread_mutex_init( &sm->sm_mutex ); + + return 0; +} + +static int +seqmod_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + seqmod_info *sm = (seqmod_info *)on->on_bi.bi_private; + + if ( sm ) { + ldap_pvt_thread_mutex_destroy( &sm->sm_mutex ); + + ch_free( sm ); + on->on_bi.bi_private = NULL; + } + + return 0; +} + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +static slap_overinst seqmod; + +int +seqmod_initialize() +{ + seqmod.on_bi.bi_type = "seqmod"; + seqmod.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + seqmod.on_bi.bi_db_open = seqmod_db_open; + seqmod.on_bi.bi_db_close = seqmod_db_close; + + seqmod.on_bi.bi_op_modify = seqmod_op_mod; + seqmod.on_bi.bi_op_modrdn = seqmod_op_mod; + seqmod.on_bi.bi_extended = seqmod_op_extended; + + return overlay_register( &seqmod ); +} + +#if SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return seqmod_initialize(); +} +#endif /* SLAPD_OVER_SEQMOD == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_SEQMOD) */ diff --git a/servers/slapd/overlays/slapover.txt b/servers/slapd/overlays/slapover.txt new file mode 100644 index 0000000..2015d8d --- /dev/null +++ b/servers/slapd/overlays/slapover.txt @@ -0,0 +1,158 @@ +slapd internal APIs + +Introduction + +Frontend, backend, database, callback, overlay - what does it all mean? + +The "frontend" refers to all the code that deals with the actual interaction +with an LDAP client. This includes the code to read requests from the network +and parse them into C data structures, all of the session management, and the +formatting of responses for transmission onto the network. It also includes the +access control engine and other features that are generic to LDAP processing, +features which are not dependent on a particular database implementation. +Because the frontend serves as the framework that ties everything together, +it should not change much over time. + +The terms "backend" and "database" have historically been used interchangeably +and/or in combination as if they are the same thing, but the code has a clear +distinction between the two. A "backend" is a type of module, and a "database" +is an instance of a backend type. Together they work with the frontend to +manage the actual data that are operated on by LDAP requests. Originally the +backend interface was relatively compact, with individual functions +corresponding to each LDAP operation type, plus functions for init, config, and +shutdown. The number of entry points has grown to allow greater flexibility, +but the concept is much the same as before. + +The language here can get a bit confusing. A backend in slapd is embodied in a +BackendInfo data structure, and a database is held in a BackendDB structure. +Originally it was all just a single Backend data structure, but things have +grown and the concept was split into these two parts. The idea behind the +distinct BackendInfo is to allow for any initialization and configuration that +may be needed by every instance of a type of database, as opposed to items that +are specific to just one instance. For example, you might have a database +library that requires an initialization routine to be called exactly once at +program startup. Then there may be a "open" function that must be called once +for each database instance. The BackendInfo.bi_open function provides the +one-time startup, while the BackendInfo.bi_db_open function provides the +per-database startup. The main feature of the BackendInfo structure is its +table of entry points for all of the database functions that it implements. +There's also a bi_private pointer that can be used to carry any configuration +state needed by the backend. (Note that this is state that applies to the +backend type, and thus to all database instances of the backend as well.) The +BackendDB structure carries all of the per-instance state for a backend +database. This includes the database suffix, ACLs, flags, various DNs, etc. It +also has a pointer to its BackendInfo, and a be_private pointer for use by the +particular backend instance. In practice, the per-type features are seldom +used, and all of the work is done in the per-instance data structures. + +Ordinarily an LDAP request is received by the slapd frontend, parsed into a +request structure, and then passed to the backend for processing. The backend +may call various utility functions in the frontend to assist in processing, and +then it eventually calls some send_ldap_result function in the frontend to send +results back to the client. The processing flow is pretty rigidly defined; even +though slapd is capable of dynamically loading new code modules, it was +difficult to add extensions that changed the basic protocol operations. If you +wanted to extend the server with special behaviors you would need to modify the +frontend or the backend or both, and generally you would need to write an +entire new backend to get some set of special features working. With OpenLDAP +2.1 we added the notion of a callback, which can intercept the results sent +from a backend before they are sent to a client. Using callbacks makes it +possible to modify the results if desired, or to simply discard the results +instead of sending them to any client. This callback feature is used +extensively in the SASL support to perform internal searches of slapd databases +when mapping authentication IDs into regular DNs. The callback mechanism is +also the basis of backglue, which allows separate databases to be searched as +if they were a single naming context. + +Very often, one needs to add just a tiny feature onto an otherwise "normal" +database. The usual way to achieve this was to use a programmable backend (like +back-perl) to preprocess various requests and then forward them back into slapd +to be handled by the real database. While this technique works, it is fairly +inefficient because it involves many transitions from network to slapd and back +again. The overlay concept introduced in OpenLDAP 2.2 allows code to be +inserted between the slapd frontend and any backend, so that incoming requests +can be intercepted before reaching the backend database. (There is also a SLAPI +plugin framework in OpenLDAP 2.2; it offers a lot of flexibility as well but is +not discussed here.) The overlay framework also uses the callback mechanism, so +outgoing results can also be intercepted by external code. All of this could +get unwieldy if a lot of overlays were being used, but there was also another +significant API change in OpenLDAP 2.2 to streamline internal processing. (See +the document "Refactoring the slapd ABI"...) + +OK, enough generalities... You should probably have a copy of slap.h in front +of you to continue here. + +What is an overlay? The structure defining it includes a BackendInfo structure +plus a few additional fields. It gets inserted into the usual frontend->backend +call chain by replacing the BackendDB's BackendInfo pointer with its own. The +framework to accomplish this is in backover.c. For a given backend, the +BackendInfo will point to a slap_overinfo structure. The slap_overinfo has a +BackendInfo that points to all of the overlay framework's entry points. It also +holds a copy of the original BackendInfo pointer, and a linked list of +slap_overinst structures. There is one slap_overinst per configured overlay, +and the set of overlays configured on a backend are treated like a stack; i.e., +the last one configured is at the top of the stack, and it executes first. + +Continuing with the stack notion - a request enters the frontend, is directed +to a backend by select_backend, and then intercepted by the top of the overlay +stack. This first overlay may do something with the request, and then return +SLAP_CB_CONTINUE, which will then cause processing to fall into the next +overlay, and so on down the stack until finally the request is handed to the +actual backend database. Likewise, when the database finishes processing and +sends a result, the overlay callback intercepts this and the topmost overlay +gets to process the result. If it returns SLAP_CB_CONTINUE then processing will +continue in the next overlay, and then any other callbacks, then finally the +result reaches the frontend for sending back to the client. At any step along +the way, a module may choose to fully process the request or result and not +allow it to propagate any further down the stack. Whenever a module returns +anything other than SLAP_CB_CONTINUE the processing stops. + +An overlay can call most frontend functions without any special consideration. +However, if a call is going to result in any backend code being invoked, then +the backend environment must be correct. During a normal backend invocation, +op->o_bd points to the BackendDB structure for the backend, and +op->o_bd->bd_info points to the BackendInfo for the backend. All of the +information a specific backend instance needs is in op->o_bd->be_private and +all of its entry points are in the BackendInfo structure. When overlays are in +use on a backend, op->o_bd->bd_info points to the BackendInfo (actually a +slap_overinfo) that contains the overlay framework. When a particular overlay +instance is executing, op->o_bd points to a copy of the original op->o_bd, and +op->o_bd->bd_info points to a slap_overinst which carries the information about +the current overlay. The slap_overinst contains an on_private pointer which can +be used to carry any configuration or state information the overlay needs. The +normal way to invoke a backend function is through the op->o_bd->bd_info table +of entry points, but obviously this must be set to the backend's original +BackendInfo in order to get to the right function. + +There are two approaches here. The slap_overinst also contains a on_info field +that points to the top slap_overinfo that wraps the current backend. The +simplest thing is for the overlay to set op->o_bd->bd_info to this on_info +value before invoking a backend function. This will cause processing of that +particular operation to begin at the top of the overlay stack, so all the other +overlays on the backend will also get a chance to handle this internal request. +The other possibility is to invoke the underlying backend directly, bypassing +the rest of the overlays, by calling through on_info->oi_orig. You should be +careful in choosing this approach, since it precludes other overlays from doing +their jobs. + +One of the more interesting uses for an overlay is to attach two (or more) +different database backends into a single execution stack. Assuming that the +basic frontend-managed information (suffix, rootdn, ACLs, etc.) will be the +same for all of the backends, the only thing the overlay needs to maintain is a +be_private and bd_info pointer for the added backends. The chain and proxycache +overlays are two complementary examples of this usage. The chain overlay +attaches a back-ldap backend to a local database backend, and allows referrals +to remote servers generated by the database to be processed by slapd instead of +being returned to the client. The proxycache overlay attaches a local database +to a back-ldap (or back-meta) backend and allows search results from remote +servers to be cached locally. In both cases the overlays must provide a bit of +glue to swap in the appropriate be_private and bd_info pointers before invoking +the attached backend, which can then be invoked as usual. + +Note on overlay initialization/destruction: you should allocate storage for +config info in the _db_init handler, and free this storage in the _db_destroy +handler. You must not free it in the _db_close handler because a module may +be opened/closed multiple times in a running slapd when using dynamic +configuration and the config info must remain intact. + +--- diff --git a/servers/slapd/overlays/sssvlv.c b/servers/slapd/overlays/sssvlv.c new file mode 100644 index 0000000..828782a --- /dev/null +++ b/servers/slapd/overlays/sssvlv.c @@ -0,0 +1,1439 @@ +/* sssvlv.c - server side sort / virtual list view */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2009-2022 The OpenLDAP Foundation. + * Portions copyright 2009 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. Support for multiple sorts per connection added + * by Raphael Ouazana. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SSSVLV + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include <ldap_avl.h> + +#include "slap.h" +#include "lutil.h" +#include "slap-config.h" + +#include "../../../libraries/liblber/lber-int.h" /* ber_rewind */ + +/* RFC2891: Server Side Sorting + * RFC2696: Paged Results + */ +#ifndef LDAP_MATCHRULE_IDENTIFIER +#define LDAP_MATCHRULE_IDENTIFIER 0x80L +#define LDAP_REVERSEORDER_IDENTIFIER 0x81L +#define LDAP_ATTRTYPES_IDENTIFIER 0x80L +#endif + +/* draft-ietf-ldapext-ldapv3-vlv-09.txt: Virtual List Views + */ +#ifndef LDAP_VLVBYINDEX_IDENTIFIER +#define LDAP_VLVBYINDEX_IDENTIFIER 0xa0L +#define LDAP_VLVBYVALUE_IDENTIFIER 0x81L +#define LDAP_VLVCONTEXT_IDENTIFIER 0x04L + +#define LDAP_VLV_SSS_MISSING 0x4C +#define LDAP_VLV_RANGE_ERROR 0x4D +#endif + +#define SAFESTR(macro_str, macro_def) ((macro_str) ? (macro_str) : (macro_def)) + +#define SSSVLV_DEFAULT_MAX_KEYS 5 +#define SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN 5 + +#define NO_PS_COOKIE (PagedResultsCookie) -1 +#define NO_VC_CONTEXT (unsigned long) -1 + +typedef struct vlv_ctrl { + int vc_before; + int vc_after; + int vc_offset; + int vc_count; + struct berval vc_value; + unsigned long vc_context; +} vlv_ctrl; + +typedef struct sort_key +{ + AttributeDescription *sk_ad; + MatchingRule *sk_ordering; + int sk_direction; /* 1=normal, -1=reverse */ +} sort_key; + +typedef struct sort_ctrl { + int sc_nkeys; + sort_key sc_keys[1]; +} sort_ctrl; + + +typedef struct sort_node +{ + int sn_conn; + int sn_session; + struct berval sn_dn; + struct berval *sn_vals; +} sort_node; + +typedef struct sssvlv_info +{ + int svi_max; /* max concurrent sorts */ + int svi_num; /* current # sorts */ + int svi_max_keys; /* max sort keys per request */ + int svi_max_percon; /* max concurrent sorts per con */ +} sssvlv_info; + +typedef struct sort_op +{ + TAvlnode *so_tree; + sort_ctrl *so_ctrl; + sssvlv_info *so_info; + int so_paged; + int so_page_size; + int so_nentries; + int so_vlv; + int so_vlv_rc; + int so_vlv_target; + int so_session; + unsigned long so_vcontext; + int so_running; +} sort_op; + +/* There is only one conn table for all overlay instances */ +/* Each conn can handle one session by context */ +static sort_op ***sort_conns; +static ldap_pvt_thread_mutex_t sort_conns_mutex; +static int ov_count; +static const char *debug_header = "sssvlv"; + +static int sss_cid; +static int vlv_cid; + +/* RFC 2981 Section 2.2 + * If a sort key is a multi-valued attribute, and an entry happens to + * have multiple values for that attribute and no other controls are + * present that affect the sorting order, then the server SHOULD use the + * least value (according to the ORDERING rule for that attribute). + */ +static struct berval* select_value( + Attribute *attr, + sort_key *key ) +{ + struct berval* ber1, *ber2; + MatchingRule *mr = key->sk_ordering; + unsigned i; + int cmp; + + ber1 = &(attr->a_nvals[0]); + ber2 = ber1+1; + for ( i = 1; i < attr->a_numvals; i++,ber2++ ) { + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, ber1, ber2 ); + if ( cmp > 0 ) { + ber1 = ber2; + } + } + + Debug(LDAP_DEBUG_TRACE, "%s: value selected for compare: %s\n", + debug_header, + SAFESTR(ber1->bv_val, "<Empty>") ); + + return ber1; +} + +static int node_cmp( const void* val1, const void* val2 ) +{ + sort_node *sn1 = (sort_node *)val1; + sort_node *sn2 = (sort_node *)val2; + sort_ctrl *sc; + MatchingRule *mr; + int i, cmp = 0; + assert( sort_conns[sn1->sn_conn] + && sort_conns[sn1->sn_conn][sn1->sn_session] + && sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl ); + sc = sort_conns[sn1->sn_conn][sn1->sn_session]->so_ctrl; + + for ( i=0; cmp == 0 && i<sc->sc_nkeys; i++ ) { + if ( BER_BVISNULL( &sn1->sn_vals[i] )) { + if ( BER_BVISNULL( &sn2->sn_vals[i] )) + cmp = 0; + else + cmp = sc->sc_keys[i].sk_direction; + } else if ( BER_BVISNULL( &sn2->sn_vals[i] )) { + cmp = sc->sc_keys[i].sk_direction * -1; + } else { + mr = sc->sc_keys[i].sk_ordering; + mr->smr_match( &cmp, 0, mr->smr_syntax, mr, + &sn1->sn_vals[i], &sn2->sn_vals[i] ); + if ( cmp ) + cmp *= sc->sc_keys[i].sk_direction; + } + } + return cmp; +} + +static int node_insert( const void *val1, const void *val2 ) +{ + /* Never return equal so that new entries are always inserted */ + return node_cmp( val1, val2 ) < 0 ? -1 : 1; +} + +static int pack_vlv_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{iie", so->so_vlv_target, so->so_nentries, + so->so_vlv_rc ); + + if ( rc != -1 && so->so_vcontext ) { + cookie.bv_val = (char *)&so->so_vcontext; + cookie.bv_len = sizeof(so->so_vcontext); + rc = ber_printf( ber, "tO", LDAP_VLVCONTEXT_IDENTIFIER, &cookie ); + } + + if ( rc != -1 ) { + rc = ber_printf( ber, "}" ); + } + + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_VLVRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_pagedresult_response_control( + Operation *op, + SlapReply *rs, + sort_op *so, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + PagedResultsCookie resp_cookie; + struct berval cookie, bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + if ( so->so_nentries > 0 ) { + resp_cookie = ( PagedResultsCookie )so->so_tree; + cookie.bv_len = sizeof( PagedResultsCookie ); + cookie.bv_val = (char *)&resp_cookie; + } else { + resp_cookie = ( PagedResultsCookie )0; + BER_BVZERO( &cookie ); + } + + op->o_conn->c_pagedresults_state.ps_cookie = resp_cookie; + op->o_conn->c_pagedresults_state.ps_count + = ((PagedResultsState *)op->o_pagedresults_state)->ps_count + + rs->sr_nentries; + + rc = ber_printf( ber, "{iO}", so->so_nentries, &cookie ); + if ( rc != -1 ) { + rc = ber_flatten2( ber, &bv, 0 ); + } + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +static int pack_sss_response_control( + Operation *op, + SlapReply *rs, + LDAPControl **ctrlsp ) +{ + LDAPControl *ctrl; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval bv; + int rc; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* Pack error code */ + rc = ber_printf(ber, "{e}", rs->sr_err); + + if ( rc != -1) + rc = ber_flatten2( ber, &bv, 0 ); + + if ( rc != -1 ) { + ctrl = (LDAPControl *)op->o_tmpalloc( sizeof(LDAPControl)+ + bv.bv_len, op->o_tmpmemctx ); + ctrl->ldctl_oid = LDAP_CONTROL_SORTRESPONSE; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_val = (char *)(ctrl+1); + ctrl->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrlsp[0] = ctrl; + } else { + ctrlsp[0] = NULL; + rs->sr_err = LDAP_OTHER; + } + + ber_free_buf( ber ); + + return rs->sr_err; +} + +/* Return the session id or -1 if unknown */ +static int find_session_by_so( + int svi_max_percon, + int conn_id, + sort_op *so ) +{ + int sess_id; + if (so == NULL) { + return -1; + } + for (sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if ( sort_conns[conn_id] && sort_conns[conn_id][sess_id] == so ) + return sess_id; + } + return -1; +} + +/* Return the session id or -1 if unknown */ +static int find_session_by_context( + int svi_max_percon, + int conn_id, + unsigned long vc_context, + PagedResultsCookie ps_cookie ) +{ + int sess_id; + for(sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if( sort_conns[conn_id] && sort_conns[conn_id][sess_id] && + ( sort_conns[conn_id][sess_id]->so_vcontext == vc_context || + (PagedResultsCookie) sort_conns[conn_id][sess_id]->so_tree == ps_cookie ) ) + return sess_id; + } + return -1; +} + +static int find_next_session( + int svi_max_percon, + int conn_id ) +{ + int sess_id; + assert(sort_conns[conn_id] != NULL); + for(sess_id = 0; sess_id < svi_max_percon; sess_id++) { + if(!sort_conns[conn_id][sess_id]) { + return sess_id; + } + } + if (sess_id >= svi_max_percon) { + return -1; + } else { + return sess_id; + } +} + +static void free_sort_op( Connection *conn, sort_op *so ) +{ + int sess_id; + + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + sess_id = find_session_by_so( so->so_info->svi_max_percon, conn->c_conn_idx, so ); + if ( sess_id > -1 ) { + sort_conns[conn->c_conn_idx][sess_id] = NULL; + so->so_info->svi_num--; + } + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + + if ( sess_id > -1 ){ + if ( so->so_tree ) { + if ( so->so_paged > SLAP_CONTROL_IGNORED ) { + TAvlnode *cur_node, *next_node; + cur_node = so->so_tree; + while ( cur_node ) { + next_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT ); + ch_free( cur_node->avl_data ); + ber_memfree( cur_node ); + + cur_node = next_node; + } + } else { + ldap_tavl_free( so->so_tree, ch_free ); + } + so->so_tree = NULL; + } + + ch_free( so ); + } +} + +static void free_sort_ops( Connection *conn, sort_op **sos, int svi_max_percon ) +{ + int sess_id; + sort_op *so; + + for( sess_id = 0; sess_id < svi_max_percon ; sess_id++ ) { + so = sort_conns[conn->c_conn_idx][sess_id]; + if ( so ) { + free_sort_op( conn, so ); + sort_conns[conn->c_conn_idx][sess_id] = NULL; + } + } +} + +static void send_list( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + TAvlnode *cur_node, *tmp_node; + vlv_ctrl *vc = op->o_controls[vlv_cid]; + int i, j, dir, rc; + BackendDB *be; + Entry *e; + LDAPControl *ctrls[2]; + + rs->sr_attrs = op->ors_attrs; + + /* FIXME: it may be better to just flatten the tree into + * an array before doing all of this... + */ + + /* Are we just counting an offset? */ + if ( BER_BVISNULL( &vc->vc_value )) { + if ( vc->vc_offset == vc->vc_count ) { + /* wants the last entry in the list */ + cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT); + so->so_vlv_target = so->so_nentries; + } else if ( vc->vc_offset == 1 ) { + /* wants the first entry in the list */ + cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_vlv_target = 1; + } else { + int target; + /* Just iterate to the right spot */ + if ( vc->vc_count && vc->vc_count != so->so_nentries ) { + if ( vc->vc_offset > vc->vc_count ) + goto range_err; + target = so->so_nentries * vc->vc_offset / vc->vc_count; + } else { + if ( vc->vc_offset > so->so_nentries ) { +range_err: + so->so_vlv_rc = LDAP_VLV_RANGE_ERROR; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + target = vc->vc_offset; + } + so->so_vlv_target = target; + /* Start at left and go right, or start at right and go left? */ + if ( target < so->so_nentries / 2 ) { + cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } else { + cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + target = so->so_nentries - target + 1; + } + for ( i=1; i<target; i++ ) + cur_node = ldap_tavl_next( cur_node, dir ); + } + } else { + /* we're looking for a specific value */ + sort_ctrl *sc = so->so_ctrl; + MatchingRule *mr = sc->sc_keys[0].sk_ordering; + sort_node *sn; + struct berval bv; + + if ( mr->smr_normalize ) { + rc = mr->smr_normalize( SLAP_MR_VALUE_OF_SYNTAX, + mr->smr_syntax, mr, &vc->vc_value, &bv, op->o_tmpmemctx ); + if ( rc ) { + so->so_vlv_rc = LDAP_INAPPROPRIATE_MATCHING; + pack_vlv_response_control( op, rs, so, ctrls ); + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + rs->sr_err = LDAP_VLV_ERROR; + return; + } + } else { + bv = vc->vc_value; + } + + sn = op->o_tmpalloc( sizeof(sort_node) + + sc->sc_nkeys * sizeof(struct berval), op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + sn->sn_conn = op->o_conn->c_conn_idx; + sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so ); + sn->sn_vals[0] = bv; + for (i=1; i<sc->sc_nkeys; i++) { + BER_BVZERO( &sn->sn_vals[i] ); + } + cur_node = ldap_tavl_find3( so->so_tree, sn, node_cmp, &j ); + /* didn't find >= match */ + if ( j > 0 ) { + if ( cur_node ) + cur_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT ); + } + op->o_tmpfree( sn, op->o_tmpmemctx ); + + if ( !cur_node ) { + so->so_vlv_target = so->so_nentries + 1; + } else { + sort_node *sn = so->so_tree->avl_data; + /* start from the left or the right side? */ + mr->smr_match( &i, 0, mr->smr_syntax, mr, &bv, &sn->sn_vals[0] ); + if ( i > 0 ) { + tmp_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT); + dir = TAVL_DIR_LEFT; + } else { + tmp_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT); + dir = TAVL_DIR_RIGHT; + } + for (i=0; tmp_node != cur_node; + tmp_node = ldap_tavl_next( tmp_node, dir ), i++); + so->so_vlv_target = (dir == TAVL_DIR_RIGHT) ? i+1 : so->so_nentries - i; + } + if ( bv.bv_val != vc->vc_value.bv_val ) + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + if ( !cur_node ) { + i = 1; + cur_node = ldap_tavl_end(so->so_tree, TAVL_DIR_RIGHT); + } else { + i = 0; + } + for ( ; i<vc->vc_before; i++ ) { + tmp_node = ldap_tavl_next( cur_node, TAVL_DIR_LEFT ); + if ( !tmp_node ) break; + cur_node = tmp_node; + } + j = i + vc->vc_after + 1; + be = op->o_bd; + for ( i=0; i<j; i++ ) { + sort_node *sn = cur_node->avl_data; + + if ( slapd_shutdown ) break; + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + cur_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT ); + if ( !cur_node ) break; + } + so->so_vlv_rc = LDAP_SUCCESS; + + op->o_bd = be; +} + +static void send_page( Operation *op, SlapReply *rs, sort_op *so ) +{ + TAvlnode *cur_node = so->so_tree; + TAvlnode *next_node = NULL; + BackendDB *be = op->o_bd; + Entry *e; + int rc; + + rs->sr_attrs = op->ors_attrs; + + while ( cur_node && rs->sr_nentries < so->so_page_size ) { + sort_node *sn = cur_node->avl_data; + + if ( slapd_shutdown ) break; + + next_node = ldap_tavl_next( cur_node, TAVL_DIR_RIGHT ); + + op->o_bd = select_backend( &sn->sn_dn, 0 ); + e = NULL; + rc = be_entry_get_rw( op, &sn->sn_dn, NULL, NULL, 0, &e ); + + ch_free( cur_node->avl_data ); + ber_memfree( cur_node ); + + cur_node = next_node; + so->so_nentries--; + + if ( e && rc == LDAP_SUCCESS ) { + rs->sr_entry = e; + rs->sr_flags = REP_ENTRY_MUSTRELEASE; + rs->sr_err = send_search_entry( op, rs ); + if ( rs->sr_err == LDAP_UNAVAILABLE ) + break; + } + } + + /* Set the first entry to send for the next page */ + so->so_tree = next_node; + if ( next_node ) + next_node->avl_left = NULL; + + op->o_bd = be; +} + +static void send_entry( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + Debug(LDAP_DEBUG_TRACE, + "%s: response control: status=%d, text=%s\n", + debug_header, rs->sr_err, SAFESTR(rs->sr_text, "<None>")); + + if ( !so->so_tree ) + return; + + /* RFC 2891: If critical then send the entries iff they were + * successfully sorted. If non-critical send all entries + * whether they were sorted or not. + */ + if ( (op->o_ctrlflag[sss_cid] != SLAP_CONTROL_CRITICAL) || + (rs->sr_err == LDAP_SUCCESS) ) + { + if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + send_list( op, rs, so ); + } else { + /* Get the first node to send */ + TAvlnode *start_node = ldap_tavl_end(so->so_tree, TAVL_DIR_LEFT); + so->so_tree = start_node; + + if ( so->so_paged <= SLAP_CONTROL_IGNORED ) { + /* Not paged result search. Send all entries. + * Set the page size to the number of entries + * so that send_page() will send all entries. + */ + so->so_page_size = so->so_nentries; + } + + send_page( op, rs, so ); + } + } +} + +static void send_result( + Operation *op, + SlapReply *rs, + sort_op *so) +{ + LDAPControl *ctrls[3]; + int rc, i = 0; + + rc = pack_sss_response_control( op, rs, ctrls ); + if ( rc == LDAP_SUCCESS ) { + i++; + rc = -1; + if ( so->so_paged > SLAP_CONTROL_IGNORED ) { + rc = pack_pagedresult_response_control( op, rs, so, ctrls+1 ); + } else if ( so->so_vlv > SLAP_CONTROL_IGNORED ) { + rc = pack_vlv_response_control( op, rs, so, ctrls+1 ); + } + if ( rc == LDAP_SUCCESS ) + i++; + } + ctrls[i] = NULL; + + if ( ctrls[0] != NULL ) + slap_add_ctrls( op, rs, ctrls ); + send_ldap_result( op, rs ); + + if ( so->so_tree == NULL ) { + /* Search finished, so clean up */ + free_sort_op( op->o_conn, so ); + } else { + so->so_running = 0; + } +} + +static int sssvlv_op_response( + Operation *op, + SlapReply *rs ) +{ + sort_ctrl *sc = op->o_controls[sss_cid]; + sort_op *so = op->o_callback->sc_private; + + if ( rs->sr_type == REP_SEARCH ) { + int i; + size_t len; + sort_node *sn, *sn2; + struct berval *bv; + char *ptr; + + len = sizeof(sort_node) + sc->sc_nkeys * sizeof(struct berval) + + rs->sr_entry->e_nname.bv_len + 1; + sn = op->o_tmpalloc( len, op->o_tmpmemctx ); + sn->sn_vals = (struct berval *)(sn+1); + + /* Build tmp list of key values */ + for ( i=0; i<sc->sc_nkeys; i++ ) { + Attribute *a = attr_find( rs->sr_entry->e_attrs, + sc->sc_keys[i].sk_ad ); + if ( a ) { + if ( a->a_numvals > 1 ) { + bv = select_value( a, &sc->sc_keys[i] ); + } else { + bv = a->a_nvals; + } + sn->sn_vals[i] = *bv; + len += bv->bv_len + 1; + } else { + BER_BVZERO( &sn->sn_vals[i] ); + } + } + + /* Now dup into regular memory */ + sn2 = ch_malloc( len ); + sn2->sn_vals = (struct berval *)(sn2+1); + AC_MEMCPY( sn2->sn_vals, sn->sn_vals, + sc->sc_nkeys * sizeof(struct berval)); + + ptr = (char *)(sn2->sn_vals + sc->sc_nkeys); + sn2->sn_dn.bv_val = ptr; + sn2->sn_dn.bv_len = rs->sr_entry->e_nname.bv_len; + AC_MEMCPY( ptr, rs->sr_entry->e_nname.bv_val, + rs->sr_entry->e_nname.bv_len ); + ptr += rs->sr_entry->e_nname.bv_len; + *ptr++ = '\0'; + for ( i=0; i<sc->sc_nkeys; i++ ) { + if ( !BER_BVISNULL( &sn2->sn_vals[i] )) { + AC_MEMCPY(ptr, sn2->sn_vals[i].bv_val, sn2->sn_vals[i].bv_len); + sn2->sn_vals[i].bv_val = ptr; + ptr += sn2->sn_vals[i].bv_len; + *ptr++ = '\0'; + } + } + op->o_tmpfree( sn, op->o_tmpmemctx ); + sn = sn2; + sn->sn_conn = op->o_conn->c_conn_idx; + sn->sn_session = find_session_by_so( so->so_info->svi_max_percon, op->o_conn->c_conn_idx, so ); + + /* Insert into the AVL tree */ + ldap_tavl_insert(&(so->so_tree), sn, node_insert, ldap_avl_dup_error); + + so->so_nentries++; + + /* Collected the keys so that they can be sorted. Thus, stop + * the entry from propagating. + */ + rs->sr_err = LDAP_SUCCESS; + } + else if ( rs->sr_type == REP_RESULT ) { + /* Remove serversort response callback. + * We don't want the entries that we are about to send to be + * processed by serversort response again. + */ + if ( op->o_callback->sc_response == sssvlv_op_response ) { + op->o_callback = op->o_callback->sc_next; + } + + send_entry( op, rs, so ); + send_result( op, rs, so ); + } + + return rs->sr_err; +} + +static int sssvlv_op_search( + Operation *op, + SlapReply *rs) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + int rc = SLAP_CB_CONTINUE; + int ok; + sort_op *so = NULL, so2; + sort_ctrl *sc; + PagedResultsState *ps; + vlv_ctrl *vc; + int sess_id; + + if ( op->o_ctrlflag[sss_cid] <= SLAP_CONTROL_IGNORED ) { + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + LDAPControl *ctrls[2]; + so2.so_vcontext = 0; + so2.so_vlv_target = 0; + so2.so_nentries = 0; + so2.so_vlv_rc = LDAP_VLV_SSS_MISSING; + so2.so_vlv = op->o_ctrlflag[vlv_cid]; + rc = pack_vlv_response_control( op, rs, &so2, ctrls ); + if ( rc == LDAP_SUCCESS ) { + ctrls[1] = NULL; + slap_add_ctrls( op, rs, ctrls ); + } + rs->sr_err = LDAP_VLV_ERROR; + rs->sr_text = "Sort control is required with VLV"; + goto leave; + } + /* Not server side sort so just continue */ + return SLAP_CB_CONTINUE; + } + + Debug(LDAP_DEBUG_TRACE, + "==> sssvlv_search: <%s> %s, control flag: %d\n", + op->o_req_dn.bv_val, op->ors_filterstr.bv_val, + op->o_ctrlflag[sss_cid]); + + sc = op->o_controls[sss_cid]; + if ( sc->sc_nkeys > si->svi_max_keys ) { + rs->sr_text = "Too many sort keys"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ps = ( op->o_pagedresults > SLAP_CONTROL_IGNORED ) ? + (PagedResultsState*)(op->o_pagedresults_state) : NULL; + vc = op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ? + op->o_controls[vlv_cid] : NULL; + + if ( ps && vc ) { + rs->sr_text = "VLV incompatible with PagedResults"; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + goto leave; + } + + ok = 1; + ldap_pvt_thread_mutex_lock( &sort_conns_mutex ); + /* Is there already a sort running on this conn? */ + sess_id = find_session_by_context( si->svi_max_percon, op->o_conn->c_conn_idx, vc ? vc->vc_context : NO_VC_CONTEXT, ps ? ps->ps_cookie : NO_PS_COOKIE ); + if ( sess_id >= 0 ) { + so = sort_conns[op->o_conn->c_conn_idx][sess_id]; + + if( so->so_running > 0 ){ + /* another thread is handling, response busy to client */ + so = NULL; + ok = 0; + } else { + + /* Is it a continuation of a VLV search? */ + if ( !vc || so->so_vlv <= SLAP_CONTROL_IGNORED || + vc->vc_context != so->so_vcontext ) { + /* Is it a continuation of a paged search? */ + if ( !ps || so->so_paged <= SLAP_CONTROL_IGNORED || + op->o_conn->c_pagedresults_state.ps_cookie != ps->ps_cookie ) { + ok = 0; + } else if ( !ps->ps_size ) { + /* Abandoning current request */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_SUCCESS; + } + } + if (( vc && so->so_paged > SLAP_CONTROL_IGNORED ) || + ( ps && so->so_vlv > SLAP_CONTROL_IGNORED )) { + /* changed from paged to vlv or vice versa, abandon */ + ok = 0; + so->so_nentries = 0; + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + } + + if ( ok ) { + /* occupy before mutex unlock */ + so->so_running = 1; + } + + } + /* Are there too many running overall? */ + } else if ( si->svi_num >= si->svi_max ) { + ok = 0; + } else if ( ( sess_id = find_next_session(si->svi_max_percon, op->o_conn->c_conn_idx ) ) < 0 ) { + ok = 0; + } else { + /* OK, this connection now has a sort running */ + si->svi_num++; + sort_conns[op->o_conn->c_conn_idx][sess_id] = &so2; + sort_conns[op->o_conn->c_conn_idx][sess_id]->so_session = sess_id; + } + ldap_pvt_thread_mutex_unlock( &sort_conns_mutex ); + if ( ok ) { + /* If we're a global overlay, this check got bypassed */ + if ( !op->ors_limit && limits_check( op, rs )) + return rs->sr_err; + /* are we continuing a VLV search? */ + if ( so && vc && vc->vc_context ) { + so->so_ctrl = sc; + send_list( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + /* are we continuing a paged search? */ + } else if ( so && ps && ps->ps_cookie ) { + so->so_ctrl = sc; + send_page( op, rs, so ); + send_result( op, rs, so ); + rc = LDAP_SUCCESS; + } else { + slap_callback *cb = op->o_tmpalloc( sizeof(slap_callback), + op->o_tmpmemctx ); + /* Install serversort response callback to handle a new search */ + if ( ps || vc ) { + so = ch_calloc( 1, sizeof(sort_op)); + } else { + so = op->o_tmpcalloc( 1, sizeof(sort_op), op->o_tmpmemctx ); + } + sort_conns[op->o_conn->c_conn_idx][sess_id] = so; + + cb->sc_cleanup = NULL; + cb->sc_response = sssvlv_op_response; + cb->sc_next = op->o_callback; + cb->sc_private = so; + cb->sc_writewait = NULL; + + so->so_tree = NULL; + so->so_ctrl = sc; + so->so_info = si; + if ( ps ) { + so->so_paged = op->o_pagedresults; + so->so_page_size = ps->ps_size; + op->o_pagedresults = SLAP_CONTROL_IGNORED; + } else { + so->so_paged = 0; + so->so_page_size = 0; + if ( vc ) { + so->so_vlv = op->o_ctrlflag[vlv_cid]; + so->so_vlv_target = 0; + so->so_vlv_rc = 0; + } else { + so->so_vlv = SLAP_CONTROL_NONE; + } + } + so->so_session = sess_id; + so->so_vlv = op->o_ctrlflag[vlv_cid]; + so->so_vcontext = (unsigned long)so; + so->so_nentries = 0; + so->so_running = 1; + + op->o_callback = cb; + } + } else { + if ( so && !so->so_nentries ) { + free_sort_op( op->o_conn, so ); + } else { + rs->sr_text = "Other sort requests already in progress"; + rs->sr_err = LDAP_BUSY; + } +leave: + rc = rs->sr_err; + send_ldap_result( op, rs ); + } + + return rc; +} + +static int get_ordering_rule( + AttributeDescription *ad, + struct berval *matchrule, + SlapReply *rs, + MatchingRule **ordering ) +{ + MatchingRule* mr; + + if ( matchrule && matchrule->bv_val ) { + mr = mr_find( matchrule->bv_val ); + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, "%s: no ordering rule function for %s\n", + debug_header, matchrule->bv_val ); + } + } + else { + mr = ad->ad_type->sat_ordering; + if ( mr == NULL ) { + rs->sr_err = LDAP_INAPPROPRIATE_MATCHING; + rs->sr_text = "serverSort control: No ordering rule"; + Debug(LDAP_DEBUG_TRACE, + "%s: no ordering rule specified and no default ordering rule for attribute %s\n", + debug_header, ad->ad_cname.bv_val ); + } + } + + *ordering = mr; + return rs->sr_err; +} + +static int count_key(BerElement *ber) +{ + char *end; + ber_len_t len; + ber_tag_t tag; + int count = 0; + + /* Server Side Sort Control is a SEQUENCE of SEQUENCE */ + for ( tag = ber_first_element( ber, &len, &end ); + tag == LBER_SEQUENCE; + tag = ber_next_element( ber, &len, end )) + { + tag = ber_skip_tag( ber, &len ); + ber_skip_data( ber, len ); + ++count; + } + ber_rewind( ber ); + + return count; +} + +static int build_key( + BerElement *ber, + SlapReply *rs, + sort_key *key ) +{ + struct berval attr; + struct berval matchrule = BER_BVNULL; + ber_int_t reverse = 0; + ber_tag_t tag; + ber_len_t len; + MatchingRule *ordering = NULL; + AttributeDescription *ad = NULL; + const char *text; + + if (( tag = ber_scanf( ber, "{" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if (( tag = ber_scanf( ber, "m", &attr )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_MATCHRULE_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "m", &matchrule )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: matchrule decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + } + + if ( tag == LDAP_REVERSEORDER_IDENTIFIER ) { + if (( tag = ber_scanf( ber, "b", &reverse )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: reverse decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + } + + if (( tag = ber_scanf( ber, "}" )) == LBER_ERROR ) { + rs->sr_text = "serverSort control: decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + return rs->sr_err; + } + + if ( slap_bv2ad( &attr, &ad, &text ) != LDAP_SUCCESS ) { + rs->sr_text = + "serverSort control: Unrecognized attribute type in sort key"; + Debug(LDAP_DEBUG_TRACE, + "%s: Unrecognized attribute type in sort key: %s\n", + debug_header, SAFESTR(attr.bv_val, "<None>") ); + rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE; + return rs->sr_err; + } + + /* get_ordering_rule will set sr_err and sr_text */ + get_ordering_rule( ad, &matchrule, rs, &ordering ); + if ( rs->sr_err != LDAP_SUCCESS ) { + return rs->sr_err; + } + + key->sk_ad = ad; + key->sk_ordering = ordering; + key->sk_direction = reverse ? -1 : 1; + + return rs->sr_err; +} + +/* Conforms to RFC4510 re: Criticality, original RFC2891 spec is broken + * Also see ITS#7253 for discussion + */ +static int sss_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + int i; + sort_ctrl *sc; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + if ( op->o_ctrlflag[sss_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "sorted results control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "sorted results control value is empty"; + } else { + rs->sr_err = LDAP_SUCCESS; + } + if ( rs->sr_err != LDAP_SUCCESS ) + return rs->sr_err; + + op->o_ctrlflag[sss_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + i = count_key( ber ); + + sc = op->o_tmpalloc( sizeof(sort_ctrl) + + (i-1) * sizeof(sort_key), op->o_tmpmemctx ); + sc->sc_nkeys = i; + op->o_controls[sss_cid] = sc; + + /* peel off initial sequence */ + ber_scanf( ber, "{" ); + + i = 0; + do { + if ( build_key( ber, rs, &sc->sc_keys[i] ) != LDAP_SUCCESS ) + break; + i++; + tag = ber_peek_tag( ber, &len ); + } while ( tag != LBER_DEFAULT ); + + return rs->sr_err; +} + +static int vlv_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + BerElementBuffer berbuf; + BerElement *ber; + ber_tag_t tag; + ber_len_t len; + vlv_ctrl *vc, vc2; + + rs->sr_err = LDAP_PROTOCOL_ERROR; + rs->sr_text = NULL; + + if ( op->o_ctrlflag[vlv_cid] > SLAP_CONTROL_IGNORED ) { + rs->sr_text = "vlv control specified multiple times"; + } else if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is absent"; + } else if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "vlv control value is empty"; + } + if ( rs->sr_text != NULL ) + return rs->sr_err; + + op->o_ctrlflag[vlv_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + + ber = (BerElement *)&berbuf; + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + rs->sr_err = LDAP_PROTOCOL_ERROR; + + tag = ber_scanf( ber, "{ii", &vc2.vc_before, &vc2.vc_after ); + if ( tag == LBER_ERROR ) { + return rs->sr_err; + } + + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVBYINDEX_IDENTIFIER ) { + tag = ber_scanf( ber, "{ii}", &vc2.vc_offset, &vc2.vc_count ); + if ( tag == LBER_ERROR ) + return rs->sr_err; + BER_BVZERO( &vc2.vc_value ); + } else if ( tag == LDAP_VLVBYVALUE_IDENTIFIER ) { + tag = ber_scanf( ber, "m", &vc2.vc_value ); + if ( tag == LBER_ERROR || BER_BVISNULL( &vc2.vc_value )) + return rs->sr_err; + } else { + return rs->sr_err; + } + tag = ber_peek_tag( ber, &len ); + if ( tag == LDAP_VLVCONTEXT_IDENTIFIER ) { + struct berval bv; + tag = ber_scanf( ber, "m", &bv ); + if ( tag == LBER_ERROR || bv.bv_len != sizeof(vc2.vc_context)) + return rs->sr_err; + AC_MEMCPY( &vc2.vc_context, bv.bv_val, bv.bv_len ); + } else { + vc2.vc_context = 0; + } + + vc = op->o_tmpalloc( sizeof(vlv_ctrl), op->o_tmpmemctx ); + *vc = vc2; + op->o_controls[vlv_cid] = vc; + rs->sr_err = LDAP_SUCCESS; + + return rs->sr_err; +} + +static int sssvlv_connection_destroy( BackendDB *be, Connection *conn ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + + if ( sort_conns[conn->c_conn_idx] ) { + free_sort_ops( conn, sort_conns[conn->c_conn_idx], si->svi_max_percon ); + } + + return LDAP_SUCCESS; +} + +static int sssvlv_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = on->on_bi.bi_private; + int rc; + int conn_index; + + /* If not set, default to 1/2 of available threads */ + if ( !si->svi_max ) + si->svi_max = connection_pool_max / 2; + + if ( dtblsize && !sort_conns ) { + ldap_pvt_thread_mutex_init( &sort_conns_mutex ); + /* accommodate for c_conn_idx == -1 */ + sort_conns = ch_calloc( dtblsize + 1, sizeof(sort_op **) ); + for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) { + sort_conns[conn_index] = ch_calloc( si->svi_max_percon, sizeof(sort_op *) ); + } + sort_conns++; + } + + rc = overlay_register_control( be, LDAP_CONTROL_SORTREQUEST ); + if ( rc == LDAP_SUCCESS ) + rc = overlay_register_control( be, LDAP_CONTROL_VLVREQUEST ); + return rc; +} + +static ConfigTable sssvlv_cfg[] = { + { "sssvlv-max", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max), + "( OLcfgOvAt:21.1 NAME 'olcSssVlvMax' " + "DESC 'Maximum number of concurrent Sort requests' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "sssvlv-maxkeys", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max_keys), + "( OLcfgOvAt:21.2 NAME 'olcSssVlvMaxKeys' " + "DESC 'Maximum number of Keys in a Sort request' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, + { .v_int = SSSVLV_DEFAULT_MAX_KEYS } }, + { "sssvlv-maxperconn", "num", + 2, 2, 0, ARG_INT|ARG_OFFSET, + (void *)offsetof(sssvlv_info, svi_max_percon), + "( OLcfgOvAt:21.3 NAME 'olcSssVlvMaxPerConn' " + "DESC 'Maximum number of concurrent paged search requests per connection' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, + { .v_int = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN } }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs sssvlv_ocs[] = { + { "( OLcfgOvOc:21.1 " + "NAME 'olcSssVlvConfig' " + "DESC 'SSS VLV configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSssVlvMax $ olcSssVlvMaxKeys $ olcSssVlvMaxPerConn ) )", + Cft_Overlay, sssvlv_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static int sssvlv_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si; + + if ( ov_count == 0 ) { + int rc; + + rc = register_supported_control2( LDAP_CONTROL_SORTREQUEST, + SLAP_CTRL_SEARCH, + NULL, + sss_parseCtrl, + 1 /* replace */, + &sss_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register Sort Request control '%s' (%d)\n", + LDAP_CONTROL_SORTREQUEST, rc ); + return rc; + } + + rc = register_supported_control2( LDAP_CONTROL_VLVREQUEST, + SLAP_CTRL_SEARCH, + NULL, + vlv_parseCtrl, + 1 /* replace */, + &vlv_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register VLV Request control '%s' (%d)\n", + LDAP_CONTROL_VLVREQUEST, rc ); +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST ); + unregister_supported_control( LDAP_CONTROL_SORTREQUEST ); +#endif /* SLAP_CONFIG_DELETE */ + return rc; + } + } + + si = (sssvlv_info *)ch_malloc(sizeof(sssvlv_info)); + on->on_bi.bi_private = si; + + si->svi_max = 0; + si->svi_num = 0; + si->svi_max_keys = SSSVLV_DEFAULT_MAX_KEYS; + si->svi_max_percon = SSSVLV_DEFAULT_MAX_REQUEST_PER_CONN; + + ov_count++; + + return LDAP_SUCCESS; +} + +static int sssvlv_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + sssvlv_info *si = (sssvlv_info *)on->on_bi.bi_private; + int conn_index; + + ov_count--; + if ( !ov_count && sort_conns) { + sort_conns--; + for ( conn_index = 0 ; conn_index < dtblsize + 1 ; conn_index++ ) { + ch_free(sort_conns[conn_index]); + } + ch_free(sort_conns); + ldap_pvt_thread_mutex_destroy( &sort_conns_mutex ); + } + +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_SORTREQUEST ); + overlay_unregister_control( be, LDAP_CONTROL_VLVREQUEST ); + if ( ov_count == 0 ) { + unregister_supported_control( LDAP_CONTROL_SORTREQUEST ); + unregister_supported_control( LDAP_CONTROL_VLVREQUEST ); + } +#endif /* SLAP_CONFIG_DELETE */ + + if ( si ) { + ch_free( si ); + on->on_bi.bi_private = NULL; + } + return LDAP_SUCCESS; +} + +static slap_overinst sssvlv; + +int sssvlv_initialize() +{ + int rc; + + sssvlv.on_bi.bi_type = "sssvlv"; + sssvlv.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + sssvlv.on_bi.bi_db_init = sssvlv_db_init; + sssvlv.on_bi.bi_db_destroy = sssvlv_db_destroy; + sssvlv.on_bi.bi_db_open = sssvlv_db_open; + sssvlv.on_bi.bi_connection_destroy = sssvlv_connection_destroy; + sssvlv.on_bi.bi_op_search = sssvlv_op_search; + + sssvlv.on_bi.bi_cf_ocs = sssvlv_ocs; + + rc = config_register_schema( sssvlv_cfg, sssvlv_ocs ); + if ( rc ) + return rc; + + rc = overlay_register( &sssvlv ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register server side sort overlay\n" ); + } + + return rc; +} + +#if SLAPD_OVER_SSSVLV == SLAPD_MOD_DYNAMIC +int init_module( int argc, char *argv[]) +{ + return sssvlv_initialize(); +} +#endif + +#endif /* SLAPD_OVER_SSSVLV */ diff --git a/servers/slapd/overlays/syncprov.c b/servers/slapd/overlays/syncprov.c new file mode 100644 index 0000000..6d749a5 --- /dev/null +++ b/servers/slapd/overlays/syncprov.c @@ -0,0 +1,4368 @@ +/* $OpenLDAP$ */ +/* syncprov.c - syncrepl provider */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_SYNCPROV + +#include <ac/string.h> +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" +#include "ldap_rq.h" + +#ifdef LDAP_DEVEL +#define CHECK_CSN 1 +#endif + +/* A modify request on a particular entry */ +typedef struct modinst { + struct modinst *mi_next; + Operation *mi_op; +} modinst; + +typedef struct modtarget { + struct modinst *mt_mods; + struct modinst *mt_tail; + struct berval mt_dn; + ldap_pvt_thread_mutex_t mt_mutex; +} modtarget; + +/* All the info of a psearch result that's shared between + * multiple queues + */ +typedef struct resinfo { + struct syncres *ri_list; + Entry *ri_e; + struct berval ri_dn; + struct berval ri_ndn; + struct berval ri_uuid; + struct berval ri_csn; + struct berval ri_cookie; + char ri_isref; + ldap_pvt_thread_mutex_t ri_mutex; +} resinfo; + +/* A queued result of a persistent search */ +typedef struct syncres { + struct syncres *s_next; /* list of results on this psearch queue */ + struct syncres *s_rilist; /* list of psearches using this result */ + resinfo *s_info; + char s_mode; +} syncres; + +/* Record of a persistent search */ +typedef struct syncops { + struct syncops *s_next; + struct syncprov_info_t *s_si; + struct berval s_base; /* ndn of search base */ + ID s_eid; /* entryID of search base */ + Operation *s_op; /* search op */ + int s_rid; + int s_sid; + struct berval s_filterstr; + int s_flags; /* search status */ +#define PS_IS_REFRESHING 0x01 +#define PS_IS_DETACHED 0x02 +#define PS_WROTE_BASE 0x04 +#define PS_FIND_BASE 0x08 +#define PS_FIX_FILTER 0x10 +#define PS_TASK_QUEUED 0x20 + + int s_inuse; /* reference count */ + struct syncres *s_res; + struct syncres *s_restail; + void *s_pool_cookie; + ldap_pvt_thread_mutex_t s_mutex; +} syncops; + +/* A received sync control */ +typedef struct sync_control { + struct sync_cookie sr_state; + int sr_rhint; +} sync_control; + +#if 0 /* moved back to slap.h */ +#define o_sync o_ctrlflag[slap_cids.sc_LDAPsync] +#endif +/* o_sync_mode uses data bits of o_sync */ +#define o_sync_mode o_ctrlflag[slap_cids.sc_LDAPsync] + +#define SLAP_SYNC_NONE (LDAP_SYNC_NONE<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_REFRESH (LDAP_SYNC_REFRESH_ONLY<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_PERSIST (LDAP_SYNC_RESERVED<<SLAP_CONTROL_SHIFT) +#define SLAP_SYNC_REFRESH_AND_PERSIST (LDAP_SYNC_REFRESH_AND_PERSIST<<SLAP_CONTROL_SHIFT) + +/* Record of which searches matched at premodify step */ +typedef struct syncmatches { + struct syncmatches *sm_next; + syncops *sm_op; +} syncmatches; + +/* Session log data */ +typedef struct slog_entry { + struct berval se_uuid; + struct berval se_csn; + int se_sid; + ber_tag_t se_tag; +} slog_entry; + +typedef struct sessionlog { + BerVarray sl_mincsn; + int *sl_sids; + int sl_numcsns; + int sl_num; + int sl_size; + int sl_playing; + TAvlnode *sl_entries; + ldap_pvt_thread_rdwr_t sl_mutex; +} sessionlog; + +/* Accesslog callback data */ +typedef struct syncprov_accesslog_deletes { + Operation *op; + SlapReply *rs; + sync_control *srs; + BerVarray ctxcsn; + int numcsns, *sids; + Avlnode *uuids; + BerVarray uuid_list; + int ndel, list_len; + char *uuid_buf; +} syncprov_accesslog_deletes; + +/* The main state for this overlay */ +typedef struct syncprov_info_t { + syncops *si_ops; + struct berval si_contextdn; + struct berval si_logbase; + BerVarray si_ctxcsn; /* ldapsync context */ + int *si_sids; + int si_numcsns; + int si_chkops; /* checkpointing info */ + int si_chktime; + int si_numops; /* number of ops since last checkpoint */ + int si_nopres; /* Skip present phase */ + int si_usehint; /* use reload hint */ + int si_active; /* True if there are active mods */ + int si_dirty; /* True if the context is dirty, i.e changes + * have been made without updating the csn. */ + time_t si_chklast; /* time of last checkpoint */ + Avlnode *si_mods; /* entries being modified */ + sessionlog *si_logs; + ldap_pvt_thread_rdwr_t si_csn_rwlock; + ldap_pvt_thread_mutex_t si_ops_mutex; + ldap_pvt_thread_mutex_t si_mods_mutex; + ldap_pvt_thread_mutex_t si_resp_mutex; +} syncprov_info_t; + +typedef struct opcookie { + slap_overinst *son; + syncmatches *smatches; + modtarget *smt; + Entry *se; + struct berval sdn; /* DN of entry, for deletes */ + struct berval sndn; + struct berval suuid; /* UUID of entry */ + struct berval sctxcsn; + short osid; /* sid of op csn */ + short rsid; /* sid of relay */ + short sreference; /* Is the entry a reference? */ + syncres ssres; +} opcookie; + +typedef struct fbase_cookie { + struct berval *fdn; /* DN of a modified entry, for scope testing */ + syncops *fss; /* persistent search we're testing against */ + int fbase; /* if TRUE we found the search base and it's still valid */ + int fscope; /* if TRUE then fdn is within the psearch scope */ +} fbase_cookie; + +static AttributeName csn_anlist[3]; +static AttributeName uuid_anlist[2]; + +static AttributeDescription *ad_reqType, *ad_reqResult, *ad_reqDN, + *ad_reqEntryUUID, *ad_minCSN, *ad_reqNewDN; + +/* Build a LDAPsync intermediate state control */ +static int +syncprov_state_ctrl( + Operation *op, + SlapReply *rs, + Entry *e, + int entry_sync_state, + LDAPControl **ctrls, + int num_ctrls, + int send_cookie, + struct berval *cookie ) +{ + Attribute* a; + int ret; + + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPControl *cp; + struct berval bv; + struct berval entryuuid_bv = BER_BVNULL; + + ber_init2( ber, 0, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + for ( a = e->e_attrs; a != NULL; a = a->a_next ) { + AttributeDescription *desc = a->a_desc; + if ( desc == slap_schema.si_ad_entryUUID ) { + entryuuid_bv = a->a_nvals[0]; + break; + } + } + + /* FIXME: what if entryuuid is NULL or empty ? */ + + if ( send_cookie && cookie ) { + ber_printf( ber, "{eOON}", + entry_sync_state, &entryuuid_bv, cookie ); + } else { + ber_printf( ber, "{eON}", + entry_sync_state, &entryuuid_bv ); + } + + ret = ber_flatten2( ber, &bv, 0 ); + if ( ret == 0 ) { + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = LDAP_CONTROL_SYNC_STATE; + cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL); + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrls[num_ctrls] = cp; + } + ber_free_buf( ber ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "slap_build_sync_ctrl: ber_flatten2 failed (%d)\n", + ret ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +/* Build a LDAPsync final state control */ +static int +syncprov_done_ctrl( + Operation *op, + SlapReply *rs, + LDAPControl **ctrls, + int num_ctrls, + int send_cookie, + struct berval *cookie, + int refreshDeletes ) +{ + int ret; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPControl *cp; + struct berval bv; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + ber_printf( ber, "{" ); + if ( send_cookie && cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDeletes == LDAP_SYNC_REFRESH_DELETES ) { + ber_printf( ber, "b", refreshDeletes ); + } + ber_printf( ber, "N}" ); + + ret = ber_flatten2( ber, &bv, 0 ); + if ( ret == 0 ) { + cp = op->o_tmpalloc( sizeof( LDAPControl ) + bv.bv_len, op->o_tmpmemctx ); + cp->ldctl_oid = LDAP_CONTROL_SYNC_DONE; + cp->ldctl_iscritical = (op->o_sync == SLAP_CONTROL_CRITICAL); + cp->ldctl_value.bv_val = (char *)&cp[1]; + cp->ldctl_value.bv_len = bv.bv_len; + AC_MEMCPY( cp->ldctl_value.bv_val, bv.bv_val, bv.bv_len ); + ctrls[num_ctrls] = cp; + } + + ber_free_buf( ber ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "syncprov_done_ctrl: ber_flatten2 failed (%d)\n", + ret ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + return LDAP_SUCCESS; +} + +static int +syncprov_sendinfo( + Operation *op, + SlapReply *rs, + int type, + struct berval *cookie, + int refreshDone, + BerVarray syncUUIDs, + int refreshDeletes ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval rspdata; + + int ret; + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + if ( type ) { + switch ( type ) { + case LDAP_TAG_SYNC_NEW_COOKIE: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "sending a new cookie=%s\n", + op->o_log_prefix, cookie->bv_val ); + ber_printf( ber, "tO", type, cookie ); + break; + case LDAP_TAG_SYNC_REFRESH_DELETE: + case LDAP_TAG_SYNC_REFRESH_PRESENT: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "%s cookie=%s\n", + op->o_log_prefix, + type == LDAP_TAG_SYNC_REFRESH_DELETE ? "refreshDelete" : "refreshPresent", + cookie ? cookie->bv_val : "" ); + ber_printf( ber, "t{", type ); + if ( cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDone == 0 ) { + ber_printf( ber, "b", refreshDone ); + } + ber_printf( ber, "N}" ); + break; + case LDAP_TAG_SYNC_ID_SET: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendinfo: " + "%s syncIdSet cookie=%s\n", + op->o_log_prefix, refreshDeletes ? "delete" : "present", + cookie ? cookie->bv_val : "" ); + ber_printf( ber, "t{", type ); + if ( cookie ) { + ber_printf( ber, "O", cookie ); + } + if ( refreshDeletes == 1 ) { + ber_printf( ber, "b", refreshDeletes ); + } + ber_printf( ber, "[W]", syncUUIDs ); + ber_printf( ber, "N}" ); + break; + default: + Debug( LDAP_DEBUG_TRACE, + "%s syncprov_sendinfo: invalid syncinfo type (%d)\n", + op->o_log_prefix, type ); + return LDAP_OTHER; + } + } + + ret = ber_flatten2( ber, &rspdata, 0 ); + + if ( ret < 0 ) { + Debug( LDAP_DEBUG_TRACE, + "syncprov_sendinfo: ber_flatten2 failed (%d)\n", + ret ); + send_ldap_error( op, rs, LDAP_OTHER, "internal error" ); + return LDAP_OTHER; + } + + rs->sr_rspoid = LDAP_SYNC_INFO; + rs->sr_rspdata = &rspdata; + send_ldap_intermediate( op, rs ); + rs->sr_rspdata = NULL; + ber_free_buf( ber ); + + return LDAP_SUCCESS; +} + +/* Find a modtarget in an AVL tree */ +static int +sp_avl_cmp( const void *c1, const void *c2 ) +{ + const modtarget *m1, *m2; + int rc; + + m1 = c1; m2 = c2; + rc = m1->mt_dn.bv_len - m2->mt_dn.bv_len; + + if ( rc ) return rc; + return ber_bvcmp( &m1->mt_dn, &m2->mt_dn ); +} + +static int +sp_uuid_cmp( const void *l, const void *r ) +{ + const struct berval *left = l, *right = r; + + return ber_bvcmp( left, right ); +} + +static int +syncprov_sessionlog_cmp( const void *l, const void *r ) +{ + const slog_entry *left = l, *right = r; + int ret = ber_bvcmp( &left->se_csn, &right->se_csn ); + if ( !ret ) + ret = ber_bvcmp( &left->se_uuid, &right->se_uuid ); + /* Only time we have two modifications with same CSN is when we detect a + * rename during replication. + * We invert the test here because LDAP_REQ_MODDN is + * numerically greater than LDAP_REQ_MODIFY but we + * want it to occur first. + */ + if ( !ret ) + ret = right->se_tag - left->se_tag; + + return ret; +} + +/* syncprov_findbase: + * finds the true DN of the base of a search (with alias dereferencing) and + * checks to make sure the base entry doesn't get replaced with a different + * entry (e.g., swapping trees via ModDN, or retargeting an alias). If a + * change is detected, any persistent search on this base must be terminated / + * reloaded. + * On the first call, we just save the DN and entryID. On subsequent calls + * we compare the DN and entryID with the saved values. + */ +static int +findbase_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + fbase_cookie *fc = sc->sc_private; + + /* If no entryID, we're looking for the first time. + * Just store whatever we got. + */ + if ( fc->fss->s_eid == NOID ) { + fc->fbase = 2; + fc->fss->s_eid = rs->sr_entry->e_id; + ber_dupbv( &fc->fss->s_base, &rs->sr_entry->e_nname ); + + } else if ( rs->sr_entry->e_id == fc->fss->s_eid && + dn_match( &rs->sr_entry->e_nname, &fc->fss->s_base )) { + + /* OK, the DN is the same and the entryID is the same. */ + fc->fbase = 1; + } + } + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "findbase failed! %d\n", rs->sr_err ); + } + return LDAP_SUCCESS; +} + +static Filter generic_filter = { LDAP_FILTER_PRESENT, { 0 }, NULL }; +static struct berval generic_filterstr = BER_BVC("(objectclass=*)"); + +static int +syncprov_findbase( Operation *op, fbase_cookie *fc ) +{ + /* Use basic parameters from syncrepl search, but use + * current op's threadctx / tmpmemctx + */ + ldap_pvt_thread_mutex_lock( &fc->fss->s_mutex ); + if ( fc->fss->s_flags & PS_FIND_BASE ) { + slap_callback cb = {0}; + Operation fop; + SlapReply frs = { REP_RESULT }; + int rc; + + fc->fss->s_flags ^= PS_FIND_BASE; + ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex ); + + fop = *fc->fss->s_op; + + fop.o_bd = fop.o_bd->bd_self; + fop.o_hdr = op->o_hdr; + fop.o_time = op->o_time; + fop.o_tincr = op->o_tincr; + fop.o_extra = op->o_extra; + + cb.sc_response = findbase_cb; + cb.sc_private = fc; + + fop.o_sync_mode = 0; /* turn off sync mode */ + fop.o_managedsait = SLAP_CONTROL_CRITICAL; + fop.o_callback = &cb; + fop.o_tag = LDAP_REQ_SEARCH; + fop.ors_scope = LDAP_SCOPE_BASE; + fop.ors_limit = NULL; + fop.ors_slimit = 1; + fop.ors_tlimit = SLAP_NO_LIMIT; + fop.ors_attrs = slap_anlist_no_attrs; + fop.ors_attrsonly = 1; + fop.ors_filter = &generic_filter; + fop.ors_filterstr = generic_filterstr; + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_findbase: searching\n", op->o_log_prefix ); + rc = fop.o_bd->be_search( &fop, &frs ); + } else { + ldap_pvt_thread_mutex_unlock( &fc->fss->s_mutex ); + fc->fbase = 1; + } + + /* After the first call, see if the fdn resides in the scope */ + if ( fc->fbase == 1 ) { + switch ( fc->fss->s_op->ors_scope ) { + case LDAP_SCOPE_BASE: + fc->fscope = dn_match( fc->fdn, &fc->fss->s_base ); + break; + case LDAP_SCOPE_ONELEVEL: { + struct berval pdn; + dnParent( fc->fdn, &pdn ); + fc->fscope = dn_match( &pdn, &fc->fss->s_base ); + break; } + case LDAP_SCOPE_SUBTREE: + fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ); + break; + case LDAP_SCOPE_SUBORDINATE: + fc->fscope = dnIsSuffix( fc->fdn, &fc->fss->s_base ) && + !dn_match( fc->fdn, &fc->fss->s_base ); + break; + } + } + + if ( fc->fbase ) + return LDAP_SUCCESS; + + /* If entryID has changed, then the base of this search has + * changed. Invalidate the psearch. + */ + return LDAP_NO_SUCH_OBJECT; +} + +/* syncprov_findcsn: + * This function has three different purposes, but they all use a search + * that filters on entryCSN so they're combined here. + * 1: at startup time, after a contextCSN has been read from the database, + * we search for all entries with CSN >= contextCSN in case the contextCSN + * was not checkpointed at the previous shutdown. + * + * 2: when the current contextCSN is known and we have a sync cookie, we search + * for one entry with CSN = the cookie CSN. If not found, try <= cookie CSN. + * If an entry is found, the cookie CSN is valid, otherwise it is stale. + * + * 3: during a refresh phase, we search for all entries with CSN <= the cookie + * CSN, and generate Present records for them. We always collect this result + * in SyncID sets, even if there's only one match. + */ +typedef enum find_csn_t { + FIND_MAXCSN = 1, + FIND_CSN = 2, + FIND_PRESENT = 3 +} find_csn_t; + +static int +findmax_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + struct berval *maxcsn = op->o_callback->sc_private; + Attribute *a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_entryCSN ); + + if ( a && ber_bvcmp( &a->a_vals[0], maxcsn ) > 0 && + slap_parse_csn_sid( &a->a_vals[0] ) == slap_serverID ) { + maxcsn->bv_len = a->a_vals[0].bv_len; + strcpy( maxcsn->bv_val, a->a_vals[0].bv_val ); + } + } + return LDAP_SUCCESS; +} + +static int +findcsn_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + + /* We just want to know that at least one exists, so it's OK if + * we exceed the unchecked limit. + */ + if ( rs->sr_err == LDAP_ADMINLIMIT_EXCEEDED || + (rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS )) { + sc->sc_private = (void *)1; + } + return LDAP_SUCCESS; +} + +/* Build a list of entryUUIDs for sending in a SyncID set */ + +#define UUID_LEN 16 + +typedef struct fpres_cookie { + int num; + BerVarray uuids; + char *last; +} fpres_cookie; + +static int +findpres_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + fpres_cookie *pc = sc->sc_private; + Attribute *a; + int ret = SLAP_CB_CONTINUE; + + switch ( rs->sr_type ) { + case REP_SEARCH: + a = attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + pc->uuids[pc->num].bv_val = pc->last; + AC_MEMCPY( pc->uuids[pc->num].bv_val, a->a_nvals[0].bv_val, + pc->uuids[pc->num].bv_len ); + pc->num++; + pc->last = pc->uuids[pc->num].bv_val; + pc->uuids[pc->num].bv_val = NULL; + } + ret = LDAP_SUCCESS; + if ( pc->num != SLAP_SYNCUUID_SET_SIZE ) + break; + /* FALLTHRU */ + case REP_RESULT: + ret = rs->sr_err; + if ( pc->num ) { + ret = syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, NULL, + 0, pc->uuids, 0 ); + pc->uuids[pc->num].bv_val = pc->last; + pc->num = 0; + pc->last = pc->uuids[0].bv_val; + } + break; + default: + break; + } + return ret; +} + +static int +syncprov_findcsn( Operation *op, find_csn_t mode, struct berval *csn ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + + slap_callback cb = {0}; + Operation fop; + SlapReply frs = { REP_RESULT }; + char buf[LDAP_PVT_CSNSTR_BUFSIZE + STRLENOF("(entryCSN<=)")]; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + struct berval maxcsn; + Filter cf; + AttributeAssertion eq = ATTRIBUTEASSERTION_INIT; + fpres_cookie pcookie; + sync_control *srs = NULL; + struct slap_limits_set fc_limits; + int i, rc = LDAP_SUCCESS, findcsn_retry = 1; + int maxid; + + if ( mode != FIND_MAXCSN ) { + srs = op->o_controls[slap_cids.sc_LDAPsync]; + } + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: mode=%s csn=%s\n", + op->o_log_prefix, + mode == FIND_MAXCSN ? + "FIND_MAXCSN" : + mode == FIND_CSN ? + "FIND_CSN" : + "FIND_PRESENT", + csn ? csn->bv_val : "" ); + + 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. */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_findcsn: csn%s=%s %sfound\n", + op->o_log_prefix, + cf.f_choice == LDAP_FILTER_EQUALITY ? "=" : "<", + cf.f_av_value.bv_val, cb.sc_private ? "" : "not " ); + if ( !cb.sc_private ) { + /* If we didn't find an exact match, then try for <= */ + if ( findcsn_retry ) { + findcsn_retry = 0; + rs_reinit( &frs, REP_RESULT ); + goto again; + } + rc = LDAP_NO_SUCH_OBJECT; + } + break; + case FIND_PRESENT: + op->o_tmpfree( pcookie.uuids, op->o_tmpmemctx ); + break; + } + + return rc; +} + +static void free_resinfo( syncres *sr ) +{ + syncres **st; + resinfo *ri = sr->s_info; + int freeit = 0; + + ldap_pvt_thread_mutex_lock( &ri->ri_mutex ); + for (st = &sr->s_info->ri_list; *st; st = &(*st)->s_rilist) { + if (*st == sr) { + *st = sr->s_rilist; + if ( !sr->s_info->ri_list ) + freeit = 1; + sr->s_info = NULL; + break; + } + } + ldap_pvt_thread_mutex_unlock( &ri->ri_mutex ); + if ( freeit ) { + ldap_pvt_thread_mutex_destroy( &ri->ri_mutex ); + if ( ri->ri_e ) + entry_free( ri->ri_e ); + if ( !BER_BVISNULL( &ri->ri_cookie )) + ch_free( ri->ri_cookie.bv_val ); + ch_free( ri ); + } +} + +#define FS_UNLINK 1 +#define FS_LOCK 2 + +static int +syncprov_free_syncop( syncops *so, int flags ) +{ + syncres *sr, *srnext; + GroupAssertion *ga, *gnext; + + if ( flags & FS_LOCK ) + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + /* already being freed, or still in use */ + if ( !so->s_inuse || --so->s_inuse > 0 ) { + if ( flags & FS_LOCK ) + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + return 0; + } + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + if (( flags & FS_UNLINK ) && so->s_si ) { + syncops **sop; + ldap_pvt_thread_mutex_lock( &so->s_si->si_ops_mutex ); + for ( sop = &so->s_si->si_ops; *sop; sop = &(*sop)->s_next ) { + if ( *sop == so ) { + *sop = so->s_next; + break; + } + } + ldap_pvt_thread_mutex_unlock( &so->s_si->si_ops_mutex ); + } + if ( so->s_flags & PS_IS_DETACHED ) { + filter_free( so->s_op->ors_filter ); + for ( ga = so->s_op->o_groups; ga; ga=gnext ) { + gnext = ga->ga_next; + ch_free( ga ); + } + ch_free( so->s_op ); + } + ch_free( so->s_base.bv_val ); + for ( sr=so->s_res; sr; sr=srnext ) { + srnext = sr->s_next; + free_resinfo( sr ); + ch_free( sr ); + } + ldap_pvt_thread_mutex_destroy( &so->s_mutex ); + ch_free( so ); + return 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, NULL ); + +#ifdef LDAP_DEBUG + if ( so->s_sid > 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: to=%03x, cookie=%s\n", + op->o_log_prefix, so->s_sid, cookie.bv_val ); + } else { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: cookie=%s\n", + op->o_log_prefix, cookie.bv_val ); + } +#endif + + e_uuid.e_attrs = &a_uuid; + a_uuid.a_desc = slap_schema.si_ad_entryUUID; + a_uuid.a_nvals = &ri->ri_uuid; + rs.sr_err = syncprov_state_ctrl( op, &rs, &e_uuid, + mode, rs.sr_ctrls, 0, 1, &cookie ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + + rs.sr_entry = &e_uuid; + if ( mode == LDAP_SYNC_ADD || mode == LDAP_SYNC_MODIFY ) { + e_uuid = *ri->ri_e; + e_uuid.e_private = NULL; + } + + switch( mode ) { + case LDAP_SYNC_ADD: + if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) { + rs.sr_ref = get_entry_referrals( op, rs.sr_entry ); + rs.sr_err = send_search_reference( op, &rs ); + ber_bvarray_free( rs.sr_ref ); + break; + } + /* fallthru */ + case LDAP_SYNC_MODIFY: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: sending %s, dn=%s\n", + op->o_log_prefix, + mode == LDAP_SYNC_ADD ? "LDAP_SYNC_ADD" : "LDAP_SYNC_MODIFY", + e_uuid.e_nname.bv_val ); + rs.sr_attrs = op->ors_attrs; + rs.sr_err = send_search_entry( op, &rs ); + break; + case LDAP_SYNC_DELETE: + Debug( LDAP_DEBUG_SYNC, "%s syncprov_sendresp: " + "sending LDAP_SYNC_DELETE, dn=%s\n", + op->o_log_prefix, ri->ri_dn.bv_val ); + e_uuid.e_attrs = NULL; + e_uuid.e_name = ri->ri_dn; + e_uuid.e_nname = ri->ri_ndn; + if ( ri->ri_isref && so->s_op->o_managedsait <= SLAP_CONTROL_IGNORED ) { + struct berval bv = BER_BVNULL; + rs.sr_ref = &bv; + rs.sr_err = send_search_reference( op, &rs ); + } else { + rs.sr_err = send_search_entry( op, &rs ); + } + break; + default: + assert(0); + } + return rs.sr_err; +} + +static void +syncprov_qstart( syncops *so ); + +/* Play back queued responses */ +static int +syncprov_qplay( Operation *op, syncops *so ) +{ + syncres *sr; + int rc = 0; + + do { + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + sr = so->s_res; + /* Exit loop with mutex held */ + if ( !sr ) + break; + so->s_res = sr->s_next; + if ( !so->s_res ) + so->s_restail = NULL; + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + + if ( !so->s_op->o_abandon ) { + + if ( sr->s_mode == LDAP_SYNC_NEW_COOKIE ) { + SlapReply rs = { REP_INTERMEDIATE }; + + rc = syncprov_sendinfo( op, &rs, LDAP_TAG_SYNC_NEW_COOKIE, + &sr->s_info->ri_cookie, 0, NULL, 0 ); + } else { + rc = syncprov_sendresp( op, sr->s_info, so, sr->s_mode ); + } + } + + free_resinfo( sr ); + ch_free( sr ); + + if ( so->s_op->o_abandon ) + continue; + + /* Exit loop with mutex held */ + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + break; + + } while (1); + + /* We now only send one change at a time, to prevent one + * psearch from hogging all the CPU. Resubmit this task if + * there are more responses queued and no errors occurred. + */ + + if ( rc == 0 && so->s_res ) { + syncprov_qstart( so ); + } + + return rc; +} + +/* 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; + operation_counter_init( op, ctx ); + + /* syncprov_qplay expects a fake db */ + be = *so->s_op->o_bd; + be.be_flags |= SLAP_DBFLAG_OVERLAY; + op->o_bd = &be; + LDAP_SLIST_FIRST(&op->o_extra) = NULL; + op->o_callback = NULL; + + rc = syncprov_qplay( op, so ); + + /* if an error occurred, or no responses left, task is no longer queued */ + if ( !rc && !so->s_res ) + rc = 1; + + /* decrement use count... */ + if ( !syncprov_free_syncop( so, FS_UNLINK )) { + if ( rc ) + /* if we didn't unlink, and task is no longer queued, clear flag */ + so->s_flags ^= PS_TASK_QUEUED; + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + } + + return NULL; +} + +/* Start the task to play back queued psearch responses */ +static void +syncprov_qstart( syncops *so ) +{ + so->s_flags |= PS_TASK_QUEUED; + so->s_inuse++; + ldap_pvt_thread_pool_submit2( &connection_pool, + syncprov_qtask, so, &so->s_pool_cookie ); +} + +/* Queue a persistent search response */ +static int +syncprov_qresp( opcookie *opc, syncops *so, int mode ) +{ + syncres *sr; + resinfo *ri; + int srsize; + struct berval csn = opc->sctxcsn; + + sr = ch_malloc( sizeof( syncres )); + sr->s_next = NULL; + sr->s_mode = mode; + if ( !opc->ssres.s_info ) { + + srsize = sizeof( resinfo ); + if ( csn.bv_len ) + srsize += csn.bv_len + 1; + + if ( opc->se ) { + Attribute *a; + ri = ch_malloc( srsize ); + ri->ri_dn = opc->se->e_name; + ri->ri_ndn = opc->se->e_nname; + a = attr_find( opc->se->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) + ri->ri_uuid = a->a_nvals[0]; + else + ri->ri_uuid.bv_len = 0; + if ( csn.bv_len ) { + ri->ri_csn.bv_val = (char *)(ri + 1); + ri->ri_csn.bv_len = csn.bv_len; + memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len ); + ri->ri_csn.bv_val[csn.bv_len] = '\0'; + } else { + ri->ri_csn.bv_val = NULL; + } + } else { + srsize += opc->suuid.bv_len + + opc->sdn.bv_len + 1 + opc->sndn.bv_len + 1; + ri = ch_malloc( srsize ); + ri->ri_dn.bv_val = (char *)(ri + 1); + ri->ri_dn.bv_len = opc->sdn.bv_len; + ri->ri_ndn.bv_val = lutil_strcopy( ri->ri_dn.bv_val, + opc->sdn.bv_val ) + 1; + ri->ri_ndn.bv_len = opc->sndn.bv_len; + ri->ri_uuid.bv_val = lutil_strcopy( ri->ri_ndn.bv_val, + opc->sndn.bv_val ) + 1; + ri->ri_uuid.bv_len = opc->suuid.bv_len; + AC_MEMCPY( ri->ri_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len ); + if ( csn.bv_len ) { + ri->ri_csn.bv_val = ri->ri_uuid.bv_val + ri->ri_uuid.bv_len; + memcpy( ri->ri_csn.bv_val, csn.bv_val, csn.bv_len ); + ri->ri_csn.bv_val[csn.bv_len] = '\0'; + } else { + ri->ri_csn.bv_val = NULL; + } + } + ri->ri_list = &opc->ssres; + ri->ri_e = opc->se; + ri->ri_csn.bv_len = csn.bv_len; + ri->ri_isref = opc->sreference; + BER_BVZERO( &ri->ri_cookie ); + ldap_pvt_thread_mutex_init( &ri->ri_mutex ); + opc->se = NULL; + opc->ssres.s_info = ri; + } + ri = opc->ssres.s_info; + sr->s_info = ri; + ldap_pvt_thread_mutex_lock( &ri->ri_mutex ); + sr->s_rilist = ri->ri_list; + ri->ri_list = sr; + if ( mode == LDAP_SYNC_NEW_COOKIE && BER_BVISNULL( &ri->ri_cookie )) { + syncprov_info_t *si = opc->son->on_bi.bi_private; + + slap_compose_sync_cookie( NULL, &ri->ri_cookie, si->si_ctxcsn, + so->s_rid, slap_serverID ? slap_serverID : -1, NULL ); + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_qresp: " + "set up a new syncres mode=%d csn=%s\n", + so->s_op->o_log_prefix, mode, csn.bv_val ? csn.bv_val : "" ); + ldap_pvt_thread_mutex_unlock( &ri->ri_mutex ); + + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + if ( !so->s_res ) { + so->s_res = sr; + } else { + so->s_restail->s_next = sr; + } + so->s_restail = sr; + + /* If the base of the psearch was modified, check it next time round */ + if ( so->s_flags & PS_WROTE_BASE ) { + so->s_flags ^= PS_WROTE_BASE; + so->s_flags |= PS_FIND_BASE; + } + if (( so->s_flags & (PS_IS_DETACHED|PS_TASK_QUEUED)) == PS_IS_DETACHED ) { + syncprov_qstart( so ); + } + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + return LDAP_SUCCESS; +} + +static int +syncprov_drop_psearch( syncops *so, int lock ) +{ + if ( so->s_flags & PS_IS_DETACHED ) { + if ( lock ) + ldap_pvt_thread_mutex_lock( &so->s_op->o_conn->c_mutex ); + so->s_op->o_conn->c_n_ops_executing--; + so->s_op->o_conn->c_n_ops_completed++; + LDAP_STAILQ_REMOVE( &so->s_op->o_conn->c_ops, so->s_op, Operation, + o_next ); + if ( lock ) + ldap_pvt_thread_mutex_unlock( &so->s_op->o_conn->c_mutex ); + } + return syncprov_free_syncop( so, FS_LOCK ); +} + +static int +syncprov_ab_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + op->o_callback = sc->sc_next; + syncprov_drop_psearch( sc->sc_private, 0 ); + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int +syncprov_op_abandon( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + syncops *so, **sop; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( sop=&si->si_ops; (so = *sop); sop = &(*sop)->s_next ) { + if ( so->s_op->o_connid == op->o_connid && + so->s_op->o_msgid == op->orn_msgid ) { + so->s_op->o_abandon = 1; + *sop = so->s_next; + break; + } + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( so ) { + /* Is this really a Cancel exop? */ + if ( op->o_tag != LDAP_REQ_ABANDON ) { + so->s_op->o_cancel = SLAP_CANCEL_ACK; + rs->sr_err = LDAP_CANCELLED; + send_ldap_result( so->s_op, rs ); + if ( so->s_flags & PS_IS_DETACHED ) { + slap_callback *cb; + cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = syncprov_ab_cleanup; + cb->sc_next = op->o_callback; + cb->sc_private = so; + op->o_callback = cb; + return SLAP_CB_CONTINUE; + } + } + syncprov_drop_psearch( so, 0 ); + } + return SLAP_CB_CONTINUE; +} + +/* Find which persistent searches are affected by this operation */ +static void +syncprov_matchops( Operation *op, opcookie *opc, int saveit ) +{ + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + + fbase_cookie fc; + syncops **pss; + Entry *e = NULL; + Attribute *a; + int rc, gonext; + struct berval newdn; + int freefdn = 0; + BackendDB *b0 = op->o_bd, db; + + fc.fdn = &op->o_req_ndn; + /* compute new DN */ + if ( op->o_tag == LDAP_REQ_MODRDN && !saveit ) { + struct berval pdn; + if ( op->orr_nnewSup ) pdn = *op->orr_nnewSup; + else dnParent( fc.fdn, &pdn ); + build_new_dn( &newdn, &pdn, &op->orr_nnewrdn, op->o_tmpmemctx ); + fc.fdn = &newdn; + freefdn = 1; + } + if ( op->o_tag != LDAP_REQ_ADD ) { + if ( !SLAP_ISOVERLAY( op->o_bd )) { + db = *op->o_bd; + op->o_bd = &db; + } + rc = overlay_entry_get_ov( op, fc.fdn, NULL, NULL, 0, &e, on ); + /* If we're sending responses now, make a copy and unlock the DB */ + if ( e && !saveit ) { + if ( !opc->se ) + opc->se = entry_dup( e ); + overlay_entry_release_ov( op, e, 0, on ); + e = opc->se; + } + if ( rc ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: " + "%s check, error finding entry dn=%s in database\n", + op->o_log_prefix, saveit ? "initial" : "final", fc.fdn->bv_val ); + op->o_bd = b0; + return; + } + } else { + e = op->ora_e; + if ( !saveit ) { + if ( !opc->se ) + opc->se = entry_dup( e ); + e = opc->se; + } + } + + if ( saveit || op->o_tag == LDAP_REQ_ADD ) { + ber_dupbv_x( &opc->sdn, &e->e_name, op->o_tmpmemctx ); + ber_dupbv_x( &opc->sndn, &e->e_nname, op->o_tmpmemctx ); + opc->sreference = is_entry_referral( e ); + a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) + ber_dupbv_x( &opc->suuid, &a->a_nvals[0], op->o_tmpmemctx ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: " + "%srecording uuid for dn=%s on opc=%p\n", + op->o_log_prefix, a ? "" : "not ", opc->sdn.bv_val, opc ); + } 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, "%s syncprov_matchops: " + "skipping original sid %03x\n", + ss->s_op->o_log_prefix, opc->osid ); + continue; + } + + /* Don't send ops back to the messenger */ + if ( opc->rsid > 0 && opc->rsid == ss->s_sid ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_matchops: " + "skipping relayed sid %03x\n", + ss->s_op->o_log_prefix, opc->rsid ); + continue; + } + + /* validate base */ + fc.fss = ss; + fc.fbase = 0; + fc.fscope = 0; + + /* If the base of the search is missing, signal a refresh */ + rc = syncprov_findbase( op, &fc ); + if ( rc != LDAP_SUCCESS ) { + SlapReply rs = {REP_RESULT}; + send_ldap_error( ss->s_op, &rs, LDAP_SYNC_REFRESH_REQUIRED, + "search base has changed" ); + snext = ss->s_next; + if ( syncprov_drop_psearch( ss, 1 ) ) + *pss = snext; + gonext = 0; + continue; + } + + /* If we're sending results now, look for this op in old matches */ + if ( !saveit ) { + syncmatches *old; + + /* Did we modify the search base? */ + if ( dn_match( &op->o_req_ndn, &ss->s_base )) { + ldap_pvt_thread_mutex_lock( &ss->s_mutex ); + ss->s_flags |= PS_WROTE_BASE; + ldap_pvt_thread_mutex_unlock( &ss->s_mutex ); + } + + for ( sm=opc->smatches, old=(syncmatches *)&opc->smatches; sm; + old=sm, sm=sm->sm_next ) { + if ( sm->sm_op == ss ) { + found = 1; + old->sm_next = sm->sm_next; + op->o_tmpfree( sm, op->o_tmpmemctx ); + break; + } + } + } + + 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, "%s syncprov_matchops: " + "sid %03x fscope %d rc %d\n", + ss->s_op->o_log_prefix, ss->s_sid, fc.fscope, rc ); + + /* check if current o_req_dn is in scope and matches filter */ + if ( fc.fscope && rc == LDAP_COMPARE_TRUE ) { + if ( saveit ) { + sm = op->o_tmpalloc( sizeof(syncmatches), op->o_tmpmemctx ); + sm->sm_next = opc->smatches; + sm->sm_op = ss; + ldap_pvt_thread_mutex_lock( &ss->s_mutex ); + ++ss->s_inuse; + ldap_pvt_thread_mutex_unlock( &ss->s_mutex ); + opc->smatches = sm; + } else { + /* if found send UPDATE else send ADD */ + syncprov_qresp( opc, ss, + found ? LDAP_SYNC_MODIFY : LDAP_SYNC_ADD ); + } + } else if ( !saveit && found ) { + /* send DELETE */ + syncprov_qresp( opc, ss, LDAP_SYNC_DELETE ); + } else if ( !saveit ) { + syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE ); + } + if ( !saveit && found ) { + /* Decrement s_inuse, was incremented when called + * with saveit == TRUE + */ + snext = ss->s_next; + if ( syncprov_free_syncop( ss, FS_LOCK ) ) { + *pss = snext; + gonext = 0; + } + } + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + + if ( op->o_tag != LDAP_REQ_ADD && e ) { + if ( !SLAP_ISOVERLAY( op->o_bd )) { + op->o_bd = &db; + } + if ( saveit ) + overlay_entry_release_ov( op, e, 0, on ); + op->o_bd = b0; + } + if ( !saveit ) { + if ( opc->ssres.s_info ) + free_resinfo( &opc->ssres ); + else if ( opc->se ) + entry_free( opc->se ); + } + 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, FS_LOCK|FS_UNLINK ); + op->o_tmpfree( sm, op->o_tmpmemctx ); + } + + /* Remove op from lock table */ + mt = opc->smt; + if ( mt ) { + modinst *mi = (modinst *)(opc+1), **m2; + ldap_pvt_thread_mutex_lock( &mt->mt_mutex ); + for (m2 = &mt->mt_mods; ; m2 = &(*m2)->mi_next) { + if ( *m2 == mi ) { + *m2 = mi->mi_next; + if ( mt->mt_tail == mi ) + mt->mt_tail = ( m2 == &mt->mt_mods ) ? NULL : (modinst *)m2; + break; + } + } + /* If there are more, promote the next one */ + if ( mt->mt_mods ) { + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + } else { + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + ldap_pvt_thread_mutex_lock( &si->si_mods_mutex ); + ldap_avl_delete( &si->si_mods, mt, sp_avl_cmp ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_destroy( &mt->mt_mutex ); + ch_free( mt->mt_dn.bv_val ); + ch_free( mt ); + } + } + if ( !BER_BVISNULL( &opc->suuid )) + op->o_tmpfree( opc->suuid.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &opc->sndn )) + op->o_tmpfree( opc->sndn.bv_val, op->o_tmpmemctx ); + if ( !BER_BVISNULL( &opc->sdn )) + op->o_tmpfree( opc->sdn.bv_val, op->o_tmpmemctx ); + op->o_callback = cb->sc_next; + + if ( opc->ssres.s_info ) { + free_resinfo( &opc->ssres ); + } + op->o_tmpfree(cb, op->o_tmpmemctx); + + return 0; +} + +static void +syncprov_checkpoint( Operation *op, slap_overinst *on ) +{ + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + Modifications mod; + Operation opm; + SlapReply rsm = {REP_RESULT}; + slap_callback cb = {0}; + BackendDB be; + BackendInfo *bi; + +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; + + int i; + for ( i=0; i<si->si_numcsns; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i )); + } +#endif + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_checkpoint: running checkpoint\n", + op->o_log_prefix ); + + mod.sml_numvals = si->si_numcsns; + mod.sml_values = si->si_ctxcsn; + mod.sml_nvalues = NULL; + mod.sml_desc = slap_schema.si_ad_contextCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + cb.sc_response = slap_null_cb; + opm = *op; + opm.o_tag = LDAP_REQ_MODIFY; + opm.o_callback = &cb; + opm.orm_modlist = &mod; + opm.orm_no_opattrs = 1; + if ( SLAP_GLUE_SUBORDINATE( op->o_bd )) { + be = *on->on_info->oi_origdb; + opm.o_bd = &be; + } + opm.o_req_dn = si->si_contextdn; + opm.o_req_ndn = si->si_contextdn; + bi = opm.o_bd->bd_info; + opm.o_bd->bd_info = on->on_info->oi_orig; + opm.o_managedsait = SLAP_CONTROL_NONCRITICAL; + opm.o_no_schema_check = 1; + opm.o_dont_replicate = 1; + opm.o_opid = -1; + opm.o_bd->be_modify( &opm, &rsm ); + + if ( rsm.sr_err == LDAP_NO_SUCH_OBJECT && + SLAP_SYNC_SUBENTRY( opm.o_bd )) { + const char *text; + char txtbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof txtbuf; + Entry *e = slap_create_context_csn_entry( opm.o_bd, NULL ); + rs_reinit( &rsm, REP_RESULT ); + slap_mods2entry( &mod, &e, 0, 1, &text, txtbuf, textlen); + opm.ora_e = e; + opm.o_bd->be_add( &opm, &rsm ); + if ( e == opm.ora_e ) + be_entry_release_w( &opm, opm.ora_e ); + } + opm.o_bd->bd_info = bi; + + if ( mod.sml_next != NULL ) { + slap_mods_free( mod.sml_next, 1 ); + } +#ifdef CHECK_CSN + for ( i=0; i<si->si_numcsns; i++ ) { + assert( !syn->ssyn_validate( syn, si->si_ctxcsn+i )); + } +#endif +} + +static void +syncprov_add_slog( Operation *op ) +{ + opcookie *opc = op->o_callback->sc_private; + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + sessionlog *sl; + slog_entry *se; + char uuidstr[40]; + int rc; + + sl = si->si_logs; + { + if ( BER_BVISEMPTY( &op->o_csn ) ) { + /* During the syncrepl refresh phase we can receive operations + * without a csn. We cannot reliably determine the consumers + * state with respect to such operations, so we ignore them and + * wipe out anything in the log if we see them. + */ + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + /* can only do this if no one else is reading the log at the moment */ + if ( !sl->sl_playing ) { + ldap_tavl_free( sl->sl_entries, (AVL_FREE)ch_free ); + sl->sl_num = 0; + sl->sl_entries = NULL; + } + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + return; + } + + /* Allocate a record. UUIDs are not NUL-terminated. */ + se = ch_malloc( sizeof( slog_entry ) + opc->suuid.bv_len + + op->o_csn.bv_len + 1 ); + se->se_tag = op->o_tag; + + se->se_uuid.bv_val = (char *)(&se[1]); + AC_MEMCPY( se->se_uuid.bv_val, opc->suuid.bv_val, opc->suuid.bv_len ); + se->se_uuid.bv_len = opc->suuid.bv_len; + + se->se_csn.bv_val = se->se_uuid.bv_val + opc->suuid.bv_len; + AC_MEMCPY( se->se_csn.bv_val, op->o_csn.bv_val, op->o_csn.bv_len ); + se->se_csn.bv_val[op->o_csn.bv_len] = '\0'; + se->se_csn.bv_len = op->o_csn.bv_len; + se->se_sid = slap_parse_csn_sid( &se->se_csn ); + + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + if ( LogTest( LDAP_DEBUG_SYNC ) ) { + uuidstr[0] = 0; + if ( !BER_BVISEMPTY( &opc->suuid ) ) { + lutil_uuidstr_from_normalized( opc->suuid.bv_val, opc->suuid.bv_len, + uuidstr, 40 ); + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "adding csn=%s to sessionlog, uuid=%s\n", + op->o_log_prefix, se->se_csn.bv_val, uuidstr ); + } + if ( !sl->sl_entries ) { + if ( !sl->sl_mincsn ) { + sl->sl_numcsns = 1; + sl->sl_mincsn = ch_malloc( 2*sizeof( struct berval )); + sl->sl_sids = ch_malloc( sizeof( int )); + sl->sl_sids[0] = se->se_sid; + ber_dupbv( sl->sl_mincsn, &se->se_csn ); + BER_BVZERO( &sl->sl_mincsn[1] ); + } + } + rc = ldap_tavl_insert( &sl->sl_entries, se, syncprov_sessionlog_cmp, ldap_avl_dup_error ); + if ( rc ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "duplicate sessionlog entry ignored: csn=%s, uuid=%s\n", + op->o_log_prefix, se->se_csn.bv_val, uuidstr ); + ch_free( se ); + goto leave; + } + sl->sl_num++; + if ( !sl->sl_playing && sl->sl_num > sl->sl_size ) { + TAvlnode *edge = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT ); + while ( sl->sl_num > sl->sl_size ) { + int i; + TAvlnode *next = ldap_tavl_next( edge, TAVL_DIR_RIGHT ); + se = edge->avl_data; + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "expiring csn=%s from sessionlog (sessionlog size=%d)\n", + op->o_log_prefix, se->se_csn.bv_val, sl->sl_num ); + for ( i=0; i<sl->sl_numcsns; i++ ) + if ( sl->sl_sids[i] >= se->se_sid ) + break; + if ( i == sl->sl_numcsns || sl->sl_sids[i] != se->se_sid ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "adding csn=%s to mincsn\n", + op->o_log_prefix, se->se_csn.bv_val ); + slap_insert_csn_sids( (struct sync_cookie *)sl, + i, se->se_sid, &se->se_csn ); + } else { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_add_slog: " + "updating mincsn for sid=%d csn=%s to %s\n", + op->o_log_prefix, se->se_sid, sl->sl_mincsn[i].bv_val, se->se_csn.bv_val ); + ber_bvreplace( &sl->sl_mincsn[i], &se->se_csn ); + } + ldap_tavl_delete( &sl->sl_entries, se, syncprov_sessionlog_cmp ); + ch_free( se ); + edge = next; + sl->sl_num--; + } + } +leave: + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + } +} + +/* Just set a flag if we found the matching entry */ +static int +playlog_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + op->o_callback->sc_private = (void *)1; + } + return rs->sr_err; +} + +/* + * Check whether the last nmods UUIDs in the uuids list exist in the database + * and (still) match the op filter, zero out the bv_len of any that still exist + * and return the number of UUIDs we have confirmed are gone now. + */ +static int +check_uuidlist_presence( + Operation *op, + struct berval *uuids, + int len, + int nmods ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Operation fop = *op; + SlapReply frs = { REP_RESULT }; + Filter mf, af; + AttributeAssertion eq = ATTRIBUTEASSERTION_INIT; + slap_callback cb = {0}; + int i, mods = nmods; + + fop.o_sync_mode = 0; + fop.o_callback = &cb; + fop.ors_limit = NULL; + fop.ors_tlimit = SLAP_NO_LIMIT; + fop.ors_attrs = slap_anlist_all_attributes; + fop.ors_attrsonly = 0; + fop.o_managedsait = SLAP_CONTROL_CRITICAL; + + af.f_choice = LDAP_FILTER_AND; + af.f_next = NULL; + af.f_and = &mf; + mf.f_choice = LDAP_FILTER_EQUALITY; + mf.f_ava = &eq; + mf.f_av_desc = slap_schema.si_ad_entryUUID; + mf.f_next = fop.ors_filter; + + fop.ors_filter = ⁡ + + cb.sc_response = playlog_cb; + + fop.o_bd->bd_info = (BackendInfo *)on->on_info; + for ( i=0; i<nmods; i++ ) { + mf.f_av_value = uuids[ len - 1 - i ]; + cb.sc_private = NULL; + fop.ors_slimit = 1; + + if ( BER_BVISEMPTY( &mf.f_av_value ) ) { + mods--; + continue; + } + + rs_reinit( &frs, REP_RESULT ); + fop.o_bd->be_search( &fop, &frs ); + if ( cb.sc_private ) { + uuids[ len - 1 - i ].bv_len = 0; + mods--; + } + } + fop.o_bd->bd_info = (BackendInfo *)on; + + return mods; +} + +/* + * On each entry we get from the DB: + * - if it's an ADD, skip + * - check we've not handled it yet, skip if we have + * - check if it's a DELETE or missing from the DB now + * - send a new syncinfo entry + * - remember we've handled it already + * + * If we exhaust the list, clear it, forgetting entries we've handled so far. + */ +static int +syncprov_accesslog_uuid_cb( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + syncprov_accesslog_deletes *uuid_progress = sc->sc_private; + Attribute *a, *attrs; + sync_control *srs = uuid_progress->srs; + struct berval *bv, csn[2] = {}, uuid[2] = {}, + add = BER_BVC("add"), + delete = BER_BVC("delete"), + modrdn = BER_BVC("modrdn"); + int cmp, sid, i, is_delete = 0, rc; + + if ( rs->sr_type != REP_SEARCH ) { + return rs->sr_err; + } + attrs = rs->sr_entry->e_attrs; + + a = attr_find( attrs, ad_reqType ); + if ( !a || a->a_numvals == 0 ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + return rs->sr_err; + } + + if ( bvmatch( &a->a_nvals[0], &add ) ) { + return rs->sr_err; + } + + if ( bvmatch( &a->a_nvals[0], &delete ) ) { + is_delete = 1; + } + + if ( bvmatch( &a->a_nvals[0], &modrdn ) ) { + a = attr_find( attrs, ad_reqDN ); + if ( !a || a->a_numvals == 0 ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + return rs->sr_err; + } + + /* Was it present in the first place? If not, skip: */ + if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) { + return rs->sr_err; + } + + a = attr_find( attrs, ad_reqNewDN ); + if ( !a || a->a_numvals == 0 ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + return rs->sr_err; + } + + /* Has it gone away? */ + if ( !dnIsSuffix( &a->a_nvals[0], &uuid_progress->op->o_req_ndn ) ) { + is_delete = 1; + } + } + + /* + * Only pick entries that are both: + */ + a = attr_find( attrs, slap_schema.si_ad_entryCSN ); + if ( !a || a->a_numvals == 0 ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + return rs->sr_err; + } + csn[0] = a->a_nvals[0]; + + sid = slap_parse_csn_sid( &csn[0] ); + + /* + * newer than cookieCSN (srs->sr_state.ctxcsn) + */ + cmp = 1; + for ( i=0; i<srs->sr_state.numcsns; i++ ) { + if ( sid == srs->sr_state.sids[i] ) { + cmp = ber_bvcmp( &csn[0], &srs->sr_state.ctxcsn[i] ); + break; + } + } + if ( cmp <= 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: " + "cmp %d, csn %s too old\n", + op->o_log_prefix, cmp, csn[0].bv_val ); + return rs->sr_err; + } + + /* + * not newer than snapshot ctxcsn (uuid_progress->ctxcsn) + */ + cmp = 0; + for ( i=0; i<uuid_progress->numcsns; i++ ) { + if ( sid == uuid_progress->sids[i] ) { + cmp = ber_bvcmp( &csn[0], &uuid_progress->ctxcsn[i] ); + break; + } + } + if ( cmp > 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: " + "cmp %d, csn %s too new\n", + op->o_log_prefix, cmp, csn[0].bv_val ); + return rs->sr_err; + } + + a = attr_find( attrs, ad_reqEntryUUID ); + if ( !a || a->a_numvals == 0 ) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION; + return rs->sr_err; + } + uuid[0] = a->a_nvals[0]; + + bv = ldap_avl_find( uuid_progress->uuids, uuid, sp_uuid_cmp ); + if ( bv ) { + /* Already checked or sent, no change */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: " + "uuid %s already checked\n", + op->o_log_prefix, a->a_vals[0].bv_val ); + return rs->sr_err; + } + + if ( !is_delete ) { + is_delete = check_uuidlist_presence( uuid_progress->op, uuid, 1, 1 ); + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_accesslog_uuid_cb: " + "uuid %s is %s present\n", + op->o_log_prefix, a->a_vals[0].bv_val, + is_delete ? "no longer" : "still" ); + + i = uuid_progress->ndel++; + + bv = &uuid_progress->uuid_list[i]; + bv->bv_val = &uuid_progress->uuid_buf[i*UUID_LEN]; + bv->bv_len = a->a_nvals[0].bv_len; + AC_MEMCPY( bv->bv_val, a->a_nvals[0].bv_val, a->a_nvals[0].bv_len ); + + rc = ldap_avl_insert( &uuid_progress->uuids, bv, sp_uuid_cmp, ldap_avl_dup_error ); + assert( rc == LDAP_SUCCESS ); + + if ( is_delete ) { + struct berval cookie; + + slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn, + srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn ); + syncprov_sendinfo( uuid_progress->op, uuid_progress->rs, + LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } + + if ( uuid_progress->ndel >= uuid_progress->list_len ) { + int ndel; + + assert( uuid_progress->ndel == uuid_progress->list_len ); + ndel = ldap_avl_free( uuid_progress->uuids, NULL ); + assert( ndel == uuid_progress->ndel ); + uuid_progress->uuids = NULL; + uuid_progress->ndel = 0; + } + + return rs->sr_err; +} + +static int +syncprov_play_sessionlog( Operation *op, SlapReply *rs, sync_control *srs, + BerVarray ctxcsn, int numcsns, int *sids, + struct berval *mincsn, int minsid ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + sessionlog *sl = si->si_logs; + int i, j, ndel, num, nmods, mmods, do_play = 0, rc = -1; + BerVarray uuids, csns; + struct berval uuid[2] = {}, csn[2] = {}; + slog_entry *se; + TAvlnode *entry; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + struct berval delcsn[2]; + + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + /* Are there any log entries, and is the consumer state + * present in the session log? + */ + if ( !sl->sl_num ) { + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + return rc; + } + assert( sl->sl_num > 0 ); + + for ( i=0; i<sl->sl_numcsns; i++ ) { + /* SID not present == new enough */ + if ( minsid < sl->sl_sids[i] ) { + do_play = 1; + break; + } + /* SID present */ + if ( minsid == sl->sl_sids[i] ) { + /* new enough? */ + if ( ber_bvcmp( mincsn, &sl->sl_mincsn[i] ) >= 0 ) + do_play = 1; + break; + } + } + /* SID not present == new enough */ + if ( i == sl->sl_numcsns ) + do_play = 1; + + if ( !do_play ) { + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + return rc; + } + + num = sl->sl_num; + i = 0; + nmods = 0; + sl->sl_playing++; + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + + uuids = op->o_tmpalloc( (num) * sizeof( struct berval ) + + num * UUID_LEN, op->o_tmpmemctx ); + uuids[0].bv_val = (char *)(uuids + num); + csns = op->o_tmpalloc( (num) * sizeof( struct berval ) + + num * LDAP_PVT_CSNSTR_BUFSIZE, op->o_tmpmemctx ); + csns[0].bv_val = (char *)(csns + num); + + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + /* Make a copy of the relevant UUIDs. Put the Deletes up front + * and everything else at the end. Do this first so we can + * let the write side manage the sessionlog again. + */ + assert( sl->sl_entries ); + + /* Find first relevant log entry. If greater than mincsn, backtrack one entry */ + { + slog_entry te = {0}; + te.se_csn = *mincsn; + entry = ldap_tavl_find3( sl->sl_entries, &te, syncprov_sessionlog_cmp, &ndel ); + } + if ( ndel > 0 && entry ) + entry = ldap_tavl_next( entry, TAVL_DIR_LEFT ); + /* if none, just start at beginning */ + if ( !entry ) + entry = ldap_tavl_end( sl->sl_entries, TAVL_DIR_LEFT ); + + do { + char uuidstr[40] = {}; + slog_entry *se = entry->avl_data; + int k; + + /* Make sure writes can still make progress */ + ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex ); + ndel = 1; + for ( k=0; k<srs->sr_state.numcsns; k++ ) { + if ( se->se_sid == srs->sr_state.sids[k] ) { + ndel = ber_bvcmp( &se->se_csn, &srs->sr_state.ctxcsn[k] ); + break; + } + } + if ( ndel <= 0 ) { + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + continue; + } + ndel = 0; + for ( k=0; k<numcsns; k++ ) { + if ( se->se_sid == sids[k] ) { + ndel = ber_bvcmp( &se->se_csn, &ctxcsn[k] ); + break; + } + } + if ( ndel > 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: " + "cmp %d, csn %s too new, we're finished\n", + op->o_log_prefix, ndel, se->se_csn.bv_val ); + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + break; + } + if ( se->se_tag == LDAP_REQ_DELETE ) { + j = i; + i++; + } else { + if ( se->se_tag == LDAP_REQ_ADD ) { + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + continue; + } + nmods++; + j = num - nmods; + } + uuids[j].bv_val = uuids[0].bv_val + (j * UUID_LEN); + AC_MEMCPY(uuids[j].bv_val, se->se_uuid.bv_val, UUID_LEN); + uuids[j].bv_len = UUID_LEN; + + csns[j].bv_val = csns[0].bv_val + (j * LDAP_PVT_CSNSTR_BUFSIZE); + AC_MEMCPY(csns[j].bv_val, se->se_csn.bv_val, se->se_csn.bv_len); + csns[j].bv_len = se->se_csn.bv_len; + /* We're printing it */ + csns[j].bv_val[csns[j].bv_len] = '\0'; + + if ( LogTest( LDAP_DEBUG_SYNC ) ) { + lutil_uuidstr_from_normalized( uuids[j].bv_val, uuids[j].bv_len, + uuidstr, 40 ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: " + "picking a %s entry uuid=%s cookie=%s\n", + op->o_log_prefix, se->se_tag == LDAP_REQ_DELETE ? "deleted" : "modified", + uuidstr, csns[j].bv_val ); + } + ldap_pvt_thread_rdwr_rlock( &sl->sl_mutex ); + } while ( (entry = ldap_tavl_next( entry, TAVL_DIR_RIGHT )) != NULL ); + ldap_pvt_thread_rdwr_runlock( &sl->sl_mutex ); + ldap_pvt_thread_rdwr_wlock( &sl->sl_mutex ); + sl->sl_playing--; + ldap_pvt_thread_rdwr_wunlock( &sl->sl_mutex ); + + ndel = i; + + /* Zero out unused slots */ + for ( i=ndel; i < num - nmods; i++ ) + uuids[i].bv_len = 0; + + /* Mods must be validated to see if they belong in this delete set. + */ + + mmods = nmods; + /* Strip any duplicates */ + for ( i=0; i<nmods; i++ ) { + for ( j=0; j<ndel; j++ ) { + if ( bvmatch( &uuids[j], &uuids[num - 1 - i] )) { + uuids[num - 1 - i].bv_len = 0; + mmods --; + break; + } + } + if ( uuids[num - 1 - i].bv_len == 0 ) continue; + for ( j=0; j<i; j++ ) { + if ( bvmatch( &uuids[num - 1 - j], &uuids[num - 1 - i] )) { + uuids[num - 1 - i].bv_len = 0; + mmods --; + break; + } + } + } + + /* Check mods now */ + if ( mmods ) { + check_uuidlist_presence( op, uuids, num, nmods ); + } + + /* ITS#8768 Send entries sorted by CSN order */ + i = j = 0; + while ( i < ndel || j < nmods ) { + struct berval cookie; + int index; + + /* Skip over duplicate mods */ + if ( j < nmods && BER_BVISEMPTY( &uuids[ num - 1 - j ] ) ) { + j++; + continue; + } + index = num - 1 - j; + + if ( i >= ndel ) { + j++; + } else if ( j >= nmods ) { + index = i++; + /* Take the oldest by CSN order */ + } else if ( ber_bvcmp( &csns[index], &csns[i] ) < 0 ) { + j++; + } else { + index = i++; + } + + uuid[0] = uuids[index]; + csn[0] = csns[index]; + + slap_compose_sync_cookie( op, &cookie, srs->sr_state.ctxcsn, + srs->sr_state.rid, slap_serverID ? slap_serverID : -1, csn ); + if ( LogTest( LDAP_DEBUG_SYNC ) ) { + char uuidstr[40]; + lutil_uuidstr_from_normalized( uuid[0].bv_val, uuid[0].bv_len, + uuidstr, 40 ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_sessionlog: " + "sending a new disappearing entry uuid=%s cookie=%s\n", + op->o_log_prefix, uuidstr, cookie.bv_val ); + } + + /* TODO: we might batch those that share the same CSN (think present + * phase), but would have to limit how many we send out at once */ + syncprov_sendinfo( op, rs, LDAP_TAG_SYNC_ID_SET, &cookie, 0, uuid, 1 ); + } + op->o_tmpfree( uuids, op->o_tmpmemctx ); + op->o_tmpfree( csns, op->o_tmpmemctx ); + + return LDAP_SUCCESS; +} + +static int +syncprov_play_accesslog( Operation *op, SlapReply *rs, sync_control *srs, + BerVarray ctxcsn, int numcsns, int *sids, + struct berval *mincsn, int minsid ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = on->on_bi.bi_private; + Operation fop; + SlapReply frs = { REP_RESULT }; + slap_callback cb = {}; + Filter *f; + syncprov_accesslog_deletes uuid_progress = { + .op = op, + .rs = rs, + .srs = srs, + .ctxcsn = ctxcsn, + .numcsns = numcsns, + .sids = sids, + }; + struct berval oldestcsn = BER_BVNULL, newestcsn = BER_BVNULL, + basedn, filterpattern = BER_BVC( + "(&" + "(entryCSN>=%s)" + "(entryCSN<=%s)" + "(reqResult=0)" + "(|" + "(reqDN:dnSubtreeMatch:=%s)" + "(reqNewDN:dnSubtreeMatch:=%s)" + ")" + "(|" + "(objectclass=auditWriteObject)" + "(objectclass=auditExtended)" + "))" ); + BackendDB *db; + Entry *e; + Attribute *a; + int *minsids, i, j = 0, rc = -1; + + assert( !BER_BVISNULL( &si->si_logbase ) ); + + db = select_backend( &si->si_logbase, 0 ); + if ( !db ) { + Debug( LDAP_DEBUG_ANY, "%s syncprov_play_accesslog: " + "No database configured to hold accesslog dn=%s\n", + op->o_log_prefix, si->si_logbase.bv_val ); + return LDAP_NO_SUCH_OBJECT; + } + + fop = *op; + fop.o_sync_mode = 0; + fop.o_bd = db; + rc = be_entry_get_rw( &fop, &si->si_logbase, NULL, ad_minCSN, 0, &e ); + if ( rc ) { + return rc; + } + + a = attr_find( e->e_attrs, ad_minCSN ); + if ( !a ) { + be_entry_release_rw( &fop, e, 0 ); + return LDAP_NO_SUCH_ATTRIBUTE; + } + + /* + * If we got here: + * - the consumer's cookie (srs->sr_state.ctxcsn) has the same sids in the + * same order as ctxcsn + * - at least one of the cookie's csns is older than its ctxcsn counterpart + * + * Now prepare the filter, we want it to be the union of all the intervals + * between the cookie and our contextCSN for each sid. Right now, we can't + * specify them separately, so just pick the boundary CSNs of non-empty + * intervals as a conservative overestimate. + * + * Also check accesslog can actually serve this query based on what's + * stored in minCSN. + */ + + assert( srs->sr_state.numcsns == numcsns ); + + minsids = slap_parse_csn_sids( a->a_nvals, a->a_numvals, op->o_tmpmemctx ); + slap_sort_csn_sids( a->a_nvals, minsids, a->a_numvals, op->o_tmpmemctx ); + for ( i=0, j=0; i < numcsns; i++ ) { + assert( srs->sr_state.sids[i] == sids[i] ); + if ( ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[i] ) >= 0 ) { + /* Consumer is up to date for this sid */ + continue; + } + for ( ; j < a->a_numvals && minsids[j] < sids[i]; j++ ) + /* Find the right minCSN, if present */; + if ( j == a->a_numvals || minsids[j] != sids[i] || + ber_bvcmp( &srs->sr_state.ctxcsn[i], &a->a_nvals[j] ) < 0 ) { + /* Consumer is missing changes for a sid and minCSN indicates we + * can't replay all relevant history */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: " + "accesslog information inadequate for log replay on csn=%s\n", + op->o_log_prefix, srs->sr_state.ctxcsn[i].bv_val ); + slap_sl_free( minsids, op->o_tmpmemctx ); + be_entry_release_rw( &fop, e, 0 ); + return 1; + } + if ( BER_BVISEMPTY( &oldestcsn ) || + ber_bvcmp( &oldestcsn, &srs->sr_state.ctxcsn[i] ) > 0 ) { + oldestcsn = srs->sr_state.ctxcsn[i]; + } + if ( BER_BVISEMPTY( &newestcsn ) || + ber_bvcmp( &newestcsn, &ctxcsn[i] ) < 0 ) { + newestcsn = ctxcsn[i]; + } + } + assert( !BER_BVISEMPTY( &oldestcsn ) && !BER_BVISEMPTY( &newestcsn ) && + ber_bvcmp( &oldestcsn, &newestcsn ) < 0 ); + slap_sl_free( minsids, op->o_tmpmemctx ); + + filter_escape_value_x( &op->o_req_ndn, &basedn, fop.o_tmpmemctx ); + /* filter_escape_value_x sets output to BVNULL if input value is empty, + * supply our own copy */ + if ( BER_BVISEMPTY( &basedn ) ) { + basedn.bv_val = ""; + } + fop.o_req_ndn = fop.o_req_dn = si->si_logbase; + fop.ors_filterstr.bv_val = fop.o_tmpalloc( + filterpattern.bv_len + + oldestcsn.bv_len + newestcsn.bv_len + 2 * basedn.bv_len, + fop.o_tmpmemctx ); + fop.ors_filterstr.bv_len = sprintf( fop.ors_filterstr.bv_val, + filterpattern.bv_val, + oldestcsn.bv_val, newestcsn.bv_val, basedn.bv_val, basedn.bv_val ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_play_accesslog: " + "prepared filter '%s', base='%s'\n", + op->o_log_prefix, fop.ors_filterstr.bv_val, si->si_logbase.bv_val ); + f = str2filter_x( &fop, fop.ors_filterstr.bv_val ); + assert( f != NULL ); + fop.ors_filter = f; + + if ( !BER_BVISEMPTY( &basedn ) ) { + fop.o_tmpfree( basedn.bv_val, fop.o_tmpmemctx ); + } + be_entry_release_rw( &fop, e, 0 ); + + /* + * Allocate memory for list_len uuids for use by the callback, populate + * with entries that we have sent or checked still match the filter. + * A disappearing entry gets its uuid sent as a delete. + * + * in the callback, we need: + * - original op and rs so we can send the message + * - sync_control + * - the uuid buffer and list and their length + * - number of uuids we already have in the list + * - the lookup structure so we don't have to check/send a uuid twice + * (AVL?) + */ + uuid_progress.list_len = SLAP_SYNCUUID_SET_SIZE; + uuid_progress.uuid_list = fop.o_tmpalloc( (uuid_progress.list_len) * sizeof(struct berval), fop.o_tmpmemctx ); + uuid_progress.uuid_buf = fop.o_tmpalloc( (uuid_progress.list_len) * UUID_LEN, fop.o_tmpmemctx ); + + cb.sc_private = &uuid_progress; + cb.sc_response = syncprov_accesslog_uuid_cb; + + fop.o_callback = &cb; + + rc = fop.o_bd->be_search( &fop, &frs ); + + ldap_avl_free( uuid_progress.uuids, NULL ); + fop.o_tmpfree( uuid_progress.uuid_buf, fop.o_tmpmemctx ); + fop.o_tmpfree( uuid_progress.uuid_list, fop.o_tmpmemctx ); + fop.o_tmpfree( fop.ors_filterstr.bv_val, fop.o_tmpmemctx ); + filter_free_x( &fop, f, 1 ); + + return rc; +} + +static int +syncprov_new_ctxcsn( opcookie *opc, syncprov_info_t *si, int csn_changed, int numvals, BerVarray vals ) +{ + unsigned i; + int j, sid; + + for ( i=0; i<numvals; i++ ) { + sid = slap_parse_csn_sid( &vals[i] ); + for ( j=0; j<si->si_numcsns; j++ ) { + if ( sid < si->si_sids[j] ) + break; + if ( sid == si->si_sids[j] ) { + if ( ber_bvcmp( &vals[i], &si->si_ctxcsn[j] ) > 0 ) { + ber_bvreplace( &si->si_ctxcsn[j], &vals[i] ); + csn_changed = 1; + } + break; + } + } + + if ( j == si->si_numcsns || sid != si->si_sids[j] ) { + slap_insert_csn_sids( (struct sync_cookie *)&si->si_ctxcsn, + j, sid, &vals[i] ); + csn_changed = 1; + } + } + if ( csn_changed ) + si->si_dirty = 0; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + + if ( csn_changed ) { + syncops *ss; + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( ss = si->si_ops; ss; ss = ss->s_next ) { + if ( ss->s_op->o_abandon ) + continue; + /* Send the updated csn to all syncrepl consumers, + * including the server from which it originated. + * The syncrepl consumer and syncprov provider on + * the originating server may be configured to store + * their csn values in different entries. + */ + syncprov_qresp( opc, ss, LDAP_SYNC_NEW_COOKIE ); + } + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + } + return csn_changed; +} + +static int +syncprov_op_response( Operation *op, SlapReply *rs ) +{ + opcookie *opc = op->o_callback->sc_private; + slap_overinst *on = opc->son; + syncprov_info_t *si = on->on_bi.bi_private; + syncmatches *sm; + + if ( rs->sr_err == LDAP_SUCCESS ) + { + struct berval maxcsn; + char cbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + int do_check = 0, have_psearches, foundit, csn_changed = 0; + + ldap_pvt_thread_mutex_lock( &si->si_resp_mutex ); + + /* Update our context CSN */ + cbuf[0] = '\0'; + maxcsn.bv_val = cbuf; + maxcsn.bv_len = sizeof(cbuf); + ldap_pvt_thread_rdwr_wlock( &si->si_csn_rwlock ); + + slap_get_commit_csn( op, &maxcsn, &foundit ); + if ( BER_BVISEMPTY( &maxcsn ) && SLAP_GLUE_SUBORDINATE( op->o_bd )) { + /* syncrepl queues the CSN values in the db where + * it is configured , not where the changes are made. + * So look for a value in the glue db if we didn't + * find any in this db. + */ + BackendDB *be = op->o_bd; + op->o_bd = select_backend( &be->be_nsuffix[0], 1); + maxcsn.bv_val = cbuf; + maxcsn.bv_len = sizeof(cbuf); + slap_get_commit_csn( op, &maxcsn, &foundit ); + op->o_bd = be; + } + if ( !BER_BVISEMPTY( &maxcsn ) ) { + int i, sid; +#ifdef CHECK_CSN + Syntax *syn = slap_schema.si_ad_contextCSN->ad_type->sat_syntax; + assert( !syn->ssyn_validate( syn, &maxcsn )); +#endif + sid = slap_parse_csn_sid( &maxcsn ); + for ( i=0; i<si->si_numcsns; i++ ) { + if ( sid < si->si_sids[i] ) + break; + if ( sid == si->si_sids[i] ) { + if ( ber_bvcmp( &maxcsn, &si->si_ctxcsn[i] ) > 0 ) { + ber_bvreplace( &si->si_ctxcsn[i], &maxcsn ); + csn_changed = 1; + } + break; + } + } + /* It's a new SID for us */ + if ( i == si->si_numcsns || sid != si->si_sids[i] ) { + slap_insert_csn_sids((struct sync_cookie *)&(si->si_ctxcsn), + i, sid, &maxcsn ); + csn_changed = 1; + } + } + + /* Don't do any processing for consumer contextCSN updates */ + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) && + op->o_tag == LDAP_REQ_MODIFY && + op->orm_modlist && + op->orm_modlist->sml_op == LDAP_MOD_REPLACE && + op->orm_modlist->sml_desc == slap_schema.si_ad_contextCSN ) { + /* Catch contextCSN updates from syncrepl. We have to look at + * all the attribute values, as there may be more than one csn + * that changed, and only one can be passed in the csn queue. + */ + csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, + op->orm_modlist->sml_numvals, op->orm_modlist->sml_values ); + if ( csn_changed ) + si->si_numops++; + goto leave; + } + if ( op->o_dont_replicate ) { + if ( csn_changed ) + si->si_numops++; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + goto leave; + } + + /* If we're adding the context entry, parse all of its contextCSNs */ + if ( op->o_tag == LDAP_REQ_ADD && + dn_match( &op->o_req_ndn, &si->si_contextdn )) { + Attribute *a = attr_find( op->ora_e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + csn_changed = syncprov_new_ctxcsn( opc, si, csn_changed, a->a_numvals, a->a_vals ); + if ( csn_changed ) + si->si_numops++; + goto added; + } + } + + if ( csn_changed ) + si->si_numops++; + if ( si->si_chkops || si->si_chktime ) { + /* Never checkpoint adding the context entry, + * it will deadlock + */ + if ( op->o_tag != LDAP_REQ_ADD || + !dn_match( &op->o_req_ndn, &si->si_contextdn )) { + if ( si->si_chkops && si->si_numops >= si->si_chkops ) { + do_check = 1; + si->si_numops = 0; + } + if ( si->si_chktime && + (op->o_time - si->si_chklast >= si->si_chktime )) { + if ( si->si_chklast ) { + do_check = 1; + si->si_chklast = op->o_time; + } else { + si->si_chklast = 1; + } + } + } + } + si->si_dirty = !csn_changed; + ldap_pvt_thread_rdwr_wunlock( &si->si_csn_rwlock ); + +added: + if ( do_check ) { + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + syncprov_checkpoint( op, on ); + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + } + + /* only update consumer ctx if this is a newer csn */ + if ( csn_changed ) { + opc->sctxcsn = maxcsn; + } + + /* Handle any persistent searches */ + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + have_psearches = ( si->si_ops != NULL ); + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( have_psearches ) { + 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 = ldap_avl_find( si->si_mods, &mtdummy, sp_avl_cmp ); + if ( mt ) { + ldap_pvt_thread_mutex_lock( &mt->mt_mutex ); + if ( mt->mt_mods == NULL ) { + /* Cannot reuse this mt, as another thread is about + * to release it in syncprov_op_cleanup. Wait for them + * to finish; our own insert is required to succeed. + */ + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + ldap_pvt_thread_yield(); + goto retry; + } + } + if ( mt ) { + mt->mt_tail->mi_next = mi; + mt->mt_tail = mi; + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + /* wait for this op to get to head of list */ + while ( mt->mt_mods != mi ) { + modinst *m2; + /* don't wait on other mods from the same thread */ + for ( m2 = mt->mt_mods; m2; m2 = m2->mi_next ) { + if ( m2->mi_op->o_threadctx == op->o_threadctx ) { + break; + } + } + if ( m2 ) + break; + + ldap_pvt_thread_mutex_unlock( &mt->mt_mutex ); + /* FIXME: if dynamic config can delete overlays or + * databases we'll have to check for cleanup here. + * Currently it's not an issue because there are + * no dynamic config deletes... + */ + if ( slapd_shutdown ) + return SLAPD_ABANDON; + + if ( !ldap_pvt_thread_pool_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 ); + ldap_avl_insert( &si->si_mods, mt, sp_avl_cmp, ldap_avl_dup_error ); + ldap_pvt_thread_mutex_unlock( &si->si_mods_mutex ); + } + opc->smt = mt; + } + + if (( have_psearches || si->si_logs ) && op->o_tag != LDAP_REQ_ADD ) + syncprov_matchops( op, opc, 1 ); + + return SLAP_CB_CONTINUE; +} + +static int +syncprov_op_extended( Operation *op, SlapReply *rs ) +{ + if ( exop_is_write( op )) + return syncprov_op_mod( op, rs ); + + return SLAP_CB_CONTINUE; +} + +typedef struct searchstate { + slap_overinst *ss_on; + syncops *ss_so; + BerVarray ss_ctxcsn; + int *ss_sids; + int ss_numcsns; +#define SS_PRESENT 0x01 +#define SS_CHANGED 0x02 + int ss_flags; +} searchstate; + +typedef struct SyncOperationBuffer { + Operation sob_op; + Opheader sob_hdr; + OpExtra sob_oe; + AttributeName sob_extra; /* not always present */ + /* Further data allocated here */ +} SyncOperationBuffer; + +static void +syncprov_detach_op( Operation *op, syncops *so, slap_overinst *on ) +{ + SyncOperationBuffer *sopbuf2; + Operation *op2; + int i, alen = 0; + size_t size; + char *ptr; + GroupAssertion *g1, *g2; + + /* count the search attrs */ + for (i=0; op->ors_attrs && !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) { + alen += op->ors_attrs[i].an_name.bv_len + 1; + } + /* Make a new copy of the operation */ + size = offsetof( SyncOperationBuffer, sob_extra ) + + (i ? ( (i+1) * sizeof(AttributeName) + alen) : 0) + + op->o_req_dn.bv_len + 1 + + op->o_req_ndn.bv_len + 1 + + op->o_ndn.bv_len + 1 + + so->s_filterstr.bv_len + 1; + sopbuf2 = ch_calloc( 1, size ); + op2 = &sopbuf2->sob_op; + op2->o_hdr = &sopbuf2->sob_hdr; + LDAP_SLIST_FIRST(&op2->o_extra) = &sopbuf2->sob_oe; + + /* Copy the fields we care about explicitly, leave the rest alone */ + *op2->o_hdr = *op->o_hdr; + op2->o_tag = op->o_tag; + op2->o_time = op->o_time; + op2->o_bd = on->on_info->oi_origdb; + op2->o_request = op->o_request; + op2->o_managedsait = op->o_managedsait; + LDAP_SLIST_FIRST(&op2->o_extra)->oe_key = on; + LDAP_SLIST_NEXT(LDAP_SLIST_FIRST(&op2->o_extra), oe_next) = NULL; + + ptr = (char *) sopbuf2 + offsetof( SyncOperationBuffer, sob_extra ); + if ( i ) { + op2->ors_attrs = (AttributeName *) ptr; + ptr = (char *) &op2->ors_attrs[i+1]; + for (i=0; !BER_BVISNULL( &op->ors_attrs[i].an_name ); i++) { + op2->ors_attrs[i] = op->ors_attrs[i]; + op2->ors_attrs[i].an_name.bv_val = ptr; + ptr = lutil_strcopy( ptr, op->ors_attrs[i].an_name.bv_val ) + 1; + } + BER_BVZERO( &op2->ors_attrs[i].an_name ); + } + + op2->o_authz = op->o_authz; + op2->o_ndn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_ndn.bv_val) + 1; + op2->o_dn = op2->o_ndn; + op2->o_req_dn.bv_len = op->o_req_dn.bv_len; + op2->o_req_dn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_req_dn.bv_val) + 1; + op2->o_req_ndn.bv_len = op->o_req_ndn.bv_len; + op2->o_req_ndn.bv_val = ptr; + ptr = lutil_strcopy(ptr, op->o_req_ndn.bv_val) + 1; + op2->ors_filterstr.bv_val = ptr; + strcpy( ptr, so->s_filterstr.bv_val ); + op2->ors_filterstr.bv_len = so->s_filterstr.bv_len; + + /* Skip the AND/GE clause that we stuck on in front */ + if ( so->s_flags & PS_FIX_FILTER ) { + op2->ors_filter = op->ors_filter->f_and->f_next; + so->s_flags ^= PS_FIX_FILTER; + } else { + op2->ors_filter = op->ors_filter; + } + op2->ors_filter = filter_dup( op2->ors_filter, NULL ); + so->s_op = op2; + + /* Copy any cached group ACLs individually */ + op2->o_groups = NULL; + for ( g1=op->o_groups; g1; g1=g1->ga_next ) { + g2 = ch_malloc( sizeof(GroupAssertion) + g1->ga_len ); + *g2 = *g1; + strcpy( g2->ga_ndn, g1->ga_ndn ); + g2->ga_next = op2->o_groups; + op2->o_groups = g2; + } + /* Don't allow any further group caching */ + op2->o_do_not_cache = 1; + + /* Add op2 to conn so abandon will find us */ + op->o_conn->c_n_ops_executing++; + op->o_conn->c_n_ops_completed--; + LDAP_STAILQ_INSERT_TAIL( &op->o_conn->c_ops, op2, o_next ); + so->s_flags |= PS_IS_DETACHED; + + /* Prevent anyone else from trying to send a result for this op */ + op->o_abandon = 1; +} + +static int +syncprov_search_response( Operation *op, SlapReply *rs ) +{ + searchstate *ss = op->o_callback->sc_private; + slap_overinst *on = ss->ss_on; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + sync_control *srs = op->o_controls[slap_cids.sc_LDAPsync]; + + if ( rs->sr_type == REP_SEARCH || rs->sr_type == REP_SEARCHREF ) { + Attribute *a; + /* If we got a referral without a referral object, there's + * something missing that we cannot replicate. Just ignore it. + * The consumer will abort because we didn't send the expected + * control. + */ + if ( !rs->sr_entry ) { + assert( rs->sr_entry != NULL ); + Debug( LDAP_DEBUG_ANY, "%s syncprov_search_response: " + "bogus referral in context\n", op->o_log_prefix ); + 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, "%s syncprov_search_response: " + "Entry %s CSN %s greater than snapshot %s\n", + op->o_log_prefix, + rs->sr_entry->e_name.bv_val, + a->a_nvals[0].bv_val, + ss->ss_ctxcsn[i].bv_val ); + return LDAP_SUCCESS; + } + } + } + + /* Don't send old entries twice */ + if ( srs->sr_state.ctxcsn ) { + for ( i=0; i<srs->sr_state.numcsns; i++ ) { + if ( sid == srs->sr_state.sids[i] && + ber_bvcmp( &a->a_nvals[0], + &srs->sr_state.ctxcsn[i] )<= 0 ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: " + "Entry %s CSN %s older or equal to ctx %s\n", + op->o_log_prefix, + rs->sr_entry->e_name.bv_val, + a->a_nvals[0].bv_val, + srs->sr_state.ctxcsn[i].bv_val ); + return LDAP_SUCCESS; + } + } + } + } + rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, + op->o_tmpmemctx ); + rs->sr_ctrls[1] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + /* If we're in delta-sync mode, always send a cookie */ + if ( si->si_nopres && si->si_usehint && a ) { + struct berval cookie; + slap_compose_sync_cookie( op, &cookie, a->a_nvals, srs->sr_state.rid, + slap_serverID ? slap_serverID : -1, NULL ); + rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry, + LDAP_SYNC_ADD, rs->sr_ctrls, 0, 1, &cookie ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } else { + rs->sr_err = syncprov_state_ctrl( op, rs, rs->sr_entry, + LDAP_SYNC_ADD, rs->sr_ctrls, 0, 0, NULL ); + } + } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) { + struct berval cookie = BER_BVNULL; + + if ( ( ss->ss_flags & SS_CHANGED ) && + ss->ss_ctxcsn && !BER_BVISNULL( &ss->ss_ctxcsn[0] )) { + slap_compose_sync_cookie( op, &cookie, ss->ss_ctxcsn, + srs->sr_state.rid, + slap_serverID ? slap_serverID : -1, NULL ); + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: cookie=%s\n", + op->o_log_prefix, cookie.bv_val ); + } + + /* Is this a regular refresh? + * Note: refresh never gets here if there were no changes + */ + if ( !ss->ss_so ) { + rs->sr_ctrls = op->o_tmpalloc( sizeof(LDAPControl *)*2, + op->o_tmpmemctx ); + rs->sr_ctrls[1] = NULL; + rs->sr_flags |= REP_CTRLS_MUSTBEFREED; + rs->sr_err = syncprov_done_ctrl( op, rs, rs->sr_ctrls, + 0, 1, &cookie, ( ss->ss_flags & SS_PRESENT ) ? LDAP_SYNC_REFRESH_PRESENTS : + LDAP_SYNC_REFRESH_DELETES ); + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + } else { + /* It's RefreshAndPersist, transition to Persist phase */ + syncprov_sendinfo( op, rs, ( ss->ss_flags & SS_PRESENT ) ? + LDAP_TAG_SYNC_REFRESH_PRESENT : LDAP_TAG_SYNC_REFRESH_DELETE, + ( ss->ss_flags & SS_CHANGED ) ? &cookie : NULL, + 1, NULL, 0 ); + if ( !BER_BVISNULL( &cookie )) + op->o_tmpfree( cookie.bv_val, op->o_tmpmemctx ); + + /* Detach this Op from frontend control */ + ldap_pvt_thread_mutex_lock( &op->o_conn->c_mutex ); + + /* But not if this connection was closed along the way */ + if ( op->o_abandon ) { + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + /* syncprov_ab_cleanup will free this syncop */ + return SLAPD_ABANDON; + + } else { + ldap_pvt_thread_mutex_lock( &ss->ss_so->s_mutex ); + /* Turn off the refreshing flag */ + ss->ss_so->s_flags ^= PS_IS_REFRESHING; + + Debug( LDAP_DEBUG_SYNC, "%s syncprov_search_response: " + "detaching op\n", op->o_log_prefix ); + syncprov_detach_op( op, ss->ss_so, on ); + + ldap_pvt_thread_mutex_unlock( &op->o_conn->c_mutex ); + + /* If there are queued responses, fire them off */ + if ( ss->ss_so->s_res ) + syncprov_qstart( ss->ss_so ); + ldap_pvt_thread_mutex_unlock( &ss->ss_so->s_mutex ); + } + + return LDAP_SUCCESS; + } + } + + return SLAP_CB_CONTINUE; +} + +static int +syncprov_op_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + slap_callback *cb; + int gotstate = 0, changed = 0, do_present = 0; + syncops *sop = NULL; + searchstate *ss; + sync_control *srs; + BerVarray ctxcsn; + int i, *sids, numcsns; + struct berval mincsn, maxcsn; + int minsid, maxsid; + int dirty = 0; + + if ( !(op->o_sync_mode & SLAP_SYNC_REFRESH) ) return SLAP_CB_CONTINUE; + + if ( op->ors_deref & LDAP_DEREF_SEARCHING ) { + send_ldap_error( op, rs, LDAP_PROTOCOL_ERROR, "illegal value for derefAliases" ); + return rs->sr_err; + } + + srs = op->o_controls[slap_cids.sc_LDAPsync]; + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "got a %ssearch with a cookie=%s\n", + op->o_log_prefix, + op->o_sync_mode & SLAP_SYNC_PERSIST ? "persistent ": "", + srs->sr_state.octet_str.bv_val ); + + /* If this is a persistent search, set it up right away */ + if ( op->o_sync_mode & SLAP_SYNC_PERSIST ) { + syncops so = {0}; + fbase_cookie fc; + opcookie opc; + slap_callback sc = {0}; + + fc.fss = &so; + fc.fbase = 0; + so.s_eid = NOID; + so.s_op = op; + so.s_flags = PS_IS_REFRESHING | PS_FIND_BASE; + /* syncprov_findbase expects to be called as a callback... */ + sc.sc_private = &opc; + opc.son = on; + ldap_pvt_thread_mutex_init( &so.s_mutex ); + cb = op->o_callback; + op->o_callback = ≻ + rs->sr_err = syncprov_findbase( op, &fc ); + op->o_callback = cb; + ldap_pvt_thread_mutex_destroy( &so.s_mutex ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + return rs->sr_err; + } + sop = ch_malloc( sizeof( syncops )); + *sop = so; + sop->s_rid = srs->sr_state.rid; + sop->s_sid = srs->sr_state.sid; + /* set refcount=2 to prevent being freed out from under us + * by abandons that occur while we're running here + */ + sop->s_inuse = 2; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + while ( si->si_active ) { + /* Wait for active mods to finish before proceeding, as they + * may already have inspected the si_ops list looking for + * consumers to replicate the change to. Using the log + * doesn't help, as we may finish playing it before the + * active mods gets added to it. + */ + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + if ( slapd_shutdown ) { +aband: + ch_free( sop->s_base.bv_val ); + 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 ); + goto aband; + } + ldap_pvt_thread_mutex_init( &sop->s_mutex ); + sop->s_next = si->si_ops; + sop->s_si = si; + si->si_ops = sop; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "registered persistent search\n", op->o_log_prefix ); + } + + /* snapshot the ctxcsn + * Note: this must not be done before the psearch setup. (ITS#8365) + */ + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + numcsns = si->si_numcsns; + if ( numcsns ) { + ber_bvarray_dup_x( &ctxcsn, si->si_ctxcsn, op->o_tmpmemctx ); + sids = op->o_tmpalloc( numcsns * sizeof(int), op->o_tmpmemctx ); + for ( i=0; i<numcsns; i++ ) + sids[i] = si->si_sids[i]; + } else { + ctxcsn = NULL; + sids = NULL; + } + dirty = si->si_dirty; + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + + /* If we have a cookie, handle the PRESENT lookups */ + if ( srs->sr_state.ctxcsn ) { + sessionlog *sl; + int i, j; + + /* If we don't have any CSN of our own yet, bail out. + */ + if ( !numcsns ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "consumer has state info but provider doesn't!"; + goto bailout; + } + + if ( !si->si_nopres ) + do_present = SS_PRESENT; + + /* If there are SIDs we don't recognize in the cookie, drop them */ + for (i=0; i<srs->sr_state.numcsns; ) { + for (j=i; j<numcsns; j++) { + if ( srs->sr_state.sids[i] <= sids[j] ) { + break; + } + } + /* not found */ + if ( j == numcsns || srs->sr_state.sids[i] != sids[j] ) { + char *tmp = srs->sr_state.ctxcsn[i].bv_val; + srs->sr_state.numcsns--; + for ( j=i; j<srs->sr_state.numcsns; j++ ) { + srs->sr_state.ctxcsn[j] = srs->sr_state.ctxcsn[j+1]; + srs->sr_state.sids[j] = srs->sr_state.sids[j+1]; + } + srs->sr_state.ctxcsn[j].bv_val = tmp; + srs->sr_state.ctxcsn[j].bv_len = 0; + continue; + } + i++; + } + + if (srs->sr_state.numcsns != numcsns) { + /* consumer doesn't have the right number of CSNs */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "consumer cookie is missing a csn we track\n", + op->o_log_prefix ); + + changed = SS_CHANGED; + if ( srs->sr_state.ctxcsn ) { + ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx ); + srs->sr_state.ctxcsn = NULL; + } + if ( srs->sr_state.sids ) { + slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx ); + srs->sr_state.sids = NULL; + } + srs->sr_state.numcsns = 0; + goto shortcut; + } + + /* Find the smallest CSN which differs from contextCSN */ + mincsn.bv_len = 0; + maxcsn.bv_len = 0; + for ( i=0,j=0; i<srs->sr_state.numcsns; i++ ) { + int newer; + while ( srs->sr_state.sids[i] != sids[j] ) j++; + if ( BER_BVISEMPTY( &maxcsn ) || ber_bvcmp( &maxcsn, + &srs->sr_state.ctxcsn[i] ) < 0 ) { + maxcsn = srs->sr_state.ctxcsn[i]; + maxsid = sids[j]; + } + newer = ber_bvcmp( &srs->sr_state.ctxcsn[i], &ctxcsn[j] ); + /* If our state is newer, tell consumer about changes */ + if ( newer < 0) { + changed = SS_CHANGED; + if ( BER_BVISEMPTY( &mincsn ) || ber_bvcmp( &mincsn, + &srs->sr_state.ctxcsn[i] ) > 0 ) { + mincsn = srs->sr_state.ctxcsn[i]; + minsid = sids[j]; + } + } else if ( newer > 0 && sids[j] == slap_serverID ) { + /* our state is older, complain to consumer */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "consumer state is newer than provider!"; + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "consumer %d state %s is newer than provider %d state %s\n", + op->o_log_prefix, sids[i], srs->sr_state.ctxcsn[i].bv_val, + sids[j], /* == slap_serverID */ + ctxcsn[j].bv_val); +bailout: + if ( sop ) { + syncops **sp = &si->si_ops; + + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + while ( *sp != sop ) + sp = &(*sp)->s_next; + *sp = sop->s_next; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + ch_free( sop->s_base.bv_val ); + ch_free( sop ); + } + rs->sr_ctrls = NULL; + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + if ( BER_BVISEMPTY( &mincsn )) { + mincsn = maxcsn; + minsid = maxsid; + } + + /* If nothing has changed, shortcut it */ + if ( !changed && !dirty ) { + do_present = 0; +no_change: if ( !(op->o_sync_mode & SLAP_SYNC_PERSIST) ) { + LDAPControl *ctrls[2]; + + ctrls[0] = NULL; + ctrls[1] = NULL; + syncprov_done_ctrl( op, rs, ctrls, 0, 0, + NULL, LDAP_SYNC_REFRESH_DELETES ); + rs->sr_ctrls = ctrls; + rs->sr_err = LDAP_SUCCESS; + send_ldap_result( op, rs ); + rs->sr_ctrls = NULL; + return rs->sr_err; + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "no change, skipping log replay\n", + op->o_log_prefix ); + goto shortcut; + } + + if ( !BER_BVISNULL( &si->si_logbase ) ) { + do_present = 0; + if ( syncprov_play_accesslog( op, rs, srs, ctxcsn, + numcsns, sids, &mincsn, minsid ) ) { + do_present = SS_PRESENT; + } + } else if ( si->si_logs ) { + do_present = 0; + if ( syncprov_play_sessionlog( op, rs, srs, ctxcsn, + numcsns, sids, &mincsn, minsid ) ) { + do_present = SS_PRESENT; + } + } else if ( ad_minCSN != NULL && si->si_nopres && si->si_usehint ) { + /* We are instructed to trust minCSN if it exists. */ + Entry *e; + Attribute *a = NULL; + int rc; + + /* + * ITS#9580 FIXME: when we've figured out and split the + * sessionlog/deltalog tracking, use the appropriate attribute + */ + rc = overlay_entry_get_ov( op, &op->o_bd->be_nsuffix[0], NULL, + ad_minCSN, 0, &e, on ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + a = attr_find( e->e_attrs, ad_minCSN ); + } + + if ( a != NULL ) { + int *minsids; + + minsids = slap_parse_csn_sids( a->a_vals, a->a_numvals, op->o_tmpmemctx ); + slap_sort_csn_sids( a->a_vals, minsids, a->a_numvals, op->o_tmpmemctx ); + + for ( i=0, j=0; i < a->a_numvals; i++ ) { + while ( j < numcsns && minsids[i] > sids[j] ) j++; + if ( j < numcsns && minsids[i] == sids[j] && + ber_bvcmp( &a->a_vals[i], &srs->sr_state.ctxcsn[j] ) <= 0 ) { + /* minCSN for this serverID is contained, keep going */ + continue; + } + /* + * Log DB's minCSN claims we can only replay from a certain + * CSN for this serverID, but consumer's cookie hasn't met that + * threshold: they need to refresh + */ + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "consumer not within recorded mincsn for DB's mincsn=%s\n", + op->o_log_prefix, a->a_vals[i].bv_val ); + rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED; + rs->sr_text = "sync cookie is stale"; + slap_sl_free( minsids, op->o_tmpmemctx ); + overlay_entry_release_ov( op, e, 0, on ); + goto bailout; + } + slap_sl_free( minsids, op->o_tmpmemctx ); + } + if ( e != NULL ) + overlay_entry_release_ov( op, e, 0, on ); + } + + /* + * If sessionlog wasn't useful, see if we can find at least one entry + * that hasn't changed based on the cookie. + * + * TODO: Using mincsn only (rather than the whole cookie) will + * under-approximate the set of entries that haven't changed, but we + * can't look up CSNs by serverid with the current indexing support. + * + * As a result, dormant serverids in the cluster become mincsns and + * more likely to make syncprov_findcsn(,FIND_CSN,) fail -> triggering + * an expensive refresh... + */ + if ( !do_present ) { + gotstate = 1; + } else if ( syncprov_findcsn( op, FIND_CSN, &mincsn ) != LDAP_SUCCESS ) { + /* No, so a reload is required */ + /* the 2.2 consumer doesn't send this hint */ + if ( si->si_usehint && srs->sr_rhint == 0 ) { + if ( ctxcsn ) + ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx ); + if ( sids ) + op->o_tmpfree( sids, op->o_tmpmemctx ); + rs->sr_err = LDAP_SYNC_REFRESH_REQUIRED; + rs->sr_text = "sync cookie is stale"; + goto bailout; + } + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "failed to find entry with csn=%s, ignoring cookie\n", + op->o_log_prefix, mincsn.bv_val ); + if ( srs->sr_state.ctxcsn ) { + ber_bvarray_free_x( srs->sr_state.ctxcsn, op->o_tmpmemctx ); + srs->sr_state.ctxcsn = NULL; + } + if ( srs->sr_state.sids ) { + slap_sl_free( srs->sr_state.sids, op->o_tmpmemctx ); + srs->sr_state.sids = NULL; + } + srs->sr_state.numcsns = 0; + } else { + gotstate = 1; + /* If changed and doing Present lookup, send Present UUIDs */ + if ( syncprov_findcsn( op, FIND_PRESENT, 0 ) != LDAP_SUCCESS ) { + if ( ctxcsn ) + ber_bvarray_free_x( ctxcsn, op->o_tmpmemctx ); + if ( sids ) + op->o_tmpfree( sids, op->o_tmpmemctx ); + goto bailout; + } + } + } else { + /* The consumer knows nothing, we know nothing. OK. */ + if (!numcsns) + goto no_change; + /* No consumer state, assume something has changed */ + changed = SS_CHANGED; + } + +shortcut: + /* Append CSN range to search filter, save original filter + * for persistent search evaluation + */ + if ( sop ) { + ldap_pvt_thread_mutex_lock( &sop->s_mutex ); + sop->s_filterstr = op->ors_filterstr; + /* correct the refcount that was set to 2 before */ + sop->s_inuse--; + } + + /* If something changed, find the changes */ + if ( gotstate && ( changed || dirty ) ) { + Filter *fand, *fava; + + fand = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + fand->f_choice = LDAP_FILTER_AND; + fand->f_next = NULL; + fava = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + fand->f_and = fava; + fava->f_choice = LDAP_FILTER_GE; + fava->f_ava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx ); + fava->f_ava->aa_desc = slap_schema.si_ad_entryCSN; +#ifdef LDAP_COMP_MATCH + fava->f_ava->aa_cf = NULL; +#endif + ber_dupbv_x( &fava->f_ava->aa_value, &mincsn, op->o_tmpmemctx ); + fava->f_next = op->ors_filter; + op->ors_filter = fand; + filter2bv_x( op, op->ors_filter, &op->ors_filterstr ); + if ( sop ) { + sop->s_flags |= PS_FIX_FILTER; + } + } + if ( sop ) { + ldap_pvt_thread_mutex_unlock( &sop->s_mutex ); + } + + /* Let our callback add needed info to returned entries */ + cb = op->o_tmpcalloc(1, sizeof(slap_callback)+sizeof(searchstate), op->o_tmpmemctx); + ss = (searchstate *)(cb+1); + ss->ss_on = on; + ss->ss_so = sop; + ss->ss_flags = do_present | changed; + ss->ss_ctxcsn = ctxcsn; + ss->ss_numcsns = numcsns; + ss->ss_sids = sids; + cb->sc_response = syncprov_search_response; + cb->sc_private = ss; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + /* If this is a persistent search and no changes were reported during + * the refresh phase, just invoke the response callback to transition + * us into persist phase + */ + if ( !changed && !dirty ) { + Debug( LDAP_DEBUG_SYNC, "%s syncprov_op_search: " + "nothing changed, finishing up initial search early\n", + op->o_log_prefix ); + rs->sr_err = LDAP_SUCCESS; + rs->sr_nentries = 0; + send_ldap_result( op, rs ); + return rs->sr_err; + } + return SLAP_CB_CONTINUE; +} + +static int +syncprov_operational( + Operation *op, + SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + /* This prevents generating unnecessarily; frontend will strip + * any statically stored copy. + */ + if ( op->o_sync != SLAP_CONTROL_NONE ) + return SLAP_CB_CONTINUE; + + if ( rs->sr_entry && + dn_match( &rs->sr_entry->e_nname, &si->si_contextdn )) { + + if ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( slap_schema.si_ad_contextCSN, rs->sr_attrs )) { + Attribute *a, **ap = NULL; + + for ( a=rs->sr_entry->e_attrs; a; a=a->a_next ) { + if ( a->a_desc == slap_schema.si_ad_contextCSN ) + break; + } + + ldap_pvt_thread_rdwr_rlock( &si->si_csn_rwlock ); + if ( si->si_ctxcsn ) { + if ( !a ) { + for ( ap = &rs->sr_operational_attrs; *ap; + ap=&(*ap)->a_next ); + + a = attr_alloc( slap_schema.si_ad_contextCSN ); + *ap = a; + } + + if ( !ap ) { + if ( rs_entry2modifiable( op, rs, on )) { + a = attr_find( rs->sr_entry->e_attrs, + slap_schema.si_ad_contextCSN ); + } + if ( a->a_nvals != a->a_vals ) { + ber_bvarray_free( a->a_nvals ); + } + a->a_nvals = NULL; + ber_bvarray_free( a->a_vals ); + a->a_vals = NULL; + a->a_numvals = 0; + } + attr_valadd( a, si->si_ctxcsn, si->si_ctxcsn, si->si_numcsns ); + } + ldap_pvt_thread_rdwr_runlock( &si->si_csn_rwlock ); + } + } + return SLAP_CB_CONTINUE; +} + +static int +syncprov_setup_accesslog(void) +{ + const char *text; + int rc = -1; + + if ( !ad_reqType ) { + if ( slap_str2ad( "reqType", &ad_reqType, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute reqType, " + "is accessslog configured?\n" ); + return rc; + } + } + + if ( !ad_reqResult ) { + if ( slap_str2ad( "reqResult", &ad_reqResult, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute reqResult, " + "is accessslog configured?\n" ); + return rc; + } + } + + if ( !ad_reqDN ) { + if ( slap_str2ad( "reqDN", &ad_reqDN, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute reqDN, " + "is accessslog configured?\n" ); + return rc; + } + } + + if ( !ad_reqEntryUUID ) { + if ( slap_str2ad( "reqEntryUUID", &ad_reqEntryUUID, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute reqEntryUUID, " + "is accessslog configured?\n" ); + return rc; + } + } + + if ( !ad_reqNewDN ) { + if ( slap_str2ad( "reqNewDN", &ad_reqNewDN, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute reqNewDN, " + "is accessslog configured?\n" ); + return rc; + } + } + + if ( !ad_minCSN ) { + if ( slap_str2ad( "minCSN", &ad_minCSN, &text ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_setup_accesslog: " + "couldn't get definition for attribute minCSN, " + "is accessslog configured?\n" ); + return rc; + } + } + + return LDAP_SUCCESS; +} + +enum { + SP_CHKPT = 1, + SP_SESSL, + SP_NOPRES, + SP_USEHINT, + SP_LOGDB +}; + +static ConfigDriver sp_cf_gen; + +static ConfigTable spcfg[] = { + { "syncprov-checkpoint", "ops> <minutes", 3, 3, 0, ARG_MAGIC|SP_CHKPT, + sp_cf_gen, "( OLcfgOvAt:1.1 NAME 'olcSpCheckpoint' " + "DESC 'ContextCSN checkpoint interval in ops and minutes' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "syncprov-sessionlog", "ops", 2, 2, 0, ARG_INT|ARG_MAGIC|SP_SESSL, + sp_cf_gen, "( OLcfgOvAt:1.2 NAME 'olcSpSessionlog' " + "DESC 'Session log size in ops' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "syncprov-nopresent", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_NOPRES, + sp_cf_gen, "( OLcfgOvAt:1.3 NAME 'olcSpNoPresent' " + "DESC 'Omit Present phase processing' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "syncprov-reloadhint", NULL, 2, 2, 0, ARG_ON_OFF|ARG_MAGIC|SP_USEHINT, + sp_cf_gen, "( OLcfgOvAt:1.4 NAME 'olcSpReloadHint' " + "DESC 'Observe Reload Hint in Request control' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "syncprov-sessionlog-source", NULL, 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|SP_LOGDB, + sp_cf_gen, "( OLcfgOvAt:1.5 NAME 'olcSpSessionlogSource' " + "DESC 'On startup, try loading sessionlog from this subtree' " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs spocs[] = { + { "( OLcfgOvOc:1.1 " + "NAME 'olcSyncProvConfig' " + "DESC 'SyncRepl Provider configuration' " + "SUP olcOverlayConfig " + "MAY ( olcSpCheckpoint " + "$ olcSpSessionlog " + "$ olcSpNoPresent " + "$ olcSpReloadHint " + "$ olcSpSessionlogSource " + ") )", + Cft_Overlay, spcfg }, + { NULL, 0, NULL } +}; + +static int +sp_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + int rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + switch ( c->type ) { + case SP_CHKPT: + if ( si->si_chkops || si->si_chktime ) { + struct berval bv; + /* we assume si_chktime is a multiple of 60 + * because the parsed value was originally + * multiplied by 60 */ + bv.bv_len = snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%d %d", si->si_chkops, si->si_chktime/60 ); + if ( bv.bv_len >= sizeof( c->cr_msg ) ) { + rc = 1; + } else { + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + } + } else { + rc = 1; + } + break; + case SP_SESSL: + if ( si->si_logs ) { + c->value_int = si->si_logs->sl_size; + } else { + rc = 1; + } + break; + case SP_NOPRES: + if ( si->si_nopres ) { + c->value_int = 1; + } else { + rc = 1; + } + break; + case SP_USEHINT: + if ( si->si_usehint ) { + c->value_int = 1; + } else { + rc = 1; + } + break; + case SP_LOGDB: + if ( BER_BVISEMPTY( &si->si_logbase ) ) { + rc = 1; + } else { + value_add_one( &c->rvalue_vals, &si->si_logbase ); + value_add_one( &c->rvalue_nvals, &si->si_logbase ); + } + break; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + switch ( c->type ) { + case SP_CHKPT: + si->si_chkops = 0; + si->si_chktime = 0; + break; + case SP_SESSL: + if ( si->si_logs ) + si->si_logs->sl_size = 0; + break; + case SP_NOPRES: + si->si_nopres = 0; + break; + case SP_USEHINT: + si->si_usehint = 0; + break; + case SP_LOGDB: + if ( !BER_BVISNULL( &si->si_logbase ) ) { + ch_free( si->si_logbase.bv_val ); + BER_BVZERO( &si->si_logbase ); + } + break; + } + return rc; + } + switch ( c->type ) { + case SP_CHKPT: + if ( lutil_atoi( &si->si_chkops, c->argv[1] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint ops # \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( si->si_chkops <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint ops # \"%d\"", + c->argv[0], si->si_chkops ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( lutil_atoi( &si->si_chktime, c->argv[2] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse checkpoint time \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( si->si_chktime <= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid checkpoint time \"%d\"", + c->argv[0], si->si_chkops ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + si->si_chktime *= 60; + break; + case SP_SESSL: { + sessionlog *sl; + int size = c->value_int; + + if ( size < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s size %d is negative", + c->argv[0], size ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + if ( size && !BER_BVISNULL( &si->si_logbase ) ) { + Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring " + "internal sessionlog, accesslog source has already been " + "configured, this results in wasteful operation\n" ); + } + sl = si->si_logs; + if ( !sl ) { + if ( !size ) break; + sl = ch_calloc( 1, sizeof( sessionlog )); + ldap_pvt_thread_rdwr_init( &sl->sl_mutex ); + si->si_logs = sl; + } + sl->sl_size = size; + } + break; + case SP_NOPRES: + si->si_nopres = c->value_int; + break; + case SP_USEHINT: + si->si_usehint = c->value_int; + if ( si->si_usehint ) { + /* Consider we might be a delta provider, but it's ok if not */ + (void)syncprov_setup_accesslog(); + } + break; + case SP_LOGDB: + if ( si->si_logs ) { + Debug( LDAP_DEBUG_ANY, "syncprov_config: while configuring " + "accesslog source, internal sessionlog has already been " + "configured, this results in wasteful operation\n" ); + } + if ( CONFIG_ONLINE_ADD( c ) ) { + if ( !select_backend( &c->value_ndn, 0 ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> no matching backend found for suffix", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + break; + } + ch_free( c->value_ndn.bv_val ); + } + si->si_logbase = c->value_ndn; + rc = syncprov_setup_accesslog(); + ch_free( c->value_dn.bv_val ); + break; + } + return rc; +} + +/* ITS#3456 we cannot run this search on the main thread, must use a + * child thread in order to insure we have a big enough stack. + */ +static void * +syncprov_db_otask( + void *ptr +) +{ + syncprov_findcsn( ptr, FIND_MAXCSN, 0 ); + return NULL; +} + +static int +syncprov_db_ocallback( + Operation *op, + SlapReply *rs +) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_err == LDAP_SUCCESS ) { + if ( rs->sr_entry->e_name.bv_len ) + op->o_callback->sc_private = (void *)1; + } + return LDAP_SUCCESS; +} + +/* ITS#9015 see if the DB is really empty */ +static void * +syncprov_db_otask2( + void *ptr +) +{ + Operation *op = ptr; + SlapReply rs = {REP_RESULT}; + slap_callback cb = {0}; + int rc; + + cb.sc_response = syncprov_db_ocallback; + + op->o_managedsait = SLAP_CONTROL_CRITICAL; + op->o_callback = &cb; + op->o_tag = LDAP_REQ_SEARCH; + op->ors_scope = LDAP_SCOPE_SUBTREE; + op->ors_limit = NULL; + op->ors_slimit = 1; + op->ors_tlimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_no_attrs; + op->ors_attrsonly = 1; + op->ors_deref = LDAP_DEREF_NEVER; + op->ors_filter = &generic_filter; + op->ors_filterstr = generic_filterstr; + rc = op->o_bd->be_search( op, &rs ); + if ( rc == LDAP_SIZELIMIT_EXCEEDED || cb.sc_private ) + op->ors_slimit = 2; + return NULL; +} + +/* Read any existing contextCSN from the underlying db. + * Then search for any entries newer than that. If no value exists, + * just generate it. Cache whatever result. + */ +static int +syncprov_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + Entry *e = NULL; + Attribute *a; + int rc; + void *thrctx = NULL; + + if ( !SLAP_LASTMOD( be )) { + Debug( LDAP_DEBUG_ANY, + "syncprov_db_open: invalid config, lastmod must be enabled\n" ); + return -1; + } + + if ( slapMode & SLAP_TOOL_MODE ) { + return 0; + } + + rc = overlay_register_control( be, LDAP_CONTROL_SYNC ); + if ( rc ) { + return rc; + } + + Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: " + "starting syncprov for suffix %s\n", + be->be_suffix[0].bv_val ); + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + + if ( SLAP_SYNC_SUBENTRY( be )) { + build_new_dn( &si->si_contextdn, be->be_nsuffix, + (struct berval *)&slap_ldapsync_cn_bv, NULL ); + } else { + si->si_contextdn = be->be_nsuffix[0]; + } + rc = overlay_entry_get_ov( op, &si->si_contextdn, NULL, + slap_schema.si_ad_contextCSN, 0, &e, on ); + + if ( e ) { + ldap_pvt_thread_t tid; + + a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + ber_bvarray_dup_x( &si->si_ctxcsn, a->a_vals, NULL ); + si->si_numcsns = a->a_numvals; + si->si_sids = slap_parse_csn_sids( si->si_ctxcsn, a->a_numvals, NULL ); + slap_sort_csn_sids( si->si_ctxcsn, si->si_sids, si->si_numcsns, NULL ); + } + overlay_entry_release_ov( op, e, 0, on ); + if ( si->si_ctxcsn && !SLAP_DBCLEAN( be )) { + op->o_tag = LDAP_REQ_SEARCH; + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + op->ors_scope = LDAP_SCOPE_SUBTREE; + ldap_pvt_thread_create( &tid, 0, syncprov_db_otask, op ); + ldap_pvt_thread_join( tid, NULL ); + } + } + + /* Didn't find a contextCSN, should we generate one? */ + if ( !si->si_ctxcsn ) { + char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ]; + struct berval csn; + + if ( SLAP_SINGLE_SHADOW( op->o_bd ) ) { + /* Not in charge of this serverID, don't generate anything. */ + goto out; + } + if ( !SLAP_SYNC_SUBENTRY( be ) && rc != LDAP_SUCCESS + && rc != LDAP_NO_SUCH_ATTRIBUTE ) { + /* If the DB is genuinely empty, don't generate one either. */ + goto out; + } + if ( !si->si_contextdn.bv_len ) { + ldap_pvt_thread_t tid; + /* a glue entry here with no contextCSN might mean an empty DB. + * we need to search for children, to be sure. + */ + op->o_req_dn = be->be_suffix[0]; + op->o_req_ndn = be->be_nsuffix[0]; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + ldap_pvt_thread_create( &tid, 0, syncprov_db_otask2, op ); + ldap_pvt_thread_join( tid, NULL ); + if ( op->ors_slimit == 1 ) + goto out; + } + + csn.bv_val = csnbuf; + csn.bv_len = sizeof( csnbuf ); + slap_get_csn( op, &csn, 0 ); + value_add_one( &si->si_ctxcsn, &csn ); + si->si_numcsns = 1; + si->si_sids = ch_malloc( sizeof(int) ); + si->si_sids[0] = slap_serverID; + Debug( LDAP_DEBUG_SYNC, "syncprov_db_open: " + "generated a new ctxcsn=%s for suffix %s\n", + csn.bv_val, be->be_suffix[0].bv_val ); + + /* make sure we do a checkpoint on close */ + si->si_numops++; + } + + /* Initialize the sessionlog mincsn */ + if ( si->si_logs && si->si_numcsns ) { + sessionlog *sl = si->si_logs; + int i; + ber_bvarray_dup_x( &sl->sl_mincsn, si->si_ctxcsn, NULL ); + sl->sl_numcsns = si->si_numcsns; + sl->sl_sids = ch_malloc( si->si_numcsns * sizeof(int) ); + for ( i=0; i < si->si_numcsns; i++ ) + sl->sl_sids[i] = si->si_sids[i]; + } + + if ( !BER_BVISNULL( &si->si_logbase ) ) { + BackendDB *db = select_backend( &si->si_logbase, 0 ); + if ( !db ) { + Debug( LDAP_DEBUG_ANY, "syncprov_db_open: " + "configured accesslog database dn='%s' not present\n", + si->si_logbase.bv_val ); + return -1; + } + } + +out: + op->o_bd->bd_info = (BackendInfo *)on; + return 0; +} + +/* Write the current contextCSN into the underlying db. + */ +static int +syncprov_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; +#ifdef SLAP_CONFIG_DELETE + syncops *so, *sonext; +#endif /* SLAP_CONFIG_DELETE */ + + if ( slapMode & SLAP_TOOL_MODE ) { + return 0; + } + if ( si->si_numops ) { + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + syncprov_checkpoint( op, on ); + } + +#ifdef SLAP_CONFIG_DELETE + if ( !slapd_shutdown ) { + ldap_pvt_thread_mutex_lock( &si->si_ops_mutex ); + for ( so=si->si_ops, sonext=so; so; so=sonext ) { + SlapReply rs = {REP_RESULT}; + rs.sr_err = LDAP_UNAVAILABLE; + ldap_pvt_thread_mutex_lock( &so->s_mutex ); + send_ldap_result( so->s_op, &rs ); + sonext=so->s_next; + if ( so->s_flags & PS_TASK_QUEUED ) + ldap_pvt_thread_pool_retract( so->s_pool_cookie ); + ldap_pvt_thread_mutex_unlock( &so->s_mutex ); + if ( !syncprov_drop_psearch( so, 0 )) + so->s_si = NULL; + } + si->si_ops=NULL; + ldap_pvt_thread_mutex_unlock( &si->si_ops_mutex ); + } + overlay_unregister_control( be, LDAP_CONTROL_SYNC ); +#endif /* SLAP_CONFIG_DELETE */ + + return 0; +} + +static int +syncprov_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + syncprov_info_t *si; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Debug( LDAP_DEBUG_ANY, + "syncprov must be instantiated within a database.\n" ); + return 1; + } + + si = ch_calloc(1, sizeof(syncprov_info_t)); + on->on_bi.bi_private = si; + ldap_pvt_thread_rdwr_init( &si->si_csn_rwlock ); + ldap_pvt_thread_mutex_init( &si->si_ops_mutex ); + ldap_pvt_thread_mutex_init( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_init( &si->si_resp_mutex ); + + csn_anlist[0].an_desc = slap_schema.si_ad_entryCSN; + csn_anlist[0].an_name = slap_schema.si_ad_entryCSN->ad_cname; + csn_anlist[1].an_desc = slap_schema.si_ad_entryUUID; + csn_anlist[1].an_name = slap_schema.si_ad_entryUUID->ad_cname; + + uuid_anlist[0].an_desc = slap_schema.si_ad_entryUUID; + uuid_anlist[0].an_name = slap_schema.si_ad_entryUUID->ad_cname; + + return 0; +} + +static int +syncprov_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + syncprov_info_t *si = (syncprov_info_t *)on->on_bi.bi_private; + + if ( si ) { + if ( si->si_logs ) { + sessionlog *sl = si->si_logs; + + ldap_tavl_free( sl->sl_entries, (AVL_FREE)ch_free ); + if ( sl->sl_mincsn ) + ber_bvarray_free( sl->sl_mincsn ); + if ( sl->sl_sids ) + ch_free( sl->sl_sids ); + + ldap_pvt_thread_rdwr_destroy(&si->si_logs->sl_mutex); + ch_free( si->si_logs ); + } + if ( si->si_ctxcsn ) + ber_bvarray_free( si->si_ctxcsn ); + if ( si->si_sids ) + ch_free( si->si_sids ); + if ( si->si_logbase.bv_val ) + ch_free( si->si_logbase.bv_val ); + ldap_pvt_thread_mutex_destroy( &si->si_resp_mutex ); + ldap_pvt_thread_mutex_destroy( &si->si_mods_mutex ); + ldap_pvt_thread_mutex_destroy( &si->si_ops_mutex ); + ldap_pvt_thread_rdwr_destroy( &si->si_csn_rwlock ); + ch_free( si ); + } + + return 0; +} + +static int syncprov_parseCtrl ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_int_t mode; + ber_len_t len; + struct berval cookie = BER_BVNULL; + sync_control *sr; + int rhint = 0; + + if ( op->o_sync != SLAP_CONTROL_NONE ) { + rs->sr_text = "Sync control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( op->o_pagedresults != SLAP_CONTROL_NONE ) { + rs->sr_text = "Sync control specified with pagedResults control"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "Sync control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "Sync control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + /* Parse the control value + * syncRequestValue ::= SEQUENCE { + * mode ENUMERATED { + * -- 0 unused + * refreshOnly (1), + * -- 2 reserved + * refreshAndPersist (3) + * }, + * cookie syncCookie OPTIONAL + * } + */ + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + if ( (tag = ber_scanf( ber, "{i" /*}*/, &mode )) == LBER_ERROR ) { + rs->sr_text = "Sync control : mode decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + switch( mode ) { + case LDAP_SYNC_REFRESH_ONLY: + mode = SLAP_SYNC_REFRESH; + break; + case LDAP_SYNC_REFRESH_AND_PERSIST: + mode = SLAP_SYNC_REFRESH_AND_PERSIST; + break; + default: + rs->sr_text = "Sync control : unknown update mode"; + return LDAP_PROTOCOL_ERROR; + } + + tag = ber_peek_tag( ber, &len ); + + if ( tag == LDAP_TAG_SYNC_COOKIE ) { + if (( ber_scanf( ber, /*{*/ "m", &cookie )) == LBER_ERROR ) { + rs->sr_text = "Sync control : cookie decoding error"; + return LDAP_PROTOCOL_ERROR; + } + tag = ber_peek_tag( ber, &len ); + } + if ( tag == LDAP_TAG_RELOAD_HINT ) { + if (( ber_scanf( ber, /*{*/ "b", &rhint )) == LBER_ERROR ) { + rs->sr_text = "Sync control : rhint decoding error"; + return LDAP_PROTOCOL_ERROR; + } + } + if (( ber_scanf( ber, /*{*/ "}")) == LBER_ERROR ) { + rs->sr_text = "Sync control : decoding error"; + return LDAP_PROTOCOL_ERROR; + } + sr = op->o_tmpcalloc( 1, sizeof(struct sync_control), op->o_tmpmemctx ); + sr->sr_rhint = rhint; + if (!BER_BVISNULL(&cookie)) { + ber_dupbv_x( &sr->sr_state.octet_str, &cookie, op->o_tmpmemctx ); + /* If parse fails, pretend no cookie was sent */ + if ( slap_parse_sync_cookie( &sr->sr_state, op->o_tmpmemctx ) || + sr->sr_state.rid == -1 ) { + if ( sr->sr_state.ctxcsn ) { + ber_bvarray_free_x( sr->sr_state.ctxcsn, op->o_tmpmemctx ); + sr->sr_state.ctxcsn = NULL; + } + sr->sr_state.numcsns = 0; + } + } + + op->o_controls[slap_cids.sc_LDAPsync] = sr; + + op->o_sync = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + op->o_sync_mode |= mode; /* o_sync_mode shares o_sync */ + + return LDAP_SUCCESS; +} + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +static slap_overinst syncprov; + +int +syncprov_initialize() +{ + int rc; + + rc = register_supported_control( LDAP_CONTROL_SYNC, + SLAP_CTRL_SEARCH, NULL, + syncprov_parseCtrl, &slap_cids.sc_LDAPsync ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "syncprov_init: Failed to register control %d\n", rc ); + return rc; + } + + syncprov.on_bi.bi_type = "syncprov"; + syncprov.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + syncprov.on_bi.bi_db_init = syncprov_db_init; + syncprov.on_bi.bi_db_destroy = syncprov_db_destroy; + syncprov.on_bi.bi_db_open = syncprov_db_open; + syncprov.on_bi.bi_db_close = syncprov_db_close; + + syncprov.on_bi.bi_op_abandon = syncprov_op_abandon; + syncprov.on_bi.bi_op_cancel = syncprov_op_abandon; + + syncprov.on_bi.bi_op_add = syncprov_op_mod; + syncprov.on_bi.bi_op_compare = syncprov_op_compare; + syncprov.on_bi.bi_op_delete = syncprov_op_mod; + syncprov.on_bi.bi_op_modify = syncprov_op_mod; + syncprov.on_bi.bi_op_modrdn = syncprov_op_mod; + syncprov.on_bi.bi_op_search = syncprov_op_search; + syncprov.on_bi.bi_extended = syncprov_op_extended; + syncprov.on_bi.bi_operational = syncprov_operational; + + syncprov.on_bi.bi_cf_ocs = spocs; + + generic_filter.f_desc = slap_schema.si_ad_objectClass; + + rc = config_register_schema( spcfg, spocs ); + if ( rc ) return rc; + + return overlay_register( &syncprov ); +} + +#if SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return syncprov_initialize(); +} +#endif /* SLAPD_OVER_SYNCPROV == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_SYNCPROV) */ diff --git a/servers/slapd/overlays/translucent.c b/servers/slapd/overlays/translucent.c new file mode 100644 index 0000000..2d31bb0 --- /dev/null +++ b/servers/slapd/overlays/translucent.c @@ -0,0 +1,1497 @@ +/* translucent.c - translucent proxy module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * Portions Copyright 2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corp. for inclusion in + * OpenLDAP Software. This work was sponsored by Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_TRANSLUCENT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" + +#include "slap-config.h" + +/* config block */ +typedef struct translucent_info { + BackendDB db; /* captive backend */ + AttributeName *local; /* valid attrs for local filters */ + AttributeName *remote; /* valid attrs for remote filters */ + int strict; + int no_glue; + int defer_db_open; + int bind_local; + int pwmod_local; +} translucent_info; + +static ConfigLDAPadd translucent_ldadd; +static ConfigCfAdd translucent_cfadd; + +static ConfigDriver translucent_cf_gen; + +enum { + TRANS_LOCAL = 1, + TRANS_REMOTE +}; + +static ConfigTable translucentcfg[] = { + { "translucent_strict", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, strict), + "( OLcfgOvAt:14.1 NAME 'olcTranslucentStrict' " + "DESC 'Reveal attribute deletion constraint violations' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "translucent_no_glue", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, no_glue), + "( OLcfgOvAt:14.2 NAME 'olcTranslucentNoGlue' " + "DESC 'Disable automatic glue records for ADD and MODRDN' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "translucent_local", "attr[,attr...]", 1, 2, 0, + ARG_MAGIC|TRANS_LOCAL, + translucent_cf_gen, + "( OLcfgOvAt:14.3 NAME 'olcTranslucentLocal' " + "DESC 'Attributes to use in local search filter' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "translucent_remote", "attr[,attr...]", 1, 2, 0, + ARG_MAGIC|TRANS_REMOTE, + translucent_cf_gen, + "( OLcfgOvAt:14.4 NAME 'olcTranslucentRemote' " + "DESC 'Attributes to use in remote search filter' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "translucent_bind_local", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, bind_local), + "( OLcfgOvAt:14.5 NAME 'olcTranslucentBindLocal' " + "DESC 'Enable local bind' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL }, + { "translucent_pwmod_local", "on|off", 1, 2, 0, + ARG_ON_OFF|ARG_OFFSET, + (void *)offsetof(translucent_info, pwmod_local), + "( OLcfgOvAt:14.6 NAME 'olcTranslucentPwModLocal' " + "DESC 'Enable local RFC 3062 Password Modify extended operation' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE)", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs translucentocs[] = { + { "( OLcfgOvOc:14.1 " + "NAME 'olcTranslucentConfig' " + "DESC 'Translucent configuration' " + "SUP olcOverlayConfig " + "MAY ( olcTranslucentStrict $ olcTranslucentNoGlue $" + " olcTranslucentLocal $ olcTranslucentRemote $" + " olcTranslucentBindLocal $ olcTranslucentPwModLocal ) )", + Cft_Overlay, translucentcfg, NULL, translucent_cfadd }, + { "( OLcfgOvOc:14.2 " + "NAME 'olcTranslucentDatabase' " + "DESC 'Translucent target database configuration' " + /* co_table is initialized in translucent_initialize() */ + "AUXILIARY )", Cft_Misc, NULL, translucent_ldadd }, + { NULL, 0, NULL } +}; +/* for translucent_init() */ + +static int +translucent_ldadd_cleanup( ConfigArgs *ca ) +{ + slap_overinst *on = ca->ca_private; + translucent_info *ov = on->on_bi.bi_private; + + ov->defer_db_open = 0; + return backend_startup_one( ca->be, &ca->reply ); +} + +static int +translucent_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca ) +{ + slap_overinst *on; + translucent_info *ov; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_ldadd\n" ); + + if ( cei->ce_type != Cft_Overlay || !cei->ce_bi || + cei->ce_bi->bi_cf_ocs != translucentocs ) + return LDAP_CONSTRAINT_VIOLATION; + + on = (slap_overinst *)cei->ce_bi; + ov = on->on_bi.bi_private; + ca->be = &ov->db; + ca->ca_private = on; + if ( CONFIG_ONLINE_ADD( ca )) + config_push_cleanup( ca, translucent_ldadd_cleanup ); + else + ov->defer_db_open = 0; + + return LDAP_SUCCESS; +} + +static int +translucent_cfadd( Operation *op, SlapReply *rs, Entry *e, ConfigArgs *ca ) +{ + CfEntryInfo *cei = e->e_private; + slap_overinst *on = (slap_overinst *)cei->ce_bi; + translucent_info *ov = on->on_bi.bi_private; + struct berval bv; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_cfadd\n" ); + + /* FIXME: should not hardcode "olcDatabase" here */ + bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ), + "olcDatabase=" SLAP_X_ORDERED_FMT "%s", + 0, ov->db.bd_info->bi_type ); + if ( bv.bv_len >= sizeof( ca->cr_msg ) ) { + return -1; + } + bv.bv_val = ca->cr_msg; + ca->be = &ov->db; + ov->defer_db_open = 0; + + /* We can only create this entry if the database is table-driven + */ + if ( ov->db.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; + } + + /* cn=config values could be deleted later, we only want one name + * per value for valx to match. */ + if ( c->op != SLAP_CONFIG_ADD && strchr( c->argv[1], ',' ) ) { + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "%s: %s: " + "Supplying multiple attribute names in a single value is " + "unsupported and will be disallowed in a future version\n", + c->log, c->argv[0] ); + } + + a2 = str2anlist( *an, c->argv[1], "," ); + if ( !a2 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s unable to parse attribute %s", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + return ARG_BAD_CONF; + } + *an = a2; + return 0; +} + +static slap_overinst translucent; + +/* +** glue_parent() +** call syncrepl_add_glue() with the parent suffix; +** +*/ + +static struct berval glue[] = { BER_BVC("top"), BER_BVC("glue"), BER_BVNULL }; + +void glue_parent(Operation *op) { + Operation nop = *op; + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + struct berval ndn = BER_BVNULL; + Attribute *a; + Entry *e; + struct berval pdn; + + dnParent( &op->o_req_ndn, &pdn ); + ber_dupbv_x( &ndn, &pdn, op->o_tmpmemctx ); + + Debug(LDAP_DEBUG_TRACE, "=> glue_parent: fabricating glue for <%s>\n", ndn.bv_val ); + + e = entry_alloc(); + e->e_id = NOID; + ber_dupbv(&e->e_name, &ndn); + ber_dupbv(&e->e_nname, &ndn); + + a = attr_alloc( slap_schema.si_ad_objectClass ); + a->a_numvals = 2; + a->a_vals = ch_malloc(sizeof(struct berval) * 3); + ber_dupbv(&a->a_vals[0], &glue[0]); + ber_dupbv(&a->a_vals[1], &glue[1]); + ber_dupbv(&a->a_vals[2], &glue[2]); + a->a_nvals = a->a_vals; + a->a_next = e->e_attrs; + e->e_attrs = a; + + a = attr_alloc( slap_schema.si_ad_structuralObjectClass ); + a->a_numvals = 1; + a->a_vals = ch_malloc(sizeof(struct berval) * 2); + ber_dupbv(&a->a_vals[0], &glue[1]); + ber_dupbv(&a->a_vals[1], &glue[2]); + a->a_nvals = a->a_vals; + a->a_next = e->e_attrs; + e->e_attrs = a; + + nop.o_req_dn = ndn; + nop.o_req_ndn = ndn; + nop.ora_e = e; + + nop.o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + syncrepl_add_glue(&nop, e); + nop.o_bd->bd_info = (BackendInfo *) on; + + op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx ); + + return; +} + +/* +** free_attr_chain() +** free only the Attribute*, not the contents; +** +*/ +void free_attr_chain(Attribute *b) { + Attribute *a; + for(a=b; a; a=a->a_next) { + a->a_vals = NULL; + a->a_nvals = NULL; + } + attrs_free( b ); + return; +} + +/* +** translucent_add() +** if not bound as root, send ACCESS error; +** if glue, glue_parent(); +** return CONTINUE; +** +*/ + +static int translucent_add(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Debug(LDAP_DEBUG_TRACE, "==> translucent_add: %s\n", + op->o_req_dn.bv_val ); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + if(!ov->no_glue) glue_parent(op); + return(SLAP_CB_CONTINUE); +} + +/* +** translucent_modrdn() +** if not bound as root, send ACCESS error; +** if !glue, glue_parent(); +** else return CONTINUE; +** +*/ + +static int translucent_modrdn(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Debug(LDAP_DEBUG_TRACE, "==> translucent_modrdn: %s -> %s\n", + op->o_req_dn.bv_val, op->orr_newrdn.bv_val ); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + if(!ov->no_glue) { + op->o_tag = LDAP_REQ_ADD; + glue_parent(op); + op->o_tag = LDAP_REQ_MODRDN; + } + return(SLAP_CB_CONTINUE); +} + +/* +** translucent_delete() +** if not bound as root, send ACCESS error; +** else return CONTINUE; +** +*/ + +static int translucent_delete(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Debug(LDAP_DEBUG_TRACE, "==> translucent_delete: %s\n", + op->o_req_dn.bv_val ); + if(!be_isroot(op)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INSUFFICIENT_ACCESS, + "user modification of overlay database not permitted"); + op->o_bd->bd_info = (BackendInfo *) on; + return(rs->sr_err); + } + return(SLAP_CB_CONTINUE); +} + +static int +translucent_tag_cb( Operation *op, SlapReply *rs ) +{ + op->o_tag = LDAP_REQ_MODIFY; + op->orm_modlist = op->o_callback->sc_private; + rs->sr_tag = slap_req2res( op->o_tag ); + + return SLAP_CB_CONTINUE; +} + +/* +** translucent_modify() +** modify in local backend if exists in both; +** otherwise, add to local backend; +** fail if not defined in captive backend; +** +*/ + +static int translucent_modify(Operation *op, SlapReply *rs) { + SlapReply nrs = { REP_RESULT }; + + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Entry *e = NULL, *re = NULL; + Attribute *a, *ax; + Modifications *m, **mm; + BackendDB *db; + int del, rc, erc = 0; + slap_callback cb = { 0 }; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_modify: %s\n", + op->o_req_dn.bv_val ); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } +/* +** fetch entry from the captive backend; +** if it did not exist, fail; +** release it, if captive backend supports this; +** +*/ + + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re); + op->o_bd = db; + if(rc != LDAP_SUCCESS || re == NULL ) { + send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT, + "attempt to modify nonexistent local record"); + return(rs->sr_err); + } +/* +** fetch entry from local backend; +** if it exists: +** foreach Modification: +** if attr not present in local: +** if Mod == LDAP_MOD_DELETE: +** if remote attr not present, return NO_SUCH; +** if remote attr present, drop this Mod; +** else force this Mod to LDAP_MOD_ADD; +** return CONTINUE; +** +*/ + + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e); + op->o_bd->bd_info = (BackendInfo *) on; + + if(e && rc == LDAP_SUCCESS) { + Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: found local entry\n" ); + for(mm = &op->orm_modlist; *mm; ) { + m = *mm; + for(a = e->e_attrs; a; a = a->a_next) + if(a->a_desc == m->sml_desc) break; + if(a) { + mm = &m->sml_next; + continue; /* found local attr */ + } + if(m->sml_op == LDAP_MOD_DELETE) { + for(a = re->e_attrs; a; a = a->a_next) + if(a->a_desc == m->sml_desc) break; + /* not found remote attr */ + if(!a) { + erc = LDAP_NO_SUCH_ATTRIBUTE; + goto release; + } + if(ov->strict) { + erc = LDAP_CONSTRAINT_VIOLATION; + goto release; + } + Debug(LDAP_DEBUG_TRACE, + "=> translucent_modify: silently dropping delete: %s\n", + m->sml_desc->ad_cname.bv_val ); + *mm = m->sml_next; + m->sml_next = NULL; + slap_mods_free(m, 1); + continue; + } + m->sml_op = LDAP_MOD_ADD; + mm = &m->sml_next; + } + erc = SLAP_CB_CONTINUE; +release: + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else + entry_free(re); + } + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + be_entry_release_r(op, e); + op->o_bd->bd_info = (BackendInfo *) on; + if(erc == SLAP_CB_CONTINUE) { + return(erc); + } else if(erc) { + send_ldap_error(op, rs, erc, + "attempt to delete nonexistent attribute"); + return(erc); + } + } + + /* don't leak remote entry copy */ + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else + entry_free(re); + } +/* +** foreach Modification: +** if MOD_ADD or MOD_REPLACE, add Attribute; +** if no Modifications were suitable: +** if strict, throw CONSTRAINT_VIOLATION; +** else, return early SUCCESS; +** fabricate Entry with new Attribute chain; +** glue_parent() for this Entry; +** call bi_op_add() in local backend; +** +*/ + + Debug(LDAP_DEBUG_TRACE, "=> translucent_modify: fabricating local add\n" ); + a = NULL; + for(del = 0, ax = NULL, m = op->orm_modlist; m; m = m->sml_next) { + Attribute atmp; + if(((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_ADD) && + ((m->sml_op & LDAP_MOD_OP) != LDAP_MOD_REPLACE)) { + Debug(LDAP_DEBUG_ANY, + "=> translucent_modify: silently dropped modification(%d): %s\n", + m->sml_op, m->sml_desc->ad_cname.bv_val ); + if((m->sml_op & LDAP_MOD_OP) == LDAP_MOD_DELETE) del++; + continue; + } + atmp.a_desc = m->sml_desc; + atmp.a_vals = m->sml_values; + atmp.a_nvals = m->sml_nvalues ? m->sml_nvalues : atmp.a_vals; + atmp.a_numvals = m->sml_numvals; + atmp.a_flags = 0; + a = attr_dup( &atmp ); + a->a_next = ax; + ax = a; + } + + if(del && ov->strict) { + attrs_free( a ); + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, + "attempt to delete attributes from local database"); + return(rs->sr_err); + } + + if(!ax) { + if(ov->strict) { + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, + "modification contained other than ADD or REPLACE"); + return(rs->sr_err); + } + /* rs->sr_text = "no valid modification found"; */ + rs->sr_err = LDAP_SUCCESS; + send_ldap_result(op, rs); + return(rs->sr_err); + } + + e = entry_alloc(); + ber_dupbv( &e->e_name, &op->o_req_dn ); + ber_dupbv( &e->e_nname, &op->o_req_ndn ); + e->e_attrs = a; + + op->o_tag = LDAP_REQ_ADD; + cb.sc_response = translucent_tag_cb; + cb.sc_private = op->orm_modlist; + op->oq_add.rs_e = e; + + glue_parent(op); + + cb.sc_next = op->o_callback; + op->o_callback = &cb; + rc = on->on_info->oi_orig->bi_op_add(op, &nrs); + if ( op->ora_e == e ) + entry_free( e ); + op->o_callback = cb.sc_next; + + return(rc); +} + +static int translucent_compare(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + AttributeAssertion *ava = op->orc_ava; + Entry *e = NULL; + BackendDB *db; + int rc; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_compare: <%s> %s:%s\n", + op->o_req_dn.bv_val, ava->aa_desc->ad_cname.bv_val, ava->aa_value.bv_val); + +/* +** if the local backend has an entry for this attribute: +** CONTINUE and let it do the compare; +** +*/ + rc = overlay_entry_get_ov(op, &op->o_req_ndn, NULL, ava->aa_desc, 0, &e, on); + if(rc == LDAP_SUCCESS && e) { + overlay_entry_release_ov(op, e, 0, on); + return(SLAP_CB_CONTINUE); + } + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } +/* +** call compare() in the captive backend; +** return the result; +** +*/ + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_op_compare(op, rs); + op->o_bd = db; + + return(rc); +} + +static int translucent_pwmod(Operation *op, SlapReply *rs) { + SlapReply nrs = { REP_RESULT }; + Operation nop; + + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + Entry *e = NULL, *re = NULL; + BackendDB *db; + int rc = 0; + slap_callback cb = { 0 }; + + if (!ov->pwmod_local) { + rs->sr_err = LDAP_CONSTRAINT_VIOLATION, + rs->sr_text = "attempt to modify password in local database"; + return rs->sr_err; + } + +/* +** fetch entry from the captive backend; +** if it did not exist, fail; +** release it, if captive backend supports this; +** +*/ + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &re); + if(rc != LDAP_SUCCESS || re == NULL ) { + send_ldap_error((op), rs, LDAP_NO_SUCH_OBJECT, + "attempt to modify nonexistent local record"); + return(rs->sr_err); + } + op->o_bd = db; +/* +** fetch entry from local backend; +** if it exists: +** return CONTINUE; +*/ + + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + rc = be_entry_get_rw(op, &op->o_req_ndn, NULL, NULL, 0, &e); + op->o_bd->bd_info = (BackendInfo *) on; + + if(e && rc == LDAP_SUCCESS) { + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else { + entry_free(re); + } + } + op->o_bd->bd_info = (BackendInfo *) on->on_info->oi_orig; + be_entry_release_r(op, e); + op->o_bd->bd_info = (BackendInfo *) on; + return SLAP_CB_CONTINUE; + } + + /* don't leak remote entry copy */ + if(re) { + if(ov->db.bd_info->bi_entry_release_rw) { + op->o_bd = &ov->db; + ov->db.bd_info->bi_entry_release_rw(op, re, 0); + op->o_bd = db; + } else { + entry_free(re); + } + } +/* +** glue_parent() for this Entry; +** call bi_op_add() in local backend; +** +*/ + e = entry_alloc(); + ber_dupbv( &e->e_name, &op->o_req_dn ); + ber_dupbv( &e->e_nname, &op->o_req_ndn ); + e->e_attrs = NULL; + + nop = *op; + nop.o_tag = LDAP_REQ_ADD; + cb.sc_response = slap_null_cb; + nop.oq_add.rs_e = e; + + glue_parent(&nop); + + nop.o_callback = &cb; + rc = on->on_info->oi_orig->bi_op_add(&nop, &nrs); + if ( nop.ora_e == e ) { + entry_free( e ); + } + + if ( rc == LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + return rc; +} + +static int translucent_exop(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + const struct berval bv_exop_pwmod = BER_BVC(LDAP_EXOP_MODIFY_PASSWD); + + Debug(LDAP_DEBUG_TRACE, "==> translucent_exop: %s\n", + op->o_req_dn.bv_val ); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + if ( bvmatch( &bv_exop_pwmod, &op->ore_reqoid ) ) { + return translucent_pwmod( op, rs ); + } + + return SLAP_CB_CONTINUE; +} + +/* +** translucent_search_cb() +** merge local data with remote data +** +** Four cases: +** 1: remote search, no local filter +** merge data and send immediately +** 2: remote search, with local filter +** merge data and save +** 3: local search, no remote filter +** merge data and send immediately +** 4: local search, with remote filter +** check list, merge, send, delete +*/ + +#define RMT_SIDE 0 +#define LCL_SIDE 1 +#define USE_LIST 2 + +typedef struct trans_ctx { + BackendDB *db; + slap_overinst *on; + Filter *orig; + TAvlnode *list; + int step; + int slimit; + AttributeName *attrs; +} trans_ctx; + +static int translucent_search_cb(Operation *op, SlapReply *rs) { + trans_ctx *tc; + BackendDB *db; + slap_overinst *on; + translucent_info *ov; + Entry *le, *re; + Attribute *a, *ax, *an, *as = NULL; + int rc; + int test_f = 0; + + tc = op->o_callback->sc_private; + + /* Don't let the op complete while we're gathering data */ + if ( rs->sr_type == REP_RESULT && ( tc->step & USE_LIST )) + return 0; + + if(rs->sr_type != REP_SEARCH || !rs->sr_entry) + return(SLAP_CB_CONTINUE); + + Debug(LDAP_DEBUG_TRACE, "==> translucent_search_cb: %s\n", + rs->sr_entry->e_name.bv_val ); + + op->ors_slimit = tc->slimit + ( tc->slimit > 0 ? 1 : 0 ); + if ( op->ors_attrs == slap_anlist_all_attributes ) { + op->ors_attrs = tc->attrs; + rs->sr_attrs = tc->attrs; + rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs ); + } + + on = tc->on; + ov = on->on_bi.bi_private; + + db = op->o_bd; + re = NULL; + + /* If we have local, get remote */ + if ( tc->step & LCL_SIDE ) { + le = rs->sr_entry; + /* If entry is already on list, use it */ + if ( tc->step & USE_LIST ) { + re = ldap_tavl_delete( &tc->list, le, entry_dn_cmp ); + if ( re ) { + rs_flush_entry( op, rs, on ); + rc = test_filter( op, re, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + rs->sr_entry = re; + + if ( tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) { + return LDAP_SIZELIMIT_EXCEEDED; + } + + return SLAP_CB_CONTINUE; + } else { + entry_free( re ); + rs->sr_entry = NULL; + return 0; + } + } + } + op->o_bd = &ov->db; + rc = be_entry_get_rw( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &re ); + if ( rc == LDAP_SUCCESS && re ) { + Entry *tmp = entry_dup( re ); + be_entry_release_r( op, re ); + re = tmp; + test_f = 1; + } + } else { + /* Else we have remote, get local */ + op->o_bd = tc->db; + le = NULL; + rc = overlay_entry_get_ov(op, &rs->sr_entry->e_nname, NULL, NULL, 0, &le, on); + if ( rc == LDAP_SUCCESS && le ) { + re = entry_dup( rs->sr_entry ); + rs_flush_entry( op, rs, on ); + } else { + le = NULL; + } + } + +/* +** if we got remote and local entry: +** foreach local attr: +** foreach remote attr: +** if match, remote attr with local attr; +** if new local, add to list; +** append new local attrs to remote; +** +*/ + + if ( re && le ) { + for(ax = le->e_attrs; ax; ax = ax->a_next) { + for(a = re->e_attrs; a; a = a->a_next) { + if(a->a_desc == ax->a_desc) { + test_f = 1; + if(a->a_vals != a->a_nvals) + ber_bvarray_free(a->a_nvals); + ber_bvarray_free(a->a_vals); + ber_bvarray_dup_x( &a->a_vals, ax->a_vals, NULL ); + if ( ax->a_vals == ax->a_nvals ) { + a->a_nvals = a->a_vals; + } else { + ber_bvarray_dup_x( &a->a_nvals, ax->a_nvals, NULL ); + } + break; + } + } + if(a) continue; + an = attr_dup(ax); + an->a_next = as; + as = an; + } + /* Dispose of local entry */ + if ( tc->step & LCL_SIDE ) { + rs_flush_entry(op, rs, on); + } else { + overlay_entry_release_ov(op, le, 0, on); + } + + /* literally append, so locals are always last */ + if(as) { + if(re->e_attrs) { + for(ax = re->e_attrs; ax->a_next; ax = ax->a_next); + ax->a_next = as; + } else { + re->e_attrs = as; + } + } + /* If both filters, save entry for later */ + if ( tc->step == (USE_LIST|RMT_SIDE) ) { + ldap_tavl_insert( &tc->list, re, entry_dn_cmp, ldap_avl_dup_error ); + rs->sr_entry = NULL; + rc = 0; + } else { + /* send it now */ + rs->sr_entry = re; + rs->sr_flags |= REP_ENTRY_MUSTBEFREED; + if ( test_f ) { + rc = test_filter( op, rs->sr_entry, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rc = SLAP_CB_CONTINUE; + } else { + rc = 0; + } + } else { + rc = SLAP_CB_CONTINUE; + } + } + } else if ( le ) { + /* Only a local entry: remote was deleted + * Ought to delete the local too... + */ + rc = 0; + } else if ( tc->step & USE_LIST ) { + /* Only a remote entry, but both filters: + * Test the complete filter + */ + rc = test_filter( op, rs->sr_entry, tc->orig ); + if ( rc == LDAP_COMPARE_TRUE ) { + rc = SLAP_CB_CONTINUE; + } else { + rc = 0; + } + } else { + /* Only a remote entry, only remote filter: + * just pass thru + */ + rc = SLAP_CB_CONTINUE; + } + + op->o_bd = db; + + if ( rc == SLAP_CB_CONTINUE && tc->slimit >= 0 && rs->sr_nentries >= tc->slimit ) { + return LDAP_SIZELIMIT_EXCEEDED; + } + + return rc; +} + +/* Dup the filter, excluding invalid elements */ +static Filter * +trans_filter_dup(Operation *op, Filter *f, AttributeName *an) +{ + Filter *n = NULL; + + if ( !f ) + return NULL; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_result = f->f_result; + n->f_next = NULL; + break; + + case LDAP_FILTER_PRESENT: + if ( ad_inlist( f->f_desc, an )) { + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_desc = f->f_desc; + n->f_next = NULL; + } + break; + + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_EXT: + if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) { + AttributeAssertion *nava; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + + nava = op->o_tmpalloc( sizeof(AttributeAssertion), op->o_tmpmemctx ); + *nava = *f->f_ava; + n->f_ava = nava; + + ber_dupbv_x( &n->f_av_value, &f->f_av_value, op->o_tmpmemctx ); + n->f_next = NULL; + } + break; + + case LDAP_FILTER_SUBSTRINGS: + if ( !f->f_av_desc || ad_inlist( f->f_av_desc, an )) { + SubstringsAssertion *nsub; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + + nsub = op->o_tmpalloc( sizeof(SubstringsAssertion), op->o_tmpmemctx ); + *nsub = *f->f_sub; + n->f_sub = nsub; + + if ( !BER_BVISNULL( &f->f_sub_initial )) + ber_dupbv_x( &n->f_sub_initial, &f->f_sub_initial, op->o_tmpmemctx ); + + ber_bvarray_dup_x( &n->f_sub_any, f->f_sub_any, op->o_tmpmemctx ); + + if ( !BER_BVISNULL( &f->f_sub_final )) + ber_dupbv_x( &n->f_sub_final, &f->f_sub_final, op->o_tmpmemctx ); + + n->f_next = NULL; + } + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + Filter **p; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_choice = f->f_choice; + n->f_next = NULL; + + for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) { + *p = trans_filter_dup( op, f, an ); + if ( !*p ) + continue; + p = &(*p)->f_next; + } + /* nothing valid in this list */ + if ( !n->f_list ) { + op->o_tmpfree( n, op->o_tmpmemctx ); + return NULL; + } + /* Only 1 element in this list */ + if ((n->f_choice & SLAPD_FILTER_MASK) != LDAP_FILTER_NOT && + !n->f_list->f_next ) { + f = n->f_list; + *n = *f; + op->o_tmpfree( f, op->o_tmpmemctx ); + } + break; + } + } + return n; +} + +static void +trans_filter_free( Operation *op, Filter *f ) +{ + Filter *n, *p, *next; + + f->f_choice &= SLAPD_FILTER_MASK; + + switch( f->f_choice ) { + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + /* Free in reverse order */ + n = NULL; + for ( p = f->f_list; p; p = next ) { + next = p->f_next; + p->f_next = n; + n = p; + } + for ( p = n; p; p = next ) { + next = p->f_next; + trans_filter_free( op, p ); + } + break; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + op->o_tmpfree( f->f_av_value.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( f->f_ava, op->o_tmpmemctx ); + break; + default: + break; + } + op->o_tmpfree( f, op->o_tmpmemctx ); +} + +static int +translucent_search_cleanup( Operation *op, SlapReply *rs ) +{ + trans_ctx *tc = op->o_callback->sc_private; + + op->ors_filter = tc->orig; + return LDAP_SUCCESS; +} + +/* +** translucent_search() +** search via captive backend; +** override results with any local data; +** +*/ + +static int translucent_search(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + slap_callback cb = { NULL, NULL, NULL, NULL }; + trans_ctx tc; + Filter *fl, *fr; + struct berval fbv; + int rc = 0; + + if ( op->o_managedsait > SLAP_CONTROL_IGNORED ) + return SLAP_CB_CONTINUE; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_search: <%s> %s\n", + op->o_req_dn.bv_val, op->ors_filterstr.bv_val ); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + fr = ov->remote ? trans_filter_dup( op, op->ors_filter, ov->remote ) : NULL; + fl = ov->local ? trans_filter_dup( op, op->ors_filter, ov->local ) : NULL; + cb.sc_response = (slap_response *) translucent_search_cb; + cb.sc_cleanup = (slap_response *) translucent_search_cleanup; + cb.sc_private = &tc; + + ov->db.be_acl = op->o_bd->be_acl; + tc.db = op->o_bd; + tc.on = on; + tc.orig = op->ors_filter; + tc.list = NULL; + tc.step = 0; + tc.slimit = op->ors_slimit; + tc.attrs = NULL; + fbv = op->ors_filterstr; + + if ( fr || !fl ) { + Operation op2; + Opheader oh; + + op2 = *op; + oh = *op->o_hdr; + oh.oh_conn = op->o_conn; + oh.oh_connid = op->o_connid; + op2.o_bd = &ov->db; + op2.o_hdr = &oh; + op2.o_extra = op->o_extra; + op2.o_callback = &cb; + + tc.attrs = op->ors_attrs; + op->ors_slimit = SLAP_NO_LIMIT; + op->ors_attrs = slap_anlist_all_attributes; + tc.step |= RMT_SIDE; + if ( fl ) { + tc.step |= USE_LIST; + op->ors_filter = fr; + filter2bv_x( op, fr, &op2.ors_filterstr ); + } + rc = ov->db.bd_info->bi_op_search( &op2, rs ); + if ( op->ors_attrs == slap_anlist_all_attributes ) + op->ors_attrs = tc.attrs; + if ( fl ) { + op->o_tmpfree( op2.ors_filterstr.bv_val, op2.o_tmpmemctx ); + } + } + + cb.sc_next = op->o_callback; + op->o_callback = &cb; + + if ( fl && !rc ) { + tc.step |= LCL_SIDE; + op->ors_filter = fl; + filter2bv_x( op, fl, &op->ors_filterstr ); + rc = overlay_op_walk( op, rs, op_search, on->on_info, on->on_next ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } + op->ors_filterstr = fbv; + op->o_callback = cb.sc_next; + rs->sr_attrs = op->ors_attrs; + rs->sr_attr_flags = slap_attr_flags( rs->sr_attrs ); + + /* Send out anything remaining on the list and finish */ + if ( tc.step & USE_LIST ) { + if ( tc.list ) { + TAvlnode *av; + + av = ldap_tavl_end( tc.list, TAVL_DIR_LEFT ); + while ( av ) { + rs->sr_entry = av->avl_data; + if ( rc == LDAP_SUCCESS && LDAP_COMPARE_TRUE == + test_filter( op, rs->sr_entry, op->ors_filter )) + { + rs->sr_flags = REP_ENTRY_MUSTBEFREED; + rc = send_search_entry( op, rs ); + } else { + entry_free( rs->sr_entry ); + } + av = ldap_tavl_next( av, TAVL_DIR_RIGHT ); + } + ldap_tavl_free( tc.list, NULL ); + rs->sr_flags = 0; + rs->sr_entry = NULL; + } + send_ldap_result( op, rs ); + } + + op->ors_slimit = tc.slimit; + + /* Free in reverse order */ + if ( fl ) + trans_filter_free( op, fl ); + if ( fr ) + trans_filter_free( op, fr ); + + return rc; +} + + +/* +** translucent_bind() +** pass bind request to captive backend; +** +*/ + +static int translucent_bind(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + translucent_info *ov = on->on_bi.bi_private; + BackendDB *db; + slap_callback sc = { 0 }, *save_cb; + int rc; + + Debug(LDAP_DEBUG_TRACE, "translucent_bind: <%s> method %d\n", + op->o_req_dn.bv_val, op->orb_method ); + + if(ov->defer_db_open) { + send_ldap_error(op, rs, LDAP_UNAVAILABLE, + "remote DB not available"); + return(rs->sr_err); + } + + if (ov->bind_local) { + sc.sc_response = slap_null_cb; + save_cb = op->o_callback; + op->o_callback = ≻ + } + + db = op->o_bd; + op->o_bd = &ov->db; + ov->db.be_acl = op->o_bd->be_acl; + rc = ov->db.bd_info->bi_op_bind(op, rs); + op->o_bd = db; + + if (ov->bind_local) { + op->o_callback = save_cb; + if (rc != LDAP_SUCCESS) { + rc = SLAP_CB_CONTINUE; + } + } + + return rc; +} + +/* +** translucent_connection_destroy() +** pass disconnect notification to captive backend; +** +*/ + +static int translucent_connection_destroy(BackendDB *be, Connection *conn) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "translucent_connection_destroy\n" ); + + rc = ov->db.bd_info->bi_connection_destroy(&ov->db, conn); + + return(rc); +} + +/* +** translucent_db_config() +** pass config directives to captive backend; +** parse unrecognized directives ourselves; +** +*/ + +static int translucent_db_config( + BackendDB *be, + const char *fname, + int lineno, + int argc, + char **argv +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_config: %s\n", + argc ? argv[0] : "" ); + + /* Something for the captive database? */ + if ( ov->db.bd_info && ov->db.bd_info->bi_db_config ) + return ov->db.bd_info->bi_db_config( &ov->db, fname, lineno, + argc, argv ); + return SLAP_CONF_UNKNOWN; +} + +/* +** translucent_db_init() +** initialize the captive backend; +** +*/ + +static int translucent_db_init(BackendDB *be, ConfigReply *cr) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_init\n" ); + + ov = ch_calloc(1, sizeof(translucent_info)); + on->on_bi.bi_private = ov; + ov->db = *be; + ov->db.be_private = NULL; + ov->defer_db_open = 1; + + if ( !backend_db_init( "ldap", &ov->db, -1, NULL )) { + Debug( LDAP_DEBUG_CONFIG, "translucent: unable to open captive back-ldap\n" ); + return 1; + } + SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NO_SCHEMA_CHECK; + SLAP_DBFLAGS(be) |= SLAP_DBFLAG_NOLASTMOD; + + return 0; +} + +/* +** translucent_db_open() +** if the captive backend has an open() method, call it; +** +*/ + +static int translucent_db_open(BackendDB *be, ConfigReply *cr) { + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_open\n" ); + + /* need to inherit something from the original database... */ + ov->db.be_def_limit = be->be_def_limit; + ov->db.be_limits = be->be_limits; + ov->db.be_acl = be->be_acl; + ov->db.be_dfltaccess = be->be_dfltaccess; + + if ( ov->defer_db_open ) + return 0; + + rc = backend_startup_one( &ov->db, cr ); + + if(rc) Debug(LDAP_DEBUG_TRACE, + "translucent: bi_db_open() returned error %d\n", rc ); + + return(rc); +} + +/* +** translucent_db_close() +** if the captive backend has a close() method, call it +** +*/ + +static int +translucent_db_close( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_close\n" ); + + if ( ov && ov->db.bd_info && ov->db.bd_info->bi_db_close ) { + rc = ov->db.bd_info->bi_db_close(&ov->db, NULL); + } + + return(rc); +} + +/* +** translucent_db_destroy() +** if the captive backend has a db_destroy() method, call it; +** free any config data +** +*/ + +static int +translucent_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + translucent_info *ov = on->on_bi.bi_private; + int rc = 0; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_db_destroy\n" ); + + if ( ov ) { + if ( ov->remote ) + anlist_free( ov->remote, 1, NULL ); + if ( ov->local ) + anlist_free( ov->local, 1, NULL ); + if ( ov->db.be_private != NULL ) { + backend_stopdown_one( &ov->db ); + } + + ldap_pvt_thread_mutex_destroy( &ov->db.be_pcsn_st.be_pcsn_mutex ); + ch_free(ov); + on->on_bi.bi_private = NULL; + } + + return(rc); +} + +/* +** translucent_initialize() +** initialize the slap_overinst with our entry points; +** +*/ + +int translucent_initialize() { + + int rc; + + /* olcDatabaseDummy is defined in slapd, and Windows + will not let us initialize a struct element with a data pointer + from another library, so we have to initialize this element + "by hand". */ + translucentocs[1].co_table = olcDatabaseDummy; + + Debug(LDAP_DEBUG_TRACE, "==> translucent_initialize\n" ); + + translucent.on_bi.bi_type = "translucent"; + translucent.on_bi.bi_db_init = translucent_db_init; + translucent.on_bi.bi_db_config = translucent_db_config; + translucent.on_bi.bi_db_open = translucent_db_open; + translucent.on_bi.bi_db_close = translucent_db_close; + translucent.on_bi.bi_db_destroy = translucent_db_destroy; + translucent.on_bi.bi_op_bind = translucent_bind; + translucent.on_bi.bi_op_add = translucent_add; + translucent.on_bi.bi_op_modify = translucent_modify; + translucent.on_bi.bi_op_modrdn = translucent_modrdn; + translucent.on_bi.bi_op_delete = translucent_delete; + translucent.on_bi.bi_op_search = translucent_search; + translucent.on_bi.bi_op_compare = translucent_compare; + translucent.on_bi.bi_connection_destroy = translucent_connection_destroy; + translucent.on_bi.bi_extended = translucent_exop; + + translucent.on_bi.bi_cf_ocs = translucentocs; + rc = config_register_schema ( translucentcfg, translucentocs ); + if ( rc ) return rc; + + return(overlay_register(&translucent)); +} + +#if SLAPD_OVER_TRANSLUCENT == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return translucent_initialize(); +} +#endif + +#endif /* SLAPD_OVER_TRANSLUCENT */ diff --git a/servers/slapd/overlays/unique.c b/servers/slapd/overlays/unique.c new file mode 100644 index 0000000..7a7c8fb --- /dev/null +++ b/servers/slapd/overlays/unique.c @@ -0,0 +1,1548 @@ +/* unique.c - attribute uniqueness module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2004-2022 The OpenLDAP Foundation. + * Portions Copyright 2004,2006-2007 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Symas Corporation for + * inclusion in OpenLDAP Software, with subsequent enhancements by + * Emily Backes at Symas Corporation. This work was sponsored by + * Hewlett-Packard. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_UNIQUE + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "slap-config.h" + +#define UNIQUE_DEFAULT_URI ("ldap:///??sub") + +static slap_overinst unique; + +typedef struct unique_attrs_s { + struct unique_attrs_s *next; /* list of attrs */ + AttributeDescription *attr; +} unique_attrs; + +typedef struct unique_domain_uri_s { + struct unique_domain_uri_s *next; + struct berval dn; + struct berval ndn; + struct berval filter; + Filter *f; + struct unique_attrs_s *attrs; + int scope; +} unique_domain_uri; + +typedef struct unique_domain_s { + struct unique_domain_s *next; + struct berval domain_spec; + struct unique_domain_uri_s *uri; + char ignore; /* polarity of attributes */ + char strict; /* null considered unique too */ + char serial; /* serialize execution */ +} unique_domain; + +typedef struct unique_data_s { + struct unique_domain_s *domains; + struct unique_domain_s *legacy; + char legacy_strict_set; + ldap_pvt_thread_mutex_t serial_mutex; +} unique_data; + +typedef struct unique_counter_s { + struct berval *ndn; + int count; +} unique_counter; + +enum { + UNIQUE_BASE = 1, + UNIQUE_IGNORE, + UNIQUE_ATTR, + UNIQUE_STRICT, + UNIQUE_URI, +}; + +static ConfigDriver unique_cf_base; +static ConfigDriver unique_cf_attrs; +static ConfigDriver unique_cf_strict; +static ConfigDriver unique_cf_uri; + +static ConfigTable uniquecfg[] = { + { "unique_base", "basedn", 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|UNIQUE_BASE, + unique_cf_base, "( OLcfgOvAt:10.1 NAME 'olcUniqueBase' " + "DESC 'Subtree for uniqueness searches' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { "unique_ignore", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_IGNORE, + unique_cf_attrs, "( OLcfgOvAt:10.2 NAME 'olcUniqueIgnore' " + "DESC 'Attributes for which uniqueness shall not be enforced' " + "EQUALITY caseIgnoreMatch " + "ORDERING caseIgnoreOrderingMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "unique_attributes", "attribute...", 2, 0, 0, ARG_MAGIC|UNIQUE_ATTR, + unique_cf_attrs, "( OLcfgOvAt:10.3 NAME 'olcUniqueAttribute' " + "DESC 'Attributes for which uniqueness shall be enforced' " + "EQUALITY caseIgnoreMatch " + "ORDERING caseIgnoreOrderingMatch " + "SUBSTR caseIgnoreSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "unique_strict", "on|off", 1, 2, 0, ARG_MAGIC|UNIQUE_STRICT, + unique_cf_strict, "( OLcfgOvAt:10.4 NAME 'olcUniqueStrict' " + "DESC 'Enforce uniqueness of null values' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL }, + { "unique_uri", "ldapuri", 2, 3, 0, ARG_MAGIC|UNIQUE_URI, + unique_cf_uri, "( OLcfgOvAt:10.5 NAME 'olcUniqueURI' " + "DESC 'List of keywords and LDAP URIs for a uniqueness domain' " + "EQUALITY caseExactMatch " + "ORDERING caseExactOrderingMatch " + "SUBSTR caseExactSubstringsMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs uniqueocs[] = { + { "( OLcfgOvOc:10.1 " + "NAME 'olcUniqueConfig' " + "DESC 'Attribute value uniqueness configuration' " + "SUP olcOverlayConfig " + "MAY ( olcUniqueBase $ olcUniqueIgnore $ " + "olcUniqueAttribute $ olcUniqueStrict $ " + "olcUniqueURI ) )", + Cft_Overlay, uniquecfg }, + { NULL, 0, NULL } +}; + +static void +unique_free_domain_uri ( unique_domain_uri *uri ) +{ + unique_domain_uri *next_uri = NULL; + unique_attrs *attr, *next_attr = NULL; + + while ( uri ) { + next_uri = uri->next; + ch_free ( uri->dn.bv_val ); + ch_free ( uri->ndn.bv_val ); + ch_free ( uri->filter.bv_val ); + filter_free( uri->f ); + attr = uri->attrs; + while ( attr ) { + next_attr = attr->next; + ch_free (attr); + attr = next_attr; + } + ch_free ( uri ); + uri = next_uri; + } +} + +/* free an entire stack of domains */ +static void +unique_free_domain ( unique_domain *domain ) +{ + unique_domain *next_domain = NULL; + + while ( domain ) { + next_domain = domain->next; + ch_free ( domain->domain_spec.bv_val ); + unique_free_domain_uri ( domain->uri ); + ch_free ( domain ); + domain = next_domain; + } +} + +static int +unique_new_domain_uri ( unique_domain_uri **urip, + const LDAPURLDesc *url_desc, + ConfigArgs *c ) +{ + int i, rc = LDAP_SUCCESS; + unique_domain_uri *uri; + struct berval bv = {0, NULL}; + BackendDB *be = (BackendDB *)c->be; + char ** attr_str; + AttributeDescription * ad; + const char * text; + + uri = ch_calloc ( 1, sizeof ( unique_domain_uri ) ); + + if ( url_desc->lud_host && url_desc->lud_host[0] ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "host <%s> not allowed in URI", + url_desc->lud_host ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( url_desc->lud_dn && url_desc->lud_dn[0] ) { + ber_str2bv( url_desc->lud_dn, 0, 0, &bv ); + rc = dnPrettyNormal( NULL, + &bv, + &uri->dn, + &uri->ndn, + NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid DN %d (%s)", + url_desc->lud_dn, rc, ldap_err2string( rc )); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( !dnIsSuffix ( &uri->ndn, &be->be_nsuffix[0] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "dn <%s> is not a suffix of backend base dn <%s>", + uri->dn.bv_val, + be->be_nsuffix[0].bv_val ); + rc = ARG_BAD_CONF; + goto exit; + } + + if ( BER_BVISNULL( &be->be_rootndn ) || BER_BVISEMPTY( &be->be_rootndn ) ) { + Debug( LDAP_DEBUG_ANY, + "slapo-unique needs a rootdn; " + "backend <%s> has none, YMMV.\n", + be->be_nsuffix[0].bv_val ); + } + } + + attr_str = url_desc->lud_attrs; + if ( attr_str ) { + for ( i=0; attr_str[i]; ++i ) { + unique_attrs * attr; + ad = NULL; + if ( slap_str2ad ( attr_str[i], &ad, &text ) + == LDAP_SUCCESS) { + attr = ch_calloc ( 1, + sizeof ( unique_attrs ) ); + attr->attr = ad; + attr->next = uri->attrs; + uri->attrs = attr; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: attribute: %s: %s", + attr_str[i], text ); + rc = ARG_BAD_CONF; + goto exit; + } + } + } + + uri->scope = url_desc->lud_scope; + if ( !uri->scope ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: uri with base scope will always be unique"); + rc = ARG_BAD_CONF; + goto exit; + } + + if (url_desc->lud_filter) { + char *ptr; + uri->f = str2filter( url_desc->lud_filter ); + if ( !uri->f ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: bad filter"); + rc = ARG_BAD_CONF; + goto exit; + } + /* make sure the strfilter is in normal form (ITS#5581) */ + filter2bv( uri->f, &uri->filter ); + ptr = strstr( uri->filter.bv_val, "(?=" /*)*/ ); + if ( ptr != NULL && ptr <= ( uri->filter.bv_val - STRLENOF( "(?=" /*)*/ ) + uri->filter.bv_len ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: bad filter"); + rc = ARG_BAD_CONF; + goto exit; + } + } +exit: + uri->next = *urip; + *urip = uri; + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + unique_free_domain_uri ( uri ); + *urip = NULL; + } + return rc; +} + +static int +unique_new_domain_uri_basic ( unique_domain_uri **urip, + ConfigArgs *c ) +{ + LDAPURLDesc *url_desc = NULL; + int rc; + + rc = ldap_url_parse ( UNIQUE_DEFAULT_URI, &url_desc ); + if ( rc ) return rc; + rc = unique_new_domain_uri ( urip, url_desc, c ); + ldap_free_urldesc ( url_desc ); + return rc; +} + +/* if *domain is non-null, it's pushed down the stack. + * note that the entire stack is freed if there is an error, + * so build added domains in a separate stack before adding them + * + * domain_specs look like + * + * [strict ][ignore ][serialize ]uri[[ uri]...] + * e.g. "ldap:///ou=foo,o=bar?uid?sub ldap:///ou=baz,o=bar?uid?sub" + * "strict ldap:///ou=accounts,o=bar?uid,uidNumber?one" + * etc + * + * so finally strictness is per-domain + * but so is ignore-state, and that would be better as a per-url thing + */ +static int +unique_new_domain ( unique_domain **domainp, + char *domain_spec, + ConfigArgs *c ) +{ + char *uri_start; + int rc = LDAP_SUCCESS; + int uri_err = 0; + unique_domain * domain; + LDAPURLDesc *url_desc, *url_descs = NULL; + + Debug(LDAP_DEBUG_TRACE, "==> unique_new_domain <%s>\n", + domain_spec ); + + domain = ch_calloc ( 1, sizeof (unique_domain) ); + ber_str2bv( domain_spec, 0, 1, &domain->domain_spec ); + + uri_start = domain_spec; + if ( strncasecmp ( uri_start, "ignore ", + STRLENOF( "ignore " ) ) == 0 ) { + domain->ignore = 1; + uri_start += STRLENOF( "ignore " ); + } + if ( strncasecmp ( uri_start, "serialize ", + STRLENOF( "serialize " ) ) == 0 ) { + domain->serial = 1; + uri_start += STRLENOF( "serialize " ); + } + if ( strncasecmp ( uri_start, "strict ", + STRLENOF( "strict " ) ) == 0 ) { + domain->strict = 1; + uri_start += STRLENOF( "strict " ); + if ( !domain->ignore + && strncasecmp ( uri_start, "ignore ", + STRLENOF( "ignore " ) ) == 0 ) { + domain->ignore = 1; + uri_start += STRLENOF( "ignore " ); + } + } + rc = ldap_url_parselist_ext ( &url_descs, uri_start, " ", 0 ); + if ( rc ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid ldap urilist", + uri_start ); + rc = ARG_BAD_CONF; + goto exit; + } + + for ( url_desc = url_descs; + url_desc; + url_desc = url_desc->lud_next ) { + rc = unique_new_domain_uri ( &domain->uri, + url_desc, + c ); + if ( rc ) { + rc = ARG_BAD_CONF; + uri_err = 1; + goto exit; + } + } + +exit: + if ( url_descs ) ldap_free_urldesc ( url_descs ); + domain->next = *domainp; + *domainp = domain; + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + unique_free_domain ( domain ); + *domainp = NULL; + } + return rc; +} + +static int +unique_cf_base( ConfigArgs *c ) +{ + BackendDB *be = (BackendDB *)c->be; + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + int rc = ARG_BAD_CONF; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + rc = 0; + if ( legacy && legacy->uri && legacy->uri->dn.bv_val ) { + rc = value_add_one ( &c->rvalue_vals, + &legacy->uri->dn ); + if ( rc ) return rc; + rc = value_add_one ( &c->rvalue_nvals, + &legacy->uri->ndn ); + if ( rc ) return rc; + } + break; + case LDAP_MOD_DELETE: + assert ( legacy && legacy->uri && legacy->uri->dn.bv_val ); + rc = 0; + ch_free ( legacy->uri->dn.bv_val ); + ch_free ( legacy->uri->ndn.bv_val ); + BER_BVZERO( &legacy->uri->dn ); + BER_BVZERO( &legacy->uri->ndn ); + if ( !legacy->uri->attrs ) { + unique_free_domain_uri ( legacy->uri ); + legacy->uri = NULL; + } + if ( !legacy->uri && !private->legacy_strict_set ) { + unique_free_domain ( legacy ); + private->legacy = legacy = NULL; + } + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( !dnIsSuffix ( &c->value_ndn, + &be->be_nsuffix[0] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "dn is not a suffix of backend base" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( !legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + if ( !legacy->uri ) + unique_new_domain_uri_basic ( &legacy->uri, c ); + ch_free ( legacy->uri->dn.bv_val ); + ch_free ( legacy->uri->ndn.bv_val ); + legacy->uri->dn = c->value_dn; + legacy->uri->ndn = c->value_ndn; + rc = 0; + break; + default: + abort(); + } + + if ( rc ) { + ch_free( c->value_dn.bv_val ); + BER_BVZERO( &c->value_dn ); + ch_free( c->value_ndn.bv_val ); + BER_BVZERO( &c->value_ndn ); + } + + return rc; +} + +static int +unique_cf_attrs( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_attrs *new_attrs = NULL; + unique_attrs *attr, *next_attr, *reverse_attrs; + unique_attrs **attrp; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + if ( legacy + && (c->type == UNIQUE_IGNORE) == legacy->ignore + && legacy->uri ) + for ( attr = legacy->uri->attrs; + attr; + attr = attr->next ) + value_add_one( &c->rvalue_vals, + &attr->attr->ad_cname ); + rc = 0; + break; + case LDAP_MOD_DELETE: + if ( legacy + && (c->type == UNIQUE_IGNORE) == legacy->ignore + && legacy->uri + && legacy->uri->attrs) { + if ( c->valx < 0 ) { /* delete all */ + for ( attr = legacy->uri->attrs; + attr; + attr = next_attr ) { + next_attr = attr->next; + ch_free ( attr ); + } + legacy->uri->attrs = NULL; + } else { /* delete by index */ + attrp = &legacy->uri->attrs; + for ( i=0; i < c->valx; ++i ) + attrp = &(*attrp)->next; + attr = *attrp; + *attrp = attr->next; + ch_free (attr); + } + if ( !legacy->uri->attrs + && !legacy->uri->dn.bv_val ) { + unique_free_domain_uri ( legacy->uri ); + legacy->uri = NULL; + } + if ( !legacy->uri && !private->legacy_strict_set ) { + unique_free_domain ( legacy ); + private->legacy = legacy = NULL; + } + } + rc = 0; + break; + case LDAP_MOD_ADD: + if ( c->argc > 2 ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "unique config: " + "Supplying multiple names in a single %s value is unsupported " + "and will be disallowed in a future version\n", + c->argv[0] ); + } + /* FALLTHRU */ + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( legacy + && legacy->uri + && legacy->uri->attrs + && (c->type == UNIQUE_IGNORE) != legacy->ignore ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set both attrs and ignore-attrs" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( !legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + if ( !legacy->uri ) + unique_new_domain_uri_basic ( &legacy->uri, c ); + rc = 0; + for ( i=1; c->argv[i]; ++i ) { + AttributeDescription * ad = NULL; + const char * text; + if ( slap_str2ad ( c->argv[i], &ad, &text ) + == LDAP_SUCCESS) { + + attr = ch_calloc ( 1, + sizeof ( unique_attrs ) ); + attr->attr = ad; + attr->next = new_attrs; + new_attrs = attr; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unique: attribute: %s: %s", + c->argv[i], text ); + for ( attr = new_attrs; + attr; + attr=next_attr ) { + next_attr = attr->next; + ch_free ( attr ); + } + rc = ARG_BAD_CONF; + break; + } + } + if ( rc ) break; + + /* (nconc legacy->uri->attrs (nreverse new_attrs)) */ + reverse_attrs = NULL; + for ( attr = new_attrs; + attr; + attr = next_attr ) { + next_attr = attr->next; + attr->next = reverse_attrs; + reverse_attrs = attr; + } + for ( attrp = &legacy->uri->attrs; + *attrp; + attrp = &(*attrp)->next ) ; + *attrp = reverse_attrs; + + legacy->ignore = ( c->type == UNIQUE_IGNORE ); + break; + default: + abort(); + } + + if ( rc ) { + Debug ( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + } + return rc; +} + +static int +unique_cf_strict( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + int rc = ARG_BAD_CONF; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + /* We process the boolean manually instead of using + * ARG_ON_OFF so that we can three-state it; + * olcUniqueStrict is either TRUE, FALSE, or missing, + * and missing is necessary to add olcUniqueURIs... + */ + if ( private->legacy_strict_set ) { + struct berval bv = legacy->strict ? slap_true_bv : slap_false_bv; + value_add_one ( &c->rvalue_vals, &bv ); + } + rc = 0; + break; + case LDAP_MOD_DELETE: + if ( legacy ) { + legacy->strict = 0; + if ( ! legacy->uri ) { + unique_free_domain ( legacy ); + private->legacy = NULL; + } + } + private->legacy_strict_set = 0; + rc = 0; + break; + case LDAP_MOD_ADD: + case SLAP_CONFIG_ADD: + if ( domains ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set legacy attrs when URIs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( ! legacy ) { + unique_new_domain ( &private->legacy, + UNIQUE_DEFAULT_URI, + c ); + legacy = private->legacy; + } + /* ... not using ARG_ON_OFF makes this necessary too */ + assert ( c->argc == 2 ); + legacy->strict = (strcasecmp ( c->argv[1], "TRUE" ) == 0); + private->legacy_strict_set = 1; + rc = 0; + break; + default: + abort(); + } + + return rc; +} + +static int +unique_cf_uri( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain = NULL, **domainp = NULL; + int rc = ARG_BAD_CONF; + int i; + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + for ( domain = domains; + domain; + domain = domain->next ) { + rc = value_add_one ( &c->rvalue_vals, + &domain->domain_spec ); + if ( rc ) break; + } + break; + case LDAP_MOD_DELETE: + if ( c->valx < 0 ) { /* delete them all! */ + unique_free_domain ( domains ); + private->domains = NULL; + } else { /* delete just one */ + domainp = &private->domains; + for ( i=0; i < c->valx && *domainp; ++i ) + domainp = &(*domainp)->next; + + /* If *domainp is null, we walked off the end + * of the list. This happens when back-config + * and the overlay are out-of-sync, like when + * rejecting changes before ITS#4752 gets + * fixed. + * + * This should never happen, but will appear + * if you backport this version of + * slapo-unique without the config-undo fixes + * + * test024 Will hit this case in such a + * situation. + */ + assert (*domainp != NULL); + + domain = *domainp; + *domainp = domain->next; + domain->next = NULL; + unique_free_domain ( domain ); + } + rc = 0; + break; + + case SLAP_CONFIG_ADD: /* fallthru */ + case LDAP_MOD_ADD: + if ( legacy ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot set Uri when legacy attrs are present" ); + Debug ( LDAP_DEBUG_CONFIG, "unique config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + rc = 0; + if ( c->line ) rc = unique_new_domain ( &domain, c->line, c ); + else rc = unique_new_domain ( &domain, c->argv[1], c ); + if ( rc ) break; + assert ( domain->next == NULL ); + for ( domainp = &private->domains; + *domainp; + domainp = &(*domainp)->next ) ; + *domainp = domain; + + break; + + default: + abort (); + } + + return rc; +} + +/* +** allocate new unique_data; +** initialize, copy basedn; +** store in on_bi.bi_private; +** +*/ + +static int +unique_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + unique_data *private; + + Debug(LDAP_DEBUG_TRACE, "==> unique_db_init\n" ); + + private = ch_calloc ( 1, sizeof ( unique_data ) ); + ldap_pvt_thread_mutex_init( &private->serial_mutex ); + on->on_bi.bi_private = private; + + return 0; +} + +static int +unique_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + unique_data *private = on->on_bi.bi_private; + + Debug(LDAP_DEBUG_TRACE, "==> unique_db_destroy\n" ); + + if ( private ) { + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + + unique_free_domain ( domains ); + unique_free_domain ( legacy ); + ldap_pvt_thread_mutex_destroy( &private->serial_mutex ); + ch_free ( private ); + on->on_bi.bi_private = NULL; + } + + return 0; +} + + +/* +** search callback +** if this is a REP_SEARCH, count++; +** +*/ + +static int count_attr_cb( + Operation *op, + SlapReply *rs +) +{ + unique_counter *uc; + + /* because you never know */ + if(!op || !rs) return(0); + + /* Only search entries are interesting */ + if(rs->sr_type != REP_SEARCH) return(0); + + uc = op->o_callback->sc_private; + + /* Ignore the current entry */ + if ( dn_match( uc->ndn, &rs->sr_entry->e_nname )) return(0); + + Debug(LDAP_DEBUG_TRACE, "==> count_attr_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN" ); + + uc->count++; + + return(0); +} + +/* count the length of one attribute ad + * (and all of its values b) + * in the proposed filter + */ +static int +count_filter_len( + unique_domain *domain, + unique_domain_uri *uri, + AttributeDescription *ad, + BerVarray b +) +{ + unique_attrs *attr; + int i; + int ks = 0; + + while ( !is_at_operational( ad->ad_type ) ) { + if ( uri->attrs ) { + for ( attr = uri->attrs; attr; attr = attr->next ) { + if ( ad == attr->attr ) { + break; + } + } + if ( ( domain->ignore && attr ) + || (!domain->ignore && !attr )) { + break; + } + } + if ( b && b[0].bv_val ) { + for (i = 0; b[i].bv_val; i++ ) { + /* note: make room for filter escaping... */ + ks += ( 3 * b[i].bv_len ) + ad->ad_cname.bv_len + STRLENOF( "(=)" ); + } + } else if ( domain->strict ) { + ks += ad->ad_cname.bv_len + STRLENOF( "(=*)" ); /* (attr=*) */ + } + break; + } + + return ks; +} + +static char * +build_filter( + unique_domain *domain, + unique_domain_uri *uri, + AttributeDescription *ad, + BerVarray b, + char *kp, + int ks, + void *ctx +) +{ + unique_attrs *attr; + int i; + + while ( !is_at_operational( ad->ad_type ) ) { + if ( uri->attrs ) { + for ( attr = uri->attrs; attr; attr = attr->next ) { + if ( ad == attr->attr ) { + break; + } + } + if ( ( domain->ignore && attr ) + || (!domain->ignore && !attr )) { + break; + } + } + if ( b && b[0].bv_val ) { + for ( i = 0; b[i].bv_val; i++ ) { + struct berval bv; + int len; + + ldap_bv2escaped_filter_value_x( &b[i], &bv, 1, ctx ); + if (!b[i].bv_len) + bv.bv_val = b[i].bv_val; + len = snprintf( kp, ks, "(%s=%s)", ad->ad_cname.bv_val, bv.bv_val ); + assert( len >= 0 && len < ks ); + kp += len; + if ( bv.bv_val != b[i].bv_val ) { + ber_memfree_x( bv.bv_val, ctx ); + } + } + } else if ( domain->strict ) { + int len; + len = snprintf( kp, ks, "(%s=*)", ad->ad_cname.bv_val ); + assert( len >= 0 && len < ks ); + kp += len; + } + break; + } + return kp; +} + +static int +unique_search( + Operation *op, + Operation *nop, + struct berval * dn, + int scope, + SlapReply *rs, + struct berval *key +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + SlapReply nrs = { REP_RESULT }; + slap_callback cb = { NULL, NULL, NULL, NULL }; /* XXX */ + unique_counter uq = { NULL, 0 }; + int rc; + char *errmsg; + int errmsgsize; + + Debug(LDAP_DEBUG_TRACE, "==> unique_search %s\n", key->bv_val ); + + nop->ors_filter = str2filter_x(nop, key->bv_val); + if(nop->ors_filter == NULL) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_OTHER, + "unique_search invalid filter"); + return(rs->sr_err); + } + + nop->ors_filterstr = *key; + + cb.sc_response = (slap_response*)count_attr_cb; + cb.sc_private = &uq; + nop->o_callback = &cb; + nop->o_tag = LDAP_REQ_SEARCH; + nop->ors_scope = scope; + nop->ors_deref = LDAP_DEREF_NEVER; + nop->ors_limit = NULL; + nop->ors_slimit = SLAP_NO_LIMIT; + nop->ors_tlimit = SLAP_NO_LIMIT; + nop->ors_attrs = slap_anlist_no_attrs; + nop->ors_attrsonly = 1; + + uq.ndn = &op->o_req_ndn; + + nop->o_req_ndn = *dn; + nop->o_ndn = op->o_bd->be_rootndn; + + nop->o_bd = on->on_info->oi_origdb; + rc = nop->o_bd->be_search(nop, &nrs); + filter_free_x(nop, nop->ors_filter, 1); + + if(rc != LDAP_SUCCESS && rc != LDAP_NO_SUCH_OBJECT) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, rc, "unique_search failed"); + rc = rs->sr_err; + } else if(uq.count) { + Debug(LDAP_DEBUG_TRACE, "=> unique_search found %d records\n", uq.count ); + + errmsgsize = sizeof("non-unique attributes found with ") + key->bv_len; + errmsg = op->o_tmpalloc(errmsgsize, op->o_tmpmemctx); + snprintf( errmsg, errmsgsize, "non-unique attributes found with %s", key->bv_val ); + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_CONSTRAINT_VIOLATION, errmsg); + op->o_tmpfree(errmsg, op->o_tmpmemctx); + rc = rs->sr_err; + } else { + Debug(LDAP_DEBUG_TRACE, "=> unique_search found no records\n" ); + rc = SLAP_CB_CONTINUE; + } + + op->o_tmpfree( key->bv_val, op->o_tmpmemctx ); + + return(rc); +} + +static int +unique_unlock( + Operation *op, + SlapReply *rs +) +{ + slap_callback *sc = op->o_callback; + unique_data *private = sc->sc_private; + + ldap_pvt_thread_mutex_unlock( &private->serial_mutex ); + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int +unique_add( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Attribute *a; + char *key, *kp; + struct berval bvkey; + int rc = SLAP_CB_CONTINUE; + int locked = 0; + + Debug(LDAP_DEBUG_TRACE, "==> unique_add <%s>\n", + op->o_req_dn.bv_val ); + + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) || ( + get_relax(op) > SLAP_CONTROL_IGNORED + && access_allowed( op, op->ora_e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) ) { + return rc; + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )) + continue; + + if ( uri->f ) { + if ( test_filter( NULL, op->ora_e, uri->f ) + == LDAP_COMPARE_FALSE ) + { + Debug( LDAP_DEBUG_TRACE, + "==> unique_add_skip<%s>\n", + op->o_req_dn.bv_val ); + continue; + } + } + + if(!(a = op->ora_e->e_attrs)) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "unique_add() got null op.ora_e.e_attrs"); + rc = rs->sr_err; + break; + + } else { + for(; a; a = a->a_next) { + ks += count_filter_len ( domain, + uri, + a->a_desc, + a->a_vals); + } + } + + /* skip this domain-uri if it isn't involved */ + if ( !ks ) continue; + + if ( domain->serial && !locked ) { + ldap_pvt_thread_mutex_lock( &private->serial_mutex ); + locked = 1; + } + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for(a = op->ora_e->e_attrs; a; a = a->a_next) + kp = build_filter(domain, + uri, + a->a_desc, + a->a_vals, + kp, + ks - ( kp - key ), + op->o_tmpmemctx); + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + if ( locked ) { + if ( rc != SLAP_CB_CONTINUE ) { + ldap_pvt_thread_mutex_unlock( &private->serial_mutex ); + } else { + slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = unique_unlock; + cb->sc_private = private; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + } + return rc; +} + + +static int +unique_modify( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Modifications *m; + Entry *e = NULL; + char *key, *kp; + struct berval bvkey; + int rc = SLAP_CB_CONTINUE; + int locked = 0; + + Debug(LDAP_DEBUG_TRACE, "==> unique_modify <%s>\n", + op->o_req_dn.bv_val ); + + if ( !op->orm_modlist ) { + Debug(LDAP_DEBUG_TRACE, "unique_modify: got empty modify op\n" ); + return rc; + } + + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) { + return rc; + } + if ( get_relax(op) > SLAP_CONTROL_IGNORED + && overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) == LDAP_SUCCESS + && e + && access_allowed( op, e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) { + overlay_entry_release_ov( op, e, 0, on ); + return rc; + } + if ( e ) { + overlay_entry_release_ov( op, e, 0, on ); + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn )) + continue; + + for ( m = op->orm_modlist; m; m = m->sml_next) + if ( (m->sml_op & LDAP_MOD_OP) + != LDAP_MOD_DELETE ) + ks += count_filter_len + ( domain, + uri, + m->sml_desc, + m->sml_values); + + /* skip this domain-uri if it isn't involved */ + if ( !ks ) continue; + + if ( domain->serial && !locked ) { + ldap_pvt_thread_mutex_lock( &private->serial_mutex ); + locked = 1; + } + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for(m = op->orm_modlist; m; m = m->sml_next) + if ( (m->sml_op & LDAP_MOD_OP) + != LDAP_MOD_DELETE ) + kp = build_filter ( domain, + uri, + m->sml_desc, + m->sml_values, + kp, + ks - (kp - key), + op->o_tmpmemctx ); + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + if ( locked ) { + if ( rc != SLAP_CB_CONTINUE ) { + ldap_pvt_thread_mutex_unlock( &private->serial_mutex ); + } else { + slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = unique_unlock; + cb->sc_private = private; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + } + return rc; +} + + +static int +unique_modrdn( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + unique_data *private = (unique_data *) on->on_bi.bi_private; + unique_domain *domains = private->domains; + unique_domain *legacy = private->legacy; + unique_domain *domain; + Operation nop = *op; + Entry *e = NULL; + char *key, *kp; + struct berval bvkey; + LDAPRDN newrdn; + struct berval bv[2]; + int rc = SLAP_CB_CONTINUE; + int locked = 0; + + Debug(LDAP_DEBUG_TRACE, "==> unique_modrdn <%s> <%s>\n", + op->o_req_dn.bv_val, op->orr_newrdn.bv_val ); + + if ( SLAPD_SYNC_IS_SYNCCONN( op->o_connid ) ) { + return rc; + } + if ( get_relax(op) > SLAP_CONTROL_IGNORED + && overlay_entry_get_ov( op, &op->o_req_ndn, NULL, NULL, 0, &e, on ) == LDAP_SUCCESS + && e + && access_allowed( op, e, + slap_schema.si_ad_entry, NULL, + ACL_MANAGE, NULL ) ) { + overlay_entry_release_ov( op, e, 0, on ); + return rc; + } + if ( e ) { + overlay_entry_release_ov( op, e, 0, on ); + } + + for ( domain = legacy ? legacy : domains; + domain; + domain = domain->next ) + { + unique_domain_uri *uri; + + for ( uri = domain->uri; + uri; + uri = uri->next ) + { + int i, len; + int ks = 0; + + if ( uri->ndn.bv_val + && !dnIsSuffix( &op->o_req_ndn, &uri->ndn ) + && (!op->orr_nnewSup + || !dnIsSuffix( op->orr_nnewSup, &uri->ndn ))) + continue; + + if ( ldap_bv2rdn_x ( &op->oq_modrdn.rs_newrdn, + &newrdn, + (char **)&rs->sr_text, + LDAP_DN_FORMAT_LDAP, + op->o_tmpmemctx ) ) { + op->o_bd->bd_info = (BackendInfo *) on->on_info; + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "unknown type(s) used in RDN"); + rc = rs->sr_err; + break; + } + + rc = SLAP_CB_CONTINUE; + for ( i=0; newrdn[i]; i++) { + AttributeDescription *ad = NULL; + if ( slap_bv2ad( &newrdn[i]->la_attr, &ad, &rs->sr_text )) { + ldap_rdnfree_x( newrdn, op->o_tmpmemctx ); + rs->sr_err = LDAP_INVALID_SYNTAX; + send_ldap_result( op, rs ); + rc = rs->sr_err; + break; + } + newrdn[i]->la_private = ad; + } + if ( rc != SLAP_CB_CONTINUE ) break; + + bv[1].bv_val = NULL; + bv[1].bv_len = 0; + + for ( i=0; newrdn[i]; i++ ) { + bv[0] = newrdn[i]->la_value; + ks += count_filter_len ( domain, + uri, + newrdn[i]->la_private, + bv); + } + + /* skip this domain if it isn't involved */ + if ( !ks ) continue; + + if ( domain->serial && !locked ) { + ldap_pvt_thread_mutex_lock( &private->serial_mutex ); + locked = 1; + } + + /* terminating NUL */ + ks += sizeof("(|)"); + + if ( uri->filter.bv_val && uri->filter.bv_len ) + ks += uri->filter.bv_len + STRLENOF ("(&)"); + kp = key = op->o_tmpalloc(ks, op->o_tmpmemctx); + + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf(kp, ks, "(&%s", uri->filter.bv_val); + assert( len >= 0 && len < ks ); + kp += len; + } + len = snprintf(kp, ks - (kp - key), "(|"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + + for ( i=0; newrdn[i]; i++) { + bv[0] = newrdn[i]->la_value; + kp = build_filter ( domain, + uri, + newrdn[i]->la_private, + bv, + kp, + ks - (kp - key ), + op->o_tmpmemctx); + } + + len = snprintf(kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + if ( uri->filter.bv_val && uri->filter.bv_len ) { + len = snprintf (kp, ks - (kp - key), ")"); + assert( len >= 0 && len < ks - (kp - key) ); + kp += len; + } + bvkey.bv_val = key; + bvkey.bv_len = kp - key; + + rc = unique_search ( op, + &nop, + uri->ndn.bv_val ? + &uri->ndn : + &op->o_bd->be_nsuffix[0], + uri->scope, + rs, + &bvkey); + + if ( rc != SLAP_CB_CONTINUE ) break; + } + if ( rc != SLAP_CB_CONTINUE ) break; + } + + if ( locked ) { + if ( rc != SLAP_CB_CONTINUE ) { + ldap_pvt_thread_mutex_unlock( &private->serial_mutex ); + } else { + slap_callback *cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + cb->sc_cleanup = unique_unlock; + cb->sc_private = private; + cb->sc_next = op->o_callback; + op->o_callback = cb; + } + } + return rc; +} + +/* +** init_module is last so the symbols resolve "for free" -- +** it expects to be called automagically during dynamic module initialization +*/ + +int +unique_initialize() +{ + int rc; + + /* statically declared just after the #includes at top */ + memset (&unique, 0, sizeof(unique)); + + unique.on_bi.bi_type = "unique"; + unique.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + unique.on_bi.bi_db_init = unique_db_init; + unique.on_bi.bi_db_destroy = unique_db_destroy; + unique.on_bi.bi_op_add = unique_add; + unique.on_bi.bi_op_modify = unique_modify; + unique.on_bi.bi_op_modrdn = unique_modrdn; + + unique.on_bi.bi_cf_ocs = uniqueocs; + rc = config_register_schema( uniquecfg, uniqueocs ); + if ( rc ) return rc; + + return(overlay_register(&unique)); +} + +#if SLAPD_OVER_UNIQUE == SLAPD_MOD_DYNAMIC && defined(PIC) +int init_module(int argc, char *argv[]) { + return unique_initialize(); +} +#endif + +#endif /* SLAPD_OVER_UNIQUE */ diff --git a/servers/slapd/overlays/valsort.c b/servers/slapd/overlays/valsort.c new file mode 100644 index 0000000..3d998e2 --- /dev/null +++ b/servers/slapd/overlays/valsort.c @@ -0,0 +1,585 @@ +/* valsort.c - sort attribute values */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2005-2022 The OpenLDAP Foundation. + * Portions copyright 2005 Symas Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. This work was sponsored by Stanford University. + */ + +/* + * This overlay sorts the values of multi-valued attributes when returning + * them in a search response. + */ +#include "portable.h" + +#ifdef SLAPD_OVER_VALSORT + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/ctype.h> + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" + +#define VALSORT_ASCEND 0 +#define VALSORT_DESCEND 1 + +#define VALSORT_ALPHA 2 +#define VALSORT_NUMERIC 4 + +#define VALSORT_WEIGHTED 8 + +typedef struct valsort_info { + struct valsort_info *vi_next; + struct berval vi_dn; + AttributeDescription *vi_ad; + slap_mask_t vi_sort; +} valsort_info; + +static int valsort_cid; + +static ConfigDriver valsort_cf_func; + +static ConfigTable valsort_cfats[] = { + { "valsort-attr", "attribute> <dn> <sort-type", 4, 5, 0, ARG_MAGIC, + valsort_cf_func, "( OLcfgOvAt:5.1 NAME 'olcValSortAttr' " + "DESC 'Sorting rule for attribute under given DN' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL } +}; + +static ConfigOCs valsort_cfocs[] = { + { "( OLcfgOvOc:5.1 " + "NAME 'olcValSortConfig' " + "DESC 'Value Sorting configuration' " + "SUP olcOverlayConfig " + "MUST olcValSortAttr )", + Cft_Overlay, valsort_cfats }, + { NULL } +}; + +static slap_verbmasks sorts[] = { + { BER_BVC("alpha-ascend"), VALSORT_ASCEND|VALSORT_ALPHA }, + { BER_BVC("alpha-descend"), VALSORT_DESCEND|VALSORT_ALPHA }, + { BER_BVC("numeric-ascend"), VALSORT_ASCEND|VALSORT_NUMERIC }, + { BER_BVC("numeric-descend"), VALSORT_DESCEND|VALSORT_NUMERIC }, + { BER_BVC("weighted"), VALSORT_WEIGHTED }, + { BER_BVNULL, 0 } +}; + +static Syntax *syn_numericString; + +static int +valsort_cf_func(ConfigArgs *c) { + slap_overinst *on = (slap_overinst *)c->bi; + valsort_info vitmp, *vi, **vip; + const char *text = NULL; + int i, is_numeric; + struct berval bv = BER_BVNULL; + + if ( c->op == SLAP_CONFIG_EMIT ) { + for ( vi = on->on_bi.bi_private; vi; vi = vi->vi_next ) { + struct berval bv2 = BER_BVNULL, bvret; + char *ptr; + int len; + + len = vi->vi_ad->ad_cname.bv_len + 1 + vi->vi_dn.bv_len + 2; + i = vi->vi_sort; + if ( i & VALSORT_WEIGHTED ) { + enum_to_verb( sorts, VALSORT_WEIGHTED, &bv2 ); + len += bv2.bv_len + 1; + i ^= VALSORT_WEIGHTED; + } + if ( i ) { + enum_to_verb( sorts, i, &bv ); + len += bv.bv_len + 1; + } + bvret.bv_val = ch_malloc( len+1 ); + bvret.bv_len = len; + + ptr = lutil_strcopy( bvret.bv_val, vi->vi_ad->ad_cname.bv_val ); + *ptr++ = ' '; + *ptr++ = '"'; + ptr = lutil_strcopy( ptr, vi->vi_dn.bv_val ); + *ptr++ = '"'; + if ( vi->vi_sort & VALSORT_WEIGHTED ) { + *ptr++ = ' '; + ptr = lutil_strcopy( ptr, bv2.bv_val ); + } + if ( i ) { + *ptr++ = ' '; + strcpy( ptr, bv.bv_val ); + } + ber_bvarray_add( &c->rvalue_vals, &bvret ); + } + i = ( c->rvalue_vals != NULL ) ? 0 : 1; + return i; + } else if ( c->op == LDAP_MOD_DELETE ) { + if ( c->valx < 0 ) { + for ( vi = on->on_bi.bi_private; vi; vi = on->on_bi.bi_private ) { + on->on_bi.bi_private = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + } else { + valsort_info **prev; + + for (i=0, prev = (valsort_info **)&on->on_bi.bi_private, + vi = *prev; vi && i<c->valx; + prev = &vi->vi_next, vi = vi->vi_next, i++ ); + (*prev)->vi_next = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + return 0; + } + vitmp.vi_ad = NULL; + i = slap_str2ad( c->argv[1], &vitmp.vi_ad, &text ); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), "<%s> %s", c->argv[0], text ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + if ( is_at_single_value( vitmp.vi_ad->ad_type )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> %s is single-valued, ignoring", c->argv[0], + vitmp.vi_ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(0); + } + is_numeric = ( vitmp.vi_ad->ad_type->sat_syntax == syn_numericString || + vitmp.vi_ad->ad_type->sat_syntax == slap_schema.si_syn_integer ) ? 1 + : 0; + ber_str2bv( c->argv[2], 0, 0, &bv ); + i = dnNormalize( 0, NULL, NULL, &bv, &vitmp.vi_dn, NULL ); + if ( i ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unable to normalize DN", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[2] ); + return(1); + } + i = verb_to_mask( c->argv[3], sorts ); + if ( BER_BVISNULL( &sorts[i].word )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[3] ); + return(1); + } + vitmp.vi_sort = sorts[i].mask; + if ( sorts[i].mask == VALSORT_WEIGHTED && c->argc == 5 ) { + i = verb_to_mask( c->argv[4], sorts ); + if ( BER_BVISNULL( &sorts[i].word )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> unrecognized sort type", c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[4] ); + return(1); + } + vitmp.vi_sort |= sorts[i].mask; + } + if (( vitmp.vi_sort & VALSORT_NUMERIC ) && !is_numeric ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "<%s> numeric sort specified for non-numeric syntax", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s (%s)!\n", + c->log, c->cr_msg, c->argv[1] ); + return(1); + } + + for ( vip = &on->on_bi.bi_private; *vip; vip = &(*vip)->vi_next ) + /* Get to the end */ ; + + vi = ch_malloc( sizeof(valsort_info) ); + *vi = vitmp; + vi->vi_next = *vip; + *vip = vi; + return 0; +} + +/* Use Insertion Sort algorithm on selected values */ +static void +do_sort( Operation *op, Attribute *a, int beg, int num, slap_mask_t sort ) +{ + int i, j, gotnvals; + struct berval tmp, ntmp, *vals = NULL, *nvals; + + gotnvals = (a->a_vals != a->a_nvals ); + + nvals = a->a_nvals + beg; + if ( gotnvals ) + vals = a->a_vals + beg; + + if ( sort & VALSORT_NUMERIC ) { + long *numbers = op->o_tmpalloc( num * sizeof(long), op->o_tmpmemctx ), + idx; + for (i=0; i<num; i++) + numbers[i] = strtol( nvals[i].bv_val, NULL, 0 ); + + for (i=1; i<num; i++) { + idx = numbers[i]; + ntmp = nvals[i]; + if ( gotnvals ) tmp = vals[i]; + j = i; + while ( j>0 ) { + int cmp = (sort & VALSORT_DESCEND) ? numbers[j-1] < idx : + numbers[j-1] > idx; + if ( !cmp ) break; + numbers[j] = numbers[j-1]; + nvals[j] = nvals[j-1]; + if ( gotnvals ) vals[j] = vals[j-1]; + j--; + } + numbers[j] = idx; + nvals[j] = ntmp; + if ( gotnvals ) vals[j] = tmp; + } + op->o_tmpfree( numbers, op->o_tmpmemctx ); + } else { + for (i=1; i<num; i++) { + ntmp = nvals[i]; + if ( gotnvals ) tmp = vals[i]; + j = i; + while ( j>0 ) { + int cmp = strcmp( nvals[j-1].bv_val, ntmp.bv_val ); + cmp = (sort & VALSORT_DESCEND) ? (cmp < 0) : (cmp > 0); + if ( !cmp ) break; + + nvals[j] = nvals[j-1]; + if ( gotnvals ) vals[j] = vals[j-1]; + j--; + } + nvals[j] = ntmp; + if ( gotnvals ) vals[j] = tmp; + } + } +} + +static int +valsort_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on; + valsort_info *vi; + Attribute *a; + + /* If this is not a search response, or it is a syncrepl response, + * or the valsort control wants raw results, pass thru unmodified. + */ + if ( rs->sr_type != REP_SEARCH || + ( _SCM(op->o_sync) > SLAP_CONTROL_IGNORED ) || + ( op->o_ctrlflag[valsort_cid] & SLAP_CONTROL_DATA0)) + return SLAP_CB_CONTINUE; + + on = (slap_overinst *) op->o_bd->bd_info; + vi = on->on_bi.bi_private; + + /* And we must have something configured */ + if ( !vi ) return SLAP_CB_CONTINUE; + + /* Find a rule whose baseDN matches this entry */ + for (; vi; vi = vi->vi_next ) { + int i, n; + + if ( !dnIsSuffix( &rs->sr_entry->e_nname, &vi->vi_dn )) + continue; + + /* Find attr that this rule affects */ + a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad ); + if ( !a ) continue; + + if ( rs_entry2modifiable( op, rs, on )) { + a = attr_find( rs->sr_entry->e_attrs, vi->vi_ad ); + } + + n = a->a_numvals; + if ( vi->vi_sort & VALSORT_WEIGHTED ) { + int j, gotnvals; + long *index = op->o_tmpalloc( n * sizeof(long), op->o_tmpmemctx ); + + gotnvals = (a->a_vals != a->a_nvals ); + + for (i=0; i<n; i++) { + char *ptr = ber_bvchr( &a->a_nvals[i], '{' ); + char *end = NULL; + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weights missing from attr %s " + "in entry %s\n", vi->vi_ad->ad_cname.bv_val, + rs->sr_entry->e_name.bv_val ); + break; + } + index[i] = strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weights misformatted " + "in entry %s\n", + rs->sr_entry->e_name.bv_val ); + break; + } + /* Strip out weights */ + ptr = a->a_nvals[i].bv_val; + end++; + for (;*end;) + *ptr++ = *end++; + *ptr = '\0'; + a->a_nvals[i].bv_len = ptr - a->a_nvals[i].bv_val; + + if ( a->a_vals != a->a_nvals ) { + ptr = a->a_vals[i].bv_val; + end = ber_bvchr( &a->a_vals[i], '}' ); + assert( end != NULL ); + end++; + for (;*end;) + *ptr++ = *end++; + *ptr = '\0'; + a->a_vals[i].bv_len = ptr - a->a_vals[i].bv_val; + } + } + /* An attr was missing weights here, ignore it */ + if ( i<n ) { + op->o_tmpfree( index, op->o_tmpmemctx ); + continue; + } + /* Insertion sort */ + for ( i=1; i<n; i++) { + long idx = index[i]; + struct berval tmp = a->a_vals[i], ntmp; + if ( gotnvals ) ntmp = a->a_nvals[i]; + j = i; + while (( j>0 ) && (index[j-1] > idx )) { + index[j] = index[j-1]; + a->a_vals[j] = a->a_vals[j-1]; + if ( gotnvals ) a->a_nvals[j] = a->a_nvals[j-1]; + j--; + } + index[j] = idx; + a->a_vals[j] = tmp; + if ( gotnvals ) a->a_nvals[j] = ntmp; + } + /* Check for secondary sort */ + if ( vi->vi_sort ^ VALSORT_WEIGHTED ) { + for ( i=0; i<n;) { + for (j=i+1; j<n; j++) { + if (index[i] != index[j]) + break; + } + if( j-i > 1 ) + do_sort( op, a, i, j-i, vi->vi_sort ); + i = j; + } + } + op->o_tmpfree( index, op->o_tmpmemctx ); + } else { + do_sort( op, a, 0, n, vi->vi_sort ); + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + valsort_info *vi = on->on_bi.bi_private; + + Attribute *a; + int i; + char *ptr, *end; + + /* See if any weighted sorting applies to this entry */ + for ( ;vi;vi=vi->vi_next ) { + if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn )) + continue; + if ( !(vi->vi_sort & VALSORT_WEIGHTED )) + continue; + a = attr_find( op->ora_e->e_attrs, vi->vi_ad ); + if ( !a ) + continue; + for (i=0; !BER_BVISNULL( &a->a_vals[i] ); i++) { + ptr = ber_bvchr(&a->a_vals[i], '{' ); + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n", + vi->vi_ad->ad_cname.bv_val ); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight missing from attribute" ); + return rs->sr_err; + } + strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n", + vi->vi_ad->ad_cname.bv_val ); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight is misformatted" ); + return rs->sr_err; + } + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + valsort_info *vi = on->on_bi.bi_private; + + Modifications *ml; + int i; + char *ptr, *end; + + /* See if any weighted sorting applies to this entry */ + for ( ;vi;vi=vi->vi_next ) { + if ( !dnIsSuffix( &op->o_req_ndn, &vi->vi_dn )) + continue; + if ( !(vi->vi_sort & VALSORT_WEIGHTED )) + continue; + for (ml = op->orm_modlist; ml; ml=ml->sml_next ) { + /* Must be a Delete Attr op, so no values to consider */ + if ( !ml->sml_values ) + continue; + if ( ml->sml_desc == vi->vi_ad ) + break; + } + if ( !ml ) + continue; + for (i=0; !BER_BVISNULL( &ml->sml_values[i] ); i++) { + ptr = ber_bvchr(&ml->sml_values[i], '{' ); + if ( !ptr ) { + Debug(LDAP_DEBUG_TRACE, "weight missing from attribute %s\n", + vi->vi_ad->ad_cname.bv_val ); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight missing from attribute" ); + return rs->sr_err; + } + strtol( ptr+1, &end, 0 ); + if ( *end != '}' ) { + Debug(LDAP_DEBUG_TRACE, "weight is misformatted in %s\n", + vi->vi_ad->ad_cname.bv_val ); + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, + "weight is misformatted" ); + return rs->sr_err; + } + } + } + return SLAP_CB_CONTINUE; +} + +static int +valsort_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + return overlay_register_control( be, LDAP_CONTROL_VALSORT ); +} + +static int +valsort_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + valsort_info *vi = on->on_bi.bi_private, *next; + +#ifdef SLAP_CONFIG_DELETE + overlay_unregister_control( be, LDAP_CONTROL_VALSORT ); +#endif /* SLAP_CONFIG_DELETE */ + + for (; vi; vi = next) { + next = vi->vi_next; + ch_free( vi->vi_dn.bv_val ); + ch_free( vi ); + } + + return 0; +} + +static int +valsort_parseCtrl( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_int_t flag = 0; + + if ( BER_BVISNULL( &ctrl->ldctl_value )) { + rs->sr_text = "valSort control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value )) { + rs->sr_text = "valSort control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + if (( tag = ber_scanf( ber, "{b}", &flag )) == LBER_ERROR ) { + rs->sr_text = "valSort control: flag decoding error"; + return LDAP_PROTOCOL_ERROR; + } + + op->o_ctrlflag[valsort_cid] = ctrl->ldctl_iscritical ? + SLAP_CONTROL_CRITICAL : SLAP_CONTROL_NONCRITICAL; + if ( flag ) + op->o_ctrlflag[valsort_cid] |= SLAP_CONTROL_DATA0; + + return LDAP_SUCCESS; +} + +static slap_overinst valsort; + +int valsort_initialize( void ) +{ + int rc; + + valsort.on_bi.bi_type = "valsort"; + valsort.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + valsort.on_bi.bi_db_destroy = valsort_destroy; + valsort.on_bi.bi_db_open = valsort_db_open; + + valsort.on_bi.bi_op_add = valsort_add; + valsort.on_bi.bi_op_modify = valsort_modify; + + valsort.on_response = valsort_response; + + valsort.on_bi.bi_cf_ocs = valsort_cfocs; + + rc = register_supported_control( LDAP_CONTROL_VALSORT, + SLAP_CTRL_SEARCH | SLAP_CTRL_HIDE, NULL, valsort_parseCtrl, + &valsort_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "Failed to register control %d\n", rc ); + return rc; + } + + syn_numericString = syn_find( "1.3.6.1.4.1.1466.115.121.1.36" ); + + rc = config_register_schema( valsort_cfats, valsort_cfocs ); + if ( rc ) return rc; + + return overlay_register(&valsort); +} + +#if SLAPD_OVER_VALSORT == SLAPD_MOD_DYNAMIC +int init_module( int argc, char *argv[]) { + return valsort_initialize(); +} +#endif + +#endif /* SLAPD_OVER_VALSORT */ |