From b527294153be3b79563c82c66102adc0004736c0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:54:12 +0200 Subject: Adding upstream version 2.6.7+dfsg. Signed-off-by: Daniel Baumann --- servers/slapd/overlays/Makefile.in | 172 + servers/slapd/overlays/README | 5 + servers/slapd/overlays/accesslog.c | 2874 +++++++++++++++++ servers/slapd/overlays/auditlog.c | 242 ++ servers/slapd/overlays/autoca.c | 1121 +++++++ servers/slapd/overlays/collect.c | 440 +++ servers/slapd/overlays/constraint.c | 1236 ++++++++ servers/slapd/overlays/dds.c | 2056 ++++++++++++ servers/slapd/overlays/deref.c | 586 ++++ servers/slapd/overlays/dyngroup.c | 234 ++ servers/slapd/overlays/dynlist.c | 2968 +++++++++++++++++ servers/slapd/overlays/homedir.c | 2074 ++++++++++++ servers/slapd/overlays/memberof.c | 2185 +++++++++++++ servers/slapd/overlays/otp.c | 1004 ++++++ servers/slapd/overlays/overlays.c | 44 + servers/slapd/overlays/pcache.c | 5815 ++++++++++++++++++++++++++++++++++ servers/slapd/overlays/ppolicy.c | 3490 ++++++++++++++++++++ servers/slapd/overlays/refint.c | 1086 +++++++ servers/slapd/overlays/remoteauth.c | 1002 ++++++ servers/slapd/overlays/retcode.c | 1577 +++++++++ servers/slapd/overlays/rwm.c | 2768 ++++++++++++++++ servers/slapd/overlays/rwm.h | 183 ++ servers/slapd/overlays/rwmconf.c | 413 +++ servers/slapd/overlays/rwmdn.c | 215 ++ servers/slapd/overlays/rwmmap.c | 1347 ++++++++ servers/slapd/overlays/seqmod.c | 207 ++ servers/slapd/overlays/slapover.txt | 158 + servers/slapd/overlays/sssvlv.c | 1439 +++++++++ servers/slapd/overlays/syncprov.c | 4412 ++++++++++++++++++++++++++ servers/slapd/overlays/translucent.c | 1497 +++++++++ servers/slapd/overlays/unique.c | 1549 +++++++++ servers/slapd/overlays/valsort.c | 585 ++++ 32 files changed, 44984 insertions(+) create mode 100644 servers/slapd/overlays/Makefile.in create mode 100644 servers/slapd/overlays/README create mode 100644 servers/slapd/overlays/accesslog.c create mode 100644 servers/slapd/overlays/auditlog.c create mode 100644 servers/slapd/overlays/autoca.c create mode 100644 servers/slapd/overlays/collect.c create mode 100644 servers/slapd/overlays/constraint.c create mode 100644 servers/slapd/overlays/dds.c create mode 100644 servers/slapd/overlays/deref.c create mode 100644 servers/slapd/overlays/dyngroup.c create mode 100644 servers/slapd/overlays/dynlist.c create mode 100644 servers/slapd/overlays/homedir.c create mode 100644 servers/slapd/overlays/memberof.c create mode 100644 servers/slapd/overlays/otp.c create mode 100644 servers/slapd/overlays/overlays.c create mode 100644 servers/slapd/overlays/pcache.c create mode 100644 servers/slapd/overlays/ppolicy.c create mode 100644 servers/slapd/overlays/refint.c create mode 100644 servers/slapd/overlays/remoteauth.c create mode 100644 servers/slapd/overlays/retcode.c create mode 100644 servers/slapd/overlays/rwm.c create mode 100644 servers/slapd/overlays/rwm.h create mode 100644 servers/slapd/overlays/rwmconf.c create mode 100644 servers/slapd/overlays/rwmdn.c create mode 100644 servers/slapd/overlays/rwmmap.c create mode 100644 servers/slapd/overlays/seqmod.c create mode 100644 servers/slapd/overlays/slapover.txt create mode 100644 servers/slapd/overlays/sssvlv.c create mode 100644 servers/slapd/overlays/syncprov.c create mode 100644 servers/slapd/overlays/translucent.c create mode 100644 servers/slapd/overlays/unique.c create mode 100644 servers/slapd/overlays/valsort.c (limited to 'servers/slapd/overlays') 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 . +## +## 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 +## . + +SRCS = overlays.c \ + accesslog.c \ + auditlog.c \ + autoca.c \ + constraint.c \ + dds.c \ + deref.c \ + dyngroup.c \ + dynlist.c \ + homedir.c \ + memberof.c \ + otp.c \ + pcache.c \ + collect.c \ + ppolicy.c \ + refint.c \ + remoteauth.c \ + retcode.c \ + rwm.c rwmconf.c rwmdn.c rwmmap.c \ + seqmod.c \ + sssvlv.c \ + syncprov.c \ + translucent.c \ + unique.c \ + valsort.c +OBJS = statover.o \ + @SLAPD_STATIC_OVERLAYS@ \ + overlays.o + +# Add here the objs that are needed by overlays, but do not make it +# into SLAPD_STATIC_OVERLAYS... +OBJDEP=rwm.o rwmconf.o rwmdn.o rwmmap.o + +LTONLY_MOD = $(LTONLY_mod) +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +MOD_DEFS = -DSLAPD_IMPORT + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBRARY = ../liboverlays.a +PROGRAMS = @SLAPD_DYNAMIC_OVERLAYS@ + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +static: $(LIBRARY) + +dynamic: $(PROGRAMS) + +accesslog.la : accesslog.lo + $(LTLINK_MOD) -module -o $@ accesslog.lo version.lo $(LINK_LIBS) + +auditlog.la : auditlog.lo + $(LTLINK_MOD) -module -o $@ auditlog.lo version.lo $(LINK_LIBS) + +autoca.la : autoca.lo + $(LTLINK_MOD) -module -o $@ autoca.lo version.lo $(LINK_LIBS) + +constraint.la : constraint.lo + $(LTLINK_MOD) -module -o $@ constraint.lo version.lo $(LINK_LIBS) + +dds.la : dds.lo + $(LTLINK_MOD) -module -o $@ dds.lo version.lo $(LINK_LIBS) + +deref.la : deref.lo + $(LTLINK_MOD) -module -o $@ deref.lo version.lo $(LINK_LIBS) + +dyngroup.la : dyngroup.lo + $(LTLINK_MOD) -module -o $@ dyngroup.lo version.lo $(LINK_LIBS) + +dynlist.la : dynlist.lo + $(LTLINK_MOD) -module -o $@ dynlist.lo version.lo $(LINK_LIBS) + +homedir.la : homedir.lo + $(LTLINK_MOD) -module -o $@ homedir.lo version.lo $(LINK_LIBS) + +memberof.la : memberof.lo + $(LTLINK_MOD) -module -o $@ memberof.lo version.lo $(LINK_LIBS) + +otp.la : otp.lo + $(LTLINK_MOD) -module -o $@ otp.lo version.lo $(LINK_LIBS) + +pcache.la : pcache.lo + $(LTLINK_MOD) -module -o $@ pcache.lo version.lo $(LINK_LIBS) + +collect.la : collect.lo + $(LTLINK_MOD) -module -o $@ collect.lo version.lo $(LINK_LIBS) + +ppolicy.la : ppolicy.lo + $(LTLINK_MOD) -module -o $@ ppolicy.lo version.lo $(LINK_LIBS) $(MODULES_LIBS) + +refint.la : refint.lo + $(LTLINK_MOD) -module -o $@ refint.lo version.lo $(LINK_LIBS) + +remoteauth.la : remoteauth.lo + $(LTLINK_MOD) -module -o $@ remoteauth.lo version.lo $(LINK_LIBS) + +retcode.la : retcode.lo + $(LTLINK_MOD) -module -o $@ retcode.lo version.lo $(LINK_LIBS) + +rwm_x.o: rwm.o rwmconf.o rwmdn.o rwmmap.o + $(LD) -r -o $@ rwm.o rwmconf.o rwmdn.o rwmmap.o + +rwm.la : rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo + $(LTLINK_MOD) -module -o $@ rwm.lo rwmconf.lo rwmdn.lo rwmmap.lo version.lo $(LINK_LIBS) + +seqmod.la : seqmod.lo + $(LTLINK_MOD) -module -o $@ seqmod.lo version.lo $(LINK_LIBS) + +sssvlv.la : sssvlv.lo + $(LTLINK_MOD) -module -o $@ sssvlv.lo version.lo $(LINK_LIBS) + +syncprov.la : syncprov.lo + $(LTLINK_MOD) -module -o $@ syncprov.lo version.lo $(LINK_LIBS) + +translucent.la : translucent.lo + $(LTLINK_MOD) -module -o $@ translucent.lo version.lo $(LINK_LIBS) + +unique.la : unique.lo + $(LTLINK_MOD) -module -o $@ unique.lo version.lo $(LINK_LIBS) + +valsort.la : valsort.lo + $(LTLINK_MOD) -module -o $@ valsort.lo version.lo $(LINK_LIBS) + +install-local: $(PROGRAMS) + @if test -n "$?" ; then \ + $(MKDIR) $(DESTDIR)$(moduledir); \ + $(LTINSTALL) $(INSTALLFLAGS) -m 755 $? $(DESTDIR)$(moduledir);\ + fi + +MKDEPFLAG = -l + +.SUFFIXES: .c .o .lo + +.c.lo: + $(LTCOMPILE_MOD) $< + +statover.o: statover.c $(srcdir)/../slap.h + +$(LIBRARY): $(OBJS) version.lo + $(AR) rs $@ $(OBJS) + +# Must fixup depends for non-libtool objects +depend-local: depend-common + @if test -n "$(OBJS)"; then \ + OBJ2=`echo $(OBJS) $(OBJDEP) | $(SED) -e 's/\.o//g'`; \ + SCR=''; for i in $$OBJ2; do SCR="$$SCR -e s/^$$i.lo:/$$i.o:/"; done; \ + mv Makefile Makefile.bak; $(SED) $$SCR Makefile.bak > Makefile && \ + $(RM) Makefile.bak; fi + +veryclean-local: + $(RM) statover.c + diff --git a/servers/slapd/overlays/README b/servers/slapd/overlays/README new file mode 100644 index 0000000..e426e4b --- /dev/null +++ b/servers/slapd/overlays/README @@ -0,0 +1,5 @@ +This directory contains a number of SLAPD overlays, some +project-maintained, some not. Some are generally usable, +others are purely experimental. Additional overlays can +be found in the contrib/slapd-modules directory. + diff --git a/servers/slapd/overlays/accesslog.c b/servers/slapd/overlays/accesslog.c new file mode 100644 index 0000000..182be57 --- /dev/null +++ b/servers/slapd/overlays/accesslog.c @@ -0,0 +1,2874 @@ +/* accesslog.c - log operations for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_ACCESSLOG + +#include + +#include +#include + +#include + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" +#include "ldap_rq.h" + +#define LOG_OP_ADD 0x001 +#define LOG_OP_DELETE 0x002 +#define LOG_OP_MODIFY 0x004 +#define LOG_OP_MODRDN 0x008 +#define LOG_OP_COMPARE 0x010 +#define LOG_OP_SEARCH 0x020 +#define LOG_OP_BIND 0x040 +#define LOG_OP_UNBIND 0x080 +#define LOG_OP_ABANDON 0x100 +#define LOG_OP_EXTENDED 0x200 +#define LOG_OP_UNKNOWN 0x400 + +#define LOG_OP_WRITES (LOG_OP_ADD|LOG_OP_DELETE|LOG_OP_MODIFY|LOG_OP_MODRDN) +#define LOG_OP_READS (LOG_OP_COMPARE|LOG_OP_SEARCH) +#define LOG_OP_SESSION (LOG_OP_BIND|LOG_OP_UNBIND|LOG_OP_ABANDON) +#define LOG_OP_ALL (LOG_OP_READS|LOG_OP_WRITES|LOG_OP_SESSION| \ + LOG_OP_EXTENDED|LOG_OP_UNKNOWN) + +typedef struct log_attr { + struct log_attr *next; + AttributeDescription *attr; +} log_attr; + +typedef struct log_base { + struct log_base *lb_next; + slap_mask_t lb_ops; + struct berval lb_base; + struct berval lb_line; +} log_base; + +typedef struct log_info { + BackendDB *li_db; + struct berval li_db_suffix; + int li_open; + + slap_mask_t li_ops; + int li_age; + int li_cycle; + struct re_s *li_task; + Filter *li_oldf; + Entry *li_old; + log_attr *li_oldattrs; + struct berval li_uuid; + int li_success; + log_base *li_bases; + BerVarray li_mincsn; + int *li_sids, li_numcsns; + + /* + * Allow partial concurrency, main operation processing serialised with + * li_op_rmutex (there might be multiple such in progress by the same + * thread at a time, think overlays), the actual logging and mincsn + * management are serialised with li_log_mutex. + * + * ITS#9538: + * Any CSN assignment should happen while li_op_rmutex is held and + * li_log_mutex should be acquired before the former has been released. + */ + ldap_pvt_thread_mutex_t li_op_rmutex; + ldap_pvt_thread_mutex_t li_log_mutex; +} log_info; + +static ConfigDriver log_cf_gen; + +enum { + LOG_DB = 1, + LOG_OPS, + LOG_PURGE, + LOG_SUCCESS, + LOG_OLD, + LOG_OLDATTR, + LOG_BASE +}; + +static ConfigTable log_cfats[] = { + { "logdb", "suffix", 2, 2, 0, ARG_DN|ARG_QUOTE|ARG_MAGIC|LOG_DB, + log_cf_gen, "( OLcfgOvAt:4.1 NAME 'olcAccessLogDB' " + "DESC 'Suffix of database for log content' " + "EQUALITY distinguishedNameMatch " + "SUP distinguishedName SINGLE-VALUE )", NULL, NULL }, + { "logops", "op|writes|reads|session|all", 2, 0, 0, + ARG_MAGIC|LOG_OPS, + log_cf_gen, "( OLcfgOvAt:4.2 NAME 'olcAccessLogOps' " + "DESC 'Operation types to log' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { "logpurge", "age> : + * + + ( 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; io_req_dn = pd.dn[i]; + op->o_req_ndn = pd.ndn[i]; + if ( !slapd_shutdown ) { + rs_reinit( &rs, REP_RESULT ); + op->o_bd->be_delete( op, &rs ); + } + ch_free( pd.ndn[i].bv_val ); + ch_free( pd.dn[i].bv_val ); + ldap_pvt_thread_pool_pausewait( &connection_pool ); + } + ch_free( pd.ndn ); + ch_free( pd.dn ); + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +log_cf_gen(ConfigArgs *c) +{ + slap_overinst *on = (slap_overinst *)c->bi; + struct log_info *li = on->on_bi.bi_private; + int rc = 0; + slap_mask_t tmask = 0; + char agebuf[2*STRLENOF("ddddd+hh:mm:ss ")]; + struct berval agebv, cyclebv; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + switch( c->type ) { + case LOG_DB: + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + value_add_one( &c->rvalue_vals, &li->li_db_suffix ); + value_add_one( &c->rvalue_nvals, &li->li_db_suffix ); + } else if ( li->li_db ) { + value_add_one( &c->rvalue_vals, li->li_db->be_suffix ); + value_add_one( &c->rvalue_nvals, li->li_db->be_nsuffix ); + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "accesslog: \"logdb \" must be specified" ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + break; + } + break; + case LOG_OPS: + rc = mask_to_verbs( logops, li->li_ops, &c->rvalue_vals ); + break; + case LOG_PURGE: + if ( !li->li_age ) { + rc = 1; + break; + } + agebv.bv_val = agebuf; + log_age_unparse( li->li_age, &agebv, sizeof( agebuf ) ); + agebv.bv_val[agebv.bv_len] = ' '; + agebv.bv_len++; + cyclebv.bv_val = agebv.bv_val + agebv.bv_len; + log_age_unparse( li->li_cycle, &cyclebv, sizeof( agebuf ) - agebv.bv_len ); + agebv.bv_len += cyclebv.bv_len; + value_add_one( &c->rvalue_vals, &agebv ); + break; + case LOG_SUCCESS: + if ( li->li_success ) + c->value_int = li->li_success; + else + rc = 1; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter2bv( li->li_oldf, &agebv ); + ber_bvarray_add( &c->rvalue_vals, &agebv ); + } + else + rc = 1; + break; + case LOG_OLDATTR: + if ( li->li_oldattrs ) { + log_attr *la; + + for ( la = li->li_oldattrs; la; la=la->next ) + value_add_one( &c->rvalue_vals, &la->attr->ad_cname ); + } + else + rc = 1; + break; + case LOG_BASE: + if ( li->li_bases ) { + log_base *lb; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + value_add_one( &c->rvalue_vals, &lb->lb_line ); + } + else + rc = 1; + break; + } + break; + case LDAP_MOD_DELETE: + switch( c->type ) { + case LOG_DB: + /* noop. this should always be a valid backend. */ + break; + case LOG_OPS: + if ( c->valx < 0 ) { + li->li_ops = 0; + } else { + rc = verbs_to_mask( 1, &c->line, logops, &tmask ); + if ( rc == 0 ) + li->li_ops &= ~tmask; + } + break; + case LOG_PURGE: + if ( li->li_task ) { + struct re_s *re = li->li_task; + li->li_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re )) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + li->li_age = 0; + li->li_cycle = 0; + break; + case LOG_SUCCESS: + li->li_success = 0; + break; + case LOG_OLD: + if ( li->li_oldf ) { + filter_free( li->li_oldf ); + li->li_oldf = NULL; + } + break; + case LOG_OLDATTR: + if ( c->valx < 0 ) { + log_attr *la, *ln; + + for ( la = li->li_oldattrs; la; la = ln ) { + ln = la->next; + ch_free( la ); + } + } else { + log_attr *la = li->li_oldattrs, **lp = &li->li_oldattrs; + int i; + + for ( i=0; i < c->valx; i++ ) { + la = *lp; + lp = &la->next; + } + *lp = la->next; + ch_free( la ); + } + break; + case LOG_BASE: + if ( c->valx < 0 ) { + log_base *lb, *ln; + + for ( lb = li->li_bases; lb; lb = ln ) { + ln = lb->lb_next; + ch_free( lb ); + } + } else { + log_base *lb = li->li_bases, **lp = &li->li_bases; + int i; + + for ( i=0; i < c->valx; i++ ) { + lb = *lp; + lp = &lb->lb_next; + } + *lp = lb->lb_next; + ch_free( lb ); + } + break; + } + break; + default: + switch( c->type ) { + case LOG_DB: + if ( CONFIG_ONLINE_ADD( c )) { + li->li_db = select_backend( &c->value_ndn, 0 ); + if ( !li->li_db ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> no matching backend found for suffix", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + } + if ( !rc && ( li->li_db->bd_self == c->be->bd_self )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<%s> invalid suffix, points to itself", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s \"%s\"\n", + c->log, c->cr_msg, c->value_dn.bv_val ); + rc = 1; + } + ch_free( c->value_ndn.bv_val ); + } else { + li->li_db_suffix = c->value_ndn; + } + ch_free( c->value_dn.bv_val ); + break; + case LOG_OPS: + rc = verbs_to_mask( c->argc, c->argv, logops, &tmask ); + if ( rc == 0 ) + li->li_ops |= tmask; + break; + case LOG_PURGE: + li->li_age = log_age_parse( c->argv[1] ); + if ( li->li_age < 1 ) { + rc = 1; + } else { + li->li_cycle = log_age_parse( c->argv[2] ); + if ( li->li_cycle < 1 ) { + rc = 1; + } else if ( slapMode & SLAP_SERVER_MODE ) { + struct re_s *re = li->li_task; + if ( re ) + re->interval.tv_sec = li->li_cycle; + else if ( li->li_open ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + li->li_task = ldap_pvt_runqueue_insert( &slapd_rq, + li->li_cycle, accesslog_purge, li, + "accesslog_purge", li->li_db ? + li->li_db->be_suffix[0].bv_val : + c->be->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + } + } + break; + case LOG_SUCCESS: + li->li_success = c->value_int; + break; + case LOG_OLD: + li->li_oldf = str2filter( c->argv[1] ); + if ( !li->li_oldf ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "bad filter!" ); + rc = 1; + } + break; + case LOG_OLDATTR: { + int i; + AttributeDescription *ad; + const char *text; + log_attr **lp = &li->li_oldattrs; + + for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ ) + lp = &(*lp)->next; + + for ( i=1; i< c->argc; i++ ) { + ad = NULL; + if ( slap_str2ad( c->argv[i], &ad, &text ) == LDAP_SUCCESS ) { + log_attr *la = ch_malloc( sizeof( log_attr )); + la->attr = ad; + if ( *lp ) { + la->next = (*lp)->next; + } else { + la->next = NULL; + } + *lp = la; + lp = &la->next; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s <%s>: %s", + c->argv[0], c->argv[i], text ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + } + } + break; + case LOG_BASE: { + int i; + slap_mask_t m = 0; + log_base **lp = &li->li_bases; + + for ( i=0; *lp && ( c->valx < 0 || i < c->valx ); i++ ) + lp = &(*lp)->lb_next; + + rc = verbstring_to_mask( logops, c->argv[1], '|', &m ); + if ( rc == 0 ) { + struct berval dn, ndn; + ber_str2bv( c->argv[2], 0, 0, &dn ); + rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ); + if ( rc == 0 ) { + log_base *lb; + struct berval mbv; + char *ptr; + mask_to_verbstring( logops, m, '|', &mbv ); + lb = ch_malloc( sizeof( log_base ) + mbv.bv_len + ndn.bv_len + 3 + 1 ); + lb->lb_line.bv_val = (char *)(lb + 1); + lb->lb_line.bv_len = mbv.bv_len + ndn.bv_len + 3; + ptr = lutil_strcopy( lb->lb_line.bv_val, mbv.bv_val ); + *ptr++ = ' '; + *ptr++ = '"'; + lb->lb_base.bv_val = ptr; + lb->lb_base.bv_len = ndn.bv_len; + ptr = lutil_strcopy( ptr, ndn.bv_val ); + *ptr++ = '"'; + lb->lb_ops = m; + lb->lb_next = li->li_bases; + if ( *lp ) { + lb->lb_next = (*lp)->lb_next; + } else { + lb->lb_next = NULL; + } + *lp = lb; + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid DN: %s", + c->argv[0], c->argv[2] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "%s invalid ops: %s", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + } + } + break; + } + break; + } + return rc; +} + +static int +logSchemaControlValidate( + Syntax *syntax, + struct berval *valp ) +{ + struct berval val, bv; + ber_len_t i; + int rc = LDAP_SUCCESS; + + assert( valp != NULL ); + + val = *valp; + + /* check minimal size */ + if ( val.bv_len < STRLENOF( "{*}" ) ) { + return LDAP_INVALID_SYNTAX; + } + + val.bv_len--; + + /* check SEQUENCE boundaries */ + if ( val.bv_val[ 0 ] != '{' /*}*/ || + val.bv_val[ val.bv_len ] != /*{*/ '}' ) + { + return LDAP_INVALID_SYNTAX; + } + + /* extract and check OID */ + for ( i = 1; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( i++; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) + { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + rc = numericoidValidate( NULL, &bv ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + /* extract and check criticality */ + if ( strncasecmp( &val.bv_val[ i ], "criticality ", STRLENOF( "criticality " ) ) == 0 ) + { + i += STRLENOF( "criticality " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + bv.bv_val = &val.bv_val[ i ]; + + for ( ; i < val.bv_len; i++ ) { + if ( ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + bv.bv_len = &val.bv_val[ i ] - bv.bv_val; + + if ( !bvmatch( &bv, &slap_true_bv ) && !bvmatch( &bv, &slap_false_bv ) ) + { + return LDAP_INVALID_SYNTAX; + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + + if ( val.bv_val[ i ] != ' ' ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + /* extract and check controlValue */ + if ( strncasecmp( &val.bv_val[ i ], "controlValue ", STRLENOF( "controlValue " ) ) == 0 ) + { + ber_len_t valueStart, valueLen; + + i += STRLENOF( "controlValue " ); + for ( ; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_INVALID_SYNTAX; + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + i++; + valueStart = i; + + for ( ; i < val.bv_len; i++ ) { + if ( val.bv_val[ i ] == '"' ) { + break; + } + + if ( !ASCII_HEX( val.bv_val[ i ] ) ) { + return LDAP_INVALID_SYNTAX; + } + } + + if ( val.bv_val[ i ] != '"' ) { + return LDAP_INVALID_SYNTAX; + } + + valueLen = i - valueStart; + if ( (valueLen/2)*2 != valueLen ) { + return LDAP_INVALID_SYNTAX; + } + + for ( i++; i < val.bv_len; i++ ) { + if ( !ASCII_SPACE( val.bv_val[ i ] ) ) { + break; + } + } + + if ( i == val.bv_len ) { + return LDAP_SUCCESS; + } + } + + return LDAP_INVALID_SYNTAX; +} + +static int +accesslog_ctrls( + LDAPControl **ctrls, + BerVarray *valsp, + BerVarray *nvalsp, + void *memctx ) +{ + long i, rc = 0; + + assert( valsp != NULL ); + assert( ctrls != NULL ); + + *valsp = NULL; + *nvalsp = NULL; + + for ( i = 0; ctrls[ i ] != NULL; i++ ) { + struct berval idx, + oid, + noid, + bv; + char *ptr, + buf[ 32 ]; + + if ( ctrls[ i ]->ldctl_oid == NULL ) { + return LDAP_PROTOCOL_ERROR; + } + + idx.bv_len = snprintf( buf, sizeof( buf ), "{%ld}", i ); + idx.bv_val = buf; + + ber_str2bv( ctrls[ i ]->ldctl_oid, 0, 0, &oid ); + noid.bv_len = idx.bv_len + oid.bv_len; + ptr = noid.bv_val = ber_memalloc_x( noid.bv_len + 1, memctx ); + ptr = lutil_strcopy( ptr, idx.bv_val ); + ptr = lutil_strcopy( ptr, oid.bv_val ); + + bv.bv_len = idx.bv_len + STRLENOF( "{}" ) + oid.bv_len; + + if ( ctrls[ i ]->ldctl_iscritical ) { + bv.bv_len += STRLENOF( " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + bv.bv_len += STRLENOF( " controlValue \"\"" ) + + 2 * ctrls[ i ]->ldctl_value.bv_len; + } + + ptr = bv.bv_val = ber_memalloc_x( bv.bv_len + 1, memctx ); + if ( ptr == NULL ) { + ber_bvarray_free( *valsp ); + *valsp = NULL; + ber_bvarray_free( *nvalsp ); + *nvalsp = NULL; + return LDAP_OTHER; + } + + ptr = lutil_strcopy( ptr, idx.bv_val ); + + *ptr++ = '{' /*}*/ ; + ptr = lutil_strcopy( ptr, oid.bv_val ); + + if ( ctrls[ i ]->ldctl_iscritical ) { + ptr = lutil_strcopy( ptr, " criticality TRUE" ); + } + + if ( !BER_BVISNULL( &ctrls[ i ]->ldctl_value ) ) { + ber_len_t j; + + ptr = lutil_strcopy( ptr, " controlValue \"" ); + for ( j = 0; j < ctrls[ i ]->ldctl_value.bv_len; j++ ) { + *ptr++ = SLAP_ESCAPE_HI(ctrls[ i ]->ldctl_value.bv_val[ j ]); + *ptr++ = SLAP_ESCAPE_LO(ctrls[ i ]->ldctl_value.bv_val[ j ]); + } + + *ptr++ = '"'; + } + + *ptr++ = '}'; + *ptr = '\0'; + + ber_bvarray_add_x( valsp, &bv, memctx ); + ber_bvarray_add_x( nvalsp, &noid, memctx ); + } + + return rc; + +} + +static Entry *accesslog_entry( Operation *op, SlapReply *rs, + log_info *li, int logop, Operation *op2 ) { + + char rdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + char nrdnbuf[STRLENOF(RDNEQ)+LDAP_LUTIL_GENTIME_BUFSIZE+8]; + + struct berval rdn, nrdn, timestamp, ntimestamp, bv; + slap_verbmasks *lo = logops+logop+EN_OFFSET; + + Entry *e = entry_alloc(); + + strcpy( rdnbuf, RDNEQ ); + rdn.bv_val = rdnbuf; + strcpy( nrdnbuf, RDNEQ ); + nrdn.bv_val = nrdnbuf; + + timestamp.bv_val = rdnbuf+STRLENOF(RDNEQ); + timestamp.bv_len = sizeof(rdnbuf) - STRLENOF(RDNEQ); + slap_timestamp( &op->o_time, ×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_callback *sc = op->o_callback; + slap_overinst *on = (slap_overinst *)sc->sc_private; + log_info *li = on->on_bi.bi_private; + Attribute *a, *last_attr; + Modifications *m; + struct berval *b, uuid = BER_BVNULL; + int i, success; + int logop; + slap_verbmasks *lo; + Entry *e = NULL, *old = NULL, *e_uuid = NULL; + char timebuf[LDAP_LUTIL_GENTIME_BUFSIZE+8]; + struct berval bv; + char *ptr; + BerVarray vals; + Operation op2 = {0}; + SlapReply rs2 = {REP_RESULT}; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + + /* ITS#9051 Make sure we only remove the callback on a final response */ + if ( rs->sr_type != REP_RESULT && rs->sr_type != REP_EXTENDED && + rs->sr_type != REP_SASL ) + return SLAP_CB_CONTINUE; + + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + + /* can't do anything if logDB isn't open */ + if ( !SLAP_DBOPEN( li->li_db ) ) { + goto skip; + } + + /* These internal ops are not logged */ + if ( op->o_dont_replicate ) + goto skip; + + /* + * ITS#9051 Technically LDAP_REFERRAL and LDAP_SASL_BIND_IN_PROGRESS + * are not errors, but they aren't really success either + */ + success = rs->sr_err == LDAP_SUCCESS || + rs->sr_err == LDAP_COMPARE_TRUE || + rs->sr_err == LDAP_COMPARE_FALSE; + if ( li->li_success && !success ) + goto skip; + + if ( !( li->li_ops & lo->mask ) ) { + log_base *lb; + + i = 0; + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + goto skip; + } + + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_csn.bv_val = csnbuf; + op2.o_csn.bv_len = sizeof(csnbuf); + + if ( !( lo->mask & LOG_OP_WRITES ) ) { + ldap_pvt_thread_mutex_lock( &li->li_op_rmutex ); + } + if ( SLAP_LASTMOD( li->li_db ) ) { + /* + * Make sure we have a CSN before we release li_op_rmutex to preserve + * ordering + */ + if ( !success || BER_BVISEMPTY( &op->o_csn ) ) { + slap_get_csn( &op2, &op2.o_csn, 1 ); + } else { + if ( !( lo->mask & LOG_OP_WRITES ) ) { + Debug( LDAP_DEBUG_ANY, "%s accesslog_response: " + "the op had a CSN assigned, if you're replicating the " + "accesslog at %s, you might lose changes\n", + op->o_log_prefix, li->li_db_suffix.bv_val ); + assert(0); + } + slap_queue_csn( &op2, &op->o_csn ); + } + } + + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + old = li->li_old; + uuid = li->li_uuid; + li->li_old = NULL; + BER_BVZERO( &li->li_uuid ); + ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex ); + + e = accesslog_entry( op, rs, li, logop, &op2 ); + + if ( !BER_BVISNULL( &op->o_req_ndn )) + attr_merge_one( e, ad_reqDN, &op->o_req_dn, &op->o_req_ndn ); + + if ( rs->sr_text ) { + ber_str2bv( rs->sr_text, 0, 0, &bv ); + attr_merge_normalize_one( e, ad_reqMessage, &bv, op->o_tmpmemctx ); + } + bv.bv_len = snprintf( timebuf, sizeof( timebuf ), "%d", rs->sr_err ); + if ( bv.bv_len < sizeof( timebuf ) ) { + bv.bv_val = timebuf; + attr_merge_one( e, ad_reqResult, &bv, NULL ); + } + + last_attr = attr_find( e->e_attrs, ad_reqResult ); + + e_uuid = old; + switch( logop ) { + case LOG_EN_ADD: + case LOG_EN_DELETE: { + char c_op; + Entry *e2; + + if ( logop == LOG_EN_ADD ) { + e2 = op->ora_e; + e_uuid = op->ora_e; + c_op = '+'; + + } else { + if ( !old ) + break; + e2 = old; + c_op = 0; + } + /* count all the vals */ + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + i += a->a_numvals; + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + for ( a=e2->e_attrs; a; a=a->a_next ) { + if ( a->a_vals ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, c_op, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( logop == LOG_EN_ADD ? ad_reqMod : ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + break; + } + + case LOG_EN_MODRDN: + case LOG_EN_MODIFY: + /* count all the mods + attributes (ITS#6545) */ + i = 0; + for ( m = op->orm_modlist; m; m = m->sml_next ) { + if ( m->sml_values ) { + i += m->sml_numvals; + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + i++; + } + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc ) { + i++; + } + } + vals = ch_malloc( (i+1) * sizeof( struct berval )); + i = 0; + + /* init flags on old entry */ + if ( old ) { + for ( a = old->e_attrs; a; a = a->a_next ) { + log_attr *la; + a->a_flags = 0; + + /* look for attrs that are always logged */ + for ( la = li->li_oldattrs; la; la = la->next ) { + if ( a->a_desc == la->attr ) { + a->a_flags = 1; + } + } + } + } + + for ( m = op->orm_modlist; m; m = m->sml_next ) { + /* Mark this attribute as modified */ + if ( old ) { + a = attr_find( old->e_attrs, m->sml_desc ); + if ( a ) { + a->a_flags = 1; + } + } + + /* don't log the RDN mods; they're explicitly logged later */ + if ( logop == LOG_EN_MODRDN && + ( m->sml_op == SLAP_MOD_SOFTADD || + m->sml_op == LDAP_MOD_DELETE ) ) + { + continue; + } + + if ( m->sml_values ) { + for ( b = m->sml_values; !BER_BVISNULL( b ); b++, i++ ) { + char c_op; + + switch ( m->sml_op ) { + case LDAP_MOD_ADD: /* FALLTHRU */ + case SLAP_MOD_SOFTADD: c_op = '+'; break; + case LDAP_MOD_DELETE: /* FALLTHRU */ + case SLAP_MOD_SOFTDEL: c_op = '-'; break; + case LDAP_MOD_REPLACE: c_op = '='; break; + case LDAP_MOD_INCREMENT: c_op = '#'; break; + + /* unknown op. there shouldn't be any of these. we + * don't know what to do with it, but we shouldn't just + * ignore it. + */ + default: c_op = '?'; break; + } + accesslog_val2val( m->sml_desc, b, c_op, &vals[i] ); + } + } else if ( m->sml_op == LDAP_MOD_DELETE || + m->sml_op == SLAP_MOD_SOFTDEL || + m->sml_op == LDAP_MOD_REPLACE ) + { + vals[i].bv_len = m->sml_desc->ad_cname.bv_len + 2; + vals[i].bv_val = ch_malloc( vals[i].bv_len + 1 ); + ptr = lutil_strcopy( vals[i].bv_val, + m->sml_desc->ad_cname.bv_val ); + *ptr++ = ':'; + if ( m->sml_op == LDAP_MOD_DELETE || m->sml_op == SLAP_MOD_SOFTDEL ) { + *ptr++ = '-'; + } else { + *ptr++ = '='; + } + *ptr = '\0'; + i++; + } + /* ITS#6545: when the same attribute is edited multiple times, + * record the transition */ + if ( m->sml_next && m->sml_desc == m->sml_next->sml_desc && + m->sml_op == m->sml_next->sml_op ) { + ber_str2bv( ":", STRLENOF(":"), 1, &vals[i] ); + i++; + } + } + + if ( i > 0 ) { + BER_BVZERO( &vals[i] ); + a = attr_alloc( ad_reqMod ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + last_attr = a; + + } else { + ch_free( vals ); + } + + if ( old ) { + /* count all the vals */ + i = 0; + for ( a = old->e_attrs; a != NULL; a = a->a_next ) { + if ( a->a_vals && a->a_flags ) { + i += a->a_numvals; + } + } + if ( i ) { + vals = ch_malloc( (i + 1) * sizeof( struct berval ) ); + i = 0; + for ( a=old->e_attrs; a; a=a->a_next ) { + if ( a->a_vals && a->a_flags ) { + for (b=a->a_vals; !BER_BVISNULL( b ); b++,i++) { + accesslog_val2val( a->a_desc, b, 0, &vals[i] ); + } + } + } + vals[i].bv_val = NULL; + vals[i].bv_len = 0; + a = attr_alloc( ad_reqOld ); + a->a_numvals = i; + a->a_vals = vals; + a->a_nvals = vals; + last_attr->a_next = a; + } + } + if ( logop == LOG_EN_MODIFY ) { + break; + } + + /* Now log the actual modRDN info */ + attr_merge_one( e, ad_reqNewRDN, &op->orr_newrdn, &op->orr_nnewrdn ); + attr_merge_one( e, ad_reqDeleteOldRDN, op->orr_deleteoldrdn ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv, + NULL ); + if ( op->orr_newSup ) { + attr_merge_one( e, ad_reqNewSuperior, op->orr_newSup, op->orr_nnewSup ); + } + attr_merge_one( e, ad_reqNewDN, &op->orr_newDN, &op->orr_nnewDN ); + break; + + case LOG_EN_COMPARE: + bv.bv_len = op->orc_ava->aa_desc->ad_cname.bv_len + 1 + + op->orc_ava->aa_value.bv_len; + bv.bv_val = op->o_tmpalloc( bv.bv_len+1, op->o_tmpmemctx ); + ptr = lutil_strcopy( bv.bv_val, op->orc_ava->aa_desc->ad_cname.bv_val ); + *ptr++ = '='; + AC_MEMCPY( ptr, op->orc_ava->aa_value.bv_val, op->orc_ava->aa_value.bv_len ); + bv.bv_val[bv.bv_len] = '\0'; + attr_merge_one( e, ad_reqAssertion, &bv, NULL ); + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + break; + + case LOG_EN_SEARCH: + attr_merge_one( e, ad_reqScope, &scopes[op->ors_scope], NULL ); + attr_merge_one( e, ad_reqDerefAliases, &derefs[op->ors_deref], NULL ); + attr_merge_one( e, ad_reqAttrsOnly, op->ors_attrsonly ? + (struct berval *)&slap_true_bv : (struct berval *)&slap_false_bv, + NULL ); + if ( !BER_BVISEMPTY( &op->ors_filterstr )) + attr_merge_normalize_one( e, ad_reqFilter, &op->ors_filterstr, op->o_tmpmemctx ); + if ( op->ors_attrs ) { + int j; + /* count them */ + for (i=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) + ; + vals = op->o_tmpalloc( (i+1) * sizeof(struct berval), + op->o_tmpmemctx ); + for (i=0, j=0; !BER_BVISNULL(&op->ors_attrs[i].an_name );i++) { + if (!BER_BVISEMPTY(&op->ors_attrs[i].an_name)) { + vals[j] = op->ors_attrs[i].an_name; + j++; + } + } + BER_BVZERO(&vals[j]); + attr_merge_normalize( e, ad_reqAttr, vals, op->o_tmpmemctx ); + op->o_tmpfree( vals, op->o_tmpmemctx ); + } + bv.bv_val = timebuf; + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", rs->sr_nentries ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqEntries, &bv, NULL ); + } /* else? */ + + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_tlimit ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqTimeLimit, &bv, NULL ); + } /* else? */ + + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->ors_slimit ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqSizeLimit, &bv, NULL ); + } /* else? */ + break; + + case LOG_EN_BIND: + bv.bv_val = timebuf; + bv.bv_len = snprintf( bv.bv_val, sizeof( timebuf ), "%d", op->o_protocol ); + if ( bv.bv_len < sizeof( timebuf ) ) { + attr_merge_one( e, ad_reqVersion, &bv, NULL ); + } /* else? */ + if ( op->orb_method == LDAP_AUTH_SIMPLE ) { + attr_merge_normalize_one( e, ad_reqMethod, &simple, op->o_tmpmemctx ); + } else { + bv.bv_len = STRLENOF("SASL()") + op->orb_mech.bv_len; + bv.bv_val = op->o_tmpalloc( bv.bv_len + 1, op->o_tmpmemctx ); + ptr = lutil_strcopy( bv.bv_val, "SASL(" ); + ptr = lutil_strcopy( ptr, op->orb_mech.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + attr_merge_normalize_one( e, ad_reqMethod, &bv, op->o_tmpmemctx ); + op->o_tmpfree( bv.bv_val, op->o_tmpmemctx ); + } + + break; + + case LOG_EN_EXTENDED: + if ( op->ore_reqdata ) { + attr_merge_one( e, ad_reqData, op->ore_reqdata, NULL ); + } + break; + + case LOG_EN_UNKNOWN: + /* we don't know its parameters, don't add any */ + break; + } + + if ( e_uuid || !BER_BVISNULL( &uuid ) ) { + struct berval *pbv = NULL; + + if ( !BER_BVISNULL( &uuid ) ) { + pbv = &uuid; + + } else { + a = attr_find( e_uuid->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + pbv = &a->a_vals[0]; + } + } + + if ( pbv ) { + attr_merge_normalize_one( e, ad_reqEntryUUID, pbv, op->o_tmpmemctx ); + } + + if ( !BER_BVISNULL( &uuid ) ) { + ber_memfree( uuid.bv_val ); + BER_BVZERO( &uuid ); + } + } + + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + /* contextCSN updates may still reach here */ + op2.o_dont_replicate = op->o_dont_replicate; + + op2.o_bd->be_add( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "%s accesslog_response: " + "got result 0x%x adding log entry %s\n", + op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val ); + } + if ( e == op2.ora_e ) entry_free( e ); + e = NULL; + + if ( ( lo->mask & LOG_OP_WRITES ) && !BER_BVISEMPTY( &op->o_csn ) ) { + Modifications mod; + int i, sid = slap_parse_csn_sid( &op->o_csn ); + + for ( i=0; i < li->li_numcsns; i++ ) { + if ( sid <= li->li_sids[i] ) break; + } + if ( i >= li->li_numcsns || sid != li->li_sids[i] ) { + /* SID not in minCSN set, add */ + struct berval bv[2]; + + Debug( LDAP_DEBUG_TRACE, "accesslog_response: " + "adding minCSN %s\n", + op->o_csn.bv_val ); + slap_insert_csn_sids( (struct sync_cookie *)&li->li_mincsn, i, + sid, &op->o_csn ); + + op2.o_tag = LDAP_REQ_MODIFY; + op2.o_req_dn = li->li_db->be_suffix[0]; + op2.o_req_ndn = li->li_db->be_nsuffix[0]; + + bv[0] = op->o_csn; + BER_BVZERO( &bv[1] ); + + mod.sml_numvals = 1; + mod.sml_values = bv; + mod.sml_nvalues = bv; + mod.sml_desc = ad_minCSN; + mod.sml_op = LDAP_MOD_ADD; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op2.orm_modlist = &mod; + op2.orm_no_opattrs = 1; + + Debug( LDAP_DEBUG_SYNC, "accesslog_response: " + "adding a new csn=%s into minCSN\n", + bv[0].bv_val ); + rs_reinit( &rs2, REP_RESULT ); + op2.o_bd->be_modify( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "accesslog_response: " + "got result 0x%x adding minCSN %s\n", + rs2.sr_err, op->o_csn.bv_val ); + } + } else if ( ber_bvcmp( &op->o_csn, &li->li_mincsn[i] ) < 0 ) { + Debug( LDAP_DEBUG_ANY, "accesslog_response: " + "csn=%s older than existing minCSN csn=%s for this sid\n", + op->o_csn.bv_val, li->li_mincsn[i].bv_val ); + } + } + +done: + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + if ( old ) entry_free( old ); + return SLAP_CB_CONTINUE; + +skip: + if ( lo->mask & LOG_OP_WRITES ) { + /* We haven't transitioned to li_log_mutex yet */ + ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex ); + } + return SLAP_CB_CONTINUE; +} + +static int +accesslog_op_misc( Operation *op, SlapReply *rs ) +{ + slap_callback *sc; + slap_verbmasks *lo; + int logop; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + + /* ignore these internal reads */ + if (( lo->mask & LOG_OP_READS ) && op->o_do_not_cache ) { + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + sc->sc_response = accesslog_response; + sc->sc_private = op->o_bd->bd_info; + + if ( op->o_callback ) { + sc->sc_next = op->o_callback->sc_next; + op->o_callback->sc_next = sc; + } else { + op->o_callback = sc; + } + return SLAP_CB_CONTINUE; +} + +static int +accesslog_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + slap_verbmasks *lo; + slap_callback *cb; + int logop; + + /* These internal ops are not logged */ + if ( op->o_dont_replicate ) + return SLAP_CB_CONTINUE; + + /* can't do anything if logDB isn't open */ + if ( !SLAP_DBOPEN( li->li_db )) + return SLAP_CB_CONTINUE; + + logop = accesslog_op2logop( op ); + lo = logops+logop+EN_OFFSET; + + if ( !( li->li_ops & lo->mask )) { + log_base *lb; + int i = 0; + + for ( lb = li->li_bases; lb; lb = lb->lb_next ) + if (( lb->lb_ops & lo->mask ) && dnIsSuffix( &op->o_req_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + cb = op->o_tmpcalloc( 1, sizeof( slap_callback ), op->o_tmpmemctx ); + cb->sc_cleanup = accesslog_response; + cb->sc_response = accesslog_response; + cb->sc_private = on; + cb->sc_next = op->o_callback; + op->o_callback = cb; + + ldap_pvt_thread_mutex_lock( &li->li_op_rmutex ); + + if ( li->li_oldf && ( op->o_tag == LDAP_REQ_DELETE || + op->o_tag == LDAP_REQ_MODIFY || + ( op->o_tag == LDAP_REQ_MODRDN && li->li_oldattrs ))) + { + int rc; + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( e ) { + if ( test_filter( op, e, li->li_oldf ) == LDAP_COMPARE_TRUE ) + li->li_old = entry_dup( e ); + be_entry_release_rw( op, e, 0 ); + } + op->o_bd->bd_info = (BackendInfo *)on; + + } else { + int rc; + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &e ); + if ( e ) { + Attribute *a = attr_find( e->e_attrs, slap_schema.si_ad_entryUUID ); + if ( a ) { + ber_dupbv( &li->li_uuid, &a->a_vals[0] ); + } + be_entry_release_rw( op, e, 0 ); + } + op->o_bd->bd_info = (BackendInfo *)on; + } + return SLAP_CB_CONTINUE; +} + +/* unbinds are broadcast to all backends; we only log it if this + * backend was used for the original bind. + */ +static int +accesslog_unbind( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + Operation op2 = {}; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + void *cids[SLAP_MAX_CIDS]; + SlapReply rs2 = {REP_RESULT}; + Entry *e; + + if ( op->o_conn->c_authz_backend != on->on_info->oi_origdb ) + return SLAP_CB_CONTINUE; + + if ( !( li->li_ops & LOG_OP_UNBIND ) ) { + log_base *lb; + int i = 0; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & LOG_OP_UNBIND ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_csn.bv_val = csnbuf; + op2.o_csn.bv_len = sizeof(csnbuf); + + ldap_pvt_thread_mutex_lock( &li->li_op_rmutex ); + + if ( SLAP_LASTMOD( li->li_db ) ) { + /* + * Make sure we have a CSN before we release li_op_rmutex to preserve + * ordering + */ + if ( BER_BVISEMPTY( &op->o_csn ) ) { + slap_get_csn( &op2, &op2.o_csn, 1 ); + } else { + Debug( LDAP_DEBUG_ANY, "%s accesslog_unbind: " + "the op had a CSN assigned, if you're replicating the " + "accesslog at %s, you might lose changes\n", + op->o_log_prefix, li->li_db_suffix.bv_val ); + assert(0); + op2.o_csn = op->o_csn; + } + } + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex ); + + e = accesslog_entry( op, rs, li, LOG_EN_UNBIND, &op2 ); + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + op2.o_controls = cids; + memset(cids, 0, sizeof( cids )); + + op2.o_bd->be_add( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "%s accesslog_unbind: " + "got result 0x%x adding log entry %s\n", + op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val ); + } + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + + if ( e == op2.ora_e ) + entry_free( e ); + + return SLAP_CB_CONTINUE; +} + +static int +accesslog_abandon( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + Operation op2 = {0}; + void *cids[SLAP_MAX_CIDS]; + SlapReply rs2 = {REP_RESULT}; + Entry *e; + char csnbuf[LDAP_PVT_CSNSTR_BUFSIZE]; + char buf[64]; + struct berval bv; + + if ( !op->o_time ) + return SLAP_CB_CONTINUE; + + if ( !( li->li_ops & LOG_OP_ABANDON )) { + log_base *lb; + int i = 0; + + for ( lb = li->li_bases; lb; lb=lb->lb_next ) + if (( lb->lb_ops & LOG_OP_ABANDON ) && dnIsSuffix( &op->o_ndn, &lb->lb_base )) { + i = 1; + break; + } + if ( !i ) + return SLAP_CB_CONTINUE; + } + + op2.o_hdr = op->o_hdr; + op2.o_tag = LDAP_REQ_ADD; + op2.o_bd = li->li_db; + op2.o_csn.bv_val = csnbuf; + op2.o_csn.bv_len = sizeof(csnbuf); + + ldap_pvt_thread_mutex_lock( &li->li_op_rmutex ); + if ( SLAP_LASTMOD( li->li_db ) ) { + /* + * Make sure we have a CSN before we release li_op_rmutex to preserve + * ordering + */ + if ( BER_BVISEMPTY( &op->o_csn ) ) { + slap_get_csn( &op2, &op2.o_csn, 1 ); + } else { + Debug( LDAP_DEBUG_ANY, "%s accesslog_abandon: " + "the op had a CSN assigned, if you're replicating the " + "accesslog at %s, you might lose changes\n", + op->o_log_prefix, li->li_db_suffix.bv_val ); + assert(0); + op2.o_csn = op->o_csn; + } + } + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + ldap_pvt_thread_mutex_unlock( &li->li_op_rmutex ); + + e = accesslog_entry( op, rs, li, LOG_EN_ABANDON, &op2 ); + bv.bv_val = buf; + bv.bv_len = snprintf( buf, sizeof( buf ), "%d", op->orn_msgid ); + if ( bv.bv_len < sizeof( buf ) ) { + attr_merge_one( e, ad_reqId, &bv, NULL ); + } /* else? */ + + op2.o_dn = li->li_db->be_rootdn; + op2.o_ndn = li->li_db->be_rootndn; + op2.o_req_dn = e->e_name; + op2.o_req_ndn = e->e_nname; + op2.ora_e = e; + op2.o_callback = &nullsc; + op2.o_controls = cids; + memset(cids, 0, sizeof( cids )); + + op2.o_bd->be_add( &op2, &rs2 ); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "%s accesslog_abandon: " + "got result 0x%x adding log entry %s\n", + op->o_log_prefix, rs2.sr_err, op2.o_req_dn.bv_val ); + } + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + if ( e == op2.ora_e ) + entry_free( e ); + + return SLAP_CB_CONTINUE; +} + +static int +accesslog_operational( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + log_info *li = on->on_bi.bi_private; + + if ( op->o_sync != SLAP_CONTROL_NONE ) + return SLAP_CB_CONTINUE; + + if ( rs->sr_entry != NULL + && dn_match( &op->o_bd->be_nsuffix[0], &rs->sr_entry->e_nname ) ) + { + Attribute **ap; + + for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) + /* just count */ ; + + if ( SLAP_OPATTRS( rs->sr_attr_flags ) || + ad_inlist( ad_auditContext, rs->sr_attrs ) ) + { + *ap = attr_alloc( ad_auditContext ); + attr_valadd( *ap, + &li->li_db->be_suffix[0], + &li->li_db->be_nsuffix[0], 1 ); + } + } + + return SLAP_CB_CONTINUE; +} + +static slap_overinst accesslog; + +static int +accesslog_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = ch_calloc(1, sizeof(log_info)); + + on->on_bi.bi_private = li; + ldap_pvt_thread_mutex_recursive_init( &li->li_op_rmutex ); + ldap_pvt_thread_mutex_init( &li->li_log_mutex ); + return 0; +} + +static int +accesslog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + log_attr *la; + + if ( li->li_oldf ) + filter_free( li->li_oldf ); + for ( la=li->li_oldattrs; la; la=li->li_oldattrs ) { + li->li_oldattrs = la->next; + ch_free( la ); + } + if ( li->li_sids ) + ch_free( li->li_sids ); + if ( li->li_mincsn ) + ber_bvarray_free( li->li_mincsn ); + if ( li->li_db_suffix.bv_val ) + ch_free( li->li_db_suffix.bv_val ); + ldap_pvt_thread_mutex_destroy( &li->li_log_mutex ); + ldap_pvt_thread_mutex_destroy( &li->li_op_rmutex ); + free( li ); + return LDAP_SUCCESS; +} + +/* Create the logdb's root entry if it's missing, load mincsn */ +static void * +accesslog_db_root( + void *ctx, + void *arg ) +{ + struct re_s *rtask = arg; + slap_overinst *on = rtask->arg; + log_info *li = on->on_bi.bi_private; + + Connection conn = {0}; + OperationBuffer opbuf; + Operation *op; + + Entry *e; + int rc; + + ldap_pvt_thread_mutex_lock( &li->li_log_mutex ); + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + op->o_bd = li->li_db; + op->o_dn = li->li_db->be_rootdn; + op->o_ndn = li->li_db->be_rootndn; + rc = be_entry_get_rw( op, li->li_db->be_nsuffix, NULL, NULL, 0, &e ); + + if ( e ) { + Attribute *a = attr_find( e->e_attrs, ad_minCSN ); + if ( !a ) { + /* TODO: find the lowest CSN we are safe to put in */ + a = attr_find( e->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + SlapReply rs = {REP_RESULT}; + Modifications mod; + BackendDB db = *li->li_db; + + op->o_bd = &db; + + mod.sml_numvals = a->a_numvals; + mod.sml_values = a->a_vals; + mod.sml_nvalues = a->a_nvals; + mod.sml_desc = ad_minCSN; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + mod.sml_next = NULL; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->o_callback = &nullsc; + SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD; + + Debug( LDAP_DEBUG_SYNC, "accesslog_db_root: " + "setting up minCSN with %d values\n", + a->a_numvals ); + + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + rc = op->o_bd->be_modify( op, &rs ); + } + } + if ( a ) { + ber_bvarray_dup_x( &li->li_mincsn, a->a_vals, NULL ); + li->li_numcsns = a->a_numvals; + li->li_sids = slap_parse_csn_sids( li->li_mincsn, li->li_numcsns, NULL ); + slap_sort_csn_sids( li->li_mincsn, li->li_sids, li->li_numcsns, NULL ); + } + be_entry_release_rw( op, e, 0 ); + } else { + SlapReply rs = {REP_RESULT}; + struct berval rdn, nrdn, attr; + char *ptr; + AttributeDescription *ad = NULL; + const char *text = NULL; + Entry *e_ctx; + BackendDB db; + + e = entry_alloc(); + ber_dupbv( &e->e_name, li->li_db->be_suffix ); + ber_dupbv( &e->e_nname, li->li_db->be_nsuffix ); + + attr_merge_one( e, slap_schema.si_ad_objectClass, + &log_container->soc_cname, NULL ); + + dnRdn( &e->e_name, &rdn ); + dnRdn( &e->e_nname, &nrdn ); + ptr = ber_bvchr( &rdn, '=' ); + + assert( ptr != NULL ); + + attr.bv_val = rdn.bv_val; + attr.bv_len = ptr - rdn.bv_val; + + slap_bv2ad( &attr, &ad, &text ); + + rdn.bv_val = ptr+1; + rdn.bv_len -= attr.bv_len + 1; + ptr = ber_bvchr( &nrdn, '=' ); + nrdn.bv_len -= ptr - nrdn.bv_val + 1; + nrdn.bv_val = ptr+1; + attr_merge_one( e, ad, &rdn, &nrdn ); + + /* Get contextCSN from main DB */ + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, op->o_bd->be_nsuffix, NULL, + slap_schema.si_ad_contextCSN, 0, &e_ctx ); + + if ( e_ctx ) { + Attribute *a; + + a = attr_find( e_ctx->e_attrs, slap_schema.si_ad_contextCSN ); + if ( a ) { + /* FIXME: contextCSN could have multiple values! + * should select the one with the server's SID */ + attr_merge_one( e, slap_schema.si_ad_entryCSN, + &a->a_vals[0], &a->a_nvals[0] ); + attr_merge( e, a->a_desc, a->a_vals, a->a_nvals ); + attr_merge( e, ad_minCSN, a->a_vals, a->a_nvals ); + } + be_entry_release_rw( op, e_ctx, 0 ); + } + db = *li->li_db; + op->o_bd = &db; + + op->o_tag = LDAP_REQ_ADD; + op->ora_e = e; + op->o_req_dn = e->e_name; + op->o_req_ndn = e->e_nname; + op->o_callback = &nullsc; + SLAP_DBFLAGS( op->o_bd ) |= SLAP_DBFLAG_NOLASTMOD; + rc = op->o_bd->be_add( op, &rs ); + if ( rs.sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_SYNC, "%s accesslog_db_root: " + "got result 0x%x adding log root entry %s\n", + op->o_log_prefix, rs.sr_err, op->o_req_dn.bv_val ); + } + if ( e == op->ora_e ) + entry_free( e ); + } + li->li_open = 1; + ldap_pvt_thread_mutex_unlock( &li->li_log_mutex ); + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_stoptask( &slapd_rq, rtask ); + ldap_pvt_runqueue_remove( &slapd_rq, rtask ); + + if ( li->li_age && li->li_cycle ) { + assert( li->li_task == NULL ); + li->li_task = ldap_pvt_runqueue_insert( &slapd_rq, + li->li_cycle, accesslog_purge, li, + "accesslog_purge", li->li_db->be_suffix[0].bv_val ); + } + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return NULL; +} + +static int +accesslog_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + + + if ( !BER_BVISEMPTY( &li->li_db_suffix )) { + li->li_db = select_backend( &li->li_db_suffix, 0 ); + ch_free( li->li_db_suffix.bv_val ); + BER_BVZERO( &li->li_db_suffix ); + } + if ( li->li_db == NULL ) { + Debug( LDAP_DEBUG_ANY, + "accesslog: \"logdb \" missing or invalid.\n" ); + return 1; + } + if ( li->li_db->bd_self == be->bd_self ) { + Debug( LDAP_DEBUG_ANY, + "accesslog: \"logdb \" is this database, cannot log to itself.\n" ); + return 1; + } + + if ( slapMode & SLAP_TOOL_MODE ) + return 0; + + if ( BER_BVISEMPTY( &li->li_db->be_rootndn )) { + ber_dupbv( &li->li_db->be_rootdn, li->li_db->be_suffix ); + ber_dupbv( &li->li_db->be_rootndn, li->li_db->be_nsuffix ); + } + + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + ldap_pvt_runqueue_insert( &slapd_rq, 3600, accesslog_db_root, on, + "accesslog_db_root", li->li_db->be_suffix[0].bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + + return 0; +} + +static int +accesslog_db_close( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + log_info *li = on->on_bi.bi_private; + struct re_s *re = li->li_task; + + li->li_open = 0; + + if ( re ) { + li->li_task = NULL; + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + if ( ldap_pvt_runqueue_isrunning( &slapd_rq, re ) ) + ldap_pvt_runqueue_stoptask( &slapd_rq, re ); + ldap_pvt_runqueue_remove( &slapd_rq, re ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + + return 0; +} + +enum { start = 0 }; + +static int +check_rdntime_syntax (struct berval *val, + int *parts, + struct berval *fraction) +{ + /* + * GeneralizedTime YYYYmmddHH[MM[SS]][(./,)d...](Z|(+/-)HH[MM]) + * GeneralizedTime supports leap seconds, UTCTime does not. + */ + static const int ceiling[9] = { 100, 100, 12, 31, 24, 60, 60, 24, 60 }; + static const int mdays[2][12] = { + /* non-leap years */ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + /* leap years */ + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } + }; + char *p, *e; + int part, c, c1, c2, tzoffset, leapyear = 0; + + p = val->bv_val; + e = p + val->bv_len; + + for (part = start; part < 7 && p < e; part++) { + c1 = *p; + if (!ASCII_DIGIT(c1)) { + break; + } + p++; + if (p == e) { + return LDAP_INVALID_SYNTAX; + } + c = *p++; + if (!ASCII_DIGIT(c)) { + return LDAP_INVALID_SYNTAX; + } + c += c1 * 10 - '0' * 11; + if ((part | 1) == 3) { + --c; + if (c < 0) { + return LDAP_INVALID_SYNTAX; + } + } + if (c >= ceiling[part]) { + if (! (c == 60 && part == 6 && start == 0)) + return LDAP_INVALID_SYNTAX; + } + parts[part] = c; + } + if (part < 5 + start) { + return LDAP_INVALID_SYNTAX; + } + for (; part < 9; part++) { + parts[part] = 0; + } + + /* leapyear check for the Gregorian calendar (year>1581) */ + if (parts[parts[1] == 0 ? 0 : 1] % 4 == 0) { + leapyear = 1; + } + + if (parts[3] >= mdays[leapyear][parts[2]]) { + return LDAP_INVALID_SYNTAX; + } + + if (start == 0) { + fraction->bv_val = p; + fraction->bv_len = 0; + if (p < e && (*p == '.' || *p == ',')) { + char *end_num; + while (++p < e && ASCII_DIGIT(*p)) { + /* EMPTY */; + } + if (p - fraction->bv_val == 1) { + return LDAP_INVALID_SYNTAX; + } + +#if 0 /* don't truncate trailing zeros */ + for (end_num = p; end_num[-1] == '0'; --end_num) { + /* EMPTY */; + } + c = end_num - fraction->bv_val; +#else + c = p - fraction->bv_val; +#endif + if (c != 1) fraction->bv_len = c; + } + } + + if (p == e) { + /* no time zone */ + return start == 0 ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; + } + + tzoffset = *p++; + switch (tzoffset) { + case 'Z': + /* UTC */ + break; + default: + return LDAP_INVALID_SYNTAX; + } + + return p != e ? LDAP_INVALID_SYNTAX : LDAP_SUCCESS; +} + +static int +rdnTimestampValidate( + Syntax *syntax, + struct berval *in ) +{ + int parts[9]; + struct berval fraction; + return check_rdntime_syntax(in, parts, &fraction); +} + +static int +rdnTimestampNormalize( + slap_mask_t usage, + Syntax *syntax, + MatchingRule *mr, + struct berval *val, + struct berval *normalized, + void *ctx ) +{ + int parts[9], rc; + unsigned int len; + struct berval fraction; + + rc = check_rdntime_syntax(val, parts, &fraction); + if (rc != LDAP_SUCCESS) { + return rc; + } + + len = STRLENOF("YYYYmmddHHMMSSZ") + fraction.bv_len; + normalized->bv_val = slap_sl_malloc( len + 1, ctx ); + if ( BER_BVISNULL( normalized ) ) { + return LBER_ERROR_MEMORY; + } + + sprintf( normalized->bv_val, "%02d%02d%02d%02d%02d%02d%02d", + parts[0], parts[1], parts[2] + 1, parts[3] + 1, + parts[4], parts[5], parts[6] ); + if ( !BER_BVISEMPTY( &fraction ) ) { + memcpy( normalized->bv_val + STRLENOF("YYYYmmddHHMMSSZ")-1, + fraction.bv_val, fraction.bv_len ); + normalized->bv_val[STRLENOF("YYYYmmddHHMMSSZ")-1] = '.'; + } + strcpy( normalized->bv_val + len-1, "Z" ); + normalized->bv_len = len; + + return LDAP_SUCCESS; +} + + +int accesslog_initialize() +{ + int i, rc; + Syntax *rdnTimestampSyntax; + MatchingRule *rdnTimestampMatch, *rdnTimestampOrdering; + + accesslog.on_bi.bi_type = "accesslog"; + accesslog.on_bi.bi_db_init = accesslog_db_init; + accesslog.on_bi.bi_db_destroy = accesslog_db_destroy; + accesslog.on_bi.bi_db_open = accesslog_db_open; + accesslog.on_bi.bi_db_close = accesslog_db_close; + + accesslog.on_bi.bi_op_add = accesslog_op_mod; + accesslog.on_bi.bi_op_bind = accesslog_op_misc; + accesslog.on_bi.bi_op_compare = accesslog_op_misc; + accesslog.on_bi.bi_op_delete = accesslog_op_mod; + accesslog.on_bi.bi_op_modify = accesslog_op_mod; + accesslog.on_bi.bi_op_modrdn = accesslog_op_mod; + accesslog.on_bi.bi_op_search = accesslog_op_misc; + accesslog.on_bi.bi_extended = accesslog_op_misc; + accesslog.on_bi.bi_op_unbind = accesslog_unbind; + accesslog.on_bi.bi_op_abandon = accesslog_abandon; + accesslog.on_bi.bi_operational = accesslog_operational; + + accesslog.on_bi.bi_cf_ocs = log_cfocs; + + nullsc.sc_response = slap_null_cb; + + rc = config_register_schema( log_cfats, log_cfocs ); + if ( rc ) return rc; + + /* log schema integration */ + for ( i=0; lsyntaxes[i].oid; i++ ) { + int code; + + code = register_syntax( &lsyntaxes[ i ].syn ); + if ( code != 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_syntax failed\n" ); + return code; + } + + if ( lsyntaxes[i].mrs != NULL ) { + code = mr_make_syntax_compat_with_mrs( + lsyntaxes[i].oid, lsyntaxes[i].mrs ); + if ( code < 0 ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: " + "mr_make_syntax_compat_with_mrs " + "failed\n" ); + return code; + } + } + } + + for ( i=0; lattrs[i].at; i++ ) { + int code; + + code = register_at( lattrs[i].at, lattrs[i].ad, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_at failed\n" ); + return -1; + } + } + + /* Inject custom normalizer for reqStart/reqEnd */ + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + rdnTimestampMatch->smr_normalize = rdnTimestampNormalize; + *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering; + rdnTimestampOrdering->smr_normalize = rdnTimestampNormalize; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + rdnTimestampSyntax->ssyn_validate = rdnTimestampValidate; + ad_reqStart->ad_type->sat_equality = rdnTimestampMatch; + ad_reqStart->ad_type->sat_ordering = rdnTimestampOrdering; + ad_reqStart->ad_type->sat_syntax = rdnTimestampSyntax; + + rdnTimestampMatch = ch_malloc( sizeof( MatchingRule )); + rdnTimestampOrdering = ch_malloc( sizeof( MatchingRule )); + rdnTimestampSyntax = ch_malloc( sizeof( Syntax )); + *rdnTimestampMatch = *ad_reqStart->ad_type->sat_equality; + *rdnTimestampOrdering = *ad_reqStart->ad_type->sat_ordering; + *rdnTimestampSyntax = *ad_reqStart->ad_type->sat_syntax; + ad_reqEnd->ad_type->sat_equality = rdnTimestampMatch; + ad_reqEnd->ad_type->sat_ordering = rdnTimestampOrdering; + ad_reqEnd->ad_type->sat_syntax = rdnTimestampSyntax; + + for ( i=0; locs[i].ot; i++ ) { + int code; + + code = register_oc( locs[i].ot, locs[i].oc, 0 ); + if ( code ) { + Debug( LDAP_DEBUG_ANY, + "accesslog_init: register_oc failed\n" ); + return -1; + } + } + + return overlay_register(&accesslog); +} + +#if SLAPD_OVER_ACCESSLOG == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return accesslog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_ACCESSLOG */ diff --git a/servers/slapd/overlays/auditlog.c b/servers/slapd/overlays/auditlog.c new file mode 100644 index 0000000..9292d4a --- /dev/null +++ b/servers/slapd/overlays/auditlog.c @@ -0,0 +1,242 @@ +/* auditlog.c - log modifications for audit/history purposes */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include +#include + +#include "slap.h" +#include "slap-config.h" +#include "ldif.h" + +typedef struct auditlog_data { + ldap_pvt_thread_mutex_t ad_mutex; + char *ad_logfile; +} auditlog_data; + +static ConfigTable auditlogcfg[] = { + { "auditlog", "filename", 2, 2, 0, + ARG_STRING|ARG_OFFSET, + (void *)offsetof(auditlog_data, ad_logfile), + "( OLcfgOvAt:15.1 NAME 'olcAuditlogFile' " + "DESC 'Filename for auditlogging' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs auditlogocs[] = { + { "( OLcfgOvOc:15.1 " + "NAME 'olcAuditlogConfig' " + "DESC 'Auditlog configuration' " + "SUP olcOverlayConfig " + "MAY ( olcAuditlogFile ) )", + Cft_Overlay, auditlogcfg }, + { NULL, 0, NULL } +}; + +static int fprint_ldif(FILE *f, char *name, char *val, ber_len_t len) { + char *s; + if((s = ldif_put(LDIF_PUT_VALUE, name, val, len)) == NULL) + return(-1); + fputs(s, f); + ber_memfree(s); + return(0); +} + +static int auditlog_response(Operation *op, SlapReply *rs) { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + FILE *f; + Attribute *a; + Modifications *m; + struct berval *b, *who = NULL, peername; + char *what, *whatm, *suffix; + time_t stamp; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) return SLAP_CB_CONTINUE; + + if ( !ad->ad_logfile ) return SLAP_CB_CONTINUE; + +/* +** add or modify: use modifiersName if present +** +*/ + switch(op->o_tag) { + case LDAP_REQ_MODRDN: what = "modrdn"; break; + case LDAP_REQ_DELETE: what = "delete"; break; + case LDAP_REQ_ADD: + what = "add"; + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if( a->a_desc == slap_schema.si_ad_modifiersName ) { + who = &a->a_vals[0]; + break; + } + break; + case LDAP_REQ_MODIFY: + what = "modify"; + for(m = op->orm_modlist; m; m = m->sml_next) + if( m->sml_desc == slap_schema.si_ad_modifiersName && + ( m->sml_op == LDAP_MOD_ADD || + m->sml_op == LDAP_MOD_REPLACE )) { + who = &m->sml_values[0]; + break; + } + break; + default: + return SLAP_CB_CONTINUE; + } + + suffix = op->o_bd->be_suffix[0].bv_len ? op->o_bd->be_suffix[0].bv_val : + "global"; + +/* +** note: this means requestor's dn when modifiersName is null +*/ + if ( !who ) + who = &op->o_dn; + + peername = op->o_conn->c_peer_name; + ldap_pvt_thread_mutex_lock(&ad->ad_mutex); + if((f = fopen(ad->ad_logfile, "a")) == NULL) { + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; + } + + stamp = slap_get_time(); + fprintf(f, "# %s %ld %s%s%s %s conn=%ld\n", + what, (long)stamp, suffix, who ? " " : "", who ? who->bv_val : "", + peername.bv_val ? peername.bv_val: "", op->o_conn->c_connid); + + if ( !BER_BVISEMPTY( &op->o_conn->c_dn ) && + (!who || !dn_match( who, &op->o_conn->c_dn ))) + fprintf(f, "# realdn: %s\n", op->o_conn->c_dn.bv_val ); + + fprintf(f, "dn: %s\nchangetype: %s\n", + op->o_req_dn.bv_val, what); + + switch(op->o_tag) { + case LDAP_REQ_ADD: + for(a = op->ora_e->e_attrs; a; a = a->a_next) + if((b = a->a_vals) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, a->a_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + break; + + case LDAP_REQ_MODIFY: + for(m = op->orm_modlist; m; m = m->sml_next) { + switch(m->sml_op & LDAP_MOD_OP) { + case LDAP_MOD_ADD: whatm = "add"; break; + case LDAP_MOD_REPLACE: whatm = "replace"; break; + case LDAP_MOD_DELETE: whatm = "delete"; break; + case LDAP_MOD_INCREMENT: whatm = "increment"; break; + default: + fprintf(f, "# MOD_TYPE_UNKNOWN:%02x\n", m->sml_op & LDAP_MOD_OP); + continue; + } + fprintf(f, "%s: %s\n", whatm, m->sml_desc->ad_cname.bv_val); + if((b = m->sml_values) != NULL) + for(i = 0; b[i].bv_val; i++) + fprint_ldif(f, m->sml_desc->ad_cname.bv_val, b[i].bv_val, b[i].bv_len); + fprintf(f, "-\n"); + } + break; + + case LDAP_REQ_MODRDN: + fprintf(f, "newrdn: %s\ndeleteoldrdn: %s\n", + op->orr_newrdn.bv_val, op->orr_deleteoldrdn ? "1" : "0"); + if(op->orr_newSup) fprintf(f, "newsuperior: %s\n", op->orr_newSup->bv_val); + break; + + case LDAP_REQ_DELETE: + /* nothing else needed */ + break; + } + + fprintf(f, "# end %s %ld\n\n", what, (long)stamp); + + fclose(f); + ldap_pvt_thread_mutex_unlock(&ad->ad_mutex); + return SLAP_CB_CONTINUE; +} + +static slap_overinst auditlog; + +static int +auditlog_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = ch_calloc(1, sizeof(auditlog_data)); + + on->on_bi.bi_private = ad; + ldap_pvt_thread_mutex_init( &ad->ad_mutex ); + return 0; +} + +static int +auditlog_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + auditlog_data *ad = on->on_bi.bi_private; + + ldap_pvt_thread_mutex_destroy( &ad->ad_mutex ); + free( ad->ad_logfile ); + free( ad ); + return 0; +} + +int auditlog_initialize() { + int rc; + + auditlog.on_bi.bi_type = "auditlog"; + auditlog.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + auditlog.on_bi.bi_db_init = auditlog_db_init; + auditlog.on_bi.bi_db_destroy = auditlog_db_destroy; + auditlog.on_response = auditlog_response; + + auditlog.on_bi.bi_cf_ocs = auditlogocs; + rc = config_register_schema( auditlogcfg, auditlogocs ); + if ( rc ) return rc; + + return overlay_register(&auditlog); +} + +#if SLAPD_OVER_AUDITLOG == SLAPD_MOD_DYNAMIC && defined(PIC) +int +init_module( int argc, char *argv[] ) +{ + return auditlog_initialize(); +} +#endif + +#endif /* SLAPD_OVER_AUDITLOG */ diff --git a/servers/slapd/overlays/autoca.c b/servers/slapd/overlays/autoca.c new file mode 100644 index 0000000..5fcd204 --- /dev/null +++ b/servers/slapd/overlays/autoca.c @@ -0,0 +1,1121 @@ +/* autoca.c - Automatic Certificate Authority */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_AUTOCA + +#include + +#include +#include + +#include "lutil.h" +#include "slap.h" +#include "slap-config.h" + +#include +#include +#include +#include + +/* 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 +#define X509_get_notBefore(x) X509_getm_notBefore(x) +#define X509_get_notAfter(x) X509_getm_notAfter(x) +#endif + +#if OPENSSL_VERSION_MAJOR >= 3 +#define BN_pseudo_rand(bn, bits, top, bottom) BN_rand(bn, bits, top, bottom) +#endif + +/* This overlay implements a certificate authority that can generate + * certificates automatically for any entry in the directory. + * On startup it generates a self-signed CA cert for the directory's + * suffix entry and uses this to sign all other certs that it generates. + * User and server certs are generated on demand, using a Search request. + */ + +#define LBER_TAG_OID ((ber_tag_t) 0x06UL) +#define LBER_TAG_UTF8 ((ber_tag_t) 0x0cUL) + +#define KEYBITS 2048 +#define MIN_KEYBITS 512 + +#define ACA_SCHEMA_ROOT "1.3.6.1.4.1.4203.666.11.11" + +#define ACA_SCHEMA_AT ACA_SCHEMA_ROOT ".1" +#define ACA_SCHEMA_OC ACA_SCHEMA_ROOT ".2" + +static AttributeDescription *ad_caCert, *ad_caPkey, *ad_usrCert, *ad_usrPkey; +static AttributeDescription *ad_mail, *ad_ipaddr; +static ObjectClass *oc_caObj, *oc_usrObj; + +static char *aca_attrs[] = { + "( " ACA_SCHEMA_AT ".1 NAME 'cAPrivateKey' " + "DESC 'X.509 CA private key, use ;binary' " + "SUP pKCS8PrivateKey )", + "( " ACA_SCHEMA_AT ".2 NAME 'userPrivateKey' " + "DESC 'X.509 user private key, use ;binary' " + "SUP pKCS8PrivateKey )", + NULL +}; + +static struct { + char *at; + AttributeDescription **ad; +} aca_attr2[] = { + { "cACertificate;binary", &ad_caCert }, + { "cAPrivateKey;binary", &ad_caPkey }, + { "userCertificate;binary", &ad_usrCert }, + { "userPrivateKey;binary", &ad_usrPkey }, + { "mail", &ad_mail }, + { NULL } +}; + +static struct { + char *ot; + ObjectClass **oc; +} aca_ocs[] = { + { "( " ACA_SCHEMA_OC ".1 NAME 'autoCA' " + "DESC 'Automated PKI certificate authority' " + "SUP pkiCA AUXILIARY " + "MAY cAPrivateKey )", &oc_caObj }, + { "( " ACA_SCHEMA_OC ".2 NAME 'autoCAuser' " + "DESC 'Automated PKI CA user' " + "SUP pkiUser AUXILIARY " + "MAY userPrivateKey )", &oc_usrObj }, + { NULL } +}; + +typedef struct autoca_info { + X509 *ai_cert; + EVP_PKEY *ai_pkey; + ObjectClass *ai_usrclass; + ObjectClass *ai_srvclass; + struct berval ai_localdn; + struct berval ai_localndn; + int ai_usrkeybits; + int ai_srvkeybits; + int ai_cakeybits; + int ai_usrdays; + int ai_srvdays; + int ai_cadays; +} autoca_info; + +/* Rewrite an LDAP DN in DER form + * Input must be valid DN, therefore no error checking is done here. + */ +static int autoca_dnbv2der( Operation *op, struct berval *bv, struct berval *der ) +{ + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + LDAPDN dn; + LDAPRDN rdn; + LDAPAVA *ava; + AttributeDescription *ad; + int irdn, iava; + + ldap_bv2dn_x( bv, &dn, LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx ); + + ber_init2( ber, NULL, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + /* count RDNs, we need them in reverse order */ + for (irdn = 0; dn[irdn]; irdn++); + irdn--; + + /* DN is a SEQuence of RDNs */ + ber_start_seq( ber, LBER_SEQUENCE ); + for (; irdn >=0; irdn--) + { + /* RDN is a SET of AVAs */ + ber_start_set( ber, LBER_SET ); + rdn = dn[irdn]; + for (iava = 0; rdn[iava]; iava++) + { + const char *text; + char oid[1024]; + struct berval bvo = { sizeof(oid), oid }; + struct berval bva; + + /* AVA is a SEQuence of attr and value */ + ber_start_seq( ber, LBER_SEQUENCE ); + ava = rdn[iava]; + ad = NULL; + slap_bv2ad( &ava->la_attr, &ad, &text ); + ber_str2bv( ad->ad_type->sat_oid, 0, 0, &bva ); + ber_encode_oid( &bva, &bvo ); + ber_put_berval( ber, &bvo, LBER_TAG_OID ); + ber_put_berval( ber, &ava->la_value, LBER_TAG_UTF8 ); + ber_put_seq( ber ); + } + ber_put_set( ber ); + } + ber_put_seq( ber ); + ber_flatten2( ber, der, 0 ); + ldap_dnfree_x( dn, op->o_tmpmemctx ); + return 0; +} + +static int autoca_genpkey(int bits, EVP_PKEY **pkey) +{ + EVP_PKEY_CTX *kctx; + int rc; + + kctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (kctx == NULL) + return -1; + if (EVP_PKEY_keygen_init(kctx) <= 0) + { + EVP_PKEY_CTX_free(kctx); + return -1; + } + if (EVP_PKEY_CTX_set_rsa_keygen_bits(kctx, bits) <= 0) + { + EVP_PKEY_CTX_free(kctx); + return -1; + } + rc = EVP_PKEY_keygen(kctx, pkey); + EVP_PKEY_CTX_free(kctx); + return rc; +} + +static int autoca_signcert(X509 *cert, EVP_PKEY *pkey) +{ + EVP_MD_CTX *ctx = EVP_MD_CTX_create(); + EVP_PKEY_CTX *pkctx = NULL; + int rc = -1; + + if ( ctx == NULL ) + return -1; + if (EVP_DigestSignInit(ctx, &pkctx, NULL, NULL, pkey)) + { + rc = X509_sign_ctx(cert, ctx); + } + EVP_MD_CTX_destroy(ctx); + return rc; +} + +#define SERIAL_BITS 64 /* should be less than 160 */ + +typedef struct myext { + char *name; + char *value; +} myext; + +static myext CAexts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "critical,CA:true" }, + { "keyUsage", "digitalSignature,cRLSign,keyCertSign" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +static myext usrExts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "CA:false" }, + { "keyUsage", "digitalSignature,nonRepudiation,keyEncipherment" }, + { "extendedKeyUsage", "clientAuth,emailProtection,codeSigning" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +static myext srvExts[] = { + { "subjectKeyIdentifier", "hash" }, + { "authorityKeyIdentifier", "keyid:always,issuer" }, + { "basicConstraints", "CA:false" }, + { "keyUsage", "digitalSignature,keyEncipherment" }, + { "extendedKeyUsage", "serverAuth,clientAuth" }, + { "nsComment", "OpenLDAP automatic certificate" }, + { NULL } +}; + +typedef struct genargs { + X509 *issuer_cert; + EVP_PKEY *issuer_pkey; + struct berval *subjectDN; + myext *cert_exts; + myext *more_exts; + X509 *newcert; + EVP_PKEY *newpkey; + struct berval dercert; + struct berval derpkey; + int keybits; + int days; +} genargs; + +static int autoca_gencert( Operation *op, genargs *args ) +{ + X509_NAME *subj_name, *issuer_name; + X509 *subj_cert; + struct berval derdn; + unsigned char *pp; + EVP_PKEY *evpk = NULL; + int rc; + + if ((subj_cert = X509_new()) == NULL) + return -1; + + autoca_dnbv2der( op, args->subjectDN, &derdn ); + pp = (unsigned char *)derdn.bv_val; + subj_name = d2i_X509_NAME( NULL, (const unsigned char **)&pp, derdn.bv_len ); + op->o_tmpfree( derdn.bv_val, op->o_tmpmemctx ); + if ( subj_name == NULL ) + { +fail1: + X509_free( subj_cert ); + return -1; + } + + rc = autoca_genpkey( args->keybits, &evpk ); + if ( rc <= 0 ) + { +fail2: + if ( subj_name ) X509_NAME_free( subj_name ); + goto fail1; + } + /* encode DER in PKCS#8 */ + { + PKCS8_PRIV_KEY_INFO *p8inf; + if (( p8inf = EVP_PKEY2PKCS8( evpk )) == NULL ) + goto fail2; + args->derpkey.bv_len = i2d_PKCS8_PRIV_KEY_INFO( p8inf, NULL ); + args->derpkey.bv_val = op->o_tmpalloc( args->derpkey.bv_len, op->o_tmpmemctx ); + pp = (unsigned char *)args->derpkey.bv_val; + i2d_PKCS8_PRIV_KEY_INFO( p8inf, &pp ); + PKCS8_PRIV_KEY_INFO_free( p8inf ); + } + args->newpkey = evpk; + + /* set random serial */ + { + BIGNUM *bn = BN_new(); + if ( bn == NULL ) + { +fail3: + EVP_PKEY_free( evpk ); + goto fail2; + } + if (!BN_pseudo_rand(bn, SERIAL_BITS, 0, 0)) + { + BN_free( bn ); + goto fail3; + } + if (!BN_to_ASN1_INTEGER(bn, X509_get_serialNumber(subj_cert))) + { + BN_free( bn ); + goto fail3; + } + BN_free(bn); + } + if (args->issuer_cert) { + issuer_name = X509_get_subject_name(args->issuer_cert); + } else { + issuer_name = subj_name; + args->issuer_cert = subj_cert; + args->issuer_pkey = evpk; + } + if (!X509_set_version(subj_cert, 2) || /* set version to V3 */ + !X509_set_issuer_name(subj_cert, issuer_name) || + !X509_set_subject_name(subj_cert, subj_name) || + !X509_gmtime_adj(X509_get_notBefore(subj_cert), 0) || + !X509_time_adj_ex(X509_get_notAfter(subj_cert), args->days, 0, NULL) || + !X509_set_pubkey(subj_cert, evpk)) + { + goto fail3; + } + X509_NAME_free(subj_name); + subj_name = NULL; + + /* set cert extensions */ + { + X509V3_CTX ctx; + X509_EXTENSION *ext; + int i; + + X509V3_set_ctx(&ctx, args->issuer_cert, subj_cert, NULL, NULL, 0); + for (i=0; args->cert_exts[i].name; i++) { + ext = X509V3_EXT_nconf(NULL, &ctx, args->cert_exts[i].name, args->cert_exts[i].value); + if ( ext == NULL ) + goto fail3; + rc = X509_add_ext(subj_cert, ext, -1); + X509_EXTENSION_free(ext); + if ( !rc ) + goto fail3; + } + if (args->more_exts) { + for (i=0; args->more_exts[i].name; i++) { + ext = X509V3_EXT_nconf(NULL, &ctx, args->more_exts[i].name, args->more_exts[i].value); + if ( ext == NULL ) + goto fail3; + rc = X509_add_ext(subj_cert, ext, -1); + X509_EXTENSION_free(ext); + if ( !rc ) + goto fail3; + } + } + } + rc = autoca_signcert( subj_cert, args->issuer_pkey ); + if ( rc < 0 ) + goto fail3; + args->dercert.bv_len = i2d_X509( subj_cert, NULL ); + args->dercert.bv_val = op->o_tmpalloc( args->dercert.bv_len, op->o_tmpmemctx ); + pp = (unsigned char *)args->dercert.bv_val; + i2d_X509( subj_cert, &pp ); + args->newcert = subj_cert; + return 0; +} + +typedef struct saveargs { + ObjectClass *oc; + struct berval *dercert; + struct berval *derpkey; + slap_overinst *on; + struct berval *dn; + struct berval *ndn; + int isca; +} saveargs; + +static int autoca_savecert( Operation *op, saveargs *args ) +{ + Modifications mod[3], *mp = mod; + struct berval bvs[6], *bp = bvs; + BackendInfo *bi; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + + if ( args->oc ) { + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = slap_schema.si_ad_objectClass; + mp->sml_op = LDAP_MOD_ADD; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = args->oc->soc_cname; + BER_BVZERO( bp ); + bp++; + mp->sml_next = mp+1; + mp++; + } + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = args->isca ? ad_caCert : ad_usrCert; + mp->sml_op = LDAP_MOD_REPLACE; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = *args->dercert; + BER_BVZERO( bp ); + bp++; + mp->sml_next = mp+1; + mp++; + + mp->sml_numvals = 1; + mp->sml_values = bp; + mp->sml_nvalues = NULL; + mp->sml_desc = args->isca ? ad_caPkey : ad_usrPkey; + mp->sml_op = LDAP_MOD_ADD; + mp->sml_flags = SLAP_MOD_INTERNAL; + *bp++ = *args->derpkey; + BER_BVZERO( bp ); + mp->sml_next = NULL; + + cb.sc_response = slap_null_cb; + bi = op->o_bd->bd_info; + op->o_bd->bd_info = args->on->on_info->oi_orig; + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = mod; + op->orm_no_opattrs = 1; + op->o_req_dn = *args->dn; + op->o_req_ndn = *args->ndn; + op->o_bd->be_modify( op, &rs ); + op->o_bd->bd_info = bi; + return rs.sr_err; +} + +static const struct berval configDN = BER_BVC("cn=config"); + +/* must run as a pool thread to avoid cn=config deadlock */ +static void * +autoca_setca_task( void *ctx, void *arg ) +{ + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + struct berval *cacert = arg; + Modifications mod; + struct berval bvs[2]; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + const char *text; + + connection_fake_init( &conn, &opbuf, ctx ); + op = &opbuf.ob_op; + + mod.sml_numvals = 1; + mod.sml_values = bvs; + mod.sml_nvalues = NULL; + mod.sml_desc = NULL; + if ( slap_str2ad( "olcTLSCACertificate;binary", &mod.sml_desc, &text )) + goto leave; + mod.sml_op = LDAP_MOD_REPLACE; + mod.sml_flags = SLAP_MOD_INTERNAL; + bvs[0] = *cacert; + BER_BVZERO( &bvs[1] ); + mod.sml_next = NULL; + + cb.sc_response = slap_null_cb; + op->o_bd = select_backend( (struct berval *)&configDN, 0 ); + if ( !op->o_bd ) + goto leave; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = &mod; + op->orm_no_opattrs = 1; + op->o_req_dn = configDN; + op->o_req_ndn = configDN; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->be_modify( op, &rs ); +leave: + ch_free( arg ); + return NULL; +} + +static int +autoca_setca( struct berval *cacert ) +{ + struct berval *bv = ch_malloc( sizeof(struct berval) + cacert->bv_len ); + bv->bv_len = cacert->bv_len; + bv->bv_val = (char *)(bv+1); + AC_MEMCPY( bv->bv_val, cacert->bv_val, bv->bv_len ); + return ldap_pvt_thread_pool_submit( &connection_pool, autoca_setca_task, bv ); +} + +static int +autoca_setlocal( Operation *op, struct berval *cert, struct berval *pkey ) +{ + Modifications mod[2]; + struct berval bvs[4]; + slap_callback cb = {0}; + SlapReply rs = {REP_RESULT}; + const char *text; + + mod[0].sml_numvals = 1; + mod[0].sml_values = bvs; + mod[0].sml_nvalues = NULL; + mod[0].sml_desc = NULL; + if ( slap_str2ad( "olcTLSCertificate;binary", &mod[0].sml_desc, &text )) + return -1; + mod[0].sml_op = LDAP_MOD_REPLACE; + mod[0].sml_flags = SLAP_MOD_INTERNAL; + bvs[0] = *cert; + BER_BVZERO( &bvs[1] ); + mod[0].sml_next = &mod[1]; + + mod[1].sml_numvals = 1; + mod[1].sml_values = &bvs[2]; + mod[1].sml_nvalues = NULL; + mod[1].sml_desc = NULL; + if ( slap_str2ad( "olcTLSCertificateKey;binary", &mod[1].sml_desc, &text )) + return -1; + mod[1].sml_op = LDAP_MOD_REPLACE; + mod[1].sml_flags = SLAP_MOD_INTERNAL; + bvs[2] = *pkey; + BER_BVZERO( &bvs[3] ); + mod[1].sml_next = NULL; + + cb.sc_response = slap_null_cb; + op->o_bd = select_backend( (struct berval *)&configDN, 0 ); + if ( !op->o_bd ) + return -1; + + op->o_tag = LDAP_REQ_MODIFY; + op->o_callback = &cb; + op->orm_modlist = mod; + op->orm_no_opattrs = 1; + op->o_req_dn = configDN; + op->o_req_ndn = configDN; + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->be_modify( op, &rs ); + return rs.sr_err; +} + +enum { + ACA_USRCLASS = 1, + ACA_SRVCLASS, + ACA_USRKEYBITS, + ACA_SRVKEYBITS, + ACA_CAKEYBITS, + ACA_USRDAYS, + ACA_SRVDAYS, + ACA_CADAYS, + ACA_LOCALDN +}; + +static int autoca_cf( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + autoca_info *ai = on->on_bi.bi_private; + int rc = 0; + + switch( c->op ) { + case SLAP_CONFIG_EMIT: + switch( c->type ) { + case ACA_USRCLASS: + if ( ai->ai_usrclass ) { + c->value_string = ch_strdup( ai->ai_usrclass->soc_cname.bv_val ); + } else { + rc = 1; + } + break; + case ACA_SRVCLASS: + if ( ai->ai_srvclass ) { + c->value_string = ch_strdup( ai->ai_srvclass->soc_cname.bv_val ); + } else { + rc = 1; + } + break; + case ACA_USRKEYBITS: + c->value_int = ai->ai_usrkeybits; + break; + case ACA_SRVKEYBITS: + c->value_int = ai->ai_srvkeybits; + break; + case ACA_CAKEYBITS: + c->value_int = ai->ai_cakeybits; + break; + case ACA_USRDAYS: + c->value_int = ai->ai_usrdays; + break; + case ACA_SRVDAYS: + c->value_int = ai->ai_srvdays; + break; + case ACA_CADAYS: + c->value_int = ai->ai_cadays; + break; + case ACA_LOCALDN: + if ( !BER_BVISNULL( &ai->ai_localdn )) { + rc = value_add_one( &c->rvalue_vals, &ai->ai_localdn ); + } else { + rc = 1; + } + break; + } + break; + case LDAP_MOD_DELETE: + switch( c->type ) { + case ACA_USRCLASS: + ai->ai_usrclass = NULL; + break; + case ACA_SRVCLASS: + ai->ai_srvclass = NULL; + break; + case ACA_LOCALDN: + if ( ai->ai_localdn.bv_val ) { + ch_free( ai->ai_localdn.bv_val ); + ch_free( ai->ai_localndn.bv_val ); + BER_BVZERO( &ai->ai_localdn ); + BER_BVZERO( &ai->ai_localndn ); + } + break; + /* single-valued attrs, all no-ops */ + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch( c->type ) { + case ACA_USRCLASS: + { + ObjectClass *oc = oc_find( c->value_string ); + if ( oc ) + ai->ai_usrclass = oc; + else + rc = 1; + } + break; + case ACA_SRVCLASS: + { + ObjectClass *oc = oc_find( c->value_string ); + if ( oc ) + ai->ai_srvclass = oc; + else + rc = 1; + } + case ACA_USRKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_usrkeybits = c->value_int; + break; + case ACA_SRVKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_srvkeybits = c->value_int; + break; + case ACA_CAKEYBITS: + if ( c->value_int < MIN_KEYBITS ) + rc = 1; + else + ai->ai_cakeybits = c->value_int; + break; + case ACA_USRDAYS: + ai->ai_usrdays = c->value_int; + break; + case ACA_SRVDAYS: + ai->ai_srvdays = c->value_int; + break; + case ACA_CADAYS: + ai->ai_cadays = c->value_int; + break; + case ACA_LOCALDN: + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix must be set" ); + Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( !dnIsSuffix( &c->value_ndn, c->be->be_nsuffix )) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "DN is not a subordinate of backend" ); + Debug( LDAP_DEBUG_CONFIG, "autoca_config: %s\n", + c->cr_msg ); + rc = ARG_BAD_CONF; + break; + } + if ( ai->ai_localdn.bv_val ) { + ch_free( ai->ai_localdn.bv_val ); + ch_free( ai->ai_localndn.bv_val ); + } + ai->ai_localdn = c->value_dn; + ai->ai_localndn = c->value_ndn; + } + } + return rc; +} + +static ConfigTable autoca_cfg[] = { + { "userClass", "objectclass", 2, 2, 0, + ARG_STRING|ARG_MAGIC|ACA_USRCLASS, autoca_cf, + "( OLcfgOvAt:22.1 NAME 'olcAutoCAuserClass' " + "DESC 'ObjectClass of user entries' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "serverClass", "objectclass", 2, 2, 0, + ARG_STRING|ARG_MAGIC|ACA_SRVCLASS, autoca_cf, + "( OLcfgOvAt:22.2 NAME 'olcAutoCAserverClass' " + "DESC 'ObjectClass of server entries' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL }, + { "userKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_USRKEYBITS, autoca_cf, + "( OLcfgOvAt:22.3 NAME 'olcAutoCAuserKeybits' " + "DESC 'Size of PrivateKey for user entries' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "serverKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_SRVKEYBITS, autoca_cf, + "( OLcfgOvAt:22.4 NAME 'olcAutoCAserverKeybits' " + "DESC 'Size of PrivateKey for server entries' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "caKeybits", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_CAKEYBITS, autoca_cf, + "( OLcfgOvAt:22.5 NAME 'olcAutoCAKeybits' " + "DESC 'Size of PrivateKey for CA certificate' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "userDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_USRDAYS, autoca_cf, + "( OLcfgOvAt:22.6 NAME 'olcAutoCAuserDays' " + "DESC 'Lifetime of user certificates in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "serverDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_SRVDAYS, autoca_cf, + "( OLcfgOvAt:22.7 NAME 'olcAutoCAserverDays' " + "DESC 'Lifetime of server certificates in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "caDays", "integer", 2, 2, 0, + ARG_INT|ARG_MAGIC|ACA_CADAYS, autoca_cf, + "( OLcfgOvAt:22.8 NAME 'olcAutoCADays' " + "DESC 'Lifetime of CA certificate in days' " + "EQUALITY integerMatch " + "SYNTAX OMsInteger SINGLE-VALUE )", NULL, NULL }, + { "localdn", "dn", 2, 2, 0, + ARG_DN|ARG_QUOTE|ARG_MAGIC|ACA_LOCALDN, autoca_cf, + "( OLcfgOvAt:22.9 NAME 'olcAutoCAlocalDN' " + "DESC 'DN of local server cert' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", NULL, NULL }, + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs autoca_ocs[] = { + { "( OLcfgOvOc:22.1 " + "NAME 'olcAutoCAConfig' " + "DESC 'AutoCA configuration' " + "SUP olcOverlayConfig " + "MAY ( olcAutoCAuserClass $ olcAutoCAserverClass $ " + "olcAutoCAuserKeybits $ olcAutoCAserverKeybits $ olcAutoCAKeyBits $ " + "olcAutoCAuserDays $ olcAutoCAserverDays $ olcAutoCADays $ " + "olcAutoCAlocalDN ) )", + Cft_Overlay, autoca_cfg }, + { NULL, 0, NULL } +}; + +static int +autoca_op_response( + Operation *op, + SlapReply *rs +) +{ + slap_overinst *on = op->o_callback->sc_private; + autoca_info *ai = on->on_bi.bi_private; + Attribute *a; + int isusr = 0; + + if (rs->sr_type != REP_SEARCH) + return SLAP_CB_CONTINUE; + + /* If root or self */ + if ( !be_isroot( op ) && + !dn_match( &rs->sr_entry->e_nname, &op->o_ndn )) + return SLAP_CB_CONTINUE; + + isusr = is_entry_objectclass( rs->sr_entry, ai->ai_usrclass, SLAP_OCF_CHECK_SUP ); + if ( !isusr ) + { + if (!is_entry_objectclass( rs->sr_entry, ai->ai_srvclass, SLAP_OCF_CHECK_SUP )) + return SLAP_CB_CONTINUE; + } + a = attr_find( rs->sr_entry->e_attrs, ad_usrPkey ); + if ( !a ) + { + Operation op2; + genargs args; + saveargs arg2; + myext extras[2]; + int rc; + + args.issuer_cert = ai->ai_cert; + args.issuer_pkey = ai->ai_pkey; + args.subjectDN = &rs->sr_entry->e_name; + args.more_exts = NULL; + if ( isusr ) + { + args.cert_exts = usrExts; + args.keybits = ai->ai_usrkeybits; + args.days = ai->ai_usrdays; + a = attr_find( rs->sr_entry->e_attrs, ad_mail ); + if ( a ) + { + extras[0].name = "subjectAltName"; + extras[1].name = NULL; + extras[0].value = op->o_tmpalloc( sizeof("email:") + a->a_vals[0].bv_len, op->o_tmpmemctx ); + sprintf(extras[0].value, "email:%s", a->a_vals[0].bv_val); + args.more_exts = extras; + } + } else + { + args.cert_exts = srvExts; + args.keybits = ai->ai_srvkeybits; + args.days = ai->ai_srvdays; + if ( ad_ipaddr && (a = attr_find( rs->sr_entry->e_attrs, ad_ipaddr ))) + { + extras[0].name = "subjectAltName"; + extras[1].name = NULL; + extras[0].value = op->o_tmpalloc( sizeof("IP:") + a->a_vals[0].bv_len, op->o_tmpmemctx ); + sprintf(extras[0].value, "IP:%s", a->a_vals[0].bv_val); + args.more_exts = extras; + } + } + rc = autoca_gencert( op, &args ); + if ( rc ) + return SLAP_CB_CONTINUE; + X509_free( args.newcert ); + EVP_PKEY_free( args.newpkey ); + + if ( is_entry_objectclass( rs->sr_entry, oc_usrObj, 0 )) + arg2.oc = NULL; + else + arg2.oc = oc_usrObj; + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE )) + { + Entry *e = entry_dup( rs->sr_entry ); + rs_replace_entry( op, rs, on, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + arg2.dercert = &args.dercert; + arg2.derpkey = &args.derpkey; + arg2.on = on; + arg2.dn = &rs->sr_entry->e_name; + arg2.ndn = &rs->sr_entry->e_nname; + arg2.isca = 0; + op2 = *op; + rc = autoca_savecert( &op2, &arg2 ); + if ( !rc ) + { + /* If this is our cert DN, configure it */ + if ( dn_match( &rs->sr_entry->e_nname, &ai->ai_localndn )) + autoca_setlocal( &op2, &args.dercert, &args.derpkey ); + attr_merge_one( rs->sr_entry, ad_usrCert, &args.dercert, NULL ); + attr_merge_one( rs->sr_entry, ad_usrPkey, &args.derpkey, NULL ); + } + op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx ); + } + + return SLAP_CB_CONTINUE; +} + +static int +autoca_op_search( + Operation *op, + SlapReply *rs +) +{ + /* we only act on a search that returns just our cert/key attrs */ + if ( op->ors_attrs && op->ors_attrs[0].an_desc == ad_usrCert && + op->ors_attrs[1].an_desc == ad_usrPkey && + op->ors_attrs[2].an_name.bv_val == NULL ) + { + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *sc = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx ); + sc->sc_response = autoca_op_response; + sc->sc_private = on; + sc->sc_next = op->o_callback; + op->o_callback = sc; + } + return SLAP_CB_CONTINUE; +} + +static int +autoca_db_init( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + autoca_info *ai; + + ai = ch_calloc(1, sizeof(autoca_info)); + on->on_bi.bi_private = ai; + + /* set defaults */ + ai->ai_usrclass = oc_find( "person" ); + ai->ai_srvclass = oc_find( "ipHost" ); + ai->ai_usrkeybits = KEYBITS; + ai->ai_srvkeybits = KEYBITS; + ai->ai_cakeybits = KEYBITS; + ai->ai_usrdays = 365; /* 1 year */ + ai->ai_srvdays = 1826; /* 5 years */ + ai->ai_cadays = 3652; /* 10 years */ + return 0; +} + +static int +autoca_db_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + autoca_info *ai = on->on_bi.bi_private; + + if ( ai->ai_cert ) + X509_free( ai->ai_cert ); + if ( ai->ai_pkey ) + EVP_PKEY_free( ai->ai_pkey ); + ch_free( ai ); + + return 0; +} + +static int +autoca_db_open( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + autoca_info *ai = on->on_bi.bi_private; + + Connection conn = { 0 }; + OperationBuffer opbuf; + Operation *op; + void *thrctx; + Entry *e = NULL; + Attribute *a; + int rc; + + if (slapMode & SLAP_TOOL_MODE) + return 0; + + if ( ! *aca_attr2[0].ad ) { + int i, code; + const char *text; + + for ( i=0; aca_attr2[i].at; i++ ) { + code = slap_str2ad( aca_attr2[i].at, aca_attr2[i].ad, &text ); + if ( code ) return code; + } + + /* Schema may not be loaded, ignore if missing */ + slap_str2ad( "ipHostNumber", &ad_ipaddr, &text ); + + for ( i=0; aca_ocs[i].ot; i++ ) { + code = register_oc( aca_ocs[i].ot, aca_ocs[i].oc, 0 ); + if ( code ) return code; + } + } + + thrctx = ldap_pvt_thread_pool_context(); + connection_fake_init2( &conn, &opbuf, thrctx, 0 ); + op = &opbuf.ob_op; + op->o_bd = be; + op->o_dn = be->be_rootdn; + op->o_ndn = be->be_rootndn; + rc = overlay_entry_get_ov( op, be->be_nsuffix, NULL, + NULL, 0, &e, on ); + + if ( e ) { + int gotoc = 0, gotat = 0; + if ( is_entry_objectclass( e, oc_caObj, 0 )) { + gotoc = 1; + a = attr_find( e->e_attrs, ad_caPkey ); + if ( a ) { + const unsigned char *pp; + pp = (unsigned char *)a->a_vals[0].bv_val; + ai->ai_pkey = d2i_AutoPrivateKey( NULL, &pp, a->a_vals[0].bv_len ); + if ( ai->ai_pkey ) + { + a = attr_find( e->e_attrs, ad_caCert ); + if ( a ) + { + pp = (unsigned char *)a->a_vals[0].bv_val; + ai->ai_cert = d2i_X509( NULL, &pp, a->a_vals[0].bv_len ); + /* If TLS wasn't configured yet, set this as our CA */ + if ( !slap_tls_ctx ) + autoca_setca( a->a_vals ); + } + } + gotat = 1; + } + } + overlay_entry_release_ov( op, e, 0, on ); + /* generate attrs, store... */ + if ( !gotat ) { + genargs args; + saveargs arg2; + + args.issuer_cert = NULL; + args.issuer_pkey = NULL; + args.subjectDN = &be->be_suffix[0]; + args.cert_exts = CAexts; + args.more_exts = NULL; + args.keybits = ai->ai_cakeybits; + args.days = ai->ai_cadays; + + rc = autoca_gencert( op, &args ); + if ( rc ) + return -1; + + ai->ai_cert = args.newcert; + ai->ai_pkey = args.newpkey; + + arg2.dn = be->be_suffix; + arg2.ndn = be->be_nsuffix; + arg2.isca = 1; + if ( !gotoc ) + arg2.oc = oc_caObj; + else + arg2.oc = NULL; + arg2.on = on; + arg2.dercert = &args.dercert; + arg2.derpkey = &args.derpkey; + + autoca_savecert( op, &arg2 ); + + /* If TLS wasn't configured yet, set this as our CA */ + if ( !slap_tls_ctx ) + autoca_setca( &args.dercert ); + + op->o_tmpfree( args.dercert.bv_val, op->o_tmpmemctx ); + op->o_tmpfree( args.derpkey.bv_val, op->o_tmpmemctx ); + } + } + + return 0; +} + +static slap_overinst autoca; + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +int autoca_initialize() { + int i, code; + + autoca.on_bi.bi_type = "autoca"; + autoca.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + autoca.on_bi.bi_db_init = autoca_db_init; + autoca.on_bi.bi_db_destroy = autoca_db_destroy; + autoca.on_bi.bi_db_open = autoca_db_open; + autoca.on_bi.bi_op_search = autoca_op_search; + + autoca.on_bi.bi_cf_ocs = autoca_ocs; + code = config_register_schema( autoca_cfg, autoca_ocs ); + if ( code ) return code; + + for ( i=0; aca_attrs[i]; i++ ) { + code = register_at( aca_attrs[i], NULL, 0 ); + if ( code ) return code; + } + + return overlay_register( &autoca ); +} + +#if SLAPD_OVER_AUTOCA == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return autoca_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_AUTOCA) */ diff --git a/servers/slapd/overlays/collect.c b/servers/slapd/overlays/collect.c new file mode 100644 index 0000000..bbc6219 --- /dev/null +++ b/servers/slapd/overlays/collect.c @@ -0,0 +1,440 @@ +/* collect.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_COLLECT + +#include + +#include +#include + +#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> 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; idxci_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; idxci_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; idxci_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 (idxon_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; idxci_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; idxci_ad_num; idx++) { + BerVarray vals = NULL; + + /* Extract the values of the desired attribute from + * the ancestor entry */ + rc = backend_attribute( op, NULL, &ci->ci_dn, + ci->ci_ad[idx], &vals, ACL_READ ); + + /* If there are any values, merge them into the + * current search result + */ + if ( vals ) { + attr_merge_normalize( rs->sr_entry, ci->ci_ad[idx], + vals, op->o_tmpmemctx ); + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + } + } + + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static slap_overinst collect; + +int collect_initialize() { + int code; + + collect.on_bi.bi_type = "collect"; + collect.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + collect.on_bi.bi_db_destroy = collect_destroy; + collect.on_bi.bi_op_modify = collect_modify; + collect.on_response = collect_response; + + collect.on_bi.bi_cf_ocs = collectocs; + code = config_register_schema( collectcfg, collectocs ); + if ( code ) return code; + + return overlay_register( &collect ); +} + +#if SLAPD_OVER_COLLECT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return collect_initialize(); +} +#endif + +#endif /* SLAPD_OVER_COLLECT */ diff --git a/servers/slapd/overlays/constraint.c b/servers/slapd/overlays/constraint.c new file mode 100644 index 0000000..f939b37 --- /dev/null +++ b/servers/slapd/overlays/constraint.c @@ -0,0 +1,1236 @@ +/* $OpenLDAP$ */ +/* constraint.c - Overlay to constrain attributes to certain values */ +/* + * Copyright 2003-2004 Hewlett-Packard Company + * Copyright 2007 Emmanuel Dreyfus + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ +/* + * Authors: Neil Dunbar + * Emmanuel Dreyfus + */ +#include "portable.h" + +#ifdef SLAPD_OVER_CONSTRAINT + +#include + +#include +#include +#include + +#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) []", + 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) && (ivalx); + i++, cpp = &cp->ap_next, cp = *cpp); + + if (cp) { + /* zap cp, and join cpp to cp->ap_next */ + *cpp = cp->ap_next; + constraint_free( cp, 1 ); + } + on->on_bi.bi_private = cn; + } + break; + + default: + abort(); + break; + } + break; + case SLAP_CONFIG_ADD: + case LDAP_MOD_ADD: + switch (c->type) { + case CONSTRAINT_ATTRIBUTE: { + int j; + char **attrs = ldap_str2charray( c->argv[1], "," ); + + for ( j = 0; attrs[j]; j++) + /* just count */ ; + ap.ap = ch_calloc( sizeof(AttributeDescription*), j + 1 ); + for ( j = 0; attrs[j]; j++) { + if ( slap_str2ad( attrs[j], &ap.ap[j], &text ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], attrs[j], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + + int is_regex = strcasecmp( c->argv[2], REGEX_STR ) == 0; + int is_neg_regex = strcasecmp( c->argv[2], NEG_REGEX_STR ) == 0; + if ( is_regex || is_neg_regex ) { + int err; + + ap.type = is_regex ? CONSTRAINT_REGEX : CONSTRAINT_NEG_REGEX; + ap.re = ch_malloc( sizeof(regex_t) ); + if ((err = regcomp( ap.re, + c->argv[3], REG_EXTENDED )) != 0) { + char errmsg[1024]; + + regerror( err, ap.re, errmsg, sizeof(errmsg) ); + ch_free(ap.re); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Illegal regular expression \"%s\": Error %s", + c->argv[0], c->argv[1], c->argv[3], errmsg); + ap.re = NULL; + rc = ARG_BAD_CONF; + goto done; + } + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + } else if ( strcasecmp( c->argv[2], SIZE_STR ) == 0 ) { + size_t size; + char *endptr; + + ap.type = CONSTRAINT_SIZE; + ap.size = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], COUNT_STR ) == 0 ) { + size_t count; + char *endptr; + + ap.type = CONSTRAINT_COUNT; + ap.count = strtoull(c->argv[3], &endptr, 10); + if ( *endptr ) + rc = ARG_BAD_CONF; + } else if ( strcasecmp( c->argv[2], URI_STR ) == 0 ) { + int err; + + ap.type = CONSTRAINT_URI; + err = ldap_url_parse(c->argv[3], &ap.lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in URI \"%s\"", + c->argv[0], c->argv[1], c->argv[3]); + ldap_free_urldesc(ap.lud); + rc = ARG_BAD_CONF; + goto done; + } + + for ( i=0; ap.lud->lud_attrs[i]; i++); + /* FIXME: This is worthless without at least one attr */ + if ( i ) { + ap.attrs = ch_malloc( (i+1)*sizeof(AttributeDescription *)); + for ( i=0; ap.lud->lud_attrs[i]; i++) { + ap.attrs[i] = NULL; + if ( slap_str2ad( ap.lud->lud_attrs[i], &ap.attrs[i], &text ) ) { + ch_free( ap.attrs ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s <%s>: %s\n", c->argv[0], ap.lud->lud_attrs[i], text ); + rc = ARG_BAD_CONF; + goto done; + } + } + ap.attrs[i] = NULL; + } + + if (ap.lud->lud_dn == NULL) { + ap.lud->lud_dn = ch_strdup(""); + } else { + struct berval dn, ndn; + + ber_str2bv( ap.lud->lud_dn, 0, 0, &dn ); + if (dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) ) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: URI %s DN normalization failed", + c->argv[0], c->argv[1], c->argv[3] ); + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memfree( ap.lud->lud_dn ); + ap.lud->lud_dn = ndn.bv_val; + } + + if (ap.lud->lud_filter == NULL) { + ap.lud->lud_filter = ch_strdup("objectClass=*"); + } else if ( ap.lud->lud_filter[0] == '(' ) { + ber_len_t len = strlen( ap.lud->lud_filter ); + if ( ap.lud->lud_filter[len - 1] != ')' ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: invalid URI filter: %s", + c->argv[0], c->argv[1], ap.lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + AC_MEMCPY( &ap.lud->lud_filter[0], &ap.lud->lud_filter[1], len - 2 ); + ap.lud->lud_filter[len - 2] = '\0'; + } + + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + + } else if ( strcasecmp( c->argv[2], SET_STR ) == 0 ) { + ap.set = 1; + ber_str2bv( c->argv[3], 0, 1, &ap.val ); + ap.type = CONSTRAINT_SET; + + } else { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Unknown constraint type: %s", + c->argv[0], c->argv[1], c->argv[2] ); + rc = ARG_BAD_CONF; + goto done; + } + + if ( c->argc > 4 ) { + int argidx; + + for ( argidx = 4; argidx < c->argc; argidx++ ) { + if ( strncasecmp( c->argv[argidx], "restrict=", STRLENOF("restrict=") ) == 0 ) { + int err; + char *arg = c->argv[argidx] + STRLENOF("restrict="); + + err = ldap_url_parse(arg, &ap.restrict_lud); + if ( err != LDAP_URL_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: Invalid restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if (ap.restrict_lud->lud_host != NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unsupported hostname in restrict URI \"%s\"", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + + if ( ap.restrict_lud->lud_attrs != NULL ) { + if ( ap.restrict_lud->lud_attrs[0] != NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: attrs not allowed in restrict URI %s\n", + c->argv[0], c->argv[1], arg); + rc = ARG_BAD_CONF; + goto done; + } + ldap_memvfree((void *)ap.restrict_lud->lud_attrs); + ap.restrict_lud->lud_attrs = NULL; + } + + if (ap.restrict_lud->lud_dn != NULL) { + if (ap.restrict_lud->lud_dn[0] == '\0') { + ldap_memfree(ap.restrict_lud->lud_dn); + ap.restrict_lud->lud_dn = NULL; + + } else { + struct berval dn, ndn; + int j; + + ber_str2bv(ap.restrict_lud->lud_dn, 0, 0, &dn); + if (dnNormalize(0, NULL, NULL, &dn, &ndn, NULL)) { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI %s DN normalization failed", + c->argv[0], c->argv[1], arg ); + rc = ARG_BAD_CONF; + goto done; + } + + assert(c->be != NULL); + if (c->be->be_nsuffix == NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI requires suffix", + c->argv[0], c->argv[1] ); + rc = ARG_BAD_CONF; + goto done; + } + + for ( j = 0; !BER_BVISNULL(&c->be->be_nsuffix[j]); j++) { + if (dnIsSuffix(&ndn, &c->be->be_nsuffix[j])) break; + } + + if (BER_BVISNULL(&c->be->be_nsuffix[j])) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI DN %s not within database naming context(s)", + c->argv[0], c->argv[1], dn.bv_val ); + rc = ARG_BAD_CONF; + goto done; + } + + ap.restrict_ndn = ndn; + } + } + + if (ap.restrict_lud->lud_filter != NULL) { + ap.restrict_filter = str2filter(ap.restrict_lud->lud_filter); + if (ap.restrict_filter == NULL) { + /* error */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: restrict URI filter %s invalid", + c->argv[0], c->argv[1], ap.restrict_lud->lud_filter ); + rc = ARG_BAD_CONF; + goto done; + } + } + + ber_str2bv(c->argv[argidx] + STRLENOF("restrict="), 0, 1, &ap.restrict_val); + + } else { + /* cleanup */ + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s %s: unrecognized arg #%d (%s)", + c->argv[0], c->argv[1], argidx, c->argv[argidx] ); + rc = ARG_BAD_CONF; + goto done; + } + } + } + +done:; + if ( rc == LDAP_SUCCESS ) { + constraint **app, *a2 = ch_calloc( sizeof(constraint), 1 ); + + a2->ap = ap.ap; + a2->type = ap.type; + a2->re = ap.re; + a2->val = ap.val; + a2->lud = ap.lud; + a2->set = ap.set; + a2->size = ap.size; + a2->count = ap.count; + if ( a2->lud ) { + ber_str2bv(a2->lud->lud_dn, 0, 0, &a2->dn); + ber_str2bv(a2->lud->lud_filter, 0, 0, &a2->filter); + } + a2->attrs = ap.attrs; + a2->restrict_lud = ap.restrict_lud; + a2->restrict_ndn = ap.restrict_ndn; + a2->restrict_filter = ap.restrict_filter; + a2->restrict_val = ap.restrict_val; + + for ( app = &on->on_bi.bi_private; *app; app = &(*app)->ap_next ) + /* Get to the end */ ; + + a2->ap_next = *app; + *app = a2; + + } else { + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, + "%s: %s\n", c->log, c->cr_msg ); + constraint_free( &ap, 0 ); + } + + ldap_memvfree((void**)attrs); + } break; + default: + abort(); + break; + } + break; + default: + abort(); + } + + return rc; +} + +static int +constraint_uri_cb( Operation *op, SlapReply *rs ) +{ + if(rs->sr_type == REP_SEARCH) { + int *foundp = op->o_callback->sc_private; + + *foundp = 1; + + Debug(LDAP_DEBUG_TRACE, "==> constraint_uri_cb <%s>\n", + rs->sr_entry ? rs->sr_entry->e_name.bv_val : "UNKNOWN_DN" ); + } + return 0; +} + +static int +constraint_violation( constraint *c, struct berval *bv, Operation *op ) +{ + if ((!c) || (!bv)) return LDAP_SUCCESS; + + switch (c->type) { + case CONSTRAINT_SIZE: + if (bv->bv_len > c->size) + return LDAP_CONSTRAINT_VIOLATION; /* size violation */ + break; + case CONSTRAINT_REGEX: + if (regexec(c->re, bv->bv_val, 0, NULL, 0) == REG_NOMATCH) + return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */ + break; + case CONSTRAINT_NEG_REGEX: + if (regexec(c->re, bv->bv_val, 0, NULL, 0) != REG_NOMATCH) + return LDAP_CONSTRAINT_VIOLATION; /* regular expression violation */ + break; + case CONSTRAINT_URI: { + Operation nop = *op; + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + slap_callback cb = { 0 }; + int i; + int found = 0; + int rc; + size_t len; + struct berval filterstr; + char *ptr; + + cb.sc_response = constraint_uri_cb; + cb.sc_private = &found; + + nop.o_protocol = LDAP_VERSION3; + nop.o_tag = LDAP_REQ_SEARCH; + nop.o_time = slap_get_time(); + if (c->lud->lud_dn) { + struct berval dn; + + ber_str2bv(c->lud->lud_dn, 0, 0, &dn); + nop.o_req_dn = dn; + nop.o_req_ndn = dn; + nop.o_bd = select_backend(&nop.o_req_ndn, 1 ); + if (!nop.o_bd) { + return LDAP_NO_SUCH_OBJECT; /* unexpected error */ + } + if (!nop.o_bd->be_search) { + return LDAP_OTHER; /* unexpected error */ + } + } else { + nop.o_req_dn = nop.o_bd->be_nsuffix[0]; + nop.o_req_ndn = nop.o_bd->be_nsuffix[0]; + nop.o_bd = on->on_info->oi_origdb; + } + nop.o_do_not_cache = 1; + nop.o_callback = &cb; + + nop.ors_scope = c->lud->lud_scope; + nop.ors_deref = LDAP_DEREF_NEVER; + nop.ors_slimit = SLAP_NO_LIMIT; + nop.ors_tlimit = SLAP_NO_LIMIT; + nop.ors_limit = NULL; + + nop.ors_attrsonly = 0; + nop.ors_attrs = slap_anlist_no_attrs; + + len = STRLENOF("(&(") + + c->filter.bv_len + + STRLENOF(")(|"); + + for (i = 0; c->attrs[i]; i++) { + len += STRLENOF("(") + + c->attrs[i]->ad_cname.bv_len + + STRLENOF("=") + + bv->bv_len + + STRLENOF(")"); + } + + len += STRLENOF("))"); + filterstr.bv_len = len; + filterstr.bv_val = op->o_tmpalloc(len + 1, op->o_tmpmemctx); + + ptr = filterstr.bv_val + + snprintf(filterstr.bv_val, len, "(&(%s)(|", c->lud->lud_filter); + for (i = 0; c->attrs[i]; i++) { + *ptr++ = '('; + ptr = lutil_strcopy( ptr, c->attrs[i]->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, bv->bv_val ); + *ptr++ = ')'; + } + *ptr++ = ')'; + *ptr++ = ')'; + *ptr++ = '\0'; + + nop.ors_filterstr = filterstr; + nop.ors_filter = str2filter_x(&nop, filterstr.bv_val); + if ( nop.ors_filter == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s constraint_violation uri filter=\"%s\" invalid\n", + op->o_log_prefix, filterstr.bv_val ); + rc = LDAP_OTHER; + + } else { + SlapReply nrs = { REP_RESULT }; + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri filter = %s\n", + filterstr.bv_val ); + + rc = nop.o_bd->be_search( &nop, &nrs ); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_violation uri rc = %d, found = %d\n", + rc, found ); + } + op->o_tmpfree(filterstr.bv_val, op->o_tmpmemctx); + + if ((rc != LDAP_SUCCESS) && (rc != LDAP_NO_SUCH_OBJECT)) { + return rc; /* unexpected error */ + } + + if (!found) + return LDAP_CONSTRAINT_VIOLATION; /* constraint violation */ + break; + } + } + + return LDAP_SUCCESS; +} + +static char * +print_message( struct berval *errtext, AttributeDescription *a ) +{ + char *ret; + int sz; + + sz = errtext->bv_len + sizeof(" on ") + a->ad_cname.bv_len; + ret = ch_malloc(sz); + snprintf( ret, sz, "%s on %s", errtext->bv_val, a->ad_cname.bv_val ); + return ret; +} + +static unsigned +constraint_count_attr(Entry *e, AttributeDescription *ad) +{ + struct Attribute *a; + + if ((a = attr_find(e->e_attrs, ad)) != NULL) + return a->a_numvals; + return 0; +} + +static int +constraint_check_restrict( Operation *op, constraint *c, Entry *e ) +{ + assert( c->restrict_lud != NULL ); + + if ( c->restrict_lud->lud_dn != NULL ) { + int diff = e->e_nname.bv_len - c->restrict_ndn.bv_len; + + if ( diff < 0 ) { + return 0; + } + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_BASE ) { + return bvmatch( &e->e_nname, &c->restrict_ndn ); + } + + if ( !dnIsSuffix( &e->e_nname, &c->restrict_ndn ) ) { + return 0; + } + + if ( c->restrict_lud->lud_scope != LDAP_SCOPE_SUBTREE ) { + struct berval pdn; + + if ( diff == 0 ) { + return 0; + } + + dnParent( &e->e_nname, &pdn ); + + if ( c->restrict_lud->lud_scope == LDAP_SCOPE_ONELEVEL + && pdn.bv_len != c->restrict_ndn.bv_len ) + { + return 0; + } + } + } + + if ( c->restrict_filter != NULL ) { + int rc; + struct berval save_dn = op->o_dn, save_ndn = op->o_ndn; + + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + rc = test_filter( op, e, c->restrict_filter ); + op->o_dn = save_dn; + op->o_ndn = save_ndn; + + if ( rc != LDAP_COMPARE_TRUE ) { + return 0; + } + } + + return 1; +} + +static int +constraint_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Attribute *a; + constraint *c = on->on_bi.bi_private, *cp; + BerVarray b = NULL; + int i; + struct berval rsv = BER_BVC("add breaks constraint"); + int rc = 0; + char *msg = NULL; + + if ( get_relax(op) || be_shadow_update( op ) ) { + return SLAP_CB_CONTINUE; + } + + if ((a = op->ora_e->e_attrs) == NULL) { + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "constraint_add: no attrs"); + return(rs->sr_err); + } + + for(; a; a = a->a_next ) { + /* we don't constrain operational attributes */ + if (is_at_operational(a->a_desc->ad_type)) continue; + + for(cp = c; cp; cp = cp->ap_next) { + int j; + for (j = 0; cp->ap[j]; j++) { + if (cp->ap[j] == a->a_desc) break; + } + if (cp->ap[j] == NULL) continue; + if ((b = a->a_vals) == NULL) continue; + + if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, op->ora_e) == 0) { + continue; + } + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_add, " + "a->a_numvals = %u, cp->count = %lu\n", + a->a_numvals, (unsigned long) cp->count ); + + switch (cp->type) { + case CONSTRAINT_COUNT: + if (a->a_numvals > cp->count) + rc = LDAP_CONSTRAINT_VIOLATION; + break; + case CONSTRAINT_SET: + if (acl_match_set(&cp->val, op, op->ora_e, NULL) == 0) + rc = LDAP_CONSTRAINT_VIOLATION; + break; + default: + for ( i = 0; b[i].bv_val; i++ ) { + rc = constraint_violation( cp, &b[i], op ); + if ( rc ) { + goto add_violation; + } + } + } + if ( rc ) + goto add_violation; + + } + } + + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; + +add_violation: + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + if (rc == LDAP_CONSTRAINT_VIOLATION ) { + msg = print_message( &rsv, a->a_desc ); + } + send_ldap_error(op, rs, rc, msg ); + ch_free(msg); + return (rs->sr_err); +} + + +static int +constraint_check_count_violation( Modifications *m, Entry *target_entry, constraint *cp ) +{ + BerVarray b = NULL; + unsigned ce = 0; + unsigned ca; + int j; + + for ( j = 0; cp->ap[j]; j++ ) { + /* Get this attribute count */ + if ( target_entry ) + ce = constraint_count_attr( target_entry, cp->ap[j] ); + + for( ; m; m = m->sml_next ) { + if ( cp->ap[j] == m->sml_desc ) { + ca = m->sml_numvals; + switch ( m->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: + if ( !ca || ca > ce ) { + ce = 0; + } else { + /* No need to check for values' validity. Invalid values + * cause the whole transaction to die anyway. */ + ce -= ca; + } + break; + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: + ce += ca; + break; + + case LDAP_MOD_REPLACE: + ce = ca; + break; + +#if 0 + /* TODO */ + case handle SLAP_MOD_ADD_IF_NOT_PRESENT: +#endif + + default: + /* impossible! assert? */ + return 1; + } + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_check_count_violation ce = %u, " + "ca = %u, cp->count = %lu\n", + ce, ca, (unsigned long) cp->count); + } + } + } + + return ( ce > cp->count ); +} + +static int +constraint_update( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *) op->o_bd->bd_info; + Backend *be = op->o_bd; + constraint *c = on->on_bi.bi_private, *cp; + Entry *target_entry = NULL, *target_entry_copy = NULL; + Modifications *modlist, *m; + BerVarray b = NULL; + int i; + struct berval rsv = BER_BVC("modify breaks constraint"); + int rc; + char *msg = NULL; + int is_v; + + if ( get_relax(op) || be_shadow_update( op ) ) { + return SLAP_CB_CONTINUE; + } + + switch ( op->o_tag ) { + case LDAP_REQ_MODIFY: + modlist = op->orm_modlist; + break; + + case LDAP_REQ_MODRDN: + modlist = op->orr_modlist; + break; + + default: + /* impossible! assert? */ + return LDAP_OTHER; + } + + Debug( LDAP_DEBUG_CONFIG|LDAP_DEBUG_NONE, "constraint_update()\n" ); + if ((m = modlist) == NULL) { + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + send_ldap_error(op, rs, LDAP_INVALID_SYNTAX, + "constraint_update() got null modlist"); + return(rs->sr_err); + } + + op->o_bd = on->on_info->oi_origdb; + rc = be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &target_entry ); + op->o_bd = be; + + /* let the backend send the error */ + if ( target_entry == NULL ) + return SLAP_CB_CONTINUE; + + /* Do we need to count attributes? */ + for(cp = c; cp; cp = cp->ap_next) { + if (cp->type == CONSTRAINT_COUNT) { + if (cp->restrict_lud && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + is_v = constraint_check_count_violation(m, target_entry, cp); + + Debug(LDAP_DEBUG_TRACE, + "==> constraint_update is_v: %d\n", is_v ); + + if (is_v) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto mod_violation; + } + } + } + + rc = LDAP_CONSTRAINT_VIOLATION; + for(;m; m = m->sml_next) { + unsigned ce = 0; + + if (is_at_operational( m->sml_desc->ad_type )) continue; + + if ((( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_ADD) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_REPLACE) && + (( m->sml_op & LDAP_MOD_OP ) != LDAP_MOD_DELETE)) + continue; + /* we only care about ADD and REPLACE modifications */ + /* and DELETE are used to track attribute count */ + if ((( b = m->sml_values ) == NULL ) || (b[0].bv_val == NULL)) + continue; + + for(cp = c; cp; cp = cp->ap_next) { + int j; + for (j = 0; cp->ap[j]; j++) { + if (cp->ap[j] == m->sml_desc) { + break; + } + } + if (cp->ap[j] == NULL) continue; + + if (cp->restrict_lud != NULL && constraint_check_restrict(op, cp, target_entry) == 0) { + continue; + } + + /* DELETE are to be ignored beyond this point */ + if (( m->sml_op & LDAP_MOD_OP ) == LDAP_MOD_DELETE) + continue; + + for ( i = 0; b[i].bv_val; i++ ) { + rc = constraint_violation( cp, &b[i], op ); + if ( rc ) { + goto mod_violation; + } + } + + if (cp->type == CONSTRAINT_SET && target_entry) { + if (target_entry_copy == NULL) { + Modifications *ml; + + target_entry_copy = entry_dup(target_entry); + + /* if rename, set the new entry's name */ + if ( op->o_tag == LDAP_REQ_MODRDN ) { + ber_bvreplace( &target_entry_copy->e_name, &op->orr_newDN ); + ber_bvreplace( &target_entry_copy->e_nname, &op->orr_nnewDN ); + } + + /* apply modifications, in an attempt + * to estimate what the entry would + * look like in case all modifications + * pass */ + for ( ml = modlist; ml; ml = ml->sml_next ) { + Modification *mod = &ml->sml_mod; + const char *text; + char textbuf[SLAP_TEXT_BUFLEN]; + size_t textlen = sizeof(textbuf); + int err; + + switch ( mod->sm_op ) { + case LDAP_MOD_ADD: + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_DELETE: + err = modify_delete_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_REPLACE: + err = modify_replace_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case LDAP_MOD_INCREMENT: + err = modify_increment_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + break; + + case SLAP_MOD_SOFTADD: + mod->sm_op = LDAP_MOD_ADD; + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_SOFTADD; + if ( err == LDAP_TYPE_OR_VALUE_EXISTS ) { + err = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_SOFTDEL: + mod->sm_op = LDAP_MOD_ADD; + err = modify_delete_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_SOFTDEL; + if ( err == LDAP_NO_SUCH_ATTRIBUTE ) { + err = LDAP_SUCCESS; + } + break; + + case SLAP_MOD_ADD_IF_NOT_PRESENT: + if ( attr_find( target_entry_copy->e_attrs, mod->sm_desc ) ) { + err = LDAP_SUCCESS; + break; + } + mod->sm_op = LDAP_MOD_ADD; + err = modify_add_values( target_entry_copy, + mod, get_permissiveModify(op), + &text, textbuf, textlen ); + mod->sm_op = SLAP_MOD_ADD_IF_NOT_PRESENT; + break; + + default: + err = LDAP_OTHER; + break; + } + + if ( err != LDAP_SUCCESS ) { + rc = err; + goto mod_violation; + } + } + } + + if ( acl_match_set(&cp->val, op, target_entry_copy, NULL) == 0) { + rc = LDAP_CONSTRAINT_VIOLATION; + goto mod_violation; + } + } + } + } + + if (target_entry) { + op->o_bd = on->on_info->oi_origdb; + be_entry_release_r(op, target_entry); + op->o_bd = be; + } + + if (target_entry_copy) { + entry_free(target_entry_copy); + } + + return SLAP_CB_CONTINUE; + +mod_violation: + /* violation */ + if (target_entry) { + op->o_bd = on->on_info->oi_origdb; + be_entry_release_r(op, target_entry); + op->o_bd = be; + } + + if (target_entry_copy) { + entry_free(target_entry_copy); + } + + op->o_bd->bd_info = (BackendInfo *)(on->on_info); + if ( rc == LDAP_CONSTRAINT_VIOLATION ) { + msg = print_message( &rsv, m->sml_desc ); + } + send_ldap_error( op, rs, LDAP_CONSTRAINT_VIOLATION, msg ); + ch_free(msg); + return (rs->sr_err); +} + +static int +constraint_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + constraint *ap, *a2; + + for ( ap = on->on_bi.bi_private; ap; ap = a2 ) { + a2 = ap->ap_next; + constraint_free( ap, 1 ); + } + + return 0; +} + +static slap_overinst constraint_ovl; + +#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC +static +#endif +int +constraint_initialize( void ) { + int rc; + + constraint_ovl.on_bi.bi_type = "constraint"; + constraint_ovl.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + constraint_ovl.on_bi.bi_db_destroy = constraint_destroy; + constraint_ovl.on_bi.bi_op_add = constraint_add; + constraint_ovl.on_bi.bi_op_modify = constraint_update; + constraint_ovl.on_bi.bi_op_modrdn = constraint_update; + + constraint_ovl.on_bi.bi_private = NULL; + + constraint_ovl.on_bi.bi_cf_ocs = constraintocs; + rc = config_register_schema( constraintcfg, constraintocs ); + if (rc) return rc; + + return overlay_register( &constraint_ovl ); +} + +#if SLAPD_OVER_CONSTRAINT == SLAPD_MOD_DYNAMIC +int init_module(int argc, char *argv[]) { + return constraint_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_CONSTRAINT) */ + diff --git a/servers/slapd/overlays/dds.c b/servers/slapd/overlays/dds.c new file mode 100644 index 0000000..c19f042 --- /dev/null +++ b/servers/slapd/overlays/dds.c @@ -0,0 +1,2056 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include +#include + +#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..93b7f69 --- /dev/null +++ b/servers/slapd/overlays/deref.c @@ -0,0 +1,586 @@ +/* deref.c - dereference overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Pierangelo Masarati + * for inclusion in OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DEREF + +#include + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "slap-config.h" + +#include "lutil.h" + +/* + * 1. Specification + * + * 1.1. Request + * + * controlValue ::= SEQUENCE OF derefSpec DerefSpec + * + * DerefSpec ::= SEQUENCE { + * derefAttr attributeDescription, ; DN-valued + * attributes AttributeList } + * + * AttributeList ::= SEQUENCE OF attr AttributeDescription + * + * derefAttr MUST be unique within controlValue + * + * + * 1.2. Response + * + * controlValue ::= SEQUENCE OF DerefRes + * + * From RFC 4511: + * PartialAttribute ::= SEQUENCE { + * type AttributeDescription, + * vals SET OF value AttributeValue } + * + * PartialAttributeList ::= SEQUENCE OF + * partialAttribute PartialAttribute + * + * DerefRes ::= SEQUENCE { + * derefAttr AttributeDescription, + * derefVal LDAPDN, + * attrVals [0] PartialAttributeList OPTIONAL } + * + * If vals is empty, partialAttribute is omitted. + * If all vals in attrVals are empty, attrVals is omitted. + * + * 2. Examples + * + * 2.1. Example + * + * 2.1.1. Request + * + * { { member, { GUID, SID } }, { memberOf, { GUID, SID } } } + * + * 2.1.2. Response + * + * { { memberOf, "cn=abartlet,cn=users,dc=abartlet,dc=net", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fa" ] }, + * { SID, [ "S-1-2-3-2345" ] } } }, + * { memberOf, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { GUID, [ "0bc11d00-e431-40a0-8767-344a320142fb" ] }, + * { SID, [ "S-1-2-3-2346" ] } } } } + * + * 2.2. Example + * + * 2.2.1. Request + * + * { { member, { cn, uid, drink } } } + * + * 2.2.2. Response + * + * { { member, "cn=ando,cn=users,dc=sys-net,dc=it", + * { { cn, [ "ando", "Pierangelo Masarati" ] }, + * { uid, [ "ando" ] } } }, + * { member, "dc=sys-net,dc=it" } } + * + * + * 3. Security considerations + * + * The control result must not disclose information the client's + * identity could not have accessed directly by performing the related + * search operations. The presence of a derefVal in the control + * response does not imply neither the existence of nor any access + * privilege to the corresponding entry. It is merely a consequence + * of the read access the client's identity has on the corresponding + * attribute's value. + */ + +#define o_deref o_ctrlflag[deref_cid] +#define o_ctrlderef o_controls[deref_cid] + +typedef struct DerefSpec { + AttributeDescription *ds_derefAttr; + AttributeDescription **ds_attributes; + int ds_nattrs; + struct DerefSpec *ds_next; +} DerefSpec; + +typedef struct DerefVal { + struct berval dv_derefSpecVal; + BerVarray *dv_attrVals; +} DerefVal; + +typedef struct DerefRes { + DerefSpec dr_spec; + DerefVal *dr_vals; + struct DerefRes *dr_next; +} DerefRes; + +typedef struct deref_cb_t { + slap_overinst *dc_on; + DerefSpec *dc_ds; +} deref_cb_t; + +static int deref_cid; +static slap_overinst deref; +static int ov_count; + +static int +deref_parseCtrl ( + Operation *op, + SlapReply *rs, + LDAPControl *ctrl ) +{ + ber_tag_t tag; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_len_t len; + char *last; + DerefSpec *dshead = NULL, **dsp = &dshead; + BerVarray attributes = NULL; + + if ( op->o_deref != SLAP_CONTROL_NONE ) { + rs->sr_text = "Dereference control specified multiple times"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISNULL( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is absent"; + return LDAP_PROTOCOL_ERROR; + } + + if ( BER_BVISEMPTY( &ctrl->ldctl_value ) ) { + rs->sr_text = "Dereference control value is empty"; + return LDAP_PROTOCOL_ERROR; + } + + ber_init2( ber, &ctrl->ldctl_value, 0 ); + + for ( tag = ber_first_element( ber, &len, &last ); + tag != LBER_DEFAULT; + tag = ber_next_element( ber, &len, last ) ) + { + struct berval derefAttr; + DerefSpec *ds, *dstmp; + const char *text; + int rc; + ber_len_t cnt = sizeof(struct berval); + ber_len_t off = 0; + + if ( ber_scanf( ber, "{m{M}}", &derefAttr, &attributes, &cnt, off ) == LBER_ERROR + || !cnt ) + { + rs->sr_text = "Dereference control: derefSpec decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + ds = (DerefSpec *)op->o_tmpcalloc( 1, + sizeof(DerefSpec) + sizeof(AttributeDescription *)*(cnt + 1), + op->o_tmpmemctx ); + ds->ds_attributes = (AttributeDescription **)&ds[ 1 ]; + ds->ds_nattrs = cnt; + + rc = slap_bv2ad( &derefAttr, &ds->ds_derefAttr, &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: derefAttr decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + for ( dstmp = dshead; dstmp && dstmp != ds; dstmp = dstmp->ds_next ) { + if ( dstmp->ds_derefAttr == ds->ds_derefAttr ) { + rs->sr_text = "Dereference control: derefAttr must be unique within control"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + if ( !( ds->ds_derefAttr->ad_type->sat_syntax->ssyn_flags & SLAP_SYNTAX_DN )) { + if ( ctrl->ldctl_iscritical ) { + rs->sr_text = "Dereference control: derefAttr syntax not distinguishedName"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + + rs->sr_err = LDAP_SUCCESS; + goto justcleanup; + } + + for ( cnt = 0; !BER_BVISNULL( &attributes[ cnt ] ); cnt++ ) { + rc = slap_bv2ad( &attributes[ cnt ], &ds->ds_attributes[ cnt ], &text ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_text = "Dereference control: attribute decoding error"; + rs->sr_err = LDAP_PROTOCOL_ERROR; + goto done; + } + } + + ber_memfree_x( attributes, op->o_tmpmemctx ); + attributes = NULL; + + *dsp = ds; + dsp = &ds->ds_next; + } + + op->o_ctrlderef = (void *)dshead; + + op->o_deref = ctrl->ldctl_iscritical + ? SLAP_CONTROL_CRITICAL + : SLAP_CONTROL_NONCRITICAL; + + rs->sr_err = LDAP_SUCCESS; + +done:; + if ( rs->sr_err != LDAP_SUCCESS ) { +justcleanup:; + for ( ; dshead; ) { + DerefSpec *dsnext = dshead->ds_next; + op->o_tmpfree( dshead, op->o_tmpmemctx ); + dshead = dsnext; + } + } + + if ( attributes != NULL ) { + ber_memfree_x( attributes, op->o_tmpmemctx ); + } + + return rs->sr_err; +} + +static int +deref_cleanup( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_RESULT || rs->sr_err == SLAPD_ABANDON ) { + op->o_tmpfree( op->o_callback, op->o_tmpmemctx ); + op->o_callback = NULL; + + op->o_tmpfree( op->o_ctrlderef, op->o_tmpmemctx ); + op->o_ctrlderef = NULL; + } + + return SLAP_CB_CONTINUE; +} + +static int +deref_response( Operation *op, SlapReply *rs ) +{ + int rc = SLAP_CB_CONTINUE; + + if ( rs->sr_type == REP_SEARCH ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *) &berbuf; + deref_cb_t *dc = (deref_cb_t *)op->o_callback->sc_private; + DerefSpec *ds; + DerefRes *dr, *drhead = NULL, **drp = &drhead; + struct berval bv = BER_BVNULL; + int nDerefRes = 0, nDerefVals = 0, nAttrs = 0, nVals = 0; + struct berval ctrlval; + LDAPControl *ctrl, *ctrlsp[2]; + AccessControlState acl_state = ACL_STATE_INIT; + static char dummy = '\0'; + Entry *ebase; + int i; + + rc = overlay_entry_get_ov( op, &rs->sr_entry->e_nname, NULL, NULL, 0, &ebase, dc->dc_on ); + if ( rc != LDAP_SUCCESS || ebase == NULL ) { + return SLAP_CB_CONTINUE; + } + + for ( ds = dc->dc_ds; ds; ds = ds->ds_next ) { + Attribute *a = attr_find( ebase->e_attrs, ds->ds_derefAttr ); + + if ( a != NULL ) { + DerefVal *dv; + BerVarray *bva; + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + NULL, ACL_READ, &acl_state ) ) + { + continue; + } + + dr = op->o_tmpcalloc( 1, + sizeof( DerefRes ) + ( sizeof( DerefVal ) + sizeof( BerVarray * ) * ds->ds_nattrs ) * ( a->a_numvals + 1 ), + op->o_tmpmemctx ); + dr->dr_spec = *ds; + dv = dr->dr_vals = (DerefVal *)&dr[ 1 ]; + bva = (BerVarray *)&dv[ a->a_numvals + 1 ]; + + bv.bv_len += ds->ds_derefAttr->ad_cname.bv_len; + nAttrs++; + nDerefRes++; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + dv[ i ].dv_attrVals = bva; + bva += ds->ds_nattrs; + + + if ( !access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[ i ], ACL_READ, &acl_state ) ) + { + dv[ i ].dv_derefSpecVal.bv_val = &dummy; + continue; + } + + ber_dupbv_x( &dv[ i ].dv_derefSpecVal, &a->a_vals[ i ], op->o_tmpmemctx ); + bv.bv_len += dv[ i ].dv_derefSpecVal.bv_len; + nVals++; + nDerefVals++; + + rc = overlay_entry_get_ov( op, &a->a_nvals[ i ], NULL, NULL, 0, &e, dc->dc_on ); + if ( rc == LDAP_SUCCESS && e != NULL ) { + int j; + + if ( access_allowed( op, e, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + for ( j = 0; j < ds->ds_nattrs; j++ ) { + Attribute *aa; + + if ( !access_allowed( op, e, ds->ds_attributes[ j ], NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + + aa = attr_find( e->e_attrs, ds->ds_attributes[ j ] ); + if ( aa != NULL ) { + unsigned k, h, last = aa->a_numvals; + + ber_bvarray_dup_x( &dv[ i ].dv_attrVals[ j ], + aa->a_vals, op->o_tmpmemctx ); + + bv.bv_len += ds->ds_attributes[ j ]->ad_cname.bv_len; + + for ( k = 0, h = 0; k < aa->a_numvals; k++ ) { + if ( !access_allowed( op, e, + aa->a_desc, + &aa->a_nvals[ k ], + ACL_READ, &acl_state ) ) + { + op->o_tmpfree( dv[ i ].dv_attrVals[ j ][ h ].bv_val, + op->o_tmpmemctx ); + dv[ i ].dv_attrVals[ j ][ h ] = dv[ i ].dv_attrVals[ j ][ --last ]; + BER_BVZERO( &dv[ i ].dv_attrVals[ j ][ last ] ); + continue; + } + bv.bv_len += dv[ i ].dv_attrVals[ j ][ h ].bv_len; + nVals++; + h++; + } + nAttrs++; + } + } + } + + overlay_entry_release_ov( op, e, 0, dc->dc_on ); + } + } + + *drp = dr; + drp = &dr->dr_next; + } + } + overlay_entry_release_ov( op, ebase, 0, dc->dc_on ); + + if ( drhead == NULL ) { + return SLAP_CB_CONTINUE; + } + + /* cook the control value */ + bv.bv_len += nVals * sizeof(struct berval) + + nAttrs * sizeof(struct berval) + + nDerefVals * sizeof(DerefVal) + + nDerefRes * sizeof(DerefRes); + bv.bv_val = op->o_tmpalloc( bv.bv_len, op->o_tmpmemctx ); + + ber_init2( ber, &bv, LBER_USE_DER ); + ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx ); + + rc = ber_printf( ber, "{" /*}*/ ); + for ( dr = drhead; dr != NULL; dr = dr->dr_next ) { + for ( i = 0; !BER_BVISNULL( &dr->dr_vals[ i ].dv_derefSpecVal ); i++ ) { + int j, first = 1; + + if ( dr->dr_vals[ i ].dv_derefSpecVal.bv_val == &dummy ) { + continue; + } + + rc = ber_printf( ber, "{OO" /*}*/, + &dr->dr_spec.ds_derefAttr->ad_cname, + &dr->dr_vals[ i ].dv_derefSpecVal ); + op->o_tmpfree( dr->dr_vals[ i ].dv_derefSpecVal.bv_val, op->o_tmpmemctx ); + for ( j = 0; j < dr->dr_spec.ds_nattrs; j++ ) { + if ( dr->dr_vals[ i ].dv_attrVals[ j ] != NULL ) { + if ( first ) { + rc = ber_printf( ber, "t{" /*}*/, + (LBER_CONSTRUCTED|LBER_CLASS_CONTEXT) ); + first = 0; + } + rc = ber_printf( ber, "{O[W]}", + &dr->dr_spec.ds_attributes[ j ]->ad_cname, + dr->dr_vals[ i ].dv_attrVals[ j ] ); + ber_bvarray_free_x( dr->dr_vals[ i ].dv_attrVals[ j ], + op->o_tmpmemctx ); + } + } + if ( !first ) { + rc = ber_printf( ber, /*{{*/ "}N}" ); + } else { + rc = ber_printf( ber, /*{*/ "}" ); + } + } + } + rc = ber_printf( ber, /*{*/ "}" ); + if ( ber_flatten2( ber, &ctrlval, 0 ) == -1 ) { + if ( op->o_deref == SLAP_CONTROL_CRITICAL ) { + rc = LDAP_CONSTRAINT_VIOLATION; + + } else { + rc = SLAP_CB_CONTINUE; + } + goto cleanup; + } + + ctrl = op->o_tmpcalloc( 1, + sizeof( LDAPControl ) + ctrlval.bv_len + 1, + op->o_tmpmemctx ); + ctrl->ldctl_value.bv_val = (char *)&ctrl[ 1 ]; + ctrl->ldctl_oid = LDAP_CONTROL_X_DEREF; + ctrl->ldctl_iscritical = 0; + ctrl->ldctl_value.bv_len = ctrlval.bv_len; + AC_MEMCPY( ctrl->ldctl_value.bv_val, ctrlval.bv_val, ctrlval.bv_len ); + ctrl->ldctl_value.bv_val[ ctrl->ldctl_value.bv_len ] = '\0'; + + ber_free_buf( ber ); + + ctrlsp[0] = ctrl; + ctrlsp[1] = NULL; + slap_add_ctrls( op, rs, ctrlsp ); + + rc = SLAP_CB_CONTINUE; + +cleanup:; + /* release all */ + for ( ; drhead != NULL; ) { + DerefRes *drnext = drhead->dr_next; + op->o_tmpfree( drhead, op->o_tmpmemctx ); + drhead = drnext; + } + + } else if ( rs->sr_type == REP_RESULT ) { + rc = deref_cleanup( op, rs ); + } + + return rc; +} + +static int +deref_op_search( Operation *op, SlapReply *rs ) +{ + if ( op->o_deref ) { + slap_callback *sc; + deref_cb_t *dc; + + sc = op->o_tmpcalloc( 1, sizeof( slap_callback ) + sizeof( deref_cb_t ), op->o_tmpmemctx ); + + dc = (deref_cb_t *)&sc[ 1 ]; + dc->dc_on = (slap_overinst *)op->o_bd->bd_info; + dc->dc_ds = (DerefSpec *)op->o_ctrlderef; + + sc->sc_response = deref_response; + sc->sc_cleanup = deref_cleanup; + sc->sc_private = (void *)dc; + + sc->sc_next = op->o_callback->sc_next; + op->o_callback->sc_next = sc; + } + + return SLAP_CB_CONTINUE; +} + +static int +deref_db_init( BackendDB *be, ConfigReply *cr) +{ + if ( ov_count == 0 ) { + int rc; + + rc = register_supported_control2( LDAP_CONTROL_X_DEREF, + SLAP_CTRL_SEARCH, + NULL, + deref_parseCtrl, + 1, /* replace */ + &deref_cid ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "deref_init: Failed to register control (%d)\n", + rc ); + return rc; + } + } + ov_count++; + return LDAP_SUCCESS; +} + +static int +deref_db_open( BackendDB *be, ConfigReply *cr) +{ + return overlay_register_control( be, LDAP_CONTROL_X_DEREF ); +} + +#ifdef SLAP_CONFIG_DELETE +static int +deref_db_destroy( BackendDB *be, ConfigReply *cr) +{ + ov_count--; + overlay_unregister_control( be, LDAP_CONTROL_X_DEREF ); + if ( ov_count == 0 ) { + unregister_supported_control( LDAP_CONTROL_X_DEREF ); + } + return 0; +} +#endif /* SLAP_CONFIG_DELETE */ + +int +deref_initialize(void) +{ + deref.on_bi.bi_type = "deref"; + deref.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + deref.on_bi.bi_db_init = deref_db_init; + deref.on_bi.bi_db_open = deref_db_open; +#ifdef SLAP_CONFIG_DELETE + deref.on_bi.bi_db_destroy = deref_db_destroy; +#endif /* SLAP_CONFIG_DELETE */ + deref.on_bi.bi_op_search = deref_op_search; + + return overlay_register( &deref ); +} + +#if SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return deref_initialize(); +} +#endif /* SLAPD_OVER_DEREF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_DEREF */ diff --git a/servers/slapd/overlays/dyngroup.c b/servers/slapd/overlays/dyngroup.c new file mode 100644 index 0000000..5d890d6 --- /dev/null +++ b/servers/slapd/overlays/dyngroup.c @@ -0,0 +1,234 @@ +/* dyngroup.c - Demonstration of overlay code */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#ifdef SLAPD_OVER_DYNGROUP + +#include + +#include +#include + +#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> o_bd->bd_info; + adpair *ap = on->on_bi.bi_private; + + /* If we've been configured and the current response is + * what we're looking for... + */ + if ( ap && op->o_tag == LDAP_REQ_COMPARE && + rs->sr_err == LDAP_NO_SUCH_ATTRIBUTE ) { + + for (;ap;ap=ap->ap_next) { + if ( op->oq_compare.rs_ava->aa_desc == ap->ap_mem ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + int cache = op->o_do_not_cache; + + op->o_do_not_cache = 1; + rs->sr_err = backend_group( op, NULL, &op->o_req_ndn, + &op->oq_compare.rs_ava->aa_value, NULL, ap->ap_uri ); + op->o_do_not_cache = cache; + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + case LDAP_NO_SUCH_OBJECT: + rs->sr_err = LDAP_COMPARE_FALSE; + break; + } + break; + } + } + } + /* Default is to just fall through to the normal processing */ + return SLAP_CB_CONTINUE; +} + +static int +dyngroup_destroy( + BackendDB *be, + ConfigReply *cr +) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + adpair *ap, *a2; + + for ( ap = on->on_bi.bi_private; ap; ap = a2 ) { + a2 = ap->ap_next; + ch_free( ap ); + } + return 0; +} + +static slap_overinst dyngroup; + +/* This overlay is set up for dynamic loading via moduleload. For static + * configuration, you'll need to arrange for the slap_overinst to be + * initialized and registered by some other function inside slapd. + */ + +int dyngroup_initialize() { + int code; + + dyngroup.on_bi.bi_type = "dyngroup"; + dyngroup.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + dyngroup.on_bi.bi_db_destroy = dyngroup_destroy; + dyngroup.on_response = dyngroup_response; + + dyngroup.on_bi.bi_cf_ocs = dgroupocs; + code = config_register_schema( dgroupcfg, dgroupocs ); + if ( code ) return code; + + return overlay_register( &dyngroup ); +} + +#if SLAPD_OVER_DYNGROUP == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dyngroup_initialize(); +} +#endif + +#endif /* defined(SLAPD_OVER_DYNGROUP) */ diff --git a/servers/slapd/overlays/dynlist.c b/servers/slapd/overlays/dynlist.c new file mode 100644 index 0000000..5c38b64 --- /dev/null +++ b/servers/slapd/overlays/dynlist.c @@ -0,0 +1,2968 @@ +/* dynlist.c - dynamic list overlay */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" + +static AttributeDescription *ad_dgIdentity, *ad_dgAuthz; +static AttributeDescription *ad_memberOf; + +typedef struct dynlist_map_t { + AttributeDescription *dlm_member_ad; + AttributeDescription *dlm_mapped_ad; + AttributeDescription *dlm_memberOf_ad; + ObjectClass *dlm_static_oc; + int dlm_memberOf_nested; + int dlm_member_oper; + int dlm_memberOf_oper; + struct dynlist_map_t *dlm_next; +} dynlist_map_t; + +typedef struct dynlist_info_t { + ObjectClass *dli_oc; + AttributeDescription *dli_ad; + struct dynlist_map_t *dli_dlm; + struct berval dli_uri; + LDAPURLDesc *dli_lud; + struct berval dli_uri_nbase; + Filter *dli_uri_filter; + struct berval dli_default_filter; + struct dynlist_info_t *dli_next; +} dynlist_info_t; + +typedef struct dynlist_gen_t { + dynlist_info_t *dlg_dli; + int dlg_memberOf; + int dlg_simple; +} dynlist_gen_t; + +#define DYNLIST_USAGE \ + "\"dynlist-attrset [uri] [[:][+[@[*]] ...]\": " + +static int +ad_infilter( AttributeDescription *ad, Filter *f ) +{ + if ( !f ) + return 0; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case SLAPD_FILTER_COMPUTED: + return 0; + case LDAP_FILTER_PRESENT: + return f->f_desc == ad; + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + return f->f_av_desc == ad; + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: { + for ( f = f->f_list; f; f = f->f_next ) + if ( ad_infilter( ad, f )) + return 1; + } + } + return 0; +} + +static Filter * +transform_filter( Operation *op, dynlist_info_t *dli, int not, Filter *orig ) +{ + Filter *f; + dynlist_map_t *dlm; + + /* Tilt the filter towards TRUE if it could match through this dli */ + int result = not ? LDAP_COMPARE_FALSE : LDAP_COMPARE_TRUE; + + if ( orig ) { + f = orig; + } else { + f = orig = filter_dup( op->ors_filter, op->o_tmpmemctx ); + } + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( f->f_av_desc == ad ) { + filter_free_x( op, f, 0 ); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = result; + break; + } + } + break; + case LDAP_FILTER_PRESENT: + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( f->f_sub_desc == ad ) { + filter_free_x( op, f, 0 ); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = result; + break; + } + } + break; + case LDAP_FILTER_SUBSTRINGS: + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( f->f_desc == ad ) { + filter_free_x( op, f, 0 ); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = result; + break; + } + } + break; + case LDAP_FILTER_EXT: + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( f->f_mr_desc == ad ) { + filter_free_x( op, f, 0 ); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = result; + break; + } + } + break; + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + for ( f = f->f_list; f; f = f->f_next ) + transform_filter( op, dli, not, f ); + break; + case LDAP_FILTER_NOT: + transform_filter( op, dli, !not, f->f_list ); + case SLAPD_FILTER_COMPUTED: + break; + } + + return orig; +} + +typedef struct dynlist_filterinst_t { + AttributeAssertion *df_a; + Entry *df_e; +} dynlist_filterinst_t; + +/* Record occurrences of ad in filter. Ignore in negated filters. */ +static void +dynlist_filter_instances( Operation *op, AttributeDescription *ad, Filter *f, int not, int *dfn, dynlist_filterinst_t **dfp ) +{ + if ( !f ) + return; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + if ( !not && f->f_av_desc == ad ) { + dynlist_filterinst_t *df = *dfp; + int n = *dfn; + df = op->o_tmprealloc( df, (n + 1) * sizeof(dynlist_filterinst_t), op->o_tmpmemctx ); + df[n].df_a = f->f_ava; + df[n++].df_e = NULL; + *dfp = df; + *dfn = n; + } + break; + case SLAPD_FILTER_COMPUTED: + case LDAP_FILTER_PRESENT: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + break; + case LDAP_FILTER_NOT: not ^= 1; + /* FALLTHRU */ + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + for ( f = f->f_list; f; f = f->f_next ) + dynlist_filter_instances( op, ad, f, not, dfn, dfp ); + } +} + +static int +dynlist_make_filter( Operation *op, Entry *e, dynlist_info_t *dli, const char *url, struct berval *oldf, struct berval *newf ) +{ + char *ptr; + int needBrackets = 0; + + assert( oldf != NULL ); + assert( newf != NULL ); + assert( !BER_BVISNULL( oldf ) ); + assert( !BER_BVISEMPTY( oldf ) ); + + if ( oldf->bv_val[0] != '(' ) { + Debug( LDAP_DEBUG_ANY, "%s: dynlist, DN=\"%s\": missing parentheses in URI=\"%s\" filter\n", + op->o_log_prefix, e->e_name.bv_val, url ); + needBrackets = 2; + } + + newf->bv_len = STRLENOF( "(&(!(objectClass=" "))" ")" ) + + dli->dli_oc->soc_cname.bv_len + oldf->bv_len + needBrackets; + newf->bv_val = op->o_tmpalloc( newf->bv_len + 1, op->o_tmpmemctx ); + if ( newf->bv_val == NULL ) { + return -1; + } + ptr = lutil_strcopy( newf->bv_val, "(&(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + if ( needBrackets ) *ptr++ = '('; + ptr = lutil_strcopy( ptr, oldf->bv_val ); + if ( needBrackets ) *ptr++ = ')'; + ptr = lutil_strcopy( ptr, ")" ); + newf->bv_len = ptr - newf->bv_val; + + return 0; +} + +/* dynlist_sc_update() callback info set by dynlist_prepare_entry() */ +typedef struct dynlist_sc_t { + dynlist_info_t *dlc_dli; + Entry *dlc_e; + char **dlc_attrs; +} dynlist_sc_t; + +static int +dynlist_sc_update( Operation *op, SlapReply *rs ) +{ + Entry *e; + Attribute *a; + int opattrs, + userattrs; + AccessControlState acl_state = ACL_STATE_INIT; + + dynlist_sc_t *dlc; + dynlist_map_t *dlm; + + if ( rs->sr_type != REP_SEARCH ) { + return 0; + } + + dlc = (dynlist_sc_t *)op->o_callback->sc_private; + e = dlc->dlc_e; + + assert( e != NULL ); + assert( rs->sr_entry != NULL ); + + /* test access to entry */ + if ( !access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + goto done; + } + + /* if there is only one member_ad, and it's not mapped, + * consider it as old-style member listing */ + dlm = dlc->dlc_dli->dli_dlm; + if ( dlm && dlm->dlm_mapped_ad == NULL && dlm->dlm_next == NULL && dlc->dlc_attrs == NULL ) { + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( access_allowed( op, rs->sr_entry, slap_schema.si_ad_entry, + NULL, ACL_READ, NULL ) ) + { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + struct berval vals[ 2 ], nvals[ 2 ]; + + vals[ 0 ] = rs->sr_entry->e_name; + BER_BVZERO( &vals[ 1 ] ); + nvals[ 0 ] = rs->sr_entry->e_nname; + BER_BVZERO( &nvals[ 1 ] ); + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = dlm->dlm_member_ad; + mod.sm_type = dlm->dlm_member_ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = 1; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + goto done; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + for ( a = rs->sr_entry->e_attrs; a != NULL; a = a->a_next ) { + BerVarray vals, nvals = NULL; + int i, j, + is_oc = a->a_desc == slap_schema.si_ad_objectClass; + + /* if attribute is not requested, skip it */ + if ( rs->sr_attrs == NULL ) { + if ( is_at_operational( a->a_desc->ad_type ) ) { + continue; + } + + } else { + if ( is_at_operational( a->a_desc->ad_type ) ) { + if ( !opattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + + } else { + if ( !userattrs && !ad_inlist( a->a_desc, rs->sr_attrs ) ) + { + continue; + } + } + } + + /* test access to attribute */ + if ( op->ors_attrsonly ) { + if ( !access_allowed( op, rs->sr_entry, a->a_desc, NULL, + ACL_READ, &acl_state ) ) + { + continue; + } + } + + /* single-value check: keep first only */ + if ( is_at_single_value( a->a_desc->ad_type ) ) { + if ( attr_find( e->e_attrs, a->a_desc ) != NULL ) { + continue; + } + } + + /* test access to attribute */ + i = a->a_numvals; + + vals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + if ( a->a_nvals != a->a_vals ) { + nvals = op->o_tmpalloc( ( i + 1 ) * sizeof( struct berval ), op->o_tmpmemctx ); + } + + for ( i = 0, j = 0; !BER_BVISNULL( &a->a_vals[i] ); i++ ) { + if ( is_oc ) { + ObjectClass *soc = oc_bvfind( &a->a_vals[i] ); + + if ( soc->soc_kind == LDAP_SCHEMA_STRUCTURAL ) { + continue; + } + } + + if ( access_allowed( op, rs->sr_entry, a->a_desc, + &a->a_nvals[i], ACL_READ, &acl_state ) ) + { + vals[j] = a->a_vals[i]; + if ( nvals ) { + nvals[j] = a->a_nvals[i]; + } + j++; + } + } + + /* if access allowed, try to add values, emulating permissive + * control to silently ignore duplicates */ + if ( j != 0 ) { + Modification mod; + const char *text = NULL; + char textbuf[1024]; + dynlist_map_t *dlm; + AttributeDescription *ad; + + BER_BVZERO( &vals[j] ); + if ( nvals ) { + BER_BVZERO( &nvals[j] ); + } + + ad = a->a_desc; + for ( dlm = dlc->dlc_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == a->a_desc ) { + if ( dlm->dlm_mapped_ad ) { + ad = dlm->dlm_mapped_ad; + } + break; + } + } + + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = ad; + mod.sm_type = ad->ad_cname; + mod.sm_values = vals; + mod.sm_nvalues = nvals; + mod.sm_numvals = j; + + (void)modify_add_values( e, &mod, /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + } + + op->o_tmpfree( vals, op->o_tmpmemctx ); + if ( nvals ) { + op->o_tmpfree( nvals, op->o_tmpmemctx ); + } + } + +done:; + if ( rs->sr_flags & REP_ENTRY_MUSTBEFREED ) { + entry_free( rs->sr_entry ); + rs->sr_entry = NULL; + rs->sr_flags &= ~REP_ENTRY_MASK; + } + + return 0; +} + +typedef struct dynlist_name_t { + struct berval dy_nname; + struct berval dy_name; + dynlist_info_t *dy_dli; + dynlist_map_t *dy_dlm; + AttributeDescription *dy_staticmember; + int dy_seen; + int dy_numuris; + TAvlnode *dy_subs; + TAvlnode *dy_sups; + LDAPURLDesc *dy_uris[]; +} dynlist_name_t; + +static void +dynlist_urlmembers( Operation *op, dynlist_name_t *dyn, slap_callback *sc ) +{ + Operation o = *op; + LDAPURLDesc *ludp; + int i; + + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + o.ors_attrs = NULL; + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + o.o_callback = sc; + o.o_do_not_cache = 1; + + for (i=0; idy_numuris; i++) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_attrs ) + continue; + o.o_req_dn.bv_val = ludp->lud_dn; + o.o_req_dn.bv_len = ludp->lud_port; + o.o_req_ndn = o.o_req_dn; + o.ors_scope = ludp->lud_scope; + o.ors_filter = (Filter *)ludp->lud_filter; + filter2bv_x( op, o.ors_filter, &o.ors_filterstr ); + o.o_bd = select_backend( &o.o_req_ndn, 1 ); + if ( o.o_bd && o.o_bd->be_search ) { + SlapReply r = { REP_SEARCH }; + r.sr_attr_flags = slap_attr_flags( o.ors_attrs ); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + (void)o.o_bd->be_search( &o, &r ); + } + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } +} + +static void +dynlist_nested_memberOf( Entry *e, AttributeDescription *ad, TAvlnode *sups ) +{ + TAvlnode *ptr; + dynlist_name_t *dyn; + Attribute *a; + + a = attr_find( e->e_attrs, ad ); + for ( ptr = ldap_tavl_end( sups, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + if ( a ) { + unsigned slot; + if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &dyn->dy_nname, &slot, NULL ) == LDAP_SUCCESS ) + continue; + } + attr_merge_one( e, ad, &dyn->dy_name, &dyn->dy_nname ); + if ( !a ) + a = attr_find( e->e_attrs, ad ); + if ( dyn->dy_sups ) + dynlist_nested_memberOf( e, ad, dyn->dy_sups ); + } +} + +typedef struct dynlist_member_t { + Entry *dm_e; + AttributeDescription *dm_ad; + Modification dm_mod; + TAvlnode *dm_groups; + struct berval dm_bv[2]; + struct berval dm_nbv[2]; + const char *dm_text; + char dm_textbuf[1024]; +} dynlist_member_t; + +static int +dynlist_ptr_cmp( const void *c1, const void *c2 ) +{ + return ( c1 < c2 ) ? -1 : c1 > c2; +} + +static int +dynlist_nested_member_dg( Operation *op, SlapReply *rs ) +{ + dynlist_member_t *dm = op->o_callback->sc_private; + + if ( rs->sr_type != REP_SEARCH ) + return LDAP_SUCCESS; + + dm->dm_bv[0] = rs->sr_entry->e_name; + dm->dm_nbv[0] = rs->sr_entry->e_nname; + modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1, + &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf )); + + return LDAP_SUCCESS; +} + +static void +dynlist_nested_member( Operation *op, slap_overinst *on, dynlist_member_t *dm, TAvlnode *subs ) +{ + TAvlnode *ptr; + dynlist_name_t *dyn; + Entry *ne; + Attribute *a, *b; + + a = attr_find( dm->dm_e->e_attrs, dm->dm_ad ); + if ( !a ) + return; + + for ( ptr = ldap_tavl_end( subs, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + if ( ldap_tavl_insert( &dm->dm_groups, dyn, dynlist_ptr_cmp, ldap_avl_dup_error )) + continue; + if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &ne, on ) != LDAP_SUCCESS || ne == NULL ) + continue; + b = attr_find( ne->e_attrs, dm->dm_ad ); + if ( b ) { + dm->dm_mod.sm_values = b->a_vals; + dm->dm_mod.sm_nvalues = b->a_nvals; + dm->dm_mod.sm_numvals = b->a_numvals; + modify_add_values( dm->dm_e, &dm->dm_mod, /* permissive */ 1, + &dm->dm_text, dm->dm_textbuf, sizeof( dm->dm_textbuf )); + } + overlay_entry_release_ov( op, ne, 0, on ); + if ( dyn->dy_numuris ) { + slap_callback cb = { 0 }; + cb.sc_private = dm; + BER_BVZERO( &dm->dm_bv[1] ); + BER_BVZERO( &dm->dm_nbv[1] ); + dm->dm_mod.sm_values = dm->dm_bv; + dm->dm_mod.sm_nvalues = dm->dm_nbv; + dm->dm_mod.sm_numvals = 1; + cb.sc_response = dynlist_nested_member_dg; + dynlist_urlmembers( op, dyn, &cb ); + } + if ( dyn->dy_subs ) + dynlist_nested_member( op, on, dm, dyn->dy_subs ); + } +} + +static int +dynlist_prepare_entry( Operation *op, SlapReply *rs, slap_overinst *on, dynlist_info_t *dli, dynlist_name_t *dyn ) +{ + Attribute *a, *id = NULL; + slap_callback cb = { 0 }; + Operation o = *op; + struct berval *url; + Entry *e; + int opattrs, + userattrs; + dynlist_sc_t dlc = { 0 }; + dynlist_map_t *dlm; + + e = rs->sr_entry; + a = attrs_find( rs->sr_entry->e_attrs, dli->dli_ad ); + if ( a == NULL ) { + /* FIXME: error? */ + goto checkdyn; + } + + opattrs = SLAP_OPATTRS( rs->sr_attr_flags ); + userattrs = SLAP_USERATTRS( rs->sr_attr_flags ); + + /* Don't generate member list if it wasn't requested */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( userattrs || ad_inlist( ad, rs->sr_attrs ) + || ad_infilter( ad, op->ors_filter )) + break; + } + + /* If nothing matched and this was a search, skip over to nesting check. + * If this was a compare, keep on going. + */ + if ( dli->dli_dlm && !dlm && o.o_acl_priv != ACL_COMPARE ) + goto checkdyn; + + if ( ad_dgIdentity && ( id = attrs_find( rs->sr_entry->e_attrs, ad_dgIdentity ))) { + Attribute *authz = NULL; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( rs->sr_entry->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + goto checkdyn; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + + /* ensure e is modifiable, but do not replace + * sr_entry yet since we have pointers into it */ + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + e = entry_dup( rs->sr_entry ); + } + + dlc.dlc_e = e; + dlc.dlc_dli = dli; + cb.sc_private = &dlc; + cb.sc_response = dynlist_sc_update; + + o.o_callback = &cb; + o.ors_deref = LDAP_DEREF_NEVER; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + o.o_do_not_cache = 1; + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + + for ( url = a->a_nvals; !BER_BVISNULL( url ); url++ ) { + LDAPURLDesc *lud = NULL; + int i, j; + struct berval dn; + int rc; + + BER_BVZERO( &o.o_req_dn ); + BER_BVZERO( &o.o_req_ndn ); + o.ors_filter = NULL; + o.ors_attrs = NULL; + BER_BVZERO( &o.ors_filterstr ); + + if ( ldap_url_parse( url->bv_val, &lud ) != LDAP_URL_SUCCESS ) { + /* FIXME: error? */ + continue; + } + + if ( lud->lud_host != NULL ) { + /* FIXME: host not allowed; reject as illegal? */ + Debug( LDAP_DEBUG_ANY, "dynlist_prepare_entry(\"%s\"): " + "illegal URI \"%s\"\n", + e->e_name.bv_val, url->bv_val ); + goto cleanup; + } + + if ( lud->lud_dn == NULL ) { + /* note that an empty base is not honored in terms + * of defaultSearchBase, because select_backend() + * is not aware of the defaultSearchBase option; + * this can be useful in case of a database serving + * the empty suffix */ + BER_BVSTR( &dn, "" ); + + } else { + ber_str2bv( lud->lud_dn, 0, 0, &dn ); + } + rc = dnPrettyNormal( NULL, &dn, &o.o_req_dn, &o.o_req_ndn, op->o_tmpmemctx ); + if ( rc != LDAP_SUCCESS ) { + /* FIXME: error? */ + goto cleanup; + } + o.ors_scope = lud->lud_scope; + + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_mapped_ad != NULL ) { + break; + } + } + + if ( dli->dli_dlm && !dlm ) { + /* if ( lud->lud_attrs != NULL ), + * the URL should be ignored */ + o.ors_attrs = slap_anlist_no_attrs; + + } else if ( lud->lud_attrs == NULL ) { + o.ors_attrs = rs->sr_attrs; + + } else { + for ( i = 0; lud->lud_attrs[i]; i++) + /* just count */ ; + + o.ors_attrs = op->o_tmpcalloc( i + 1, sizeof( AttributeName ), op->o_tmpmemctx ); + for ( i = 0, j = 0; lud->lud_attrs[i]; i++) { + const char *text = NULL; + + ber_str2bv( lud->lud_attrs[i], 0, 0, &o.ors_attrs[j].an_name ); + o.ors_attrs[j].an_desc = NULL; + (void)slap_bv2ad( &o.ors_attrs[j].an_name, &o.ors_attrs[j].an_desc, &text ); + /* FIXME: ignore errors... */ + + if ( ad_infilter( o.ors_attrs[j].an_desc, op->ors_filter )) { + /* if referenced in filter, must retrieve */ + } else if ( rs->sr_attrs == NULL ) { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + continue; + } + + } else { + if ( o.ors_attrs[j].an_desc != NULL && + is_at_operational( o.ors_attrs[j].an_desc->ad_type ) ) + { + if ( !opattrs ) { + continue; + } + + if ( !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + + } else { + if ( !userattrs && + o.ors_attrs[j].an_desc != NULL && + !ad_inlist( o.ors_attrs[j].an_desc, rs->sr_attrs ) ) + { + /* lookup if mapped -- linear search, + * not very efficient unless list + * is very short */ + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad == o.ors_attrs[j].an_desc ) { + break; + } + } + + if ( dlm == NULL ) { + continue; + } + } + } + } + + j++; + } + + if ( j == 0 ) { + goto cleanup; + } + + BER_BVZERO( &o.ors_attrs[j].an_name ); + } + dlc.dlc_attrs = lud->lud_attrs; + + if ( lud->lud_filter == NULL ) { + ber_dupbv_x( &o.ors_filterstr, + &dli->dli_default_filter, op->o_tmpmemctx ); + + } else { + /* don't allow recursion in lists */ + if ( lud->lud_attrs ) { + struct berval flt; + ber_str2bv( lud->lud_filter, 0, 0, &flt ); + if ( dynlist_make_filter( op, rs->sr_entry, dli, url->bv_val, &flt, &o.ors_filterstr ) ) { + /* error */ + goto cleanup; + } + } else { + ber_str2bv( lud->lud_filter, 0, 0, &o.ors_filterstr ); + } + } + o.ors_filter = str2filter_x( op, o.ors_filterstr.bv_val ); + if ( o.ors_filter == NULL ) { + goto cleanup; + } + + o.o_bd = select_backend( &o.o_req_ndn, 1 ); + if ( o.o_bd && o.o_bd->be_search ) { + SlapReply r = { REP_SEARCH }; + r.sr_attr_flags = slap_attr_flags( o.ors_attrs ); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + (void)o.o_bd->be_search( &o, &r ); + } + +cleanup:; + if ( id ) { + slap_op_groups_free( &o ); + } + if ( o.ors_filter ) { + filter_free_x( &o, o.ors_filter, 1 ); + } + if ( o.ors_attrs && o.ors_attrs != rs->sr_attrs + && o.ors_attrs != slap_anlist_no_attrs ) + { + op->o_tmpfree( o.ors_attrs, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_dn ) ) { + op->o_tmpfree( o.o_req_dn.bv_val, op->o_tmpmemctx ); + } + if ( !BER_BVISNULL( &o.o_req_ndn ) ) { + op->o_tmpfree( o.o_req_ndn.bv_val, op->o_tmpmemctx ); + } + if ( lud->lud_attrs ) { + assert( BER_BVISNULL( &o.ors_filterstr ) + || o.ors_filterstr.bv_val != lud->lud_filter ); + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } else { + if ( o.ors_filterstr.bv_val != lud->lud_filter ) + op->o_tmpfree( o.ors_filterstr.bv_val, op->o_tmpmemctx ); + } + ldap_free_urldesc( lud ); + } + +checkdyn: + /* handle nested groups */ + if ( dyn && ( dyn->dy_sups || dyn->dy_subs )) { + /* ensure e is modifiable */ + if ( e == rs->sr_entry && !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) { + e = entry_dup( rs->sr_entry ); + rs_replace_entry( op, rs, on, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + if ( dyn->dy_subs ) { + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_member_ad ) { + dynlist_member_t dm; + dm.dm_groups = NULL; + dm.dm_mod.sm_op = LDAP_MOD_ADD; + dm.dm_mod.sm_desc = dlm->dlm_member_ad; + dm.dm_mod.sm_type = dlm->dlm_member_ad->ad_cname; + dm.dm_e = e; + dm.dm_ad = dlm->dlm_member_ad; + dynlist_nested_member( op, on, &dm, dyn->dy_subs ); + if ( dm.dm_groups ) + ldap_tavl_free( dm.dm_groups, NULL ); + } + } + } + if ( dyn->dy_sups ) { + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups ); + } + } + } + } + + if ( e != rs->sr_entry ) { + rs_replace_entry( op, rs, on, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } + + return SLAP_CB_CONTINUE; +} + +static int +dynlist_check_scope( Operation *op, Entry *e, dynlist_info_t *dli ) +{ + if ( dli->dli_lud ) { + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) && + !dnIsSuffixScope( &e->e_nname, + &dli->dli_uri_nbase, + dli->dli_lud->lud_scope )) + return 0; + if ( dli->dli_uri_filter && test_filter( op, e, + dli->dli_uri_filter ) != LDAP_COMPARE_TRUE ) + return 0; + } + return 1; +} + +static int +dynlist_compare( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli; + Operation o = *op; + Entry *e = NULL; + dynlist_map_t *dlm; + BackendDB *be; + int ret = SLAP_CB_CONTINUE; + + if ( get_manageDSAit( op ) ) + return SLAP_CB_CONTINUE; + + for ( ; dli != NULL; dli = dli->dli_next ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + /* builtin dyngroup evaluator only works for DNs */ + if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName ) + continue; + if ( op->oq_compare.rs_ava->aa_desc == ad ) + break; + } + + if ( dlm ) { + /* This compare is for one of the attributes we're + * interested in. We'll use slapd's existing dyngroup + * evaluator to get the answer we want. + */ + BerVarray id = NULL, authz = NULL; + + if ( e == NULL && ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL )) + { + return SLAP_CB_CONTINUE; + } + if ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) || + !dynlist_check_scope( op, e, dli )) { + continue; + } + + o.o_do_not_cache = 1; + + if ( ad_dgIdentity && backend_attribute( &o, e, &o.o_req_ndn, + ad_dgIdentity, &id, ACL_READ ) == LDAP_SUCCESS ) + { + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( id ) && !be_isroot( op ) + && backend_attribute( &o, e, &o.o_req_ndn, + ad_dgAuthz, &authz, ACL_READ ) == LDAP_SUCCESS ) + { + + rs->sr_err = slap_sasl_matches( op, authz, + &o.o_ndn, &o.o_ndn ); + ber_bvarray_free_x( authz, op->o_tmpmemctx ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto done; + } + } + + o.o_dn = *id; + o.o_ndn = *id; + o.o_groups = NULL; /* authz changed, invalidate cached groups */ + } + + rs->sr_err = backend_group( &o, e, &o.o_req_ndn, + &o.oq_compare.rs_ava->aa_value, dli->dli_oc, dli->dli_ad ); + switch ( rs->sr_err ) { + case LDAP_SUCCESS: + rs->sr_err = LDAP_COMPARE_TRUE; + break; + + case LDAP_NO_SUCH_OBJECT: + /* NOTE: backend_group() returns noSuchObject + * if op_ndn does not exist; however, since + * dynamic list expansion means that the + * member attribute is virtually present, the + * non-existence of the asserted value implies + * the assertion is FALSE rather than + * UNDEFINED */ + rs->sr_err = LDAP_COMPARE_FALSE; + + /* If also using static groups, fallback to + * vanilla compare + */ + if ( dlm->dlm_static_oc ) + return SLAP_CB_CONTINUE; + + break; + } + +done:; + if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx ); + overlay_entry_release_ov( &o, e, 0, on ); + + send_ldap_result( op, rs ); + return rs->sr_err; + } + } + + be = select_backend( &o.o_req_ndn, 1 ); + if ( !be || !be->be_search ) { + return SLAP_CB_CONTINUE; + } + + if ( e == NULL && ( overlay_entry_get_ov( &o, &o.o_req_ndn, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL )) + { + return SLAP_CB_CONTINUE; + } + + /* check for dynlist objectClass; done if not found */ + dli = (dynlist_info_t *)dlg->dlg_dli; + while ( dli != NULL && ( !is_entry_objectclass_or_sub( e, dli->dli_oc ) || + !dynlist_check_scope( op, e, dli ))) { + dli = dli->dli_next; + } + if ( dli == NULL ) { + goto release; + } + + if ( ad_dgIdentity ) { + Attribute *id = attrs_find( e->e_attrs, ad_dgIdentity ); + if ( id ) { + Attribute *authz; + + /* if not rootdn and dgAuthz is present, + * check if user can be authorized as dgIdentity */ + if ( ad_dgAuthz && !BER_BVISEMPTY( &id->a_nvals[0] ) && !be_isroot( op ) + && ( authz = attrs_find( e->e_attrs, ad_dgAuthz ) ) ) + { + if ( slap_sasl_matches( op, authz->a_nvals, + &o.o_ndn, &o.o_ndn ) != LDAP_SUCCESS ) + { + goto release; + } + } + + o.o_dn = id->a_vals[0]; + o.o_ndn = id->a_nvals[0]; + o.o_groups = NULL; + } + } + + /* generate dynamic list with dynlist_response() and compare */ + { + SlapReply r = { REP_SEARCH }; + Attribute *a; + AttributeName an[2]; + + o.o_tag = LDAP_REQ_SEARCH; + o.ors_limit = NULL; + o.ors_tlimit = SLAP_NO_LIMIT; + o.ors_slimit = SLAP_NO_LIMIT; + + o.ors_filterstr = *slap_filterstr_objectClass_pres; + o.ors_filter = (Filter *) slap_filter_objectClass_pres; + + o.ors_scope = LDAP_SCOPE_BASE; + o.ors_deref = LDAP_DEREF_NEVER; + an[0].an_name = op->orc_ava->aa_desc->ad_cname; + an[0].an_desc = op->orc_ava->aa_desc; + BER_BVZERO( &an[1].an_name ); + o.ors_attrs = an; + o.ors_attrsonly = 0; + r.sr_entry = e; + r.sr_attrs = an; + + o.o_acl_priv = ACL_COMPARE; + dynlist_prepare_entry( &o, &r, on, dli, NULL ); + a = attrs_find( r.sr_entry->e_attrs, op->orc_ava->aa_desc ); + + ret = LDAP_NO_SUCH_ATTRIBUTE; + for ( ; a ; a = attrs_find( a->a_next, op->orc_ava->aa_desc )) { + ret = LDAP_COMPARE_FALSE; + if ( attr_valfind( a, + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, + &op->orc_ava->aa_value, NULL, op->o_tmpmemctx ) == LDAP_SUCCESS ) { + ret = LDAP_COMPARE_TRUE; + break; + } + } + rs->sr_err = ret; + + if ( r.sr_entry != e ) + entry_free( r.sr_entry ); + send_ldap_result( op, rs ); + } + +release:; + if ( e != NULL ) { + overlay_entry_release_ov( &o, e, 0, on ); + } + + return ret; +} + +#define WANT_MEMBEROF 1 +#define WANT_MEMBER 2 + +typedef struct dynlist_search_t { + slap_overinst *ds_on; + TAvlnode *ds_names; + TAvlnode *ds_fnodes; + dynlist_info_t *ds_dli; + dynlist_map_t *ds_dlm; + Filter *ds_origfilter; + struct berval ds_origfilterbv; + int ds_want; + int ds_found; +} dynlist_search_t; + +static int +dynlist_avl_cmp( const void *c1, const void *c2 ) +{ + const dynlist_name_t *n1, *n2; + int rc; + n1 = c1; n2 = c2; + + rc = n1->dy_nname.bv_len - n2->dy_nname.bv_len; + if ( rc ) return rc; + return ber_bvcmp( &n1->dy_nname, &n2->dy_nname ); +} + +/* build a list of dynamic entries */ +static int +dynlist_search1resp( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) { + dynlist_search_t *ds = op->o_callback->sc_private; + Attribute *a, *b = NULL; + + if ( ds->ds_dlm && ds->ds_dlm->dlm_static_oc && is_entry_objectclass( rs->sr_entry, ds->ds_dlm->dlm_static_oc, 0 )) + b = attr_find( rs->sr_entry->e_attrs, ds->ds_dlm->dlm_member_ad ); + a = attr_find( rs->sr_entry->e_attrs, ds->ds_dli->dli_ad ); + + /* enforce scope of dynamic entries */ + if ( a && !dynlist_check_scope( op, rs->sr_entry, ds->ds_dli )) + a = NULL; + + if ( a || b ) { + unsigned len; + dynlist_name_t *dyn; + struct berval bv, nbase; + LDAPURLDesc *ludp; + int i, j = 0; + + if ( a ) + len = a->a_numvals * sizeof(LDAPURLDesc *); + else + len = 0; + + dyn = ch_calloc(1, sizeof(dynlist_name_t)+rs->sr_entry->e_nname.bv_len + 1 + + rs->sr_entry->e_name.bv_len + 1 + len); + dyn->dy_name.bv_val = ((char *)(dyn+1)) + len; + dyn->dy_name.bv_len = rs->sr_entry->e_name.bv_len; + dyn->dy_nname.bv_val = dyn->dy_name.bv_val + dyn->dy_name.bv_len + 1; + dyn->dy_nname.bv_len = rs->sr_entry->e_nname.bv_len; + dyn->dy_dli = ds->ds_dli; + dyn->dy_dlm = ds->ds_dlm; + if ( a ) { + Filter *f; + /* parse and validate the URIs */ + for (i=0; ia_numvals; i++) { + if (ldap_url_parse( a->a_vals[i].bv_val, &ludp ) != LDAP_URL_SUCCESS ) + continue; + if (( ludp->lud_host && *ludp->lud_host) + || ludp->lud_exts ) { + skipit: + ldap_free_urldesc( ludp ); + continue; + } + ber_str2bv( ludp->lud_dn, 0, 0, &bv ); + if ( dnNormalize( 0, NULL, NULL, &bv, &nbase, op->o_tmpmemctx ) != LDAP_SUCCESS ) + goto skipit; + ldap_memfree( ludp->lud_dn ); + ludp->lud_dn = ldap_strdup( nbase.bv_val ); + op->o_tmpfree( nbase.bv_val, op->o_tmpmemctx ); + /* cheat here, reuse fields */ + ludp->lud_port = nbase.bv_len; + if ( ludp->lud_filter && *ludp->lud_filter ) { + f = str2filter( ludp->lud_filter ); + if ( f == NULL ) + goto skipit; + ldap_memfree( ludp->lud_filter ); + } else { + f = ch_malloc( sizeof( Filter )); + f->f_choice = SLAPD_FILTER_COMPUTED; + f->f_result = LDAP_COMPARE_TRUE; + f->f_next = NULL; + } + ludp->lud_filter = (char *)f; + dyn->dy_uris[j] = ludp; + j++; + } + } + dyn->dy_numuris = j; + memcpy(dyn->dy_name.bv_val, rs->sr_entry->e_name.bv_val, rs->sr_entry->e_name.bv_len ); + memcpy(dyn->dy_nname.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len ); + if ( b ) + dyn->dy_staticmember = ds->ds_dlm->dlm_member_ad; + + if ( ldap_tavl_insert( &ds->ds_names, dyn, dynlist_avl_cmp, ldap_avl_dup_error )) { + for (i=dyn->dy_numuris-1; i>=0; i--) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_filter ) { + filter_free( (Filter *)ludp->lud_filter ); + ludp->lud_filter = NULL; + } + ldap_free_urldesc( ludp ); + } + ch_free( dyn ); + } else { + ds->ds_found++; + } + } + } + return 0; +} + +/* replace a filter clause (memberOf=) with an expansion + * of its dynamic members + * using (&(entryDN=)) + */ +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; ia_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=) with an expansion + * of its static members + * using (|(entryDN=)[...]) + */ +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; ia_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=) with an expansion of + * its members. + */ +static int +dynlist_filter_group( Operation *op, dynlist_name_t *dyn, Filter *n, dynlist_search_t *ds ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + Entry *e; + Attribute *a; + int rc = -1; + + if ( ldap_tavl_insert( &ds->ds_fnodes, dyn, dynlist_ptr_cmp, ldap_avl_dup_error )) + return 0; + + if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &e, on ) != + LDAP_SUCCESS || e == NULL ) { + return -1; + } + if ( ds->ds_dlm->dlm_static_oc && is_entry_objectclass( e, ds->ds_dlm->dlm_static_oc, 0 )) { + a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad ); + if ( a ) { + rc = dynlist_filter_stgroup( op, n, a ); + } + } else { + a = attr_find( e->e_attrs, ds->ds_dli->dli_ad ); + if ( a ) { + rc = dynlist_filter_dyngroup( op, n, a ); + } + } + overlay_entry_release_ov( op, e, 0, on ); + if ( dyn->dy_subs && !rc ) { + TAvlnode *ptr; + for ( ptr = ldap_tavl_end( dyn->dy_subs, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dyn = ptr->avl_data; + rc = dynlist_filter_group( op, dyn, n, ds ); + if ( rc ) + break; + } + } + return rc; +} + +/* Dup the filter, replacing any references to given ad with group evaluation */ +static Filter * +dynlist_filter_dup( Operation *op, Filter *f, AttributeDescription *ad, dynlist_search_t *ds ) +{ + Filter *n; + + if ( !f ) + return NULL; + + switch( f->f_choice & SLAPD_FILTER_MASK ) { + case LDAP_FILTER_EQUALITY: + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_next = NULL; + if ( f->f_av_desc == ad ) { + dynlist_name_t *dyn = ldap_tavl_find( ds->ds_names, &f->f_av_value, dynlist_avl_cmp ); + n->f_choice = SLAPD_FILTER_COMPUTED; + if ( dyn && !dynlist_filter_group( op, dyn, n, ds )) + break; + } + n->f_choice = LDAP_FILTER_EQUALITY; + n->f_ava = ava_dup( f->f_ava, op->o_tmpmemctx ); + break; + case SLAPD_FILTER_COMPUTED: + case LDAP_FILTER_PRESENT: + case LDAP_FILTER_GE: + case LDAP_FILTER_LE: + case LDAP_FILTER_APPROX: + case LDAP_FILTER_SUBSTRINGS: + case LDAP_FILTER_EXT: + n = filter_dup( f, op->o_tmpmemctx ); + break; + + case LDAP_FILTER_NOT: + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: { + Filter **p; + + n = op->o_tmpalloc( sizeof(Filter), op->o_tmpmemctx ); + n->f_next = NULL; + n->f_choice = f->f_choice; + + for ( p = &n->f_list, f = f->f_list; f; f = f->f_next ) { + *p = dynlist_filter_dup( op, f, ad, ds ); + if ( !*p ) + continue; + p = &(*p)->f_next; + } + } + break; + } + return n; +} + +static void +dynlist_search_free( void *ptr ) +{ + dynlist_name_t *dyn = (dynlist_name_t *)ptr; + LDAPURLDesc *ludp; + int i; + + for (i=dyn->dy_numuris-1; i>=0; i--) { + ludp = dyn->dy_uris[i]; + if ( ludp->lud_filter ) { + filter_free( (Filter *)ludp->lud_filter ); + ludp->lud_filter = NULL; + } + ldap_free_urldesc( ludp ); + } + if ( dyn->dy_subs ) + ldap_tavl_free( dyn->dy_subs, NULL ); + if ( dyn->dy_sups ) + ldap_tavl_free( dyn->dy_sups, NULL ); + ch_free( ptr ); +} + +static int +dynlist_search_cleanup( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_RESULT || op->o_abandon || + rs->sr_err == SLAPD_ABANDON ) { + slap_callback *sc = op->o_callback; + dynlist_search_t *ds = op->o_callback->sc_private; + ldap_tavl_free( ds->ds_names, dynlist_search_free ); + if ( ds->ds_fnodes ) + ldap_tavl_free( ds->ds_fnodes, NULL ); + if ( ds->ds_origfilter ) { + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + filter_free_x( op, op->ors_filter, 1 ); + op->ors_filter = ds->ds_origfilter; + op->ors_filterstr = ds->ds_origfilterbv; + } + op->o_callback = sc->sc_next; + op->o_tmpfree( sc, op->o_tmpmemctx ); + + } + return 0; +} + +static int +dynlist_test_dynmember(Operation *op, dynlist_name_t *dyn, Entry *e) +{ + LDAPURLDesc *ludp; + struct berval nbase, bv; + int i, rc = LDAP_COMPARE_FALSE; + + for (i=0; idy_numuris; i++) { + ludp = dyn->dy_uris[i]; + nbase.bv_val = ludp->lud_dn; + nbase.bv_len = ludp->lud_port; + if ( ludp->lud_attrs ) + continue; + switch( ludp->lud_scope ) { + case LDAP_SCOPE_BASE: + if ( !dn_match( &nbase, &e->e_nname )) + continue; + break; + case LDAP_SCOPE_ONELEVEL: + dnParent( &e->e_nname, &bv ); + if ( !dn_match( &nbase, &bv )) + continue; + break; + case LDAP_SCOPE_SUBTREE: + if ( !dnIsSuffix( &e->e_nname, &nbase )) + continue; + break; + case LDAP_SCOPE_SUBORDINATE: + if ( dn_match( &nbase, &e->e_nname ) || + !dnIsSuffix( &e->e_nname, &nbase )) + continue; + break; + } + if ( !ludp->lud_filter ) /* there really should always be a filter */ + rc = LDAP_COMPARE_TRUE; + else + rc = test_filter( op, e, (Filter *)ludp->lud_filter ); + if ( rc == LDAP_COMPARE_TRUE ) + break; + } + return rc; +} + +static int +dynlist_test_membership(Operation *op, slap_overinst *on, dynlist_name_t *dyn, Entry *e) +{ + if ( dyn->dy_staticmember ) { + Entry *grp; + if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &grp, on ) == LDAP_SUCCESS && grp ) { + Attribute *a = attr_find( grp->e_attrs, dyn->dy_staticmember ); + int rc; + if ( a ) { + rc = value_find_ex( dyn->dy_staticmember, SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH, a->a_nvals, &e->e_nname, op->o_tmpmemctx ); + rc = ( rc == LDAP_SUCCESS ) ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE; + } else { + rc = LDAP_COMPARE_FALSE; + } + overlay_entry_release_ov( op, grp, 0, on ); + return rc; + } + } + return dynlist_test_dynmember( op, dyn, e ); +} + +static void +dynlist_add_memberOf(Operation *op, SlapReply *rs, dynlist_search_t *ds) +{ + TAvlnode *ptr; + Entry *e = rs->sr_entry; + dynlist_name_t *dyn; + Attribute *a; + + /* See if there are any memberOf values to attach to this entry */ + for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + dynlist_map_t *dlm; + dyn = ptr->avl_data; + for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + if ( dynlist_test_membership( op, ds->ds_on, dyn, e ) == LDAP_COMPARE_TRUE ) { + /* ensure e is modifiable, but do not replace + * sr_entry yet since we have pointers into it */ + if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) && e == rs->sr_entry ) { + e = entry_dup( rs->sr_entry ); + } + a = attr_find( e->e_attrs, dlm->dlm_memberOf_ad ); + if ( a ) { + unsigned slot; + if ( attr_valfind( a, SLAP_MR_EQUALITY | SLAP_MR_VALUE_OF_ASSERTION_SYNTAX | + SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH | + SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH, + &dyn->dy_nname, &slot, NULL ) != LDAP_SUCCESS ) + a = NULL; + } + if ( !a ) + attr_merge_one( e, dlm->dlm_memberOf_ad, &dyn->dy_name, &dyn->dy_nname ); + if ( dyn->dy_sups ) { + dynlist_nested_memberOf( e, dlm->dlm_memberOf_ad, dyn->dy_sups ); + } + break; + } + } + } + } + if ( e != rs->sr_entry ) { + rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e ); + rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED; + } +} + +/* See if a DN-valued filter attribute belongs to a dyngroup */ +static int +dynmember( dynlist_name_t *dyn, Filter *f, int ndf, dynlist_filterinst_t *df ) +{ + int i; + int ret = 1; /* default to accepting everything */ + + for ( i = 0; i < ndf; i++ ) { + if ( df[i].df_e ) { + ret = dynlist_test_dynmember( NULL, dyn, df[i].df_e ) == LDAP_COMPARE_TRUE; + if ( ret ) + break; + } + } + return ret; +} + +/* process the search responses */ +static int +dynlist_search2resp( Operation *op, SlapReply *rs ) +{ + dynlist_search_t *ds = op->o_callback->sc_private; + dynlist_name_t *dyn; + int rc; + + if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) { + rc = SLAP_CB_CONTINUE; + /* See if this is one of our dynamic groups */ + dyn = NULL; + if ( ds->ds_names ) { + dyn = ldap_tavl_find( ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp ); + if ( dyn ) { + dyn->dy_seen = 1; + rc = dynlist_prepare_entry( op, rs, ds->ds_on, dyn->dy_dli, dyn ); + } else if ( ds->ds_want ) + dynlist_add_memberOf( op, rs, ds ); + } + /* Then check for dynamic lists */ + if ( dyn == NULL ) { + dynlist_info_t *dli; + Attribute *a = attr_find ( rs->sr_entry->e_attrs, slap_schema.si_ad_objectClass ); + if ( a ) { + for ( dli = ds->ds_dli; dli; dli = dli->dli_next ) { + if ( is_entry_objectclass_or_sub( rs->sr_entry, dli->dli_oc ) && + dynlist_check_scope( op, rs->sr_entry, dli )) + rc = dynlist_prepare_entry( op, rs, ds->ds_on, dli, NULL ); + } + } + } + if ( ds->ds_origfilter && test_filter( op, rs->sr_entry, ds->ds_origfilter ) != LDAP_COMPARE_TRUE ) { + rs_flush_entry( op, rs, NULL ); + return LDAP_SUCCESS; + } + return rc; + } else if ( rs->sr_type == REP_RESULT && rs->sr_err == LDAP_SUCCESS ) { + slap_overinst *on = ds->ds_on; + TAvlnode *ptr, *skip = NULL; + SlapReply r = *rs; + dynlist_map_t *dlm = NULL; + Filter *f = ds->ds_origfilter ? ds->ds_origfilter : op->ors_filter; + dynlist_filterinst_t *df = NULL; + int ndf = 0; + + if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) + return SLAP_CB_CONTINUE; + + /* Check for any unexpanded dynamic group entries that weren't picked up + * by the original search filter. + */ + ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); + while ( ptr ) { + dyn = ptr->avl_data; + if ( dyn->dy_seen ) + goto next; + dyn->dy_seen = 1; + if ( !dnIsSuffixScope( &dyn->dy_nname, &op->o_req_ndn, op->ors_scope )) + goto next; + /* can only pre-check if this is a dyngroup, otherwise just build the entry */ + if ( dyn->dy_dli->dli_dlm && !dyn->dy_dli->dli_dlm->dlm_next && + dyn->dy_dlm && !dyn->dy_dlm->dlm_mapped_ad ) { + if ( !dlm ) { + AttributeDescription *ad; + int i; + dlm = dyn->dy_dlm; + ad = dlm->dlm_member_ad; + /* can only pre-check DN-valued attrs */ + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) { + /* find any instances of this ad in the filter */ + dynlist_filter_instances( op, ad, f, 0, &ndf, &df ); + for ( i = 0; i < ndf; i++ ) { + overlay_entry_get_ov( op, &df[i].df_a->aa_value, NULL, NULL, 0, &df[i].df_e, on ); + } + } + } else if ( dlm != dyn->dy_dlm ) { /* if a different map, do it later */ + if ( !skip ) + skip = ptr; + dyn->dy_seen = 0; /* we'll want to process it next time thru */ + goto next; + } + /* only pre-check for non-nested */ + if ( !dyn->dy_sups && !dyn->dy_subs && ndf && !dynmember( dyn, f, ndf, df )) + goto next; + } + if ( overlay_entry_get_ov( op, &dyn->dy_nname, NULL, NULL, 0, &r.sr_entry, on ) != LDAP_SUCCESS || + r.sr_entry == NULL ) + goto next; + r.sr_flags = REP_ENTRY_MUSTRELEASE; + dynlist_prepare_entry( op, &r, on, dyn->dy_dli, dyn ); + if ( test_filter( op, r.sr_entry, f ) == LDAP_COMPARE_TRUE ) { + r.sr_attrs = op->ors_attrs; + rs->sr_err = send_search_entry( op, &r ); + if ( rs->sr_err != LDAP_SUCCESS ) + break; + r.sr_entry = NULL; + } + if ( r.sr_entry ) + rs_flush_entry( op, &r, NULL ); +next: + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT ); + if ( !ptr ) { + int i; + for ( i = 0; io_tmpfree( df, op->o_tmpmemctx ); + ndf = 0; + if ( skip ) { /* go back for dyns we skipped */ + ptr = skip; + skip = NULL; + dlm = NULL; + df = NULL; + } + } + } + if ( ndf ) { + int i; + for ( i = 0; io_tmpfree( df, op->o_tmpmemctx ); + } + rs->sr_nentries = r.sr_nentries; + } + return SLAP_CB_CONTINUE; +} + +static void +dynlist_fix_filter( Operation *op, AttributeDescription *ad, dynlist_search_t *ds ) +{ + Filter *f; + f = dynlist_filter_dup( op, op->ors_filter, ad, ds ); + if ( ds->ds_origfilter ) { + filter_free_x( op, op->ors_filter, 1 ); + op->o_tmpfree( op->ors_filterstr.bv_val, op->o_tmpmemctx ); + } else { + ds->ds_origfilter = op->ors_filter; + ds->ds_origfilterbv = op->ors_filterstr; + } + op->ors_filter = f; + filter2bv_x( op, f, &op->ors_filterstr ); +} + +typedef struct dynlist_link_t { + dynlist_search_t *dl_ds; + dynlist_name_t *dl_sup; +} dynlist_link_t; + +static int +dynlist_nestlink_dg( Operation *op, SlapReply *rs ) +{ + dynlist_link_t *dll = op->o_callback->sc_private; + dynlist_search_t *ds = dll->dl_ds; + dynlist_name_t *di = dll->dl_sup, *dj; + + if ( rs->sr_type != REP_SEARCH ) + return LDAP_SUCCESS; + + dj = ldap_tavl_find( dll->dl_ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp ); + if ( dj ) { + if ( ds->ds_want & WANT_MEMBEROF ) { + ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + if ( ds->ds_want & WANT_MEMBER ) { + ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + } + return LDAP_SUCCESS; +} + +/* Connect all nested groups to their parents/children */ +static void +dynlist_nestlink( Operation *op, dynlist_search_t *ds ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_name_t *di, *dj; + TAvlnode *ptr; + Entry *e; + Attribute *a; + int i; + + for ( ptr = ldap_tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr; + ptr = ldap_tavl_next( ptr, TAVL_DIR_RIGHT )) { + di = ptr->avl_data; + if ( ds->ds_dlm ) { + if ( overlay_entry_get_ov( op, &di->dy_nname, NULL, NULL, 0, &e, on ) != LDAP_SUCCESS || e == NULL ) + continue; + a = attr_find( e->e_attrs, ds->ds_dlm->dlm_member_ad ); + if ( a ) { + for ( i=0; i < a->a_numvals; i++ ) { + dj = ldap_tavl_find( ds->ds_names, &a->a_nvals[i], dynlist_avl_cmp ); + if ( dj ) { + if ( ds->ds_want & WANT_MEMBEROF ) { + ldap_tavl_insert( &dj->dy_sups, di, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + if ( ds->ds_want & WANT_MEMBER ) { + ldap_tavl_insert( &di->dy_subs, dj, dynlist_ptr_cmp, ldap_avl_dup_error ); + } + } + } + } + overlay_entry_release_ov( op, e, 0, on ); + } + + if ( di->dy_numuris ) { + slap_callback cb = { 0 }; + dynlist_link_t dll; + dll.dl_ds = ds; + dll.dl_sup = di; + cb.sc_private = &dll; + cb.sc_response = dynlist_nestlink_dg; + dynlist_urlmembers( op, di, &cb ); + } + } +} + +static int +dynlist_search( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli; + Operation o = *op; + dynlist_map_t *dlm; + Filter f[4]; + AttributeAssertion ava[2]; + AttributeName an[2] = {0}; + + slap_callback *sc; + dynlist_search_t *ds; + ObjectClass *static_oc; + int nested, found, tmpwant; + int opattrs, userattrs; + + if ( get_manageDSAit( op ) ) + return SLAP_CB_CONTINUE; + + sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(dynlist_search_t), op->o_tmpmemctx ); + sc->sc_private = (void *)(sc+1); + ds = sc->sc_private; + + memset( o.o_ctrlflag, 0, sizeof( o.o_ctrlflag )); + o.o_managedsait = SLAP_CONTROL_CRITICAL; + o.o_do_not_cache = 1; + + /* Are we using memberOf, and does it affect this request? */ + if ( dlg->dlg_memberOf ) { + int attrflags = slap_attr_flags( op->ors_attrs ); + opattrs = SLAP_OPATTRS( attrflags ); + userattrs = SLAP_USERATTRS( attrflags ); + } + + if (dlg->dlg_simple) + goto simple; + /* Find all groups in scope. For group expansion + * we only need the groups within the search scope, but + * for memberOf populating, we need all dyngroups. + */ + for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) { + static_oc = NULL; + nested = 0; + tmpwant = 0; + + if ( !dli->dli_dlm ) { + /* A dynamic list returning arbitrary attrs: + * we don't know what attrs it might return, + * so we can't check if any of its attrs are + * in the filter. So assume none of them are. + * + * If filtering is desired, the filterable attrs + * must be explicitly mapped (even to + * themselves if nothing else). + */ + continue; + } else { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + int want = 0; + + /* is attribute in filter? */ + if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) { + want |= WANT_MEMBEROF; + /* with nesting, filter attributes also require nestlink */ + if ( dlm->dlm_memberOf_nested ) { + /* WANT_ flags have inverted meaning here: + * to satisfy (memberOf=) filter, we need to also + * find all subordinate groups. No special + * treatment is needed for (member=) since we + * already search all group entries. + */ + want |= WANT_MEMBER; + } + } + + /* if attribute is not requested, skip it */ + if ( op->ors_attrs == NULL ) { + if ( !dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } else { + if ( ad_inlist( dlm->dlm_memberOf_ad, op->ors_attrs )) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && ad_inlist( dlm->dlm_member_ad, op->ors_attrs )) + want |= WANT_MEMBER; + } else { + if ( opattrs ) { + if ( dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } + if ( userattrs ) { + if ( !dlm->dlm_memberOf_oper ) { + want |= WANT_MEMBEROF; + if ( dlm->dlm_memberOf_nested && !dlm->dlm_member_oper ) + want |= WANT_MEMBER; + } + } + } + } + if ( want ) { + nested = dlm->dlm_memberOf_nested; + ds->ds_want = tmpwant = want; + if ( dlm->dlm_static_oc ) { + static_oc = dlm->dlm_static_oc; + ds->ds_dlm = dlm; + } + } + } + { + AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad; + if ( ad_infilter( ad, op->ors_filter )) { + tmpwant |= WANT_MEMBER; + ds->ds_want = tmpwant; + ds->ds_dlm = dlm; + } + } + } + } + + if ( tmpwant ) { + Filter *f_new = NULL; + + if ( tmpwant == WANT_MEMBER ) { + /* + * If we only need to list groups, not their members, keep the + * filter, assuming any references to mapped attributes make it + * succeed. + * + * A nested groups search will indicate that it needs both. + */ + f_new = transform_filter( op, dli, 0, NULL ); + } + + if ( static_oc ) { + f[0].f_choice = LDAP_FILTER_AND; + f[0].f_list = &f[1]; + f[0].f_next = NULL; + f[1].f_choice = LDAP_FILTER_OR; + f[1].f_list = &f[2]; + f[1].f_next = f_new; + f[2].f_choice = LDAP_FILTER_EQUALITY; + f[2].f_next = &f[3]; + f[2].f_ava = &ava[0]; + f[2].f_av_desc = slap_schema.si_ad_objectClass; + f[2].f_av_value = dli->dli_oc->soc_cname; + f[3].f_choice = LDAP_FILTER_EQUALITY; + f[3].f_ava = &ava[1]; + f[3].f_av_desc = slap_schema.si_ad_objectClass; + f[3].f_av_value = static_oc->soc_cname; + f[3].f_next = NULL; + } else { + f[0].f_choice = LDAP_FILTER_AND; + f[0].f_list = &f[1]; + f[0].f_next = NULL; + f[1].f_choice = LDAP_FILTER_EQUALITY; + f[1].f_ava = ava; + f[1].f_av_desc = slap_schema.si_ad_objectClass; + f[1].f_av_value = dli->dli_oc->soc_cname; + f[1].f_next = f_new; + } + + if ( o.o_callback != sc ) { + o.o_callback = sc; + o.ors_filter = f; + if ( tmpwant ) { + o.o_req_dn = op->o_bd->be_suffix[0]; + o.o_req_ndn = op->o_bd->be_nsuffix[0]; + o.ors_scope = LDAP_SCOPE_SUBTREE; + } else { + o.o_req_dn = op->o_req_dn; + o.o_req_ndn = op->o_req_ndn; + o.ors_scope = op->ors_scope; + } + o.ors_attrsonly = 0; + o.ors_attrs = an; + o.o_bd = select_backend( op->o_bd->be_nsuffix, 1 ); + BER_BVZERO( &o.ors_filterstr ); + sc->sc_response = dynlist_search1resp; + } + + ds->ds_dli = dli; + if ( o.ors_filterstr.bv_val ) + o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx ); + filter2bv_x( &o, f, &o.ors_filterstr ); + an[0].an_desc = dli->dli_ad; + an[0].an_name = dli->dli_ad->ad_cname; + found = ds->ds_found; + { + SlapReply r = { REP_SEARCH }; + (void)o.o_bd->be_search( &o, &r ); + } + o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx ); + o.ors_filterstr.bv_val = NULL; + filter_free_x( &o, f_new, 1 ); + if ( found != ds->ds_found && nested ) + dynlist_nestlink( op, ds ); + } + } +simple: + + if ( dlg->dlg_dli || ds->ds_names != NULL ) { + sc->sc_response = dynlist_search2resp; + sc->sc_cleanup = dynlist_search_cleanup; + sc->sc_next = op->o_callback; + op->o_callback = sc; + ds->ds_on = on; + + /* dynamic lists need this */ + ds->ds_dli = dlg->dlg_dli; + + /* see if filter needs fixing */ + if ( dlg->dlg_memberOf ) { + for ( dli = dlg->dlg_dli; dli; dli = dli->dli_next ) { + for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) { + if ( dlm->dlm_memberOf_ad ) { + + /* if attribute is in filter, fix it */ + if ( ad_infilter( dlm->dlm_memberOf_ad, op->ors_filter )) { + ds->ds_dli = dli; + ds->ds_dlm = dlm; + dynlist_fix_filter( op, dlm->dlm_memberOf_ad, ds ); + } + } + } + } + } + + } else { + op->o_tmpfree( sc, op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; +} + +static int +dynlist_build_def_filter( dynlist_info_t *dli ) +{ + char *ptr; + + dli->dli_default_filter.bv_len = STRLENOF( "(!(objectClass=" "))" ) + + dli->dli_oc->soc_cname.bv_len; + dli->dli_default_filter.bv_val = ch_malloc( dli->dli_default_filter.bv_len + 1 ); + if ( dli->dli_default_filter.bv_val == NULL ) { + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: malloc failed.\n" ); + return -1; + } + + ptr = lutil_strcopy( dli->dli_default_filter.bv_val, "(!(objectClass=" ); + ptr = lutil_strcopy( ptr, dli->dli_oc->soc_cname.bv_val ); + ptr = lutil_strcopy( ptr, "))" ); + + assert( ptr == &dli->dli_default_filter.bv_val[dli->dli_default_filter.bv_len] ); + + return 0; +} + +enum { + DL_ATTRSET = 1, + DL_ATTRPAIR, + DL_ATTRPAIR_COMPAT, + DL_LAST +}; + +static ConfigDriver dl_cfgen; + +/* XXXmanu 255 is the maximum arguments we allow. Can we go beyond? */ +static ConfigTable dlcfg[] = { + { "dynlist-attrset", "group-oc> [uri] <[mapped:]member-ad> [...]", + 3, 0, 0, ARG_MAGIC|DL_ATTRSET, dl_cfgen, + "( OLcfgOvAt:8.1 NAME ( 'olcDynListAttrSet' 'olcDlAttrSet' ) " + "DESC 'Dynamic list: , , ' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "dynlist-attrpair", "member-ad> 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 \": " + "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 \": " + "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 \": " + "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 \": " + "URL attributeDescription \"%s\" already mapped.\n", + ad->ad_cname.bv_val ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", + c->log, c->cr_msg ); +#if 0 + /* make it a warning... */ + return 1; +#endif + } + } + + *dlip = (dynlist_info_t *)ch_calloc( 1, sizeof( dynlist_info_t ) ); + + (*dlip)->dli_oc = oc; + (*dlip)->dli_ad = ad; + (*dlip)->dli_dlm = (dynlist_map_t *)ch_calloc( 1, sizeof( dynlist_map_t ) ); + (*dlip)->dli_dlm->dlm_member_ad = member_ad; + (*dlip)->dli_dlm->dlm_mapped_ad = NULL; + + rc = dynlist_build_def_filter( *dlip ); + + } break; + + default: + rc = 1; + break; + } + + return rc; +} + +static int +dynlist_db_init( + BackendDB *be, + ConfigReply *cr) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + dynlist_gen_t *dlg; + + if ( SLAP_ISGLOBALOVERLAY( be ) ) { + Debug( LDAP_DEBUG_ANY, "dynlist cannot be used as global overlay.\n" ); + return 1; + } + + dlg = (dynlist_gen_t *)ch_calloc( 1, sizeof( *dlg )); + on->on_bi.bi_private = dlg; + + return 0; +} + +static int +dynlist_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli; + ObjectClass *oc = NULL; + AttributeDescription *ad = NULL; + const char *text; + int rc; + + if ( dli == NULL ) { + dli = ch_calloc( 1, sizeof( dynlist_info_t ) ); + dlg->dlg_dli = dli; + } + + for ( ; dli; dli = dli->dli_next ) { + if ( dli->dli_oc == NULL ) { + if ( oc == NULL ) { + oc = oc_find( "groupOfURLs" ); + if ( oc == NULL ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch objectClass \"groupOfURLs\"" ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg ); + return 1; + } + } + + dli->dli_oc = oc; + } + + if ( dli->dli_ad == NULL ) { + if ( ad == NULL ) { + rc = slap_str2ad( "memberURL", &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"memberURL\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s.\n", cr->msg ); + return 1; + } + } + + dli->dli_ad = ad; + } + + if ( BER_BVISNULL( &dli->dli_default_filter ) ) { + rc = dynlist_build_def_filter( dli ); + if ( rc != 0 ) { + return rc; + } + } + } + + if ( ad_dgIdentity == NULL ) { + rc = slap_str2ad( "dgIdentity", &ad_dgIdentity, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgIdentity\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg ); + /* Just a warning */ + } + } + + if ( ad_dgAuthz == NULL ) { + rc = slap_str2ad( "dgAuthz", &ad_dgAuthz, &text ); + if ( rc != LDAP_SUCCESS ) { + snprintf( cr->msg, sizeof( cr->msg), + "unable to fetch attributeDescription \"dgAuthz\": %d (%s)", + rc, text ); + Debug( LDAP_DEBUG_ANY, "dynlist_db_open: %s\n", cr->msg ); + /* Just a warning */ + } + } + + return 0; +} + +static int +dynlist_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *) be->bd_info; + + if ( on->on_bi.bi_private ) { + dynlist_gen_t *dlg = (dynlist_gen_t *)on->on_bi.bi_private; + dynlist_info_t *dli = dlg->dlg_dli, + *dli_next; + + for ( dli_next = dli; dli_next; dli = dli_next ) { + dynlist_map_t *dlm; + dynlist_map_t *dlm_next; + + dli_next = dli->dli_next; + + if ( !BER_BVISNULL( &dli->dli_uri ) ) { + ch_free( dli->dli_uri.bv_val ); + } + + if ( dli->dli_lud != NULL ) { + ldap_free_urldesc( dli->dli_lud ); + } + + if ( !BER_BVISNULL( &dli->dli_uri_nbase ) ) { + ber_memfree( dli->dli_uri_nbase.bv_val ); + } + + if ( dli->dli_uri_filter != NULL ) { + filter_free( dli->dli_uri_filter ); + } + + ch_free( dli->dli_default_filter.bv_val ); + + dlm = dli->dli_dlm; + while ( dlm != NULL ) { + dlm_next = dlm->dlm_next; + ch_free( dlm ); + dlm = dlm_next; + } + ch_free( dli ); + } + ch_free( dlg ); + } + + return 0; +} + +static slap_overinst dynlist = { { NULL } }; +#ifdef TAKEOVER_DYNGROUP +static char *obsolete_names[] = { + "dyngroup", + NULL +}; +#endif + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC */ +int +dynlist_initialize(void) +{ + const char *text; + int rc = 0; + + /* See if we need to define memberOf opattr */ + rc = slap_str2ad( "memberOf", &ad_memberOf, &text ); + if ( rc ) { + rc = register_at( + "( 1.2.840.113556.1.2.102 " + "NAME 'memberOf' " + "DESC 'Group that the entry belongs to' " + "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' " + "EQUALITY distinguishedNameMatch " /* added */ + "USAGE dSAOperation " /* added; questioned */ + "NO-USER-MODIFICATION " /* added */ + "X-ORIGIN 'iPlanet Delegated Administrator' )", + &ad_memberOf, 0 ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "dynlist_initialize: register_at (memberOf) failed\n" ); + return rc; + } + } + + dynlist.on_bi.bi_type = "dynlist"; + +#ifdef TAKEOVER_DYNGROUP + /* makes dynlist incompatible with dyngroup */ + dynlist.on_bi.bi_obsolete_names = obsolete_names; +#endif + + dynlist.on_bi.bi_flags = SLAPO_BFLAG_SINGLE; + dynlist.on_bi.bi_db_init = dynlist_db_init; + dynlist.on_bi.bi_db_config = config_generic_wrapper; + dynlist.on_bi.bi_db_open = dynlist_db_open; + dynlist.on_bi.bi_db_destroy = dynlist_db_destroy; + + dynlist.on_bi.bi_op_search = dynlist_search; + dynlist.on_bi.bi_op_compare = dynlist_compare; + + dynlist.on_bi.bi_cf_ocs = dlocs; + + rc = config_register_schema( dlcfg, dlocs ); + if ( rc ) { + return rc; + } + + return overlay_register( &dynlist ); +} + +#if SLAPD_OVER_DYNLIST == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return dynlist_initialize(); +} +#endif + +#endif /* SLAPD_OVER_DYNLIST */ diff --git a/servers/slapd/overlays/homedir.c b/servers/slapd/overlays/homedir.c new file mode 100644 index 0000000..159090e --- /dev/null +++ b/servers/slapd/overlays/homedir.c @@ -0,0 +1,2074 @@ +/* homedir.c - create/remove user home directories */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#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> bi; + homedir_data *data = (homedir_data *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + + assert( data != NULL ); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: { + int i; + homedir_regexp *r; + struct berval bv; + char buf[4096]; + + bv.bv_val = buf; + for ( i = 0, r = data->regexps; r != NULL; ++i, r = r->next ) { + bv.bv_len = snprintf( buf, sizeof(buf), "{%d}%s %s", i, + r->match, r->replace ); + if ( bv.bv_len >= sizeof(buf) ) { + Debug( LDAP_DEBUG_ANY, "homedir_regexp_cfg: " + "emit serialization failed: size %lu\n", + (unsigned long)bv.bv_len ); + return ARG_BAD_CONF; + } + value_add_one( &c->rvalue_vals, &bv ); + } + rc = 0; + } break; + + case LDAP_MOD_DELETE: + if ( c->valx < 0 ) { /* delete all values */ + homedir_regexp *r, *rnext; + + for ( r = data->regexps; r != NULL; r = rnext ) { + rnext = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + } + data->regexps = NULL; + rc = 0; + + } else { /* delete value by index*/ + homedir_regexp **rp, *r; + int i; + + for ( i = 0, rp = &data->regexps; i < c->valx; + ++i, rp = &(*rp)->next ) + ; + + r = *rp; + *rp = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + + rc = 0; + } + break; + + case LDAP_MOD_ADD: /* fallthrough */ + case SLAP_CONFIG_ADD: { /* add values */ + char *match = c->argv[1]; + char *replace = c->argv[2]; + regex_t compiled; + homedir_regexp **rp, *r; + + memset( &compiled, 0, sizeof(compiled) ); + rc = regcomp( &compiled, match, REG_EXTENDED ); + if ( rc ) { + regerror( rc, &compiled, c->cr_msg, sizeof(c->cr_msg) ); + regfree( &compiled ); + return ARG_BAD_CONF; + } + + r = ch_calloc( 1, sizeof(homedir_regexp) ); + r->match = strdup( match ); + r->replace = strdup( replace ); + r->compiled = compiled; + + if ( c->valx == -1 ) { /* append */ + for ( rp = &data->regexps; ( *rp ) != NULL; + rp = &(*rp)->next ) + ; + *rp = r; + + } else { /* insert at valx */ + int i; + for ( i = 0, rp = &data->regexps; i < c->valx; + rp = &(*rp)->next, ++i ) + ; + r->next = *rp; + *rp = r; + } + rc = 0; + break; + } + default: + abort(); + } + + return rc; +} + +static int +homedir_style_cfg( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + homedir_data *data = (homedir_data *)on->on_bi.bi_private; + int rc = ARG_BAD_CONF; + struct berval bv; + + assert( data != NULL ); + + switch ( c->op ) { + case SLAP_CONFIG_EMIT: + bv.bv_val = data->style == DEL_IGNORE ? "IGNORE" : + data->style == DEL_DELETE ? "DELETE" : + "ARCHIVE"; + bv.bv_len = strlen( bv.bv_val ); + rc = value_add_one( &c->rvalue_vals, &bv ); + if ( rc != 0 ) return ARG_BAD_CONF; + break; + + case LDAP_MOD_DELETE: + data->style = DEL_IGNORE; + rc = 0; + break; + + case LDAP_MOD_ADD: /* fallthrough */ + case SLAP_CONFIG_ADD: /* add values */ + if ( strcasecmp( c->argv[1], "IGNORE" ) == 0 ) + data->style = DEL_IGNORE; + else if ( strcasecmp( c->argv[1], "DELETE" ) == 0 ) + data->style = DEL_DELETE; + else if ( strcasecmp( c->argv[1], "ARCHIVE" ) == 0 ) + data->style = DEL_ARCHIVE; + else { + Debug( LDAP_DEBUG_ANY, "homedir_style_cfg: " + "unrecognized style keyword\n" ); + return ARG_BAD_CONF; + } + rc = 0; + break; + + default: + abort(); + } + + return rc; +} + +#define HOMEDIR_NULLWRAP(x) ( ( x ) == NULL ? "unknown" : (x) ) +static void +report_errno( const char *parent_func, const char *func, const char *filename ) +{ + int save_errno = errno; + char ebuf[1024]; + + Debug( LDAP_DEBUG_ANY, "homedir: " + "%s: %s: \"%s\": %d (%s)\n", + HOMEDIR_NULLWRAP(parent_func), HOMEDIR_NULLWRAP(func), + HOMEDIR_NULLWRAP(filename), save_errno, + AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) ); +} + +static int +copy_link( + const char *dest_file, + const char *source_file, + const struct stat *st, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + char *buf = NULL; + int rc; + + assert( dest_file != NULL ); + assert( source_file != NULL ); + assert( st != NULL ); + assert( (st->st_mode & S_IFMT) == S_IFLNK ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_link: %s to %s\n", + source_file, dest_file ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_link: %s uid %ld gid %ld\n", + dest_file, (long)uidn, (long)gidn ); + + /* calloc +1 for terminator */ + buf = ber_memcalloc_x( 1, st->st_size + 1, ctx ); + if ( buf == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "copy_link: alloc failed\n" ); + return 1; + } + rc = readlink( source_file, buf, st->st_size ); + if ( rc == -1 ) { + report_errno( "copy_link", "readlink", source_file ); + goto fail; + } + rc = symlink( buf, dest_file ); + if ( rc ) { + report_errno( "copy_link", "symlink", dest_file ); + goto fail; + } + rc = lchown( dest_file, uidn, gidn ); + if ( rc ) { + report_errno( "copy_link", "lchown", dest_file ); + goto fail; + } + goto out; + +fail: + rc = 1; + +out: + if ( buf != NULL ) ber_memfree_x( buf, ctx ); + return rc; +} + +static int +copy_blocks( + FILE *source, + FILE *dest, + const char *source_file, + const char *dest_file ) +{ + char buf[4096]; + size_t nread = 0; + int done = 0; + + while ( !done ) { + nread = fread( buf, 1, sizeof(buf), source ); + if ( nread == 0 ) { + if ( feof( source ) ) { + done = 1; + } else if ( ferror( source ) ) { + if ( source_file != NULL ) + Debug( LDAP_DEBUG_ANY, "homedir: " + "read error on %s\n", + source_file ); + goto fail; + } + } else { + size_t nwritten = 0; + nwritten = fwrite( buf, 1, nread, dest ); + if ( nwritten < nread ) { + if ( dest_file != NULL ) + Debug( LDAP_DEBUG_ANY, "homedir: " + "write error on %s\n", + dest_file ); + goto fail; + } + } + } + return 0; +fail: + return 1; +} + +static int +copy_file( + const char *dest_file, + const char *source_file, + uid_t uid, + gid_t gid, + int mode ) +{ + FILE *source = NULL; + FILE *dest = NULL; + int rc; + + assert( dest_file != NULL ); + assert( source_file != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s to %s mode 0%o\n", + source_file, dest_file, mode ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s uid %ld gid %ld\n", + dest_file, (long)uid, (long)gid ); + + source = fopen( source_file, "rb" ); + if ( source == NULL ) { + report_errno( "copy_file", "fopen", source_file ); + goto fail; + } + dest = fopen( dest_file, "wb" ); + if ( dest == NULL ) { + report_errno( "copy_file", "fopen", dest_file ); + goto fail; + } + + rc = copy_blocks( source, dest, source_file, dest_file ); + if ( rc != 0 ) goto fail; + + fclose( source ); + source = NULL; + rc = fclose( dest ); + dest = NULL; + if ( rc != 0 ) { + report_errno( "copy_file", "fclose", dest_file ); + goto fail; + } + + /* set owner/permission */ + rc = lchown( dest_file, uid, gid ); + if ( rc != 0 ) { + report_errno( "copy_file", "lchown", dest_file ); + goto fail; + } + rc = chmod( dest_file, mode ); + if ( rc != 0 ) { + report_errno( "copy_file", "chmod", dest_file ); + goto fail; + } + + rc = 0; + goto out; +fail: + rc = 1; +out: + if ( source != NULL ) fclose( source ); + if ( dest != NULL ) fclose( dest ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_file: %s to %s exit %d\n", + source_file, dest_file, rc ); + return rc; +} + +static void +free_name_list( name_list *names, void *ctx ) +{ + name_list *next; + + while ( names != NULL ) { + next = names->next; + if ( names->name != NULL ) ber_memfree_x( names->name, ctx ); + ber_memfree_x( names, ctx ); + names = next; + } +} + +static int +grab_names( const char *dir_path, name_list **names, void *ctx ) +{ + int locked = 0; + DIR *dir = NULL; + struct dirent *entry = NULL; + name_list **tail = NULL; + int dir_path_len = 0; + int rc = 0; + + assert( dir_path != NULL ); + assert( names != NULL ); + assert( *names == NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: %s\n", dir_path ); + + tail = names; + dir_path_len = strlen( dir_path ); + ldap_pvt_thread_mutex_lock( &readdir_mutex ); + locked = 1; + + dir = opendir( dir_path ); + if ( dir == NULL ) { + report_errno( "grab_names", "opendir", dir_path ); + goto fail; + } + + while ( ( entry = readdir( dir ) ) != NULL ) { + /* no d_namelen in ac/dirent.h */ + int d_namelen = strlen( entry->d_name ); + int full_len; + + /* Skip . and .. */ + if ( ( d_namelen == 1 && entry->d_name[0] == '.' ) || + ( d_namelen == 2 && entry->d_name[0] == '.' && + entry->d_name[1] == '.' ) ) { + continue; + } + + *tail = ber_memcalloc_x( 1, sizeof(**tail), ctx ); + if ( *tail == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "grab_names: list alloc failed\n" ); + goto fail; + } + (*tail)->next = NULL; + + /* +1 for dirsep, +1 for term */ + full_len = dir_path_len + 1 + d_namelen + 1; + (*tail)->name = ber_memalloc_x( full_len, ctx ); + if ( (*tail)->name == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "grab_names: name alloc failed\n" ); + goto fail; + } + snprintf( (*tail)->name, full_len, "%s" LDAP_DIRSEP "%s", + dir_path, entry->d_name ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: found \"%s\"\n", + (*tail)->name ); + + rc = lstat( (*tail)->name, &(*tail)->st ); + if ( rc ) { + report_errno( "grab_names", "lstat", (*tail)->name ); + goto fail; + } + + tail = &(*tail)->next; + } + closedir( dir ); + ldap_pvt_thread_mutex_unlock( &readdir_mutex ); + locked = 0; + + dir = NULL; + goto success; + +success: + rc = 0; + goto out; +fail: + rc = 1; + goto out; +out: + if ( dir != NULL ) closedir( dir ); + if ( locked ) ldap_pvt_thread_mutex_unlock( &readdir_mutex ); + if ( rc != 0 && *names != NULL ) { + free_name_list( *names, ctx ); + *names = NULL; + } + Debug( LDAP_DEBUG_TRACE, "homedir: " + "grab_names: %s exit %d\n", + dir_path, rc ); + return rc; +} + +static int +traverse( const char *path, const traverse_cb *cb, void *ctx ) +{ + name_list *next_name = NULL; + name_list_list *dir_stack = NULL; + name_list_list *next_dir; + int rc = 0; + + assert( path != NULL ); + assert( cb != NULL ); + assert( cb->pre_func || cb->post_func ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: %s\n", path ); + + dir_stack = ber_memcalloc_x( 1, sizeof(*dir_stack), ctx ); + if ( dir_stack == NULL ) goto alloc_fail; + dir_stack->next = NULL; + dir_stack->list = ber_memcalloc_x( 1, sizeof(name_list), ctx ); + if ( dir_stack->list == NULL ) goto alloc_fail; + rc = lstat( path, &dir_stack->list->st ); + if ( rc != 0 ) { + report_errno( "traverse", "lstat", path ); + goto fail; + } + dir_stack->list->next = NULL; + dir_stack->list->name = ber_strdup_x( path, ctx ); + if ( dir_stack->list->name == NULL ) goto alloc_fail; + + while ( dir_stack != NULL ) { + while ( dir_stack->list != NULL ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: top of loop with \"%s\"\n", + dir_stack->list->name ); + + if ( cb->pre_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->pre_func( cb->pre_private, dir_stack->list->name, + &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + if ( (dir_stack->list->st.st_mode & S_IFMT) == S_IFDIR ) { + /* push dir onto stack */ + next_dir = dir_stack; + dir_stack = ber_memalloc_x( sizeof(*dir_stack), ctx ); + if ( dir_stack == NULL ) { + dir_stack = next_dir; + goto alloc_fail; + } + dir_stack->list = NULL; + dir_stack->next = next_dir; + rc = grab_names( + dir_stack->next->list->name, &dir_stack->list, ctx ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: grab_names %s failed\n", + dir_stack->next->list->name ); + goto fail; + } + } else { + /* just a file */ + if ( cb->post_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->post_func( cb->post_private, + dir_stack->list->name, &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + next_name = dir_stack->list->next; + ber_memfree_x( dir_stack->list->name, ctx ); + ber_memfree_x( dir_stack->list, ctx ); + dir_stack->list = next_name; + } + } + /* Time to pop a directory off the stack */ + next_dir = dir_stack->next; + ber_memfree_x( dir_stack, ctx ); + dir_stack = next_dir; + if ( dir_stack != NULL ) { + if ( cb->post_func != NULL ) { + traverse_cb_ret cb_rc; + cb_rc = cb->post_func( cb->post_private, dir_stack->list->name, + &dir_stack->list->st, ctx ); + + if ( cb_rc == TRAVERSE_CB_DONE ) goto cb_done; + if ( cb_rc == TRAVERSE_CB_FAIL ) goto cb_fail; + } + next_name = dir_stack->list->next; + ber_memfree_x( dir_stack->list->name, ctx ); + ber_memfree_x( dir_stack->list, ctx ); + dir_stack->list = next_name; + } + } + + goto success; + +cb_done: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse: cb signaled completion\n" ); +success: + rc = 0; + goto out; + +cb_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: cb signaled failure\n" ); + goto fail; +alloc_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse: allocation failed\n" ); +fail: + rc = 1; + goto out; + +out: + while ( dir_stack != NULL ) { + free_name_list( dir_stack->list, ctx ); + next_dir = dir_stack->next; + ber_memfree_x( dir_stack, ctx ); + dir_stack = next_dir; + } + return rc; +} + +static traverse_cb_ret +traverse_copy_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + copy_private *cp = private; + char *dest_name = NULL; + int source_name_len; + int dest_name_len; + int rc; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: %s entering\n", + name ); + + assert( cp->source_prefix_len >= 0 ); + assert( cp->dest_prefix != NULL ); + assert( cp->dest_prefix_len > 1 ); + + source_name_len = strlen( name ); + assert( source_name_len >= cp->source_prefix_len ); + /* +1 for terminator */ + dest_name_len = + source_name_len + cp->dest_prefix_len - cp->source_prefix_len + 1; + dest_name = ber_memalloc_x( dest_name_len, ctx ); + if ( dest_name == NULL ) goto alloc_fail; + + snprintf( dest_name, dest_name_len, "%s%s", cp->dest_prefix, + name + cp->source_prefix_len ); + + switch ( st->st_mode & S_IFMT ) { + case S_IFDIR: + rc = mkdir( dest_name, st->st_mode & 06775 ); + if ( rc ) { + int save_errno = errno; + switch ( save_errno ) { + case EEXIST: + /* directory already present; nothing to do */ + goto exists; + break; + case ENOENT: + /* FIXME: should mkdir -p here */ + /* fallthrough for now */ + default: + report_errno( "traverse_copy_pre", "mkdir", dest_name ); + goto fail; + } + } + rc = lchown( dest_name, cp->uidn, cp->gidn ); + if ( rc ) { + report_errno( "traverse_copy_pre", "lchown", dest_name ); + goto fail; + } + rc = chmod( dest_name, st->st_mode & 07777 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "chmod", dest_name ); + goto fail; + } + break; + case S_IFREG: + rc = copy_file( + dest_name, name, cp->uidn, cp->gidn, st->st_mode & 07777 ); + if ( rc ) goto fail; + break; + case S_IFIFO: + rc = mkfifo( dest_name, 0700 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "mkfifo", dest_name ); + goto fail; + } + rc = lchown( dest_name, cp->uidn, cp->gidn ); + if ( rc ) { + report_errno( "traverse_copy_pre", "lchown", dest_name ); + goto fail; + } + rc = chmod( dest_name, st->st_mode & 07777 ); + if ( rc ) { + report_errno( "traverse_copy_pre", "chmod", dest_name ); + goto fail; + } + break; + case S_IFLNK: + rc = copy_link( dest_name, name, st, cp->uidn, cp->gidn, ctx ); + if ( rc ) goto fail; + break; + default: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: skipping special: %s\n", + name ); + } + + goto success; + +alloc_fail: + Debug( LDAP_DEBUG_ANY, "homedir: " + "traverse_copy_pre: allocation failed\n" ); +fail: + rc = TRAVERSE_CB_FAIL; + goto out; + +exists: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: \"%s\" already exists," + " skipping the rest\n", + dest_name ); + rc = TRAVERSE_CB_DONE; + goto out; + +success: + rc = TRAVERSE_CB_CONTINUE; +out: + if ( dest_name != NULL ) ber_memfree_x( dest_name, ctx ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_copy_pre: exit %d\n", rc ); + return rc; +} + +static int +copy_tree( + const char *dest_path, + const char *source_path, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + traverse_cb cb; + copy_private cp; + int rc; + + assert( dest_path != NULL ); + assert( source_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_tree: %s to %s entering\n", + source_path, dest_path ); + + cb.pre_func = traverse_copy_pre; + cb.post_func = NULL; + cb.pre_private = &cp; + cb.post_private = NULL; + + cp.source_prefix_len = strlen( source_path ); + cp.dest_prefix = dest_path; + cp.dest_prefix_len = strlen( dest_path ); + cp.uidn = uidn; + cp.gidn = gidn; + + if ( cp.source_prefix_len <= cp.dest_prefix_len && + strncmp( source_path, dest_path, cp.source_prefix_len ) == 0 && + ( cp.source_prefix_len == cp.dest_prefix_len || + dest_path[cp.source_prefix_len] == LDAP_DIRSEP[0] ) ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "copy_tree: aborting: %s contains %s\n", + source_path, dest_path ); + return 1; + } + + rc = traverse( source_path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "copy_tree: %s exit %d\n", source_path, + rc ); + + return rc; +} + +static int +homedir_provision( + const char *dest_path, + const char *skel_path, + uid_t uidn, + gid_t gidn, + void *ctx ) +{ + int rc; + + assert( dest_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s from skeleton %s\n", + dest_path, skel_path == NULL ? "(none)" : skel_path ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s uidn %ld gidn %ld\n", + dest_path, (long)uidn, (long)gidn ); + + if ( skel_path == NULL ) { + rc = mkdir( dest_path, 0700 ); + if ( rc ) { + int save_errno = errno; + switch ( save_errno ) { + case EEXIST: + /* directory already present; nothing to do */ + /* but down chown either */ + rc = 0; + goto out; + break; + default: + report_errno( "provision_homedir", "mkdir", dest_path ); + goto fail; + } + } + rc = lchown( dest_path, uidn, gidn ); + if ( rc ) { + report_errno( "provision_homedir", "lchown", dest_path ); + goto fail; + } + + } else { + rc = copy_tree( dest_path, skel_path, uidn, gidn, ctx ); + } + + goto out; + +fail: + rc = 1; + goto out; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_provision: %s to %s exit %d\n", + skel_path, dest_path, rc ); + return rc; +} + +/* traverse func for rm -rf */ +static traverse_cb_ret +traverse_remove_post( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s entering\n", + name ); + + if ( (st->st_mode & S_IFMT) == S_IFDIR ) { + rc = rmdir( name ); + if ( rc != 0 ) { + report_errno( "traverse_remove_post", "rmdir", name ); + goto fail; + } + } else { + rc = unlink( name ); + if ( rc != 0 ) { + report_errno( "traverse_remove_post", "unlink", name ); + goto fail; + } + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s exit continue\n", + name ); + return TRAVERSE_CB_CONTINUE; + +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_remove_post: %s exit failure\n", + name ); + return TRAVERSE_CB_FAIL; +} + +static int +delete_tree( const char *path, void *ctx ) +{ + const static traverse_cb cb = { NULL, traverse_remove_post, NULL, NULL }; + int rc; + + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "delete_tree: %s entering\n", path ); + + rc = traverse( path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "delete_tree: %s exit %d\n", path, rc ); + + return rc; +} + +static int +get_tar_name( + const char *path, + const char *tar_path, + char *tar_name, + int name_size ) +{ + int rc = 0; + const char *ch; + int fd = -1; + int counter = 0; + time_t now; + + assert( path != NULL ); + assert( tar_path != NULL ); + assert( tar_name != NULL ); + + for ( ch = path + strlen( path ); + *ch != LDAP_DIRSEP[0] && ch > path; + --ch ) + ; + if ( ch <= path || strlen( ch ) < 2 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "get_tar_name: unable to construct a tar name from input " + "path \"%s\"\n", + path ); + goto fail; + } + ++ch; /* skip past sep */ + time( &now ); + + while ( fd < 0 ) { + snprintf( tar_name, name_size, "%s" LDAP_DIRSEP "%s-%ld-%d.tar", + tar_path, ch, (long)now, counter ); + fd = open( tar_name, O_WRONLY|O_CREAT|O_EXCL, 0600 ); + if ( fd < 0 ) { + int save_errno = errno; + if ( save_errno != EEXIST ) { + report_errno( "get_tar_name", "open", tar_name ); + goto fail; + } + ++counter; + } + } + + rc = 0; + goto out; + +fail: + rc = 1; + *tar_name = '\0'; +out: + if ( fd >= 0 ) close( fd ); + return rc; +} + +/* traverse func for rechown */ +static traverse_cb_ret +traverse_chown_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + chown_private *cp = private; + uid_t set_uidn = -1; + gid_t set_gidn = -1; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s entering\n", + name ); + + if ( st->st_uid == cp->old_uidn ) set_uidn = cp->new_uidn; + if ( st->st_gid == cp->old_gidn ) set_gidn = cp->new_gidn; + + if ( set_uidn != (uid_t)-1 || set_gidn != (gid_t)-1 ) { + rc = lchown( name, set_uidn, set_gidn ); + if ( rc ) { + report_errno( "traverse_chown_pre", "lchown", name ); + goto fail; + } + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s exit continue\n", + name ); + return TRAVERSE_CB_CONTINUE; + +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_chown_pre: %s exit failure\n", + name ); + return TRAVERSE_CB_FAIL; +} + +static int +chown_tree( + const char *path, + uid_t old_uidn, + uid_t new_uidn, + gid_t old_gidn, + gid_t new_gidn, + void *ctx ) +{ + traverse_cb cb; + chown_private cp; + int rc; + + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "chown_tree: %s entering\n", path ); + + cb.pre_func = traverse_chown_pre; + cb.post_func = NULL; + cb.pre_private = &cp; + cb.post_private = NULL; + + cp.old_uidn = old_uidn; + cp.new_uidn = new_uidn; + cp.old_gidn = old_gidn; + cp.new_gidn = new_gidn; + + rc = traverse( path, &cb, ctx ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "chown_tree: %s exit %d\n", path, rc ); + + return rc; +} + +static int +homedir_rename( const char *source_path, const char *dest_path ) +{ + int rc = 0; + + assert( source_path != NULL ); + assert( dest_path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_rename: %s to %s\n", + source_path, dest_path ); + rc = rename( source_path, dest_path ); + if ( rc != 0 ) { + char ebuf[1024]; + int save_errno = errno; + + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_rename: rename(\"%s\", \"%s\"): (%s)\n", + source_path, dest_path, + AC_STRERROR_R( save_errno, ebuf, sizeof(ebuf) ) ); + } + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_rename: %s to %s exit %d\n", + source_path, dest_path, rc ); + return rc; +} + +/* FIXME: This assumes ASCII; needs fixing for z/OS */ +static int +tar_set_header( ustar_header *tar, const struct stat *st, const char *name ) +{ + int name_len; + int rc; + const char *ch, *end; + + assert( tar != NULL ); + assert( st != NULL ); + assert( name != NULL ); + assert( sizeof(*tar) == 512 ); + assert( sizeof(tar->name) == 100 ); + assert( sizeof(tar->prefix) == 155 ); + assert( sizeof(tar->checksum) == 8 ); + + memset( tar, 0, sizeof(*tar) ); + + assert( name[0] == LDAP_DIRSEP[0] ); + name += 1; /* skip leading / */ + + name_len = strlen( name ); + + /* fits in tar->name? */ + /* Yes, name and prefix do not need a trailing nul. */ + if ( name_len <= 100 ) { + strncpy( tar->name, name, 100 ); + + /* try fit in tar->name + tar->prefix */ + } else { + /* try to find something to stick into tar->name */ + for ( ch = name + name_len - 100, end = name + name_len; + ch < end && *ch != LDAP_DIRSEP[0]; + ++ch ) + ; + if ( end - ch > 0 ) /* +1 skip past sep */ + ch++; + else { + /* reset; name too long for UStar */ + Debug( LDAP_DEBUG_ANY, "homedir: " + "tar_set_header: name too long: \"%s\"\n", + name ); + ch = name + name_len - 100; + } + strncpy( tar->name, ch + 1, 100 ); + { + int prefix_len = ( ch - 1 ) - name; + if ( prefix_len > 155 ) prefix_len = 155; + strncpy( tar->prefix, name, prefix_len ); + } + } + + snprintf( tar->mode, 8, "%06lo ", (long)st->st_mode & 07777 ); + snprintf( tar->uid, 8, "%06lo ", (long)st->st_uid ); + snprintf( tar->gid, 8, "%06lo ", (long)st->st_gid ); + snprintf( tar->mtime, 12, "%010lo ", (long)st->st_mtime ); + snprintf( tar->size, 12, "%010lo ", (long)0 ); + switch ( st->st_mode & S_IFMT ) { + case S_IFREG: + tar->typeflag[0] = '0'; + snprintf( tar->size, 12, "%010lo ", (long)st->st_size ); + break; + case S_IFLNK: + tar->typeflag[0] = '2'; + rc = readlink( name - 1, tar->linkname, 99 ); + if ( rc == -1 ) { + report_errno( "tar_set_header", "readlink", name ); + goto fail; + } + break; + case S_IFCHR: + tar->typeflag[0] = '3'; + /* FIXME: this is probably wrong but shouldn't likely be an issue */ + snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 ); + snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff ); + break; + case S_IFBLK: + tar->typeflag[0] = '4'; + /* FIXME: this is probably wrong but shouldn't likely be an issue */ + snprintf( tar->devmajor, 8, "%06lo ", (long)st->st_rdev >> 16 ); + snprintf( tar->devminor, 8, "%06lo ", (long)st->st_rdev & 0xffff ); + break; + case S_IFDIR: + tar->typeflag[0] = '5'; + break; + case S_IFIFO: + tar->typeflag[0] = '6'; + break; + default: + goto fail; + } + snprintf( tar->magic, 6, "ustar" ); + tar->version[0] = '0'; + tar->version[1] = '0'; + + { + unsigned char *uch = (unsigned char *)tar; + unsigned char *uend = uch + 512; + unsigned long sum = 0; + + memset( &tar->checksum, ' ', sizeof(tar->checksum) ); + + for ( ; uch < uend; ++uch ) + sum += *uch; + + /* zero-padded, six octal digits, followed by NUL then space (!) */ + /* Yes, that's terminated exactly reverse of the others. */ + snprintf( tar->checksum, sizeof(tar->checksum) - 1, "%06lo", sum ); + } + + return 0; +fail: + return 1; +} + +static traverse_cb_ret +traverse_tar_pre( + void *private, + const char *name, + const struct stat *st, + void *ctx ) +{ + int rc; + traverse_cb_ret cbrc; + tar_private *tp = private; + ustar_header tar; + FILE *source = NULL; + + assert( private != NULL ); + assert( name != NULL ); + assert( st != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s entering\n", name ); + + switch ( st->st_mode & S_IFMT ) { + case S_IFREG: + if ( sizeof(st->st_size) > 4 && ( st->st_size >> 33 ) >= 1 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s is larger than 8GiB POSIX UStar " + "file size limit\n", + name ); + goto fail; + } + /* fallthrough */ + case S_IFDIR: + case S_IFLNK: + case S_IFIFO: + case S_IFCHR: + case S_IFBLK: + rc = tar_set_header( &tar, st, name ); + if ( rc ) goto fail; + break; + default: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: skipping \"%s\" mode %o\n", + name, st->st_mode ); + goto done; + } + + rc = fwrite( &tar, 1, 512, tp->file ); + if ( rc != 512 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: write error in tar header\n" ); + goto fail; + } + + if ( (st->st_mode & S_IFMT) == S_IFREG ) { + source = fopen( name, "rb" ); + if ( source == NULL ) { + report_errno( "traverse_tar_pre", "fopen", name ); + goto fail; + } + rc = copy_blocks( source, tp->file, name, tp->name ); + if ( rc != 0 ) goto fail; + fclose( source ); + source = NULL; + } + + { /* advance to end of record */ + off_t pos = ftello( tp->file ); + if ( pos == -1 ) { + report_errno( "traverse_tar_pre", "ftello", tp->name ); + goto fail; + } + pos += ( 512 - ( pos % 512 ) ) % 512; + rc = fseeko( tp->file, pos, SEEK_SET ); + if ( rc != 0 ) { + report_errno( "traverse_tar_pre", "fseeko", tp->name ); + goto fail; + } + } + +done: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s exit continue\n", + name ); + cbrc = TRAVERSE_CB_CONTINUE; + goto out; +fail: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "traverse_tar_pre: %s exit failure\n", + name ); + cbrc = TRAVERSE_CB_FAIL; + +out: + if ( source != NULL ) fclose( source ); + return cbrc; +} + +static int +tar_tree( const char *path, const char *tar_name, void *ctx ) +{ + traverse_cb cb; + tar_private tp; + int rc; + + assert( path != NULL ); + assert( tar_name != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "tar_tree: %s into %s entering\n", path, + tar_name ); + + cb.pre_func = traverse_tar_pre; + cb.post_func = NULL; + cb.pre_private = &tp; + cb.post_private = NULL; + + tp.name = tar_name; + tp.file = fopen( tar_name, "wb" ); + if ( tp.file == NULL ) { + report_errno( "tar_tree", "fopen", tar_name ); + goto fail; + } + + rc = traverse( path, &cb, ctx ); + if ( rc != 0 ) goto fail; + + { + off_t pos = ftello( tp.file ); + if ( pos == -1 ) { + report_errno( "tar_tree", "ftello", tp.name ); + goto fail; + } + pos += 1024; /* two zero records */ + pos += ( 10240 - ( pos % 10240 ) ) % 10240; + rc = ftruncate( fileno( tp.file ), pos ); + if ( rc != 0 ) { + report_errno( "tar_tree", "ftrunctate", tp.name ); + goto fail; + } + } + + rc = fclose( tp.file ); + tp.file = NULL; + if ( rc != 0 ) { + report_errno( "tar_tree", "fclose", tp.name ); + goto fail; + } + goto out; + +fail: + rc = 1; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "tar_tree: %s exit %d\n", path, rc ); + if ( tp.file != NULL ) fclose( tp.file ); + return rc; +} + +static int +homedir_deprovision( const homedir_data *data, const char *path, void *ctx ) +{ + int rc = 0; + char tar_name[1024]; + + assert( data != NULL ); + assert( path != NULL ); + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: %s entering\n", + path ); + + switch ( data->style ) { + case DEL_IGNORE: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: style is ignore\n" ); + break; + case DEL_ARCHIVE: + if ( data->archive_path == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_deprovision: archive path not set\n" ); + goto fail; + } + rc = get_tar_name( path, data->archive_path, tar_name, 1024 ); + if ( rc != 0 ) goto fail; + rc = tar_tree( path, tar_name, ctx ); + if ( rc != 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_deprovision: archive failed, not deleting\n" ); + goto fail; + } + /* fall-through */ + case DEL_DELETE: + rc = delete_tree( path, ctx ); + break; + default: + abort(); + } + + rc = 0; + goto out; + +fail: + rc = 1; +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_deprovision: %s leaving\n", + path ); + + return rc; +} + +/* FIXME: This assumes ASCII; needs fixing for z/OS */ +/* FIXME: This should also be in a slapd library function somewhere */ +#define MAX_MATCHES ( 10 ) +static int +homedir_match( + const homedir_regexp *r, + const char *homedir, + char *result, + size_t result_size ) +{ + int rc; + int n; + regmatch_t matches[MAX_MATCHES]; + char *resc, *repc; + + assert( r != NULL ); + assert( homedir != NULL ); + assert( result_size > 1 ); + + memset( matches, 0, sizeof(matches) ); + rc = regexec( &r->compiled, homedir, MAX_MATCHES, matches, 0 ); + if ( rc ) { + if ( rc != REG_NOMATCH ) { + char msg[256]; + regerror( rc, &r->compiled, msg, sizeof(msg) ); + Debug( LDAP_DEBUG_ANY, "homedir_match: " + "%s\n", msg ); + } + return rc; + } + + for ( resc = result, repc = r->replace; + result_size > 1 && *repc != '\0'; + ++repc, ++resc, --result_size ) { + switch ( *repc ) { + case '$': + ++repc; + n = ( *repc ) - '0'; + if ( n < 0 || n > ( MAX_MATCHES - 1 ) || + matches[n].rm_so < 0 ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "invalid regex term expansion in \"%s\" " + "at char %ld, n is %d\n", + r->replace, (long)( repc - r->replace ), n ); + return 1; + } + { + size_t match_len = matches[n].rm_eo - matches[n].rm_so; + const char *match_start = homedir + matches[n].rm_so; + if ( match_len >= result_size ) goto too_long; + + memcpy( resc, match_start, match_len ); + result_size -= match_len; + resc += match_len - 1; + } + break; + + case '\\': + ++repc; + /* fallthrough */ + + default: + *resc = *repc; + } + } + *resc = '\0'; + if ( *repc != '\0' ) goto too_long; + + return 0; + +too_long: + Debug( LDAP_DEBUG_ANY, "homedir: " + "regex expansion of %s too long\n", + r->replace ); + *result = '\0'; + return 1; +} + +/* Sift through an entry for interesting values + * return 0 on success and set vars + * return 1 if homedir is not present or not valid + * sets presence if any homedir attributes are noticed + */ +static int +harvest_values( + const homedir_data *data, + const Entry *e, + char *home_buf, + int home_buf_size, + uid_t *uidn, + gid_t *gidn, + int *presence ) +{ + Attribute *a; + char *homedir = NULL; + + assert( data != NULL ); + assert( e != NULL ); + assert( home_buf != NULL ); + assert( home_buf_size > 1 ); + assert( uidn != NULL ); + assert( gidn != NULL ); + assert( presence != NULL ); + + *presence = 0; + if ( e == NULL ) return 1; + *uidn = 0; + *gidn = 0; + + for ( a = e->e_attrs; a->a_next != NULL; a = a->a_next ) { + if ( a->a_desc == data->home_ad ) { + homedir = a->a_vals[0].bv_val; + *presence = 1; + } else if ( a->a_desc == data->uidn_ad ) { + *uidn = (uid_t)strtol( a->a_vals[0].bv_val, NULL, 10 ); + *presence = 1; + } else if ( a->a_desc == data->gidn_ad ) { + *gidn = (gid_t)strtol( a->a_vals[0].bv_val, NULL, 10 ); + *presence = 1; + } + } + if ( homedir != NULL ) { + homedir_regexp *r; + + for ( r = data->regexps; r != NULL; r = r->next ) { + int rc = homedir_match( r, homedir, home_buf, home_buf_size ); + if ( rc == 0 ) return 0; + } + } + + return 1; +} + +static int +homedir_mod_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *cb = NULL; + slap_callback **cbp = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: entering\n" ); + + for ( cbp = &op->o_callback; + *cbp != NULL && (*cbp)->sc_cleanup != homedir_mod_cleanup; + cbp = &(*cbp)->sc_next ) + ; + + if ( *cbp == NULL ) goto out; + cb = *cbp; + + cb_data = (homedir_cb_data *)cb->sc_private; + e = cb_data->entry; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: found <%s>\n", + e->e_nname.bv_val ); + entry_free( e ); + op->o_tmpfree( cb_data, op->o_tmpmemctx ); + *cbp = cb->sc_next; + op->o_tmpfree( cb, op->o_tmpmemctx ); + +out: + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_cleanup: leaving\n" ); + return SLAP_CB_CONTINUE; +} + +static int +homedir_mod_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = NULL; + homedir_data *data = NULL; + slap_callback *cb = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + int rc = SLAP_CB_CONTINUE; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: entering\n" ); + + if ( rs->sr_err != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: op was not successful\n" ); + goto out; + } + + /* Retrieve stashed entry */ + for ( cb = op->o_callback; + cb != NULL && cb->sc_cleanup != homedir_mod_cleanup; + cb = cb->sc_next ) + ; + if ( cb == NULL ) goto out; + cb_data = (homedir_cb_data *)cb->sc_private; + e = cb_data->entry; + on = cb_data->on; + data = on->on_bi.bi_private; + assert( e != NULL ); + assert( data != NULL ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: found <%s>\n", + e->e_nname.bv_val ); + + switch ( op->o_tag ) { + case LDAP_REQ_DELETE: { + char home_buf[1024]; + uid_t uidn = 0; + gid_t gidn = 0; + int presence; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: successful delete found\n" ); + rc = harvest_values( data, e, home_buf, sizeof(home_buf), &uidn, + &gidn, &presence ); + if ( rc == 0 && uidn >= data->min_uid ) { + homedir_deprovision( data, home_buf, op->o_tmpmemctx ); + } else { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: skipping\n" ); + } + rc = SLAP_CB_CONTINUE; + break; + } + + case LDAP_REQ_MODIFY: + case LDAP_REQ_MODRDN: { + Operation nop = *op; + Entry *old_entry = e; + Entry *new_entry = NULL; + Entry *etmp; + char old_home[1024]; + char new_home[1024]; + uid_t old_uidn, new_uidn; + uid_t old_gidn, new_gidn; + int old_valid = 0; + int new_valid = 0; + int old_presence, new_presence; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: successful modify/modrdn found\n" ); + + /* retrieve the revised entry */ + nop.o_bd = on->on_info->oi_origdb; + rc = overlay_entry_get_ov( + &nop, &op->o_req_ndn, NULL, NULL, 0, &etmp, on ); + if ( etmp != NULL ) { + new_entry = entry_dup( etmp ); + overlay_entry_release_ov( &nop, etmp, 0, on ); + } + if ( rc || new_entry == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: unable to get revised <%s>\n", + op->o_req_ndn.bv_val ); + if ( new_entry != NULL ) { + entry_free( new_entry ); + new_entry = NULL; + } + } + + /* analyze old and new */ + rc = harvest_values( data, old_entry, old_home, 1024, &old_uidn, + &old_gidn, &old_presence ); + if ( rc == 0 && old_uidn >= data->min_uid ) old_valid = 1; + if ( new_entry != NULL ) { + rc = harvest_values( data, new_entry, new_home, 1024, &new_uidn, + &new_gidn, &new_presence ); + if ( rc == 0 && new_uidn >= data->min_uid ) new_valid = 1; + entry_free( new_entry ); + new_entry = NULL; + } + + if ( new_valid && !old_valid ) { /* like an add */ + if ( old_presence ) + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: old entry is now valid\n" ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like an add\n" ); + homedir_provision( new_home, data->skeleton_path, new_uidn, + new_gidn, op->o_tmpmemctx ); + + } else if ( old_valid && !new_valid && + !new_presence ) { /* like a del */ + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like a del\n" ); + homedir_deprovision( data, old_home, op->o_tmpmemctx ); + + } else if ( new_valid && old_valid ) { /* change */ + int did_something = 0; + + if ( strcmp( old_home, new_home ) != 0 ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: treating like a rename\n" ); + homedir_rename( old_home, new_home ); + did_something = 1; + } + if ( old_uidn != new_uidn || old_gidn != new_gidn ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: rechowning\n" ); + chown_tree( new_home, old_uidn, new_uidn, old_gidn, + new_gidn, op->o_tmpmemctx ); + did_something = 1; + } + if ( !did_something ) { + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: nothing to do\n" ); + } + } else if ( old_presence || new_presence ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_mod_response: <%s> values present " + "but invalid; ignoring\n", + op->o_req_ndn.bv_val ); + } + rc = SLAP_CB_CONTINUE; + break; + } + + default: + rc = SLAP_CB_CONTINUE; + } + +out: + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_mod_response: leaving\n" ); + return rc; +} + +static int +homedir_op_mod( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *cb = NULL; + homedir_cb_data *cb_data = NULL; + Entry *e = NULL; + Entry *se = NULL; + Operation nop = *op; + int rc; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_op_mod: entering\n" ); + + /* retrieve the entry */ + nop.o_bd = on->on_info->oi_origdb; + rc = overlay_entry_get_ov( &nop, &op->o_req_ndn, NULL, NULL, 0, &e, on ); + if ( e != NULL ) { + se = entry_dup( e ); + overlay_entry_release_ov( &nop, e, 0, on ); + e = se; + } + if ( rc || e == NULL ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "homedir_op_mod: unable to get <%s>\n", + op->o_req_ndn.bv_val ); + goto out; + } + + /* Allocate the callback to hold the entry */ + cb = op->o_tmpalloc( sizeof(slap_callback), op->o_tmpmemctx ); + cb_data = op->o_tmpalloc( sizeof(homedir_cb_data), op->o_tmpmemctx ); + cb->sc_cleanup = homedir_mod_cleanup; + cb->sc_response = homedir_mod_response; + cb->sc_private = cb_data; + cb_data->entry = e; + e = NULL; + cb_data->on = on; + cb->sc_next = op->o_callback; + op->o_callback = cb; + +out: + if ( e != NULL ) entry_free( e ); + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_op_mod: leaving\n" ); + return SLAP_CB_CONTINUE; +} + +static int +homedir_response( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + homedir_data *data = on->on_bi.bi_private; + + Debug( LDAP_DEBUG_TRACE, "homedir: " + "homedir_response: entering\n" ); + if ( rs->sr_err != LDAP_SUCCESS || data == NULL ) return SLAP_CB_CONTINUE; + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: { /* Check for new homedir */ + char home_buf[1024]; + uid_t uidn = 0; + gid_t gidn = 0; + int rc, presence; + + rc = harvest_values( data, op->ora_e, home_buf, sizeof(home_buf), + &uidn, &gidn, &presence ); + if ( rc == 0 && uidn >= data->min_uid ) { + homedir_provision( home_buf, data->skeleton_path, uidn, gidn, + op->o_tmpmemctx ); + } + return SLAP_CB_CONTINUE; + } + + default: + return SLAP_CB_CONTINUE; + } + + return SLAP_CB_CONTINUE; +} + +static int +homedir_db_init( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + homedir_data *data = ch_calloc( 1, sizeof(homedir_data) ); + const char *text; + + if ( slap_str2ad( "homeDirectory", &data->home_ad, &text ) || + slap_str2ad( "uidNumber", &data->uidn_ad, &text ) || + slap_str2ad( "gidNumber", &data->gidn_ad, &text ) ) { + Debug( LDAP_DEBUG_ANY, "homedir: " + "nis schema not available\n" ); + return 1; + } + + data->skeleton_path = strdup( DEFAULT_SKEL ); + data->min_uid = DEFAULT_MIN_UID; + data->archive_path = NULL; + + on->on_bi.bi_private = data; + return 0; +} + +static int +homedir_db_destroy( BackendDB *be, ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + homedir_data *data = on->on_bi.bi_private; + homedir_regexp *r, *rnext; + + if ( data != NULL ) { + for ( r = data->regexps; r != NULL; r = rnext ) { + rnext = r->next; + ch_free( r->match ); + ch_free( r->replace ); + regfree( &r->compiled ); + ch_free( r ); + } + data->regexps = NULL; + if ( data->skeleton_path != NULL ) ch_free( data->skeleton_path ); + if ( data->archive_path != NULL ) ch_free( data->archive_path ); + ch_free( data ); + } + + return 0; +} + +int +homedir_initialize() +{ + int rc; + + assert( ' ' == 32 ); /* Lots of ASCII requirements for now */ + + memset( &homedir, 0, sizeof(homedir) ); + + homedir.on_bi.bi_type = "homedir"; + homedir.on_bi.bi_db_init = homedir_db_init; + homedir.on_bi.bi_db_destroy = homedir_db_destroy; + homedir.on_bi.bi_op_delete = homedir_op_mod; + homedir.on_bi.bi_op_modify = homedir_op_mod; + homedir.on_response = homedir_response; + + homedir.on_bi.bi_cf_ocs = homedirocs; + rc = config_register_schema( homedircfg, homedirocs ); + if ( rc ) return rc; + + ldap_pvt_thread_mutex_init( &readdir_mutex ); + + return overlay_register( &homedir ); +} + +int +homedir_terminate() +{ + ldap_pvt_thread_mutex_destroy( &readdir_mutex ); + return 0; +} + +#if SLAPD_OVER_HOMEDIR == SLAPD_MOD_DYNAMIC && defined(PIC) +int +init_module( int argc, char *argv[] ) +{ + return homedir_initialize(); +} + +int +term_module() +{ + return homedir_terminate(); +} +#endif + +#endif /* SLAPD_OVER_HOMEDIR */ diff --git a/servers/slapd/overlays/memberof.c b/servers/slapd/overlays/memberof.c new file mode 100644 index 0000000..5affbbf --- /dev/null +++ b/servers/slapd/overlays/memberof.c @@ -0,0 +1,2185 @@ +/* memberof.c - back-reference for group membership */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2005-2007 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 + * . + */ +/* 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 + +#include "ac/string.h" +#include "ac/socket.h" + +#include "slap.h" +#include "slap-config.h" +#include "lutil.h" + +/* + * Glossary: + * + * GROUP a group object (an entry with GROUP_OC + * objectClass) + * MEMBER a member object (an entry whose DN is + * listed as MEMBER_AT value of a GROUP) + * GROUP_OC the objectClass of the group object + * (default: groupOfNames) + * MEMBER_AT the membership attribute, DN-valued; + * note: nameAndOptionalUID is tolerated + * as soon as the optionalUID is absent + * (default: member) + * MEMBER_OF reverse membership attribute + * (default: memberOf) + * + * - add: + * - if the entry that is being added is a GROUP, + * the MEMBER_AT defined as values of the add operation + * get the MEMBER_OF value directly from the request. + * + * if configured to do so, the MEMBER objects do not exist, + * and no relax control is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if (configured to do so,) the referenced GROUP exists, + * the relax control is set and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries. + * + * - modify: + * - if the entry being modified is a GROUP_OC and the + * MEMBER_AT attribute is modified, the MEMBER_OF value + * of the (existing) MEMBER_AT entries that are affected + * is modified according to the request: + * - if a MEMBER is removed from the group, + * delete the corresponding MEMBER_OF + * - if a MEMBER is added to a group, + * add the corresponding MEMBER_OF + * + * We need to determine, from the database, if it is + * a GROUP_OC, and we need to check, from the + * modification list, if the MEMBER_AT attribute is being + * affected, and what MEMBER_AT values are affected. + * + * if configured to do so, the entries corresponding to + * the MEMBER_AT values do not exist, and no relax control + * is issued, either: + * - fail + * - drop non-existing members + * (by default: don't muck with values) + * + * - if configured to do so, the referenced GROUP exists, + * (the relax control is set) and the user has + * "manage" privileges, allow to add MEMBER_OF values to + * generic entries; the change is NOT automatically reflected + * in the MEMBER attribute of the GROUP referenced + * by the value of MEMBER_OF; a separate modification, + * with or without relax control, needs to be performed. + * + * - modrdn: + * - if the entry being renamed is a GROUP, the MEMBER_OF + * value of the (existing) MEMBER objects is modified + * accordingly based on the newDN of the GROUP. + * + * We need to determine, from the database, if it is + * a GROUP; the list of MEMBER objects is obtained from + * the database. + * + * Non-existing MEMBER objects are ignored, since the + * MEMBER_AT is not being addressed by the operation. + * + * - if the entry being renamed has the MEMBER_OF attribute, + * the corresponding MEMBER value must be modified in the + * respective group entries. + * + * + * - delete: + * - if the entry being deleted is a GROUP, the (existing) + * MEMBER objects are modified accordingly; a copy of the + * values of the MEMBER_AT is saved and, if the delete + * succeeds, the MEMBER_OF value of the (existing) MEMBER + * objects is deleted. + * + * We need to determine, from the database, if it is + * a GROUP. + * + * Non-existing MEMBER objects are ignored, since the entry + * is being deleted. + * + * - if the entry being deleted has the MEMBER_OF attribute, + * the corresponding value of the MEMBER_AT must be deleted + * from the respective GROUP entries. + */ + +#define SLAPD_MEMBEROF_ATTR "memberOf" + +static AttributeDescription *ad_member; +static AttributeDescription *ad_memberOf; + +static ObjectClass *oc_group; + +static slap_overinst memberof; + +typedef struct memberof_t { + struct berval mo_dn; + struct berval mo_ndn; + + ObjectClass *mo_oc_group; + AttributeDescription *mo_ad_member; + AttributeDescription *mo_ad_memberof; + + struct berval mo_groupFilterstr; + AttributeAssertion mo_groupAVA; + Filter mo_groupFilter; + + struct berval mo_memberFilterstr; + Filter mo_memberFilter; + + unsigned mo_flags; +#define MEMBEROF_NONE 0x00U +#define MEMBEROF_FDANGLING_DROP 0x01U +#define MEMBEROF_FDANGLING_ERROR 0x02U +#define MEMBEROF_FDANGLING_MASK (MEMBEROF_FDANGLING_DROP|MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_FREFINT 0x04U +#define MEMBEROF_FREVERSE 0x08U + + ber_int_t mo_dangling_err; + +#define MEMBEROF_CHK(mo,f) \ + (((mo)->mo_flags & (f)) == (f)) +#define MEMBEROF_DANGLING_CHECK(mo) \ + ((mo)->mo_flags & MEMBEROF_FDANGLING_MASK) +#define MEMBEROF_DANGLING_DROP(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_DROP) +#define MEMBEROF_DANGLING_ERROR(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FDANGLING_ERROR) +#define MEMBEROF_REFINT(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREFINT) +#define MEMBEROF_REVERSE(mo) \ + MEMBEROF_CHK((mo),MEMBEROF_FREVERSE) +} memberof_t; + +typedef enum memberof_is_t { + MEMBEROF_IS_NONE = 0x00, + MEMBEROF_IS_GROUP = 0x01, + MEMBEROF_IS_MEMBER = 0x02, + MEMBEROF_IS_BOTH = (MEMBEROF_IS_GROUP|MEMBEROF_IS_MEMBER) +} memberof_is_t; + +typedef struct memberof_cookie_t { + AttributeDescription *ad; + BerVarray vals; + int foundit; +} memberof_cookie_t; + +typedef struct memberof_cbinfo_t { + slap_overinst *on; + BerVarray member; + BerVarray memberof; + memberof_is_t what; +} memberof_cbinfo_t; + +static void +memberof_set_backend( Operation *op_target, Operation *op, slap_overinst *on ) +{ + BackendInfo *bi = op->o_bd->bd_info; + + if ( bi->bi_type == memberof.on_bi.bi_type ) + op_target->o_bd->bd_info = (BackendInfo *)on->on_info; +} + +static int +memberof_isGroupOrMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + } + + return 0; +} + +/* + * callback for internal search that saves the member attribute values + * of groups being deleted. + */ +static int +memberof_saveMember_cb( Operation *op, SlapReply *rs ) +{ + if ( rs->sr_type == REP_SEARCH ) { + memberof_cookie_t *mc; + Attribute *a; + + mc = (memberof_cookie_t *)op->o_callback->sc_private; + mc->foundit = 1; + + assert( rs->sr_entry != NULL ); + assert( rs->sr_entry->e_attrs != NULL ); + + a = attr_find( rs->sr_entry->e_attrs, mc->ad ); + if ( a != NULL ) { + ber_bvarray_dup_x( &mc->vals, a->a_nvals, op->o_tmpmemctx ); + + assert( attr_find( a->a_next, mc->ad ) == NULL ); + } + } + + return 0; +} + +/* + * the delete hook performs an internal search that saves the member + * attribute values of groups being deleted. + */ +static int +memberof_isGroupOrMember( Operation *op, memberof_cbinfo_t *mci ) +{ + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + slap_callback cb = { 0 }; + BackendInfo *bi = op->o_bd->bd_info; + AttributeName an[ 2 ]; + + memberof_is_t iswhat = MEMBEROF_IS_NONE; + memberof_cookie_t mc; + + assert( mci->what != MEMBEROF_IS_NONE ); + + cb.sc_private = &mc; + if ( op->o_tag == LDAP_REQ_DELETE ) { + cb.sc_response = memberof_saveMember_cb; + + } else { + cb.sc_response = memberof_isGroupOrMember_cb; + } + + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + BER_BVZERO( &an[ 1 ].an_name ); + op2.ors_attrs = an; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + if ( mci->what & MEMBEROF_IS_GROUP ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_member; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_member; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_groupFilterstr; + op2.ors_filter = &mo->mo_groupFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_GROUP; + if ( mc.vals ) mci->member = mc.vals; + + } + } + + if ( mci->what & MEMBEROF_IS_MEMBER ) { + SlapReply rs2 = { REP_RESULT }; + + mc.ad = mo->mo_ad_memberof; + mc.foundit = 0; + mc.vals = NULL; + an[ 0 ].an_desc = mo->mo_ad_memberof; + an[ 0 ].an_name = an[ 0 ].an_desc->ad_cname; + op2.ors_filterstr = mo->mo_memberFilterstr; + op2.ors_filter = &mo->mo_memberFilter; + op2.o_do_not_cache = 1; /* internal search, don't log */ + + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_search( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + + if ( mc.foundit ) { + iswhat |= MEMBEROF_IS_MEMBER; + if ( mc.vals ) mci->memberof = mc.vals; + + } + } + + mci->what = iswhat; + + return LDAP_SUCCESS; +} + +/* + * response callback that adds memberof values when a group is modified. + */ +static void +memberof_value_modify( + Operation *op, + struct berval *ndn, + AttributeDescription *ad, + struct berval *old_dn, + struct berval *old_ndn, + struct berval *new_dn, + struct berval *new_ndn ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Operation op2 = *op; + unsigned long opid = op->o_opid; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb = { NULL, slap_null_cb, NULL, NULL }; + Modifications mod[ 2 ] = { { { 0 } } }, *ml; + struct berval values[ 4 ], nvalues[ 4 ]; + int mcnt = 0; + + if ( old_ndn != NULL && new_ndn != NULL && + ber_bvcmp( old_ndn, new_ndn ) == 0 ) { + /* DNs compare equal, it's a noop */ + return; + } + + op2.o_tag = LDAP_REQ_MODIFY; + + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + + op2.o_callback = &cb; + op2.o_dn = op->o_bd->be_rootdn; + op2.o_ndn = op->o_bd->be_rootndn; + op2.orm_modlist = NULL; + + /* Internal ops, never replicate these */ + op2.o_opid = 0; /* shared with op, saved above */ + op2.orm_no_opattrs = 1; + op2.o_dont_replicate = 1; + + if ( !BER_BVISNULL( &mo->mo_ndn ) ) { + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 0 ]; + ml->sml_values[ 0 ] = mo->mo_dn; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 0 ]; + ml->sml_nvalues[ 0 ] = mo->mo_ndn; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = slap_schema.si_ad_modifiersName; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_op = LDAP_MOD_REPLACE; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + mcnt++; + } + + ml = &mod[ mcnt ]; + ml->sml_numvals = 1; + ml->sml_values = &values[ 2 ]; + BER_BVZERO( &ml->sml_values[ 1 ] ); + ml->sml_nvalues = &nvalues[ 2 ]; + BER_BVZERO( &ml->sml_nvalues[ 1 ] ); + ml->sml_desc = ad; + ml->sml_type = ml->sml_desc->ad_cname; + ml->sml_flags = SLAP_MOD_INTERNAL; + ml->sml_next = op2.orm_modlist; + op2.orm_modlist = ml; + + if ( new_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( new_dn ) ); + assert( !BER_BVISNULL( new_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_ADD; + + ml->sml_values[ 0 ] = *new_dn; + ml->sml_nvalues[ 0 ] = *new_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: memberof_value_modify DN=\"%s\" add %s=\"%s\" failed err=%d\n", + op->o_log_prefix, op2.o_req_dn.bv_val, + ad->ad_cname.bv_val, new_dn->bv_val, rs2.sr_err ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + assert( mcnt == 0 || op2.orm_modlist->sml_next == &mod[ 0 ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + + mod[ 0 ].sml_next = NULL; + } + + if ( old_ndn != NULL ) { + BackendInfo *bi = op2.o_bd->bd_info; + OpExtra oex; + + assert( !BER_BVISNULL( old_dn ) ); + assert( !BER_BVISNULL( old_ndn ) ); + + ml = &mod[ mcnt ]; + ml->sml_op = LDAP_MOD_DELETE; + + ml->sml_values[ 0 ] = *old_dn; + ml->sml_nvalues[ 0 ] = *old_ndn; + + oex.oe_key = (void *)&memberof; + LDAP_SLIST_INSERT_HEAD(&op2.o_extra, &oex, oe_next); + memberof_set_backend( &op2, op, on ); + (void)op->o_bd->be_modify( &op2, &rs2 ); + op2.o_bd->bd_info = bi; + LDAP_SLIST_REMOVE(&op2.o_extra, &oex, OpExtra, oe_next); + if ( rs2.sr_err != LDAP_SUCCESS ) { + Debug(LDAP_DEBUG_ANY, + "%s: memberof_value_modify DN=\"%s\" delete %s=\"%s\" failed err=%d\n", + op->o_log_prefix, op2.o_req_dn.bv_val, + ad->ad_cname.bv_val, old_dn->bv_val, rs2.sr_err ); + } + + assert( op2.orm_modlist == &mod[ mcnt ] ); + ml = op2.orm_modlist->sml_next; + if ( mcnt == 1 ) { + assert( ml == &mod[ 0 ] ); + ml = ml->sml_next; + } + if ( ml != NULL ) { + slap_mods_free( ml, 1 ); + } + } + /* restore original opid */ + op->o_opid = opid; + + /* FIXME: if old_group_ndn doesn't exist, both delete __and__ + * add will fail; better split in two operations, although + * not optimal in terms of performance. At least it would + * move towards self-repairing capabilities. */ +} + +static int +memberof_cleanup( Operation *op, SlapReply *rs ) +{ + slap_callback *sc = op->o_callback; + memberof_cbinfo_t *mci = sc->sc_private; + + op->o_callback = sc->sc_next; + if ( mci->memberof ) + ber_bvarray_free_x( mci->memberof, op->o_tmpmemctx ); + if ( mci->member ) + ber_bvarray_free_x( mci->member, op->o_tmpmemctx ); + op->o_tmpfree( sc, op->o_tmpmemctx ); + return 0; +} + +static int memberof_res_add( Operation *op, SlapReply *rs ); +static int memberof_res_delete( Operation *op, SlapReply *rs ); +static int memberof_res_modify( Operation *op, SlapReply *rs ); +static int memberof_res_modrdn( Operation *op, SlapReply *rs ); + +static int +memberof_op_add( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Attribute **ap, **map = NULL; + int rc = SLAP_CB_CONTINUE; + int i; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( op->ora_e->e_attrs == NULL ) { + /* FIXME: global overlay; need to deal with */ + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "consistency checks not implemented when overlay " + "is instantiated as global.\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ap = &op->ora_e->e_attrs; *ap; ap = &(*ap)->a_next ) { + Attribute *a = *ap; + + if ( a->a_desc == mo->mo_ad_memberof ) { + map = ap; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) + && is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + for ( ap = &op->ora_e->e_attrs; *ap; ) { + Attribute *a = *ap; + + if ( !is_ad_subtype( a->a_desc, mo->mo_ad_member ) ) { + ap = &a->a_next; + continue; + } + + assert( a->a_nvals != NULL ); + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e = NULL; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + if ( rc == LDAP_SUCCESS ) { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_vals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + a->a_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + } + + /* If all values have been removed, + * remove the attribute itself. */ + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *ap = a->a_next; + attr_free( a ); + + } else { + ap = &a->a_next; + } + } + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + } + + if ( map != NULL ) { + Attribute *a = *map; + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, op->ora_e, mo->mo_ad_memberof, + &a->a_nvals[ i ], ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done; + } + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &a->a_nvals[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_add(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->ora_e->e_name.bv_val, + a->a_nvals[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &a->a_nvals[ j ] ); j++ ); + ber_memfree( a->a_vals[ i ].bv_val ); + BER_BVZERO( &a->a_vals[ i ] ); + if ( a->a_nvals != a->a_vals ) { + ber_memfree( a->a_nvals[ i ].bv_val ); + BER_BVZERO( &a->a_nvals[ i ] ); + } + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &a->a_vals[ i ], &a->a_vals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( a->a_nvals != a->a_vals ) { + AC_MEMCPY( &a->a_nvals[ i ], &a->a_nvals[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, ACL_WADD, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &a->a_nvals[ 0 ] ) ) { + *map = a->a_next; + attr_free( a ); + } + } + + rc = SLAP_CB_CONTINUE; + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_add; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + sc->sc_next = op->o_callback; + op->o_callback = sc; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_delete( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_delete; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = MEMBEROF_IS_GROUP; + if ( MEMBEROF_REFINT( mo ) ) { + mci->what = MEMBEROF_IS_BOTH; + } + + memberof_isGroupOrMember( op, mci ); + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +static int +memberof_op_modify( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + Modifications **mlp, **mmlp = NULL; + int rc = SLAP_CB_CONTINUE, save_member = 0; + struct berval save_dn, save_ndn; + slap_callback *sc; + memberof_cbinfo_t *mci, mcis; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( mlp = &op->orm_modlist; *mlp; mlp = &(*mlp)->sml_next ) { + Modifications *ml = *mlp; + + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mmlp = mlp; + break; + } + } + } + + save_dn = op->o_dn; + save_ndn = op->o_ndn; + mcis.on = on; + mcis.what = MEMBEROF_IS_GROUP; + + if ( memberof_isGroupOrMember( op, &mcis ) == LDAP_SUCCESS + && ( mcis.what & MEMBEROF_IS_GROUP ) ) + { + Modifications *ml; + + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_member ) { + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case LDAP_MOD_REPLACE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + save_member = 1; + break; + } + } + } + + + if ( MEMBEROF_DANGLING_CHECK( mo ) + && !get_relax( op ) ) + { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + + assert( op->orm_modlist != NULL ); + + for ( mlp = &op->orm_modlist; *mlp; ) { + Modifications *ml = *mlp; + int i; + + if ( !is_ad_subtype( ml->sml_desc, mo->mo_ad_member ) ) { + mlp = &ml->sml_next; + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + /* we don't care about cancellations: if the value + * exists, fine; if it doesn't, we let the underlying + * database fail as appropriate; */ + mlp = &ml->sml_next; + break; + + case LDAP_MOD_REPLACE: + /* Handle this just like a delete (see above) */ + if ( !ml->sml_values ) { + mlp = &ml->sml_next; + break; + } + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + /* NOTE: right now, the attributeType we use + * for member must have a normalized value */ + assert( ml->sml_nvalues != NULL ); + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + if ( be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ) == LDAP_SUCCESS ) + { + be_entry_release_r( op, e ); + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as group member"; + send_ldap_result( op, rs ); + goto done; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "member=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_dn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + i--; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + + } else { + mlp = &ml->sml_next; + } + + break; + + default: + assert( 0 ); + } + } + } + } + + if ( mmlp != NULL ) { + Modifications *ml = *mmlp; + int i; + Entry *target; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = be_entry_get_rw( op, &op->o_req_ndn, + NULL, NULL, 0, &target ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + rc = rs->sr_err = LDAP_NO_SUCH_OBJECT; + send_ldap_result( op, rs ); + goto done; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( ml->sml_nvalues != NULL ) { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WDEL, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( get_relax( op ) ) { + continue; + } + + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "deleting non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + NULL, + ACL_WDEL, NULL ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + { + AccessControlState acl_state = ACL_STATE_INIT; + + for ( i = 0; !BER_BVISNULL( &ml->sml_nvalues[ i ] ); i++ ) { + Entry *e; + + op->o_bd->bd_info = (BackendInfo *)on->on_info; + /* access is checked with the original identity */ + rc = access_allowed( op, target, + mo->mo_ad_memberof, + &ml->sml_nvalues[ i ], + ACL_WADD, + &acl_state ); + if ( rc == 0 ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = NULL; + send_ldap_result( op, rs ); + goto done2; + } + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ml->sml_nvalues[i], &save_ndn )) + continue; + + rc = be_entry_get_rw( op, &ml->sml_nvalues[ i ], + NULL, NULL, 0, &e ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc != LDAP_SUCCESS ) { + if ( MEMBEROF_DANGLING_ERROR( mo ) ) { + rc = rs->sr_err = mo->mo_dangling_err; + rs->sr_text = "adding non-existing object " + "as memberof"; + send_ldap_result( op, rs ); + goto done2; + } + + if ( MEMBEROF_DANGLING_DROP( mo ) ) { + int j; + + Debug( LDAP_DEBUG_ANY, "%s: memberof_op_modify(\"%s\"): " + "memberof=\"%s\" does not exist (stripping...)\n", + op->o_log_prefix, op->o_req_ndn.bv_val, + ml->sml_nvalues[ i ].bv_val ); + + for ( j = i + 1; !BER_BVISNULL( &ml->sml_nvalues[ j ] ); j++ ); + ber_memfree( ml->sml_values[ i ].bv_val ); + BER_BVZERO( &ml->sml_values[ i ] ); + if ( ml->sml_nvalues != ml->sml_values ) { + ber_memfree( ml->sml_nvalues[ i ].bv_val ); + BER_BVZERO( &ml->sml_nvalues[ i ] ); + } + ml->sml_numvals--; + if ( j - i == 1 ) { + break; + } + + AC_MEMCPY( &ml->sml_values[ i ], &ml->sml_values[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + if ( ml->sml_nvalues != ml->sml_values ) { + AC_MEMCPY( &ml->sml_nvalues[ i ], &ml->sml_nvalues[ i + 1 ], + sizeof( struct berval ) * ( j - i ) ); + } + i--; + } + + continue; + } + + /* access is checked with the original identity */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = access_allowed( op, e, mo->mo_ad_member, + &op->o_req_ndn, + ACL_WDEL, NULL ); + be_entry_release_r( op, e ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( !rc ) { + rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS; + rs->sr_text = "insufficient access to object referenced by memberof"; + send_ldap_result( op, rs ); + goto done; + } + } + + if ( BER_BVISNULL( &ml->sml_nvalues[ 0 ] ) ) { + *mmlp = ml->sml_next; + slap_mod_free( &ml->sml_mod, 0 ); + free( ml ); + } + + } break; + + default: + assert( 0 ); + } + +done2:; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + be_entry_release_r( op, target ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modify; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + mci->what = mcis.what; + + if ( save_member ) { + op->o_dn = op->o_bd->be_rootdn; + op->o_ndn = op->o_bd->be_rootndn; + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_member, &mci->member, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + } + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + rc = SLAP_CB_CONTINUE; + +done:; + op->o_dn = save_dn; + op->o_ndn = save_ndn; + op->o_bd->bd_info = (BackendInfo *)on; + + return rc; +} + +static int +memberof_op_modrdn( Operation *op, SlapReply *rs ) +{ + slap_overinst *on = (slap_overinst *)op->o_bd->bd_info; + slap_callback *sc; + memberof_cbinfo_t *mci; + OpExtra *oex; + + LDAP_SLIST_FOREACH( oex, &op->o_extra, oe_next ) { + if ( oex->oe_key == (void *)&memberof ) + return SLAP_CB_CONTINUE; + } + + sc = op->o_tmpalloc( sizeof(slap_callback)+sizeof(*mci), op->o_tmpmemctx ); + sc->sc_private = sc+1; + sc->sc_response = memberof_res_modrdn; + sc->sc_cleanup = memberof_cleanup; + sc->sc_writewait = 0; + mci = sc->sc_private; + mci->on = on; + mci->member = NULL; + mci->memberof = NULL; + + sc->sc_next = op->o_callback; + op->o_callback = sc; + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds memberof values when a group is added. + */ +static int +memberof_res_add( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + Attribute *ma; + + ma = attr_find( op->ora_e->e_attrs, mo->mo_ad_memberof ); + if ( ma != NULL ) { + /* relax is required to allow to add + * a non-existing member */ + op->o_relax = SLAP_CONTROL_CRITICAL; + + for ( i = 0; !BER_BVISNULL( &ma->a_nvals[ i ] ); i++ ) { + + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &ma->a_nvals[i], &op->o_req_ndn )) + continue; + + /* the modification is attempted + * with the original identity */ + memberof_value_modify( op, + &ma->a_nvals[ i ], mo->mo_ad_member, + NULL, NULL, &op->o_req_dn, &op->o_req_ndn ); + } + } + } + + if ( is_entry_objectclass_or_sub( op->ora_e, mo->mo_oc_group ) ) { + Attribute *a; + + for ( a = attrs_find( op->ora_e->e_attrs, mo->mo_ad_member ); + a != NULL; + a = attrs_find( a->a_next, mo->mo_ad_member ) ) + { + for ( i = 0; !BER_BVISNULL( &a->a_nvals[ i ] ); i++ ) { + /* ITS#6670 Ignore member pointing to this entry */ + if ( dn_match( &a->a_nvals[i], &op->o_req_ndn )) + continue; + + memberof_value_modify( op, + &a->a_nvals[ i ], + mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, + &op->o_req_ndn ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that deletes memberof values when a group is deleted. + */ +static int +memberof_res_delete( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + BerVarray vals; + int i; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + vals = mci->member; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( MEMBEROF_REFINT( mo ) ) { + vals = mci->memberof; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes memberof values when a group + * is modified. + */ +static int +memberof_res_modify( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc; + Modifications *ml, *mml = NULL; + BerVarray vals; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + if ( MEMBEROF_REVERSE( mo ) ) { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc == mo->mo_ad_memberof ) { + mml = ml; + break; + } + } + } + + if ( mml != NULL ) { + BerVarray vals = mml->sml_nvalues; + + switch ( mml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + /* delete all ... */ + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->o_req_ndn, + mo->mo_ad_memberof, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + + if ( ml->sml_op == LDAP_MOD_DELETE || !mml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT: /* ITS#7487 */ + assert( vals != NULL ); + + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + + if ( mci->what & MEMBEROF_IS_GROUP ) + { + for ( ml = op->orm_modlist; ml; ml = ml->sml_next ) { + if ( ml->sml_desc != mo->mo_ad_member ) { + continue; + } + + switch ( ml->sml_op ) { + case LDAP_MOD_DELETE: + case SLAP_MOD_SOFTDEL: /* ITS#7487: can be used by syncrepl (in mirror mode?) */ + vals = ml->sml_nvalues; + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + break; + } + /* fall thru */ + + case LDAP_MOD_REPLACE: + vals = mci->member; + + /* delete all ... */ + if ( vals != NULL ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + NULL, NULL ); + } + } + + if ( ml->sml_op == LDAP_MOD_DELETE || ml->sml_op == SLAP_MOD_SOFTDEL || !ml->sml_values ) { + break; + } + /* fall thru */ + + case LDAP_MOD_ADD: + case SLAP_MOD_SOFTADD: /* ITS#7487 */ + case SLAP_MOD_ADD_IF_NOT_PRESENT : /* ITS#7487 */ + assert( ml->sml_nvalues != NULL ); + vals = ml->sml_nvalues; + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + NULL, NULL, + &op->o_req_dn, &op->o_req_ndn ); + } + break; + + default: + assert( 0 ); + } + } + } + + return SLAP_CB_CONTINUE; +} + +/* + * response callback that adds/deletes member values when a group member + * is renamed. + */ +static int +memberof_res_modrdn( Operation *op, SlapReply *rs ) +{ + memberof_cbinfo_t *mci = op->o_callback->sc_private; + slap_overinst *on = mci->on; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc; + BerVarray vals; + + struct berval save_dn, save_ndn; + + if ( rs->sr_err != LDAP_SUCCESS ) { + return SLAP_CB_CONTINUE; + } + + mci->what = MEMBEROF_IS_GROUP; + if ( MEMBEROF_REFINT( mo ) ) { + mci->what |= MEMBEROF_IS_MEMBER; + } + + save_dn = op->o_req_dn; + save_ndn = op->o_req_ndn; + + op->o_req_dn = op->orr_newDN; + op->o_req_ndn = op->orr_nnewDN; + rc = memberof_isGroupOrMember( op, mci ); + op->o_req_dn = save_dn; + op->o_req_ndn = save_ndn; + + if ( rc != LDAP_SUCCESS || mci->what == MEMBEROF_IS_NONE ) { + goto done; + } + + if ( mci->what & MEMBEROF_IS_GROUP ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->orr_nnewDN, + mo->mo_ad_member, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_memberof, + &op->o_req_dn, &op->o_req_ndn, + &op->orr_newDN, &op->orr_nnewDN ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + + if ( MEMBEROF_REFINT( mo ) && ( mci->what & MEMBEROF_IS_MEMBER ) ) { + op->o_bd->bd_info = (BackendInfo *)on->on_info; + rc = backend_attribute( op, NULL, &op->orr_nnewDN, + mo->mo_ad_memberof, &vals, ACL_READ ); + op->o_bd->bd_info = (BackendInfo *)on; + + if ( rc == LDAP_SUCCESS ) { + for ( i = 0; !BER_BVISNULL( &vals[ i ] ); i++ ) { + memberof_value_modify( op, + &vals[ i ], mo->mo_ad_member, + &op->o_req_dn, &op->o_req_ndn, + &op->orr_newDN, &op->orr_nnewDN ); + } + ber_bvarray_free_x( vals, op->o_tmpmemctx ); + } + } + +done:; + return SLAP_CB_CONTINUE; +} + + +static int +memberof_db_init( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo; + const char *text = NULL; + int rc; + + mo = (memberof_t *)ch_calloc( 1, sizeof( memberof_t ) ); + + /* safe default */ + mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION; + + if ( !ad_memberOf ) { + rc = slap_str2ad( SLAPD_MEMBEROF_ATTR, &ad_memberOf, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "memberof_db_init: " + "unable to find attribute=\"%s\": %s (%d)\n", + SLAPD_MEMBEROF_ATTR, text, rc ); + return rc; + } + } + + if ( !ad_member ) { + rc = slap_str2ad( SLAPD_GROUP_ATTR, &ad_member, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, "memberof_db_init: " + "unable to find attribute=\"%s\": %s (%d)\n", + SLAPD_GROUP_ATTR, text, rc ); + return rc; + } + } + + if ( !oc_group ) { + oc_group = oc_find( SLAPD_GROUP_CLASS ); + if ( oc_group == NULL ) { + Debug( LDAP_DEBUG_ANY, + "memberof_db_init: " + "unable to find objectClass=\"%s\"\n", + SLAPD_GROUP_CLASS ); + return 1; + } + } + + on->on_bi.bi_private = (void *)mo; + + return 0; +} + +enum { + MO_DN = 1, + MO_DANGLING, + MO_REFINT, + MO_GROUP_OC, + MO_MEMBER_AD, + MO_MEMBER_OF_AD, +#if 0 + MO_REVERSE, +#endif + + MO_DANGLING_ERROR, + + MO_LAST +}; + +static ConfigDriver mo_cf_gen; + +#define OID "1.3.6.1.4.1.7136.2.666.4" +#define OIDAT OID ".1.1" +#define OIDCFGAT OID ".1.2" +#define OIDOC OID ".2.1" +#define OIDCFGOC OID ".2.2" + + +static ConfigTable mo_cfg[] = { + { "memberof-dn", "modifiersName", + 2, 2, 0, ARG_MAGIC|ARG_QUOTE|ARG_DN|MO_DN, mo_cf_gen, + "( OLcfgOvAt:18.0 NAME 'olcMemberOfDN' " + "DESC 'DN to be used as modifiersName' " + "EQUALITY distinguishedNameMatch " + "SYNTAX OMsDN SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-dangling", "ignore|drop|error", + 2, 2, 0, ARG_MAGIC|MO_DANGLING, mo_cf_gen, + "( OLcfgOvAt:18.1 NAME 'olcMemberOfDangling' " + "DESC 'Behavior with respect to dangling members, " + "constrained to ignore, drop, error' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-refint", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REFINT, mo_cf_gen, + "( OLcfgOvAt:18.2 NAME 'olcMemberOfRefInt' " + "DESC 'Take care of referential integrity' " + "EQUALITY booleanMatch " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-group-oc", "objectClass", + 2, 2, 0, ARG_MAGIC|MO_GROUP_OC, mo_cf_gen, + "( OLcfgOvAt:18.3 NAME 'olcMemberOfGroupOC' " + "DESC 'Group objectClass' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-member-ad", "member attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_AD, mo_cf_gen, + "( OLcfgOvAt:18.4 NAME 'olcMemberOfMemberAD' " + "DESC 'member attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { "memberof-memberof-ad", "memberOf attribute", + 2, 2, 0, ARG_MAGIC|ARG_ATDESC|MO_MEMBER_OF_AD, mo_cf_gen, + "( OLcfgOvAt:18.5 NAME 'olcMemberOfMemberOfAD' " + "DESC 'memberOf attribute' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + +#if 0 + { "memberof-reverse", "true|FALSE", + 2, 2, 0, ARG_MAGIC|ARG_ON_OFF|MO_REVERSE, mo_cf_gen, + "( OLcfgOvAt:18.6 NAME 'olcMemberOfReverse' " + "DESC 'Take care of referential integrity " + "also when directly modifying memberOf' " + "SYNTAX OMsBoolean SINGLE-VALUE )", + NULL, NULL }, +#endif + + { "memberof-dangling-error", "error code", + 2, 2, 0, ARG_MAGIC|MO_DANGLING_ERROR, mo_cf_gen, + "( OLcfgOvAt:18.7 NAME 'olcMemberOfDanglingError' " + "DESC 'Error code returned in case of dangling back reference' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED } +}; + +static ConfigOCs mo_ocs[] = { + { "( OLcfgOvOc:18.1 " + "NAME ( 'olcMemberOfConfig' 'olcMemberOf' ) " + "DESC 'Member-of configuration' " + "SUP olcOverlayConfig " + "MAY ( " + "olcMemberOfDN " + "$ olcMemberOfDangling " + "$ olcMemberOfDanglingError" + "$ olcMemberOfRefInt " + "$ olcMemberOfGroupOC " + "$ olcMemberOfMemberAD " + "$ olcMemberOfMemberOfAD " +#if 0 + "$ olcMemberOfReverse " +#endif + ") " + ")", + Cft_Overlay, mo_cfg, NULL, NULL }, + { NULL, 0, NULL } +}; + +static slap_verbmasks dangling_mode[] = { + { BER_BVC( "ignore" ), MEMBEROF_NONE }, + { BER_BVC( "drop" ), MEMBEROF_FDANGLING_DROP }, + { BER_BVC( "error" ), MEMBEROF_FDANGLING_ERROR }, + { BER_BVNULL, 0 } +}; + +static int +memberof_make_group_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ch_free( mo->mo_groupFilterstr.bv_val ); + } + + mo->mo_groupFilter.f_choice = LDAP_FILTER_EQUALITY; + mo->mo_groupFilter.f_ava = &mo->mo_groupAVA; + + mo->mo_groupFilter.f_av_desc = slap_schema.si_ad_objectClass; + mo->mo_groupFilter.f_av_value = mo->mo_oc_group->soc_cname; + + mo->mo_groupFilterstr.bv_len = STRLENOF( "(=)" ) + + slap_schema.si_ad_objectClass->ad_cname.bv_len + + mo->mo_oc_group->soc_cname.bv_len; + ptr = mo->mo_groupFilterstr.bv_val = ch_malloc( mo->mo_groupFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, slap_schema.si_ad_objectClass->ad_cname.bv_val ); + *ptr++ = '='; + ptr = lutil_strcopy( ptr, mo->mo_oc_group->soc_cname.bv_val ); + *ptr++ = ')'; + *ptr = '\0'; + + return 0; +} + +static int +memberof_make_member_filter( memberof_t *mo ) +{ + char *ptr; + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ch_free( mo->mo_memberFilterstr.bv_val ); + } + + mo->mo_memberFilter.f_choice = LDAP_FILTER_PRESENT; + mo->mo_memberFilter.f_desc = mo->mo_ad_memberof; + + mo->mo_memberFilterstr.bv_len = STRLENOF( "(=*)" ) + + mo->mo_ad_memberof->ad_cname.bv_len; + ptr = mo->mo_memberFilterstr.bv_val = ch_malloc( mo->mo_memberFilterstr.bv_len + 1 ); + *ptr++ = '('; + ptr = lutil_strcopy( ptr, mo->mo_ad_memberof->ad_cname.bv_val ); + ptr = lutil_strcopy( ptr, "=*)" ); + + return 0; +} + +static int +mo_cf_gen( ConfigArgs *c ) +{ + slap_overinst *on = (slap_overinst *)c->bi; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int i, rc = 0; + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch( c->type ) { + case MO_DN: + if ( mo->mo_dn.bv_val != NULL) { + value_add_one( &c->rvalue_vals, &mo->mo_dn ); + value_add_one( &c->rvalue_nvals, &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + enum_to_verb( dangling_mode, (mo->mo_flags & MEMBEROF_FDANGLING_MASK), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case MO_DANGLING_ERROR: + if ( mo->mo_flags & MEMBEROF_FDANGLING_ERROR ) { + char buf[ SLAP_TEXT_BUFLEN ]; + enum_to_verb( slap_ldap_response_code, mo->mo_dangling_err, &bv ); + if ( BER_BVISNULL( &bv ) ) { + bv.bv_len = snprintf( buf, sizeof( buf ), "0x%x", mo->mo_dangling_err ); + if ( bv.bv_len < sizeof( buf ) ) { + bv.bv_val = buf; + } else { + rc = 1; + break; + } + } + value_add_one( &c->rvalue_vals, &bv ); + } else { + rc = 1; + } + break; + + case MO_REFINT: + c->value_int = MEMBEROF_REFINT( mo ); + break; + +#if 0 + case MO_REVERSE: + c->value_int = MEMBEROF_REVERSE( mo ); + break; +#endif + + case MO_GROUP_OC: + if ( mo->mo_oc_group != NULL ){ + value_add_one( &c->rvalue_vals, &mo->mo_oc_group->soc_cname ); + } + break; + + case MO_MEMBER_AD: + c->value_ad = mo->mo_ad_member; + break; + + case MO_MEMBER_OF_AD: + c->value_ad = mo->mo_ad_memberof; + break; + + default: + assert( 0 ); + return 1; + } + + return rc; + + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + BER_BVZERO( &mo->mo_dn ); + BER_BVZERO( &mo->mo_ndn ); + } + break; + + case MO_DANGLING: + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + break; + + case MO_DANGLING_ERROR: + mo->mo_dangling_err = LDAP_CONSTRAINT_VIOLATION; + break; + + case MO_REFINT: + mo->mo_flags &= ~MEMBEROF_FREFINT; + break; + +#if 0 + case MO_REVERSE: + mo->mo_flags &= ~MEMBEROF_FREVERSE; + break; +#endif + + case MO_GROUP_OC: + mo->mo_oc_group = oc_group; + memberof_make_group_filter( mo ); + break; + + case MO_MEMBER_AD: + mo->mo_ad_member = ad_member; + break; + + case MO_MEMBER_OF_AD: + mo->mo_ad_memberof = ad_memberOf; + memberof_make_member_filter( mo ); + break; + + default: + assert( 0 ); + return 1; + } + + } else { + switch( c->type ) { + case MO_DN: + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + mo->mo_dn = c->value_dn; + mo->mo_ndn = c->value_ndn; + break; + + case MO_DANGLING: + i = verb_to_mask( c->argv[ 1 ], dangling_mode ); + if ( BER_BVISNULL( &dangling_mode[ i ].word ) ) { + return 1; + } + + mo->mo_flags &= ~MEMBEROF_FDANGLING_MASK; + mo->mo_flags |= dangling_mode[ i ].mask; + break; + + case MO_DANGLING_ERROR: + i = verb_to_mask( c->argv[ 1 ], slap_ldap_response_code ); + if ( !BER_BVISNULL( &slap_ldap_response_code[ i ].word ) ) { + mo->mo_dangling_err = slap_ldap_response_code[ i ].mask; + } else if ( lutil_atoix( &mo->mo_dangling_err, c->argv[ 1 ], 0 ) ) { + return 1; + } + break; + + case MO_REFINT: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREFINT; + + } else { + mo->mo_flags &= ~MEMBEROF_FREFINT; + } + break; + +#if 0 + case MO_REVERSE: + if ( c->value_int ) { + mo->mo_flags |= MEMBEROF_FREVERSE; + + } else { + mo->mo_flags &= ~MEMBEROF_FREVERSE; + } + break; +#endif + + case MO_GROUP_OC: { + ObjectClass *oc = NULL; + + oc = oc_find( c->argv[ 1 ] ); + if ( oc == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to find group objectClass=\"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_oc_group = oc; + memberof_make_group_filter( mo ); + } break; + + case MO_MEMBER_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "member attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_ad_member = ad; + } break; + + case MO_MEMBER_OF_AD: { + AttributeDescription *ad = c->value_ad; + + if ( !is_at_syntax( ad->ad_type, SLAPD_DN_SYNTAX ) /* e.g. "member" */ + && !is_at_syntax( ad->ad_type, SLAPD_NAMEUID_SYNTAX ) ) /* e.g. "uniqueMember" */ + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "memberof attribute=\"%s\" must either " + "have DN (%s) or nameUID (%s) syntax", + c->argv[ 1 ], SLAPD_DN_SYNTAX, SLAPD_NAMEUID_SYNTAX ); + Debug( LDAP_DEBUG_CONFIG, "%s: %s.\n", + c->log, c->cr_msg ); + return 1; + } + + mo->mo_ad_memberof = ad; + memberof_make_member_filter( mo ); + } break; + + default: + assert( 0 ); + return 1; + } + } + + return 0; +} + +static int +memberof_db_open( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + int rc; + + if ( !mo->mo_ad_memberof ) { + mo->mo_ad_memberof = ad_memberOf; + } + + if ( ! mo->mo_ad_member ) { + mo->mo_ad_member = ad_member; + } + + if ( ! mo->mo_oc_group ) { + mo->mo_oc_group = oc_group; + } + + if ( BER_BVISNULL( &mo->mo_dn ) && !BER_BVISNULL( &be->be_rootdn ) ) { + ber_dupbv( &mo->mo_dn, &be->be_rootdn ); + ber_dupbv( &mo->mo_ndn, &be->be_rootndn ); + } + + if ( BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + memberof_make_group_filter( mo ); + } + + if ( BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + memberof_make_member_filter( mo ); + } + + return 0; +} + +static int +memberof_db_destroy( + BackendDB *be, + ConfigReply *cr ) +{ + slap_overinst *on = (slap_overinst *)be->bd_info; + memberof_t *mo = (memberof_t *)on->on_bi.bi_private; + + if ( mo ) { + if ( !BER_BVISNULL( &mo->mo_dn ) ) { + ber_memfree( mo->mo_dn.bv_val ); + ber_memfree( mo->mo_ndn.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_groupFilterstr ) ) { + ber_memfree( mo->mo_groupFilterstr.bv_val ); + } + + if ( !BER_BVISNULL( &mo->mo_memberFilterstr ) ) { + ber_memfree( mo->mo_memberFilterstr.bv_val ); + } + + ber_memfree( mo ); + } + + return 0; +} + +static struct { + char *desc; + AttributeDescription **adp; +} as[] = { + { "( 1.2.840.113556.1.2.102 " + "NAME 'memberOf' " + "DESC 'Group that the entry belongs to' " + "SYNTAX '1.3.6.1.4.1.1466.115.121.1.12' " + "EQUALITY distinguishedNameMatch " /* added */ + "USAGE dSAOperation " /* added; questioned */ + "NO-USER-MODIFICATION " /* added */ + "X-ORIGIN 'iPlanet Delegated Administrator' )", + &ad_memberOf }, + { NULL } +}; + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +static +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ +int +memberof_initialize( void ) +{ + int code, i; + + for ( i = 0; as[ i ].desc != NULL; i++ ) { + code = register_at( as[ i ].desc, as[ i ].adp, 1 ); + if ( code && code != SLAP_SCHERR_ATTR_DUP ) { + Debug( LDAP_DEBUG_ANY, + "memberof_initialize: register_at #%d failed\n", + i ); + return code; + } + } + + memberof.on_bi.bi_type = "memberof"; + + memberof.on_bi.bi_db_init = memberof_db_init; + memberof.on_bi.bi_db_open = memberof_db_open; + memberof.on_bi.bi_db_destroy = memberof_db_destroy; + + memberof.on_bi.bi_op_add = memberof_op_add; + memberof.on_bi.bi_op_delete = memberof_op_delete; + memberof.on_bi.bi_op_modify = memberof_op_modify; + memberof.on_bi.bi_op_modrdn = memberof_op_modrdn; + + memberof.on_bi.bi_cf_ocs = mo_ocs; + + code = config_register_schema( mo_cfg, mo_ocs ); + if ( code ) return code; + + return overlay_register( &memberof ); +} + +#if SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return memberof_initialize(); +} +#endif /* SLAPD_OVER_MEMBEROF == SLAPD_MOD_DYNAMIC */ + +#endif /* SLAPD_OVER_MEMBEROF */ diff --git a/servers/slapd/overlays/otp.c b/servers/slapd/overlays/otp.c new file mode 100644 index 0000000..590ee50 --- /dev/null +++ b/servers/slapd/overlays/otp.c @@ -0,0 +1,1004 @@ +/* otp.c - OATH 2-factor authentication module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * Copyright 2015-2022 The OpenLDAP Foundation. + * Portions Copyright 2015 by Howard Chu, Symas Corp. + * Portions Copyright 2016-2017 by Michael Ströder + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work includes code from the lastbind overlay. + */ + +#include + +#ifdef SLAPD_OVER_OTP + +#if HAVE_STDINT_H +#include +#endif + +#include +#include +#include "lutil.h" +#include +#include +#include +/* include socket.h to get sys/types.h and/or winsock2.h */ +#include + +#if HAVE_OPENSSL +#include +#include + +#define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_LENGTH + +#if OPENSSL_VERSION_MAJOR >= 3 +#define TOTP_SHA1 SN_sha1 +#define TOTP_SHA224 SN_sha224 +#define TOTP_SHA256 SN_sha256 +#define TOTP_SHA384 SN_sha384 +#define TOTP_SHA512 SN_sha512 +#define TOTP_HMAC_CTX EVP_MAC_CTX * +#else +#define TOTP_SHA1 EVP_sha1() +#define TOTP_SHA224 EVP_sha224() +#define TOTP_SHA256 EVP_sha256() +#define TOTP_SHA384 EVP_sha384() +#define TOTP_SHA512 EVP_sha512() +#define TOTP_HMAC_CTX HMAC_CTX * +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +static HMAC_CTX * +HMAC_CTX_new( void ) +{ + HMAC_CTX *ctx = OPENSSL_malloc( sizeof(*ctx) ); + if ( ctx != NULL ) { + HMAC_CTX_init( ctx ); + } + return ctx; +} + +static void +HMAC_CTX_free( HMAC_CTX *ctx ) +{ + if ( ctx != NULL ) { + HMAC_CTX_cleanup( ctx ); + OPENSSL_free( ctx ); + } +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ + +#if OPENSSL_VERSION_MAJOR >= 3 +static EVP_MAC *evp_mac; +#define HMAC_setup( ctx, key, len, hash ) \ + { OSSL_PARAM params[2]; \ + ctx = EVP_MAC_CTX_new( evp_mac ); \ + params[0] = OSSL_PARAM_construct_utf8_string( "digest", (char *)hash, 0 ); \ + params[1] = OSSL_PARAM_construct_end(); \ + EVP_MAC_init( ctx, key, len, params ); } +#define HMAC_crunch( ctx, buf, len ) EVP_MAC_update( ctx, buf, len ) +#define HMAC_finish( ctx, dig, dlen ) \ + { size_t outlen; \ + EVP_MAC_final( ctx, dig, &outlen, TOTP_SHA512_DIGEST_LENGTH ); \ + dlen = outlen; } \ + EVP_MAC_CTX_free( ctx ) + +#else +#define HMAC_setup( ctx, key, len, hash ) \ + ctx = HMAC_CTX_new(); \ + HMAC_Init_ex( ctx, key, len, hash, 0 ) +#define HMAC_crunch( ctx, buf, len ) HMAC_Update( ctx, buf, len ) +#define HMAC_finish( ctx, dig, dlen ) \ + HMAC_Final( ctx, dig, &dlen ); \ + HMAC_CTX_free( ctx ) +#endif + +#elif HAVE_GNUTLS +#include + +#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; + } + } + +#if OPENSSL_VERSION_MAJOR >= 3 + evp_mac = EVP_MAC_fetch( NULL, "HMAC", "provider=default" ); +#endif + return overlay_register( &otp ); +} + +#if SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC +int +init_module( int argc, char *argv[] ) +{ + return otp_initialize(); +} +#endif /* SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC */ + +#endif /* defined(SLAPD_OVER_OTP) */ diff --git a/servers/slapd/overlays/overlays.c b/servers/slapd/overlays/overlays.c new file mode 100644 index 0000000..8290200 --- /dev/null +++ b/servers/slapd/overlays/overlays.c @@ -0,0 +1,44 @@ +/* overlays.c - Static overlay framework */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by Howard Chu for inclusion in + * OpenLDAP Software. + */ + +#include "portable.h" + +#include "slap.h" + +extern OverlayInit slap_oinfo[]; + +int +overlay_init(void) +{ + int i, rc = 0; + + for ( i= 0 ; slap_oinfo[i].ov_type; i++ ) { + rc = slap_oinfo[i].ov_init(); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, + "%s overlay setup failed, err %d\n", + slap_oinfo[i].ov_type, rc ); + break; + } + } + + return rc; +} diff --git a/servers/slapd/overlays/pcache.c b/servers/slapd/overlays/pcache.c new file mode 100644 index 0000000..2b947e4 --- /dev/null +++ b/servers/slapd/overlays/pcache.c @@ -0,0 +1,5815 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ +/* 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 + +#include +#include + +#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:///????x-uuid=,x-template=