summaryrefslogtreecommitdiffstats
path: root/servers/slapd/back-asyncmeta
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:54:12 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 17:54:12 +0000
commitb527294153be3b79563c82c66102adc0004736c0 (patch)
tree9b423a224848441885190b5ea7cf0feb23510c9d /servers/slapd/back-asyncmeta
parentInitial commit. (diff)
downloadopenldap-b527294153be3b79563c82c66102adc0004736c0.tar.xz
openldap-b527294153be3b79563c82c66102adc0004736c0.zip
Adding upstream version 2.6.7+dfsg.upstream/2.6.7+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers/slapd/back-asyncmeta')
-rw-r--r--servers/slapd/back-asyncmeta/Makefile.in50
-rw-r--r--servers/slapd/back-asyncmeta/add.c370
-rw-r--r--servers/slapd/back-asyncmeta/back-asyncmeta.h788
-rw-r--r--servers/slapd/back-asyncmeta/bind.c1739
-rw-r--r--servers/slapd/back-asyncmeta/candidates.c239
-rw-r--r--servers/slapd/back-asyncmeta/compare.c312
-rw-r--r--servers/slapd/back-asyncmeta/config.c2536
-rw-r--r--servers/slapd/back-asyncmeta/conn.c1222
-rw-r--r--servers/slapd/back-asyncmeta/delete.c304
-rw-r--r--servers/slapd/back-asyncmeta/dncache.c228
-rw-r--r--servers/slapd/back-asyncmeta/init.c475
-rw-r--r--servers/slapd/back-asyncmeta/map.c224
-rw-r--r--servers/slapd/back-asyncmeta/message_queue.c236
-rw-r--r--servers/slapd/back-asyncmeta/meta_result.c1825
-rw-r--r--servers/slapd/back-asyncmeta/modify.c360
-rw-r--r--servers/slapd/back-asyncmeta/modrdn.c375
-rw-r--r--servers/slapd/back-asyncmeta/proto-asyncmeta.h53
-rw-r--r--servers/slapd/back-asyncmeta/search.c970
18 files changed, 12306 insertions, 0 deletions
diff --git a/servers/slapd/back-asyncmeta/Makefile.in b/servers/slapd/back-asyncmeta/Makefile.in
new file mode 100644
index 0000000..c609458
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/Makefile.in
@@ -0,0 +1,50 @@
+## Makefile.in for back-asyncmeta
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 The OpenLDAP Foundation.
+## Portions Copyright 2016 Symas Corporation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This work was developed by Symas Corporation
+## based on back-meta module for inclusion in OpenLDAP Software.
+## This work was sponsored by Ericsson
+
+SRCS = init.c config.c search.c message_queue.c bind.c add.c compare.c \
+ delete.c modify.c modrdn.c map.c \
+ conn.c candidates.c dncache.c meta_result.c
+OBJS = init.lo config.lo search.lo message_queue.lo bind.lo add.lo compare.lo \
+ delete.lo modify.lo modrdn.lo map.lo \
+ conn.lo candidates.lo dncache.lo meta_result.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-asyncmeta"
+BUILD_MOD = @BUILD_ASYNCMETA@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_ASYNCMETA@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(LIBS) $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+
+LIBBASE = back_asyncmeta
+
+XINCPATH = -I.. -I$(srcdir)/..
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
diff --git a/servers/slapd/back-asyncmeta/add.c b/servers/slapd/back-asyncmeta/add.c
new file mode 100644
index 0000000..55277ee
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/add.c
@@ -0,0 +1,370 @@
+/* add.c - add request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+
+
+int
+asyncmeta_error_cleanup(Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate)
+{
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ if (asyncmeta_bc_in_queue(mc,bc) == NULL || bc->bc_active > 1) {
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ return LDAP_SUCCESS;
+ }
+ asyncmeta_drop_bc(mc, bc);
+ slap_sl_mem_setctx(op->o_threadctx, op->o_tmpmemctx);
+ operation_counter_init( op, op->o_threadctx );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+ return LDAP_SUCCESS;
+}
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ int isupdate;
+ Attribute *a;
+ int i;
+ LDAPMod **attrs;
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = {0, NULL};
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+ int rc;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* Count number of attributes in entry ( +1 ) */
+ for ( i = 1, a = op->ora_e->e_attrs; a; i++, a = a->a_next );
+
+ /* Create array of LDAPMods for ldap_add() */
+ attrs = op->o_tmpalloc(sizeof( LDAPMod * )*i, op->o_tmpmemctx);
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) {
+ int j;
+
+ if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ attrs[ i ] = op->o_tmpalloc( sizeof( LDAPMod ), op->o_tmpmemctx );
+ if ( attrs[ i ] == NULL ) {
+ continue;
+ }
+ attrs[ i ]->mod_op = LDAP_MOD_BVALUES;
+ attrs[ i ]->mod_type = a->a_desc->ad_cname.bv_val;
+ j = a->a_numvals;
+ attrs[ i ]->mod_bvalues = op->o_tmpalloc( ( j + 1 ) * sizeof( struct berval * ), op->o_tmpmemctx );
+ for (j=0; j<a->a_numvals; j++) {
+ attrs[ i ]->mod_bvalues[ j ] = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ if ( a->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &a->a_vals[ j ], attrs[ i ]->mod_bvalues[ j ] );
+ else
+ *attrs[ i ]->mod_bvalues[ j ] = a->a_vals[ j ];
+ }
+
+ attrs[ i ]->mod_bvalues[ j ] = NULL;
+ i++;
+ }
+ attrs[ i ] = NULL;
+
+ asyncmeta_set_msc_time(msc);
+
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_add_req( msc->msc_ld, mdn.bv_val, attrs, ctrls, NULL, &msgid);
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_add_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_ADD,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ ldap_pvt_thread_yield();
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send add request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_add_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+
+int
+asyncmeta_back_add( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_TRACE, "==> asyncmeta_back_add: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_add[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_ADD ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ current_time = slap_get_time();
+ if (bc->timeout && bc->stoptime < current_time) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug(LDAP_DEBUG_TRACE , "%s asyncmeta_back_add: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_add_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/back-asyncmeta.h b/servers/slapd/back-asyncmeta/back-asyncmeta.h
new file mode 100644
index 0000000..a5ae6cd
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/back-asyncmeta.h
@@ -0,0 +1,788 @@
+/* back-asyncmeta.h - main header file for back-asyncmeta module */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef SLAPD_LDAP_H
+#error "include servers/slapd/back-ldap/back-ldap.h before this file!"
+#endif /* SLAPD_LDAP_H */
+
+#ifndef SLAPD_ASYNCMETA_H
+#define SLAPD_ASYNCMETA_H
+
+#ifdef LDAP_DEVEL
+#define SLAPD_META_CLIENT_PR 1
+#endif /* LDAP_DEVEL */
+
+#include "proto-asyncmeta.h"
+
+#include "ldap_rq.h"
+
+LDAP_BEGIN_DECL
+
+/*
+ * Set META_BACK_PRINT_CONNTREE larger than 0 to dump the connection tree (debug only)
+ */
+#ifndef META_BACK_PRINT_CONNTREE
+#define META_BACK_PRINT_CONNTREE 0
+#endif /* !META_BACK_PRINT_CONNTREE */
+
+/*
+ * A a_metasingleconn_t can be in the following, mutually exclusive states:
+ *
+ * - none (0x0U)
+ * - creating META_BACK_FCONN_CREATING
+ * - initialized META_BACK_FCONN_INITED
+ * - binding LDAP_BACK_FCONN_BINDING
+ * - bound/anonymous LDAP_BACK_FCONN_ISBOUND/LDAP_BACK_FCONN_ISANON
+ *
+ * possible modifiers are:
+ *
+ * - privileged LDAP_BACK_FCONN_ISPRIV
+ * - privileged, TLS LDAP_BACK_FCONN_ISTLS
+ * - subjected to idassert LDAP_BACK_FCONN_ISIDASR
+ * - tainted LDAP_BACK_FCONN_TAINTED
+ */
+
+#define META_BACK_FCONN_INITED (0x00100000U)
+#define META_BACK_FCONN_CREATING (0x00200000U)
+#define META_BACK_FCONN_INVALID (0x00400000U)
+
+#define META_BACK_CONN_INITED(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INITED)
+#define META_BACK_CONN_INITED_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_INITED, (mlc))
+#define META_BACK_CONN_CREATING(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_CREATING)
+#define META_BACK_CONN_CREATING_CPY(lc, mlc) LDAP_BACK_CONN_CPY((lc), META_BACK_FCONN_CREATING, (mlc))
+#define META_BACK_CONN_INVALID(lc) LDAP_BACK_CONN_ISSET((lc), META_BACK_FCONN_INVALID)
+#define META_BACK_CONN_INVALID_SET(lc) LDAP_BACK_CONN_SET((lc), META_BACK_FCONN_INVALID)
+#define META_BACK_CONN_INVALID_CLEAR(lc) LDAP_BACK_CONN_CLEAR((lc), META_BACK_FCONN_INVALID)
+
+struct a_metainfo_t;
+struct a_metaconn_t;
+struct a_metatarget_t;
+#define META_NOT_CANDIDATE ((ber_tag_t)0x0)
+#define META_CANDIDATE ((ber_tag_t)0x1)
+#define META_BINDING ((ber_tag_t)0x2)
+#define META_RETRYING ((ber_tag_t)0x4)
+
+typedef struct bm_context_t {
+ LDAP_STAILQ_ENTRY(bm_context_t) bc_next;
+ struct a_metaconn_t *bc_mc;
+ time_t timeout;
+ time_t stoptime;
+ ldap_back_send_t sendok;
+ ldap_back_send_t retrying;
+ int candidate_match;
+ volatile int bc_active;
+ int searchtime; /* stoptime is a search timelimit */
+ int is_ok;
+ int is_root;
+ volatile sig_atomic_t bc_invalid;
+ SlapReply rs;
+ Operation *op;
+ Operation copy_op;
+ LDAPControl **ctrls;
+ int *msgids;
+ int *nretries; /* number of times to retry a failed send on an msc */
+ struct berval c_peer_name; /* peer name of original op->o_conn*/
+ SlapReply *candidates;
+} bm_context_t;
+
+typedef struct a_metasingleconn_t {
+#define META_CND_ISSET(rs,f) ( ( (rs)->sr_tag & (f) ) == (f) )
+#define META_CND_SET(rs,f) ( (rs)->sr_tag |= (f) )
+#define META_CND_CLEAR(rs,f) ( (rs)->sr_tag &= ~(f) )
+
+#define META_CANDIDATE_RESET(rs) ( (rs)->sr_tag = 0 )
+#define META_IS_CANDIDATE(rs) META_CND_ISSET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_SET(rs) META_CND_SET( (rs), META_CANDIDATE )
+#define META_CANDIDATE_CLEAR(rs) META_CND_CLEAR( (rs), META_CANDIDATE )
+#define META_IS_BINDING(rs) META_CND_ISSET( (rs), META_BINDING )
+#define META_BINDING_SET(rs) META_CND_SET( (rs), META_BINDING )
+#define META_BINDING_CLEAR(rs) META_CND_CLEAR( (rs), META_BINDING )
+#define META_IS_RETRYING(rs) META_CND_ISSET( (rs), META_RETRYING )
+#define META_RETRYING_SET(rs) META_CND_SET( (rs), META_RETRYING )
+#define META_RETRYING_CLEAR(rs) META_CND_CLEAR( (rs), META_RETRYING )
+
+ LDAP *msc_ld;
+ LDAP *msc_ldr;
+ time_t msc_time;
+ time_t msc_binding_time;
+ time_t msc_result_time;
+ struct berval msc_bound_ndn;
+ struct berval msc_cred;
+ unsigned msc_mscflags;
+
+ /* NOTE: lc_lcflags is redefined to msc_mscflags to reuse the macros
+ * defined for back-ldap */
+#define lc_lcflags msc_mscflags
+ volatile int msc_active;
+ /* Connection for the select */
+ Connection *conn;
+} a_metasingleconn_t;
+
+typedef struct a_metaconn_t {
+ ldapconn_base_t lc_base;
+#define mc_base lc_base
+//#define mc_conn mc_base.lcb_conn
+//#define mc_local_ndn mc_base.lcb_local_ndn
+//#define mc_refcnt mc_base.lcb_refcnt
+//#define mc_create_time mc_base.lcb_create_time
+//#define mc_time mc_base.lcb_time
+
+ LDAP_TAILQ_ENTRY(a_metaconn_t) mc_q;
+
+ /* NOTE: msc_mscflags is used to recycle the #define
+ * in metasingleconn_t */
+ unsigned msc_mscflags;
+ int mc_active;
+
+ /*
+ * means that the connection is bound;
+ * of course only one target actually is ...
+ */
+ int mc_authz_target;
+#define META_BOUND_NONE (-1)
+#define META_BOUND_ALL (-2)
+
+ struct a_metainfo_t *mc_info;
+
+ int pending_ops;
+ ldap_pvt_thread_mutex_t mc_om_mutex;
+ /* queue for pending operations */
+ LDAP_STAILQ_HEAD(BCList, bm_context_t) mc_om_list;
+ /* supersedes the connection stuff */
+ a_metasingleconn_t *mc_conns;
+} a_metaconn_t;
+
+typedef enum meta_st_t {
+#if 0 /* todo */
+ META_ST_EXACT = LDAP_SCOPE_BASE,
+#endif
+ META_ST_SUBTREE = LDAP_SCOPE_SUBTREE,
+ META_ST_SUBORDINATE = LDAP_SCOPE_SUBORDINATE,
+ META_ST_REGEX /* last + 1 */
+} meta_st_t;
+
+typedef struct a_metasubtree_t {
+ meta_st_t ms_type;
+ union {
+ struct berval msu_dn;
+ struct {
+ struct berval msr_regex_pattern;
+ regex_t msr_regex;
+ } msu_regex;
+ } ms_un;
+#define ms_dn ms_un.msu_dn
+#define ms_regex ms_un.msu_regex.msr_regex
+#define ms_regex_pattern ms_un.msu_regex.msr_regex_pattern
+
+ struct a_metasubtree_t *ms_next;
+} a_metasubtree_t;
+
+typedef struct metafilter_t {
+ struct metafilter_t *mf_next;
+ struct berval mf_regex_pattern;
+ regex_t mf_regex;
+} metafilter_t;
+
+typedef struct a_metacommon_t {
+ int mc_version;
+ int mc_nretries;
+#define META_RETRY_UNDEFINED (-2)
+#define META_RETRY_FOREVER (-1)
+#define META_RETRY_NEVER (0)
+#define META_RETRY_DEFAULT (2)
+
+ unsigned mc_flags;
+#define META_BACK_CMN_ISSET(mc,f) ( ( (mc)->mc_flags & (f) ) == (f) )
+#define META_BACK_CMN_QUARANTINE(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_QUARANTINE )
+#define META_BACK_CMN_CHASE_REFERRALS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_CHASE_REFERRALS )
+#define META_BACK_CMN_NOREFS(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOREFS )
+#define META_BACK_CMN_NOUNDEFFILTER(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_NOUNDEFFILTER )
+#define META_BACK_CMN_SAVECRED(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_SAVECRED )
+#define META_BACK_CMN_ST_REQUEST(mc) META_BACK_CMN_ISSET( (mc), LDAP_BACK_F_ST_REQUEST )
+
+#ifdef SLAPD_META_CLIENT_PR
+ /*
+ * client-side paged results:
+ * -1: accept unsolicited paged results responses
+ * 0: off
+ * >0: always request paged results with size == mt_ps
+ */
+#define META_CLIENT_PR_DISABLE (0)
+#define META_CLIENT_PR_ACCEPT_UNSOLICITED (-1)
+ ber_int_t mc_ps;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ slap_retry_info_t mc_quarantine;
+ time_t mc_network_timeout;
+ struct timeval mc_bind_timeout;
+#define META_BIND_TIMEOUT LDAP_BACK_RESULT_UTIMEOUT
+ time_t mc_timeout[ SLAP_OP_LAST ];
+} a_metacommon_t;
+
+typedef struct a_metatarget_t {
+ char *mt_uri;
+ ldap_pvt_thread_mutex_t mt_uri_mutex;
+
+ /* TODO: we might want to enable different strategies
+ * for different targets */
+ LDAP_REBIND_PROC *mt_rebind_f;
+ LDAP_URLLIST_PROC *mt_urllist_f;
+ void *mt_urllist_p;
+
+ metafilter_t *mt_filter;
+ a_metasubtree_t *mt_subtree;
+ /* F: subtree-include; T: subtree-exclude */
+ int mt_subtree_exclude;
+
+ int mt_scope;
+
+ struct berval mt_psuffix; /* pretty suffix */
+ struct berval mt_nsuffix; /* normalized suffix */
+
+ struct berval mt_lsuffixm; /* local suffix for massage */
+ struct berval mt_rsuffixm; /* remote suffix for massage */
+
+ struct berval mt_binddn;
+ struct berval mt_bindpw;
+
+ /* we only care about the TLS options here */
+ slap_bindconf mt_tls;
+
+ slap_idassert_t mt_idassert;
+#define mt_idassert_mode mt_idassert.si_mode
+#define mt_idassert_authcID mt_idassert.si_bc.sb_authcId
+#define mt_idassert_authcDN mt_idassert.si_bc.sb_binddn
+#define mt_idassert_passwd mt_idassert.si_bc.sb_cred
+#define mt_idassert_authzID mt_idassert.si_bc.sb_authzId
+#define mt_idassert_authmethod mt_idassert.si_bc.sb_method
+#define mt_idassert_sasl_mech mt_idassert.si_bc.sb_saslmech
+#define mt_idassert_sasl_realm mt_idassert.si_bc.sb_realm
+#define mt_idassert_secprops mt_idassert.si_bc.sb_secprops
+#define mt_idassert_tls mt_idassert.si_bc.sb_tls
+#define mt_idassert_flags mt_idassert.si_flags
+#define mt_idassert_authz mt_idassert.si_authz
+
+ sig_atomic_t mt_isquarantined;
+ ldap_pvt_thread_mutex_t mt_quarantine_mutex;
+
+ a_metacommon_t mt_mc;
+#define mt_nretries mt_mc.mc_nretries
+#define mt_flags mt_mc.mc_flags
+#define mt_version mt_mc.mc_version
+#define mt_ps mt_mc.mc_ps
+#define mt_network_timeout mt_mc.mc_network_timeout
+#define mt_bind_timeout mt_mc.mc_bind_timeout
+#define mt_timeout mt_mc.mc_timeout
+#define mt_quarantine mt_mc.mc_quarantine
+
+#define META_BACK_TGT_ISSET(mt,f) ( ( (mt)->mt_flags & (f) ) == (f) )
+#define META_BACK_TGT_ISMASK(mt,m,f) ( ( (mt)->mt_flags & (m) ) == (f) )
+
+#define META_BACK_TGT_SAVECRED(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_SAVECRED )
+
+#define META_BACK_TGT_USE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_USE_TLS )
+#define META_BACK_TGT_PROPAGATE_TLS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_PROPAGATE_TLS )
+#define META_BACK_TGT_TLS_CRITICAL(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_TLS_CRITICAL )
+
+#define META_BACK_TGT_CHASE_REFERRALS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_CHASE_REFERRALS )
+
+#define META_BACK_TGT_T_F(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK, LDAP_BACK_F_T_F )
+#define META_BACK_TGT_T_F_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_T_F_MASK2, LDAP_BACK_F_T_F_DISCOVER )
+
+#define META_BACK_TGT_ABANDON(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_ABANDON )
+#define META_BACK_TGT_IGNORE(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_IGNORE )
+#define META_BACK_TGT_CANCEL(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK, LDAP_BACK_F_CANCEL_EXOP )
+#define META_BACK_TGT_CANCEL_DISCOVER(mt) META_BACK_TGT_ISMASK( (mt), LDAP_BACK_F_CANCEL_MASK2, LDAP_BACK_F_CANCEL_EXOP_DISCOVER )
+#define META_BACK_TGT_QUARANTINE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_QUARANTINE )
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define META_BACK_TGT_ST_REQUEST(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_REQUEST )
+#define META_BACK_TGT_ST_RESPONSE(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_ST_RESPONSE )
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define META_BACK_TGT_NOREFS(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOREFS )
+#define META_BACK_TGT_NOUNDEFFILTER(mt) META_BACK_TGT_ISSET( (mt), LDAP_BACK_F_NOUNDEFFILTER )
+
+#define META_BACK_CFG_MAX_PENDING_OPS 0x80
+#define META_BACK_CFG_MAX_TARGET_CONNS 0xFF
+#define META_BACK_CFG_DEFAULT_OPS_TIMEOUT 0x02
+
+/* the interval of the timeout checking loop in microseconds
+ * possibly make this configurable? */
+#define META_BACK_CFG_MAX_TIMEOUT_LOOP 0x70000
+ slap_mask_t mt_rep_flags;
+ int mt_timeout_ops;
+} a_metatarget_t;
+
+typedef struct a_metadncache_t {
+ ldap_pvt_thread_mutex_t mutex;
+ Avlnode *tree;
+
+#define META_DNCACHE_DISABLED (0)
+#define META_DNCACHE_FOREVER ((time_t)(-1))
+ time_t ttl; /* seconds; 0: no cache, -1: no expiry */
+} a_metadncache_t;
+
+typedef struct a_metacandidates_t {
+ int mc_ntargets;
+ SlapReply *mc_candidates;
+} a_metacandidates_t;
+
+/*
+ * Hook to allow mucking with a_metainfo_t/a_metatarget_t when quarantine is over
+ */
+typedef int (*asyncmeta_quarantine_f)( struct a_metainfo_t *, int target, void * );
+
+struct meta_out_message_t;
+
+typedef struct a_metainfo_t {
+ int mi_ntargets;
+ int mi_defaulttarget;
+#define META_DEFAULT_TARGET_NONE (-1)
+
+#define mi_nretries mi_mc.mc_nretries
+#define mi_flags mi_mc.mc_flags
+#define mi_version mi_mc.mc_version
+#define mi_ps mi_mc.mc_ps
+#define mi_network_timeout mi_mc.mc_network_timeout
+#define mi_bind_timeout mi_mc.mc_bind_timeout
+#define mi_timeout mi_mc.mc_timeout
+#define mi_quarantine mi_mc.mc_quarantine
+
+ a_metatarget_t **mi_targets;
+ a_metacandidates_t *mi_candidates;
+
+ LDAP_REBIND_PROC *mi_rebind_f;
+ LDAP_URLLIST_PROC *mi_urllist_f;
+
+ a_metadncache_t mi_cache;
+
+ struct {
+ int mic_num;
+ LDAP_TAILQ_HEAD(mc_conn_priv_q, a_metaconn_t) mic_priv;
+ } mi_conn_priv[ LDAP_BACK_PCONN_LAST ];
+ int mi_conn_priv_max;
+
+ /* NOTE: quarantine uses the connection mutex */
+ asyncmeta_quarantine_f mi_quarantine_f;
+ void *mi_quarantine_p;
+
+#define li_flags mi_flags
+/* uses flags as defined in <back-ldap/back-ldap.h> */
+#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP
+#define META_BACK_F_ONERR_REPORT (0x02000000U)
+#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT)
+#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U)
+#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */
+#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */
+
+#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP )
+#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT )
+#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) )
+
+#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND )
+#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS )
+#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON )
+#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON )
+
+#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE )
+
+ time_t mi_idle_timeout;
+ struct re_s *mi_task;
+
+ a_metacommon_t mi_mc;
+ ldap_extra_t *mi_ldap_extra;
+
+ int mi_max_timeout_ops;
+ int mi_max_pending_ops;
+ int mi_max_target_conns;
+ /* mutex for access to the connection structures */
+ ldap_pvt_thread_mutex_t mi_mc_mutex;
+ int mi_num_conns;
+ int mi_next_conn;
+ a_metaconn_t *mi_conns;
+
+ struct berval mi_suffix;
+} a_metainfo_t;
+
+typedef enum meta_op_type {
+ META_OP_ALLOW_MULTIPLE = 0,
+ META_OP_REQUIRE_SINGLE,
+ META_OP_REQUIRE_ALL
+} meta_op_type;
+
+/* Whatever context asyncmeta_dn_massage needs... */
+typedef struct a_dncookie {
+ Operation *op;
+ struct a_metatarget_t *target;
+ void *memctx;
+ int to_from;
+} a_dncookie;
+
+
+#define MASSAGE_REQ 0
+#define MASSAGE_REP 1
+
+extern void
+asyncmeta_dn_massage(a_dncookie *dc, struct berval *dn,
+ struct berval *res);
+
+extern void
+asyncmeta_filter_map_rewrite(
+ a_dncookie *dc,
+ Filter *f,
+ struct berval *fstr );
+
+extern void
+asyncmeta_back_referral_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals );
+
+extern a_metaconn_t *
+asyncmeta_getconn(
+ Operation *op,
+ SlapReply *rs,
+ SlapReply *candidates,
+ int *candidate,
+ ldap_back_send_t sendok,
+ int alloc_new);
+
+
+extern int
+asyncmeta_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock );
+
+extern void
+asyncmeta_quarantine(
+ Operation *op,
+ a_metainfo_t *mi,
+ SlapReply *rs,
+ int candidate );
+
+extern int
+asyncmeta_proxy_authz_cred(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method );
+
+extern int
+asyncmeta_controls_add(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int isroot,
+ LDAPControl ***pctrls );
+
+extern int
+asyncmeta_LTX_init_module(
+ int argc,
+ char *argv[] );
+
+/*
+ * Candidate stuff
+ */
+extern int
+asyncmeta_is_candidate(
+ a_metatarget_t *mt,
+ struct berval *ndn,
+ int scope );
+
+extern int
+asyncmeta_select_unique_candidate(
+ a_metainfo_t *mi,
+ struct berval *ndn );
+
+extern int
+asyncmeta_clear_unused_candidates(
+ Operation *op,
+ int candidate,
+ a_metaconn_t *mc,
+ SlapReply *candidates);
+
+/*
+ * Dn cache stuff (experimental)
+ */
+extern int
+asyncmeta_dncache_cmp(
+ const void *c1,
+ const void *c2 );
+
+extern int
+asyncmeta_dncache_dup(
+ void *c1,
+ void *c2 );
+
+#define META_TARGET_NONE (-1)
+#define META_TARGET_MULTIPLE (-2)
+extern int
+asyncmeta_dncache_get_target(
+ a_metadncache_t *cache,
+ struct berval *ndn );
+
+extern int
+meta_dncache_update_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn,
+ int target );
+
+extern int
+asyncmeta_dncache_delete_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn );
+
+extern void
+asyncmeta_dncache_free( void *entry );
+
+extern int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms );
+
+extern void
+asyncmeta_filter_destroy( metafilter_t *mf );
+
+extern int
+asyncmeta_target_finish( a_metainfo_t *mi, a_metatarget_t *mt,
+ const char *log, char *msg, size_t msize
+);
+
+
+extern LDAP_REBIND_PROC asyncmeta_back_default_rebind;
+extern LDAP_URLLIST_PROC asyncmeta_back_default_urllist;
+
+/* IGNORE means that target does not (no longer) participate
+ * in the search;
+ * NOTREADY means the search on that target has not been initialized yet
+ */
+#define META_MSGID_IGNORE (-1)
+#define META_MSGID_NEED_BIND (-2)
+#define META_MSGID_CONNECTING (-3)
+#define META_MSGID_UNDEFINED (-4)
+#define META_MSGID_GOT_BIND (-5)
+
+typedef enum meta_search_candidate_t {
+ META_SEARCH_UNDEFINED = -2,
+ META_SEARCH_ERR = -1,
+ META_SEARCH_NOT_CANDIDATE,
+ META_SEARCH_CANDIDATE,
+ META_SEARCH_BINDING,
+ META_SEARCH_NEED_BIND,
+ META_SEARCH_CONNECTING
+} meta_search_candidate_t;
+
+Operation* asyncmeta_copy_op(Operation *op);
+void asyncmeta_clear_bm_context(bm_context_t *bc);
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc);
+void asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc);
+void asyncmeta_drop_bc_from_fconn(bm_context_t *bc);
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate);
+
+void* asyncmeta_op_handle_result(void *ctx, void *arg);
+int asyncmeta_back_cleanup( Operation *op, SlapReply *rs, bm_context_t *bm );
+
+int
+asyncmeta_clear_one_msc(
+ Operation *op,
+ a_metaconn_t *msc,
+ int candidate,
+ int unbind,
+ const char * caller);
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi );
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg);
+
+int
+asyncmeta_start_timeout_loop(a_metatarget_t *mt, a_metainfo_t *mi);
+
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc);
+
+int asyncmeta_back_cancel(
+ a_metaconn_t *mc,
+ Operation *op,
+ ber_int_t msgid,
+ int candidate );
+
+void
+asyncmeta_send_result(bm_context_t* bc, int error, char *text);
+
+int asyncmeta_new_bm_context(Operation *op,
+ SlapReply *rs,
+ bm_context_t **new_bc,
+ int ntargets,
+ a_metainfo_t *mi);
+
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc);
+int asyncmeta_start_one_listener(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc, int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_search_start(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ struct berval *prcookie,
+ ber_int_t prsize,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_dobind_init(
+ Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(
+ Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+meta_search_candidate_t
+asyncmeta_back_add_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock);
+
+bm_context_t *
+asyncmeta_bc_in_queue(a_metaconn_t *mc,
+ bm_context_t *bc);
+
+int
+asyncmeta_error_cleanup(Operation *op,
+ SlapReply *rs,
+ bm_context_t *bc,
+ a_metaconn_t *mc,
+ int candidate);
+
+int
+asyncmeta_reset_msc(Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller);
+
+
+void
+asyncmeta_back_conn_free(
+ void *v_mc );
+
+void asyncmeta_log_msc(a_metasingleconn_t *msc);
+void asyncmeta_log_conns(a_metainfo_t *mi);
+
+void asyncmeta_get_timestamp(char *buf);
+
+int
+asyncmeta_dncache_update_entry(a_metadncache_t *cache,
+ struct berval *ndn,
+ int target );
+
+void
+asyncmeta_dnattr_result_rewrite(a_dncookie *dc,
+ BerVarray a_vals);
+
+void
+asyncmeta_referral_result_rewrite(a_dncookie *dc,
+ BerVarray a_vals);
+
+meta_search_candidate_t
+asyncmeta_send_all_pending_ops(a_metaconn_t *mc,
+ int candidate,
+ void *ctx,
+ int dolock);
+meta_search_candidate_t
+asyncmeta_return_bind_errors(a_metaconn_t *mc,
+ int candidate,
+ SlapReply *bind_result,
+ void *ctx,
+ int dolock);
+
+int
+asyncmeta_db_has_pending_ops(a_metainfo_t *mi);
+
+int
+asyncmeta_db_has_mscs(a_metainfo_t *mi);
+
+/* The the maximum time in seconds after a result has been received on a connection,
+ * after which it can be reset if a sender error occurs. Should this be configurable? */
+#define META_BACK_RESULT_INTERVAL (2)
+
+extern int asyncmeta_debug;
+
+LDAP_END_DECL
+
+#endif /* SLAPD_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/bind.c b/servers/slapd/back-asyncmeta/bind.c
new file mode 100644
index 0000000..78c0e57
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/bind.c
@@ -0,0 +1,1739 @@
+/* bind.c - bind request handler functions for binding
+ * to remote targets for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/libldap/ldap-int.h"
+
+#define AVL_INTERNAL
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "lutil_ldap.h"
+
+#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12"
+
+static int
+asyncmeta_proxy_authz_bind(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock );
+
+static int
+asyncmeta_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate );
+
+int
+asyncmeta_back_bind( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metaconn_t *mc = NULL;
+
+ int rc = LDAP_OTHER,
+ i,
+ gotit = 0,
+ isroot = 0;
+
+ SlapReply *candidates = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_ARGS, "%s asyncmeta_back_bind: dn=\"%s\".\n",
+ op->o_log_prefix, op->o_req_dn.bv_val );
+
+ /* the test on the bind method should be superfluous */
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case LDAP_SUCCESS:
+ if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) {
+ /* frontend will return success */
+ return rs->sr_err;
+ }
+
+ isroot = 1;
+ /* fallthru */
+
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* be_rootdn_bind() sent result */
+ return rs->sr_err;
+ }
+
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = op->o_tmpcalloc(mi->mi_ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+
+ /* we need asyncmeta_getconn() not send result even on error,
+ * because we want to intercept the error and make it
+ * invalidCredentials */
+ mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_BIND_DONTSEND, 1 );
+ if ( !mc ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_back_bind: no target " "for dn \"%s\" (%d%s%s).\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ rs->sr_err, rs->sr_text ? ". " : "",
+ rs->sr_text ? rs->sr_text : "" );
+
+ /* FIXME: there might be cases where we don't want
+ * to map the error onto invalidCredentials */
+ switch ( rs->sr_err ) {
+ case LDAP_NO_SUCH_OBJECT:
+ case LDAP_UNWILLING_TO_PERFORM:
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ rs->sr_text = NULL;
+ break;
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+ }
+
+ /*
+ * Each target is scanned ...
+ */
+ mc->mc_authz_target = META_BOUND_NONE;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+ int lerr;
+
+ /*
+ * Skip non-candidates
+ */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( gotit == 0 ) {
+ /* set rc to LDAP_SUCCESS only if at least
+ * one candidate has been tried */
+ rc = LDAP_SUCCESS;
+ gotit = 1;
+
+ } else if ( !isroot ) {
+ /*
+ * A bind operation is expected to have
+ * ONE CANDIDATE ONLY!
+ */
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_back_bind: more than one"
+ " candidate selected...\n",
+ op->o_log_prefix );
+ }
+
+ if ( isroot ) {
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE
+ || BER_BVISNULL( &mt->mt_idassert_authcDN ) )
+ {
+ a_metasingleconn_t *msc = &mc->mc_conns[ i ];
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ continue;
+ }
+
+
+ (void)asyncmeta_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 );
+ lerr = rs->sr_err;
+
+ } else {
+ lerr = asyncmeta_single_bind( op, rs, mc, i );
+ }
+
+ if ( lerr != LDAP_SUCCESS ) {
+ rc = rs->sr_err = lerr;
+
+ /* FIXME: in some cases (e.g. unavailable)
+ * do not assume it's not candidate; rather
+ * mark this as an error to be eventually
+ * reported to client */
+ META_CANDIDATE_CLEAR( &candidates[ i ] );
+ break;
+ }
+ }
+
+ if ( mc != NULL ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metasingleconn_t *msc = &mc->mc_conns[ i ];
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ }
+ }
+ asyncmeta_back_conn_free( mc );
+ }
+
+ /*
+ * rc is LDAP_SUCCESS if at least one bind succeeded,
+ * err is the last error that occurred during a bind;
+ * if at least (and at most?) one bind succeeds, fine.
+ */
+ if ( rc != LDAP_SUCCESS ) {
+
+ /*
+ * deal with bind failure ...
+ */
+
+ /*
+ * no target was found within the naming context,
+ * so bind must fail with invalid credentials
+ */
+ if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ } else {
+ rs->sr_err = slap_map_api2result( rs );
+ }
+ send_ldap_result( op, rs );
+ return rs->sr_err;
+
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_bind_op_result(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int msgid,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ LDAPMessage *res;
+ struct timeval tv;
+ int rc;
+ int nretries = mt->mt_nretries;
+
+ Debug( LDAP_DEBUG_TRACE,
+ ">>> %s asyncmeta_bind_op_result[%d]\n",
+ op->o_log_prefix, candidate );
+
+ /* make sure this is clean */
+ assert( rs->sr_ctrls == NULL );
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ time_t stoptime = (time_t)(-1),
+ timeout;
+ int timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ const char *timeout_text = "Operation timed out";
+ slap_op_t opidx = slap_req2op( op->o_tag );
+
+ /* since timeout is not specified, compute and use
+ * the one specific to the ongoing operation */
+ if ( opidx == LDAP_REQ_SEARCH ) {
+ if ( op->ors_tlimit <= 0 ) {
+ timeout = 0;
+
+ } else {
+ timeout = op->ors_tlimit;
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ }
+
+ } else {
+ timeout = mt->mt_timeout[ opidx ];
+ }
+
+ /* better than nothing :) */
+ if ( timeout == 0 ) {
+ if ( mi->mi_idle_timeout ) {
+ timeout = mi->mi_idle_timeout;
+
+ }
+ }
+
+ if ( timeout ) {
+ stoptime = op->o_time + timeout;
+ }
+
+ LDAP_BACK_TV_SET( &tv );
+
+ /*
+ * handle response!!!
+ */
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case 0:
+ if ( nretries != META_RETRY_NEVER
+ || ( timeout && slap_get_time() <= stoptime ) )
+ {
+ ldap_pvt_thread_yield();
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ tv = mt->mt_bind_timeout;
+ goto retry;
+ }
+
+ /* don't let anyone else use this handler,
+ * because there's a pending bind that will not
+ * be acknowledged */
+ assert( LDAP_BACK_CONN_BINDING( msc ) );
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_bind_op_result ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ break;
+
+ case -1:
+ ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER,
+ &rs->sr_err );
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_bind_op_result[%d]: err=%d (%s) nretries=%d.\n",
+ op->o_log_prefix, candidate, rs->sr_err,
+ ldap_err2string(rs->sr_err), nretries );
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) {
+ msc->msc_time = op->o_time;
+ }
+
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err,
+ NULL, NULL, NULL, NULL, 1 );
+ if ( rc != LDAP_SUCCESS ) {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+ break;
+ }
+ }
+
+ rs->sr_err = slap_map_api2result( rs );
+ Debug( LDAP_DEBUG_TRACE,
+ "<<< %s asyncmeta_bind_op_result[%d] err=%d\n",
+ op->o_log_prefix, candidate, rs->sr_err );
+
+ return rs->sr_err;
+}
+
+/*
+ * asyncmeta_single_bind
+ *
+ * attempts to perform a bind with creds
+ */
+static int
+asyncmeta_single_bind(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ int msgid;
+ a_dncookie dc;
+ struct berval save_o_dn;
+ int save_o_do_not_cache;
+ LDAPControl **ctrls = NULL;
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ch_free( msc->msc_bound_ndn.bv_val );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ /* destroy sensitive data */
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ch_free( msc->msc_cred.bv_val );
+ BER_BVZERO( &msc->msc_cred );
+ }
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* don't add proxyAuthz; set the bindDN */
+ save_o_dn = op->o_dn;
+ save_o_do_not_cache = op->o_do_not_cache;
+ op->o_do_not_cache = 1;
+ op->o_dn = op->o_req_dn;
+
+ ctrls = op->o_ctrls;
+ rs->sr_err = asyncmeta_controls_add( op, rs, mc, candidate, be_isroot(op), &ctrls );
+ op->o_dn = save_o_dn;
+ op->o_do_not_cache = save_o_do_not_cache;
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* FIXME: this fixes the bind problem right now; we need
+ * to use the asynchronous version to get the "matched"
+ * and more in case of failure ... */
+ /* FIXME: should we check if at least some of the op->o_ctrls
+ * can/should be passed? */
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val,
+ LDAP_SASL_SIMPLE, &op->orb_cred,
+ ctrls, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ /* If defined, proxyAuthz will be used also when
+ * back-ldap is the authorizing backend; for this
+ * purpose, a successful bind is followed by a
+ * bind with the configured identity assertion */
+ /* NOTE: use with care */
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ asyncmeta_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 );
+ if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ goto return_results;
+ }
+ goto cache_refresh;
+ }
+
+ ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn );
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ mc->mc_authz_target = candidate;
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &op->orb_cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+
+cache_refresh:;
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED
+ && !BER_BVISEMPTY( &op->o_req_ndn ) )
+ {
+ ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+ &op->o_req_ndn, candidate );
+ }
+
+return_results:;
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ asyncmeta_quarantine( op, mi, rs, candidate );
+ }
+ ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+ msc->msc_ld = NULL;
+ ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+ msc->msc_ldr = NULL;
+ return rs->sr_err;
+}
+
+
+/*
+ * asyncmeta_back_default_rebind
+ *
+ * This is a callback used for chasing referrals using the same
+ * credentials as the original user on this session.
+ */
+int
+asyncmeta_back_default_rebind(
+ LDAP *ld,
+ LDAP_CONST char *url,
+ ber_tag_t request,
+ ber_int_t msgid,
+ void *params )
+{
+ a_metasingleconn_t *msc = ( a_metasingleconn_t * )params;
+
+ return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val,
+ LDAP_SASL_SIMPLE, &msc->msc_cred,
+ NULL, NULL, NULL );
+}
+
+/*
+ * meta_back_default_urllist
+ *
+ * This is a callback used for mucking with the urllist
+ */
+int
+asyncmeta_back_default_urllist(
+ LDAP *ld,
+ LDAPURLDesc **urllist,
+ LDAPURLDesc **url,
+ void *params )
+{
+ a_metatarget_t *mt = (a_metatarget_t *)params;
+ LDAPURLDesc **urltail;
+
+ if ( urllist == url ) {
+ return LDAP_SUCCESS;
+ }
+
+ for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next )
+ /* count */ ;
+
+ *urltail = *urllist;
+ *urllist = *url;
+ *url = NULL;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ }
+
+ ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+
+ return LDAP_SUCCESS;
+}
+
+int
+asyncmeta_back_cancel(
+ a_metaconn_t *mc,
+ Operation *op,
+ ber_int_t msgid,
+ int candidate )
+{
+
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ int rc = LDAP_OTHER;
+ struct timeval tv = { 0, 0 };
+ ber_socket_t s;
+
+ Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n",
+ op->o_log_prefix, candidate, msgid );
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n already reset",
+ op->o_log_prefix, candidate, msgid );
+ return LDAP_SUCCESS;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ return rc;
+ }
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ rc = LDAP_SERVER_DOWN;
+ return rc;
+ }
+ /* default behavior */
+ if ( META_BACK_TGT_ABANDON( mt ) ) {
+ rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL );
+
+ } else if ( META_BACK_TGT_IGNORE( mt ) ) {
+ rc = ldap_pvt_discard( msc->msc_ld, msgid );
+
+ } else if ( META_BACK_TGT_CANCEL( mt ) ) {
+ rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL );
+
+ } else {
+ assert( 0 );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_cancel[%d] err=%d\n",
+ op->o_log_prefix, candidate, rc );
+
+ return rc;
+}
+
+
+
+/*
+ * asyncmeta_back_proxy_authz_cred()
+ *
+ * prepares credentials & method for meta_back_proxy_authz_bind();
+ * or, if method is SASL, performs the SASL bind directly.
+ */
+int
+asyncmeta_back_proxy_authz_cred(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ struct berval *binddn,
+ struct berval *bindcred,
+ int *method )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval ndn;
+ int dobind = 0;
+ struct timeval old_tv = {0, 0};
+ struct timeval bind_tv = { mt->mt_timeout[ SLAP_OP_BIND ], 0};
+ /* don't proxyAuthz if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+
+ } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+
+ } else {
+ ndn = op->o_ndn;
+ }
+ rs->sr_err = LDAP_SUCCESS;
+
+ /*
+ * FIXME: we need to let clients use proxyAuthz
+ * otherwise we cannot do symmetric pools of servers;
+ * we have to live with the fact that a user can
+ * authorize itself as any ID that is allowed
+ * by the authzTo directive of the "proxyauthzdn".
+ */
+ /*
+ * NOTE: current Proxy Authorization specification
+ * and implementation do not allow proxy authorization
+ * control to be provided with Bind requests
+ */
+ /*
+ * if no bind took place yet, but the connection is bound
+ * and the "proxyauthzdn" is set, then bind as
+ * "proxyauthzdn" and explicitly add the proxyAuthz
+ * control to every operation with the dn bound
+ * to the connection as control value.
+ */
+
+ /* bind as proxyauthzdn only if no idassert mode
+ * is requested, or if the client's identity
+ * is authorized */
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) )
+ {
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ }
+ }
+ break;
+
+ default:
+ /* NOTE: rootdn can always idassert */
+ if ( BER_BVISNULL( &ndn )
+ && mt->mt_idassert_authz == NULL
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ rs->sr_err = LDAP_INAPPROPRIATE_AUTH;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+
+ } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) {
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+
+ } else {
+ authcDN = ndn;
+ }
+ rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz,
+ &authcDN, &authcDN );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+ *binddn = slap_empty_bv;
+ *bindcred = slap_empty_bv;
+ break;
+ }
+ }
+
+ *binddn = mt->mt_idassert_authcDN;
+ *bindcred = mt->mt_idassert_passwd;
+ dobind = 1;
+ break;
+ }
+
+ if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) {
+#ifdef HAVE_CYRUS_SASL
+ void *defaults = NULL;
+ struct berval authzID = BER_BVNULL;
+ int freeauthz = 0;
+
+ /* if SASL supports native authz, prepare for it */
+ if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) &&
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ authzID = mt->mt_idassert_authzID;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+
+ case LDAP_BACK_IDASSERT_SELF:
+ if ( BER_BVISNULL( &ndn ) ) {
+ /* connection is not authc'd, so don't idassert */
+ BER_BVSTR( &authzID, "dn:" );
+ break;
+ }
+ authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len;
+ authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx );
+ AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ),
+ ndn.bv_val, ndn.bv_len + 1 );
+ freeauthz = 1;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_secprops != NULL ) {
+ rs->sr_err = ldap_set_option( msc->msc_ld,
+ LDAP_OPT_X_SASL_SECPROPS,
+ (void *)mt->mt_idassert_secprops );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv);
+
+ if (mt->mt_timeout[ SLAP_OP_BIND ] > 0 ) {
+ rs->sr_err = ldap_set_option( msc->msc_ld,
+ LDAP_OPT_TIMEOUT,
+ (void *)&bind_tv );
+
+ if ( rs->sr_err != LDAP_OPT_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ goto done;
+ }
+ }
+ defaults = lutil_sasl_defaults( msc->msc_ld,
+ mt->mt_idassert_sasl_mech.bv_val,
+ mt->mt_idassert_sasl_realm.bv_val,
+ mt->mt_idassert_authcID.bv_val,
+ mt->mt_idassert_passwd.bv_val,
+ authzID.bv_val );
+ if ( defaults == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ goto done;
+ }
+
+ rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val,
+ mt->mt_idassert_sasl_mech.bv_val, NULL, NULL,
+ LDAP_SASL_QUIET, lutil_sasl_interact,
+ defaults );
+
+ /* restore the old timeout just in case */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv );
+
+ rs->sr_err = slap_map_api2result( rs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_back_proxy_authz_cred failed bind msc: %p\n",
+ time_buf, msc );
+ }
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+
+ } else {
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+
+ lutil_sasl_freedefs( defaults );
+ if ( freeauthz ) {
+ slap_sl_free( authzID.bv_val, op->o_tmpmemctx );
+ }
+
+ goto done;
+#endif /* HAVE_CYRUS_SASL */
+ }
+
+ *method = mt->mt_idassert_authmethod;
+ switch ( mt->mt_idassert_authmethod ) {
+ case LDAP_AUTH_NONE:
+ BER_BVSTR( binddn, "" );
+ BER_BVSTR( bindcred, "" );
+ /* fallthru */
+
+ case LDAP_AUTH_SIMPLE:
+ break;
+
+ default:
+ /* unsupported! */
+ LDAP_BACK_CONN_ISBOUND_CLEAR( msc );
+ rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED;
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ break;
+ }
+
+done:;
+
+ if ( !BER_BVISEMPTY( binddn ) ) {
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+ }
+
+ return rs->sr_err;
+}
+
+static int
+asyncmeta_proxy_authz_bind(
+ a_metaconn_t *mc,
+ int candidate,
+ Operation *op,
+ SlapReply *rs,
+ ldap_back_send_t sendok,
+ int dolock )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval binddn = BER_BVC( "" ),
+ cred = BER_BVC( "" );
+ int method = LDAP_AUTH_NONE,
+ rc;
+
+ rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method );
+ if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ int msgid;
+
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+ for (;;) {
+ rs->sr_err = ldap_sasl_bind( msc->msc_ld,
+ binddn.bv_val, LDAP_SASL_SIMPLE,
+ &cred, NULL, NULL, &msgid );
+ if ( rs->sr_err != LDAP_X_CONNECTING ) {
+ break;
+ }
+ ldap_pvt_thread_yield();
+ }
+
+ rc = asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock );
+ if ( rc == LDAP_SUCCESS ) {
+ /* set rebind stuff in case of successful proxyAuthz bind,
+ * so that referral chasing is attempted using the right
+ * identity */
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+
+ if ( META_BACK_TGT_SAVECRED( mt ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc );
+ }
+ }
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ return LDAP_BACK_CONN_ISBOUND( msc );
+}
+
+
+static int
+asyncmeta_back_proxy_authz_ctrl(Operation *op,
+ SlapReply *rs,
+ struct berval *bound_ndn,
+ int version,
+ int isroot,
+ slap_idassert_t *si,
+ LDAPControl *ctrl )
+{
+ slap_idassert_mode_t mode;
+ struct berval assertedID,
+ ndn;
+
+ rs->sr_err = SLAP_CB_CONTINUE;
+
+ /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID,
+ * but if it is not set this test fails. We need a different
+ * means to detect if idassert is enabled */
+ if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) )
+ && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) )
+ && BER_BVISNULL( &si->si_bc.sb_saslmech ) )
+ {
+ goto done;
+ }
+
+ if ( !op->o_conn || op->o_do_not_cache || ( isroot ) ) {
+ goto done;
+ }
+
+ if ( op->o_tag == LDAP_REQ_BIND ) {
+ ndn = op->o_req_ndn;
+
+#if 0
+ } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) {
+ ndn = op->o_conn->c_ndn;
+#endif
+ } else {
+ ndn = op->o_ndn;
+ }
+
+ if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) {
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we do not want to perform proxyAuthz
+ * on behalf of the client, because this would
+ * be performed with "proxyauthzdn" privileges.
+ *
+ * This might actually be too strict, since
+ * the "proxyauthzdn" authzTo, and each entry's
+ * authzFrom attributes may be crafted
+ * to avoid unwanted proxyAuthz to take place.
+ */
+#if 0
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+#endif
+ goto done;
+ }
+
+ if ( !BER_BVISNULL( bound_ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ goto done;
+ }
+
+ if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) {
+ goto done;
+ }
+
+ } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) {
+ if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) )
+ {
+ /* already asserted in SASL via native authz */
+ goto done;
+ }
+
+ } else if ( si->si_authz && !isroot ) {
+ int rc;
+ struct berval authcDN;
+
+ if ( BER_BVISNULL( &ndn ) ) {
+ authcDN = slap_empty_bv;
+ } else {
+ authcDN = ndn;
+ }
+ rc = slap_sasl_matches( op, si->si_authz,
+ &authcDN, &authcDN );
+ if ( rc != LDAP_SUCCESS ) {
+ if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ /* ndn is not authorized
+ * to use idassert */
+ rs->sr_err = rc;
+ }
+ goto done;
+ }
+ }
+
+ if ( op->o_proxy_authz ) {
+ /*
+ * FIXME: we can:
+ * 1) ignore the already set proxyAuthz control
+ * 2) leave it in place, and don't set ours
+ * 3) add both
+ * 4) reject the operation
+ *
+ * option (4) is very drastic
+ * option (3) will make the remote server reject
+ * the operation, thus being equivalent to (4)
+ * option (2) will likely break the idassert
+ * assumptions, so we cannot accept it;
+ * option (1) means that we are contradicting
+ * the client's request.
+ *
+ * I think (4) is the only correct choice.
+ */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "proxyAuthz not allowed within namingContext";
+ }
+
+ if ( op->o_is_auth_check ) {
+ mode = LDAP_BACK_IDASSERT_NOASSERT;
+
+ } else {
+ mode = si->si_mode;
+ }
+
+ switch ( mode ) {
+ case LDAP_BACK_IDASSERT_LEGACY:
+ /* original behavior:
+ * assert the client's identity */
+ case LDAP_BACK_IDASSERT_SELF:
+ assertedID = ndn;
+ break;
+
+ case LDAP_BACK_IDASSERT_ANONYMOUS:
+ /* assert "anonymous" */
+ assertedID = slap_empty_bv;
+ break;
+
+ case LDAP_BACK_IDASSERT_NOASSERT:
+ /* don't assert; bind as proxyauthzdn */
+ goto done;
+
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ /* assert idassert DN */
+ assertedID = si->si_bc.sb_authzId;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ /* if we got here, "" is allowed to proxyAuthz */
+ if ( BER_BVISNULL( &assertedID ) ) {
+ assertedID = slap_empty_bv;
+ }
+
+ /* don't idassert the bound DN (ITS#4497) */
+ if ( dn_match( &assertedID, bound_ndn ) ) {
+ goto done;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ;
+ ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL );
+
+ switch ( si->si_mode ) {
+ /* already in u:ID or dn:DN form */
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ /* needs the dn: prefix */
+ default:
+ ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" );
+ ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1,
+ op->o_tmpmemctx );
+ AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) );
+ AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ],
+ assertedID.bv_val, assertedID.bv_len + 1 );
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+
+ /* Older versions of <draft-weltman-ldapv3-proxy> required
+ * to encode the value of the authzID (and called it proxyDN);
+ * this hack provides compatibility with those DSAs that
+ * implement it this way */
+ if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ struct berval authzID = ctrl->ldctl_value;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ tag = ber_printf( ber, "O", &authzID );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber;
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ } else if ( si->si_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ struct berval authzID = ctrl->ldctl_value,
+ tmp;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+
+ if ( strncasecmp( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ) != 0 ) {
+ rs->sr_err = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ tmp = authzID;
+ tmp.bv_val += STRLENOF( "dn:" );
+ tmp.bv_len -= STRLENOF( "dn:" );
+
+ ber_init2( ber, 0, LBER_USE_DER );
+ ber_set_option( ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ /* apparently, Mozilla API encodes this
+ * as "SEQUENCE { LDAPDN }" */
+ tag = ber_printf( ber, "{O}", &tmp );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ if ( ber_flatten2( ber, &ctrl->ldctl_value, 1 ) == -1 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto free_ber2;
+ }
+
+ ctrl->ldctl_oid = LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ;
+ rs->sr_err = LDAP_SUCCESS;
+
+free_ber2:;
+ op->o_tmpfree( authzID.bv_val, op->o_tmpmemctx );
+ ber_free_buf( ber );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+ }
+
+done:;
+
+ return rs->sr_err;
+}
+
+/*
+ * Add controls;
+ *
+ * if any needs to be added, it is prepended to existing ones,
+ * in a newly allocated array. The companion function
+ * mi->mi_ldap_extra->controls_free() must be used to restore the original
+ * status of op->o_ctrls.
+ */
+int
+asyncmeta_controls_add( Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int isroot,
+ LDAPControl ***pctrls )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ LDAPControl **ctrls = NULL;
+ /* set to the maximum number of controls this backend can add */
+ LDAPControl c[ 2 ] = {{ 0 }};
+ int n = 0, i, j1 = 0, j2 = 0, skipped = 0;
+
+ *pctrls = NULL;
+
+ rs->sr_err = LDAP_SUCCESS;
+
+ /* don't add controls if protocol is not LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __before__ existing ones here */
+
+ /* proxyAuthz for identity assertion */
+ switch ( asyncmeta_back_proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn,
+ mt->mt_version, isroot, &mt->mt_idassert, &c[ j1 ] ) )
+ {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j1++;
+ break;
+
+ default:
+ goto done;
+ }
+
+ /* put controls that go __after__ existing ones here */
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ /* session tracking */
+ if ( META_BACK_TGT_ST_REQUEST( mt ) ) {
+ switch ( slap_ctrl_session_tracking_request_add( op, rs, &c[ j1 + j2 ] ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ case LDAP_SUCCESS:
+ j2++;
+ break;
+
+ default:
+ goto done;
+ }
+ }
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ if ( rs->sr_err == SLAP_CB_CONTINUE ) {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ /* if nothing to do, just bail out */
+ if ( j1 == 0 && j2 == 0 ) {
+ goto done;
+ }
+
+ assert( j1 + j2 <= (int) (sizeof( c )/sizeof( c[0] )) );
+
+ if ( op->o_ctrls ) {
+ for ( n = 0; op->o_ctrls[ n ]; n++ )
+ /* just count ctrls */ ;
+ }
+
+ ctrls = op->o_tmpalloc( (n + j1 + j2 + 1) * sizeof( LDAPControl * ) + ( j1 + j2 ) * sizeof( LDAPControl ),
+ op->o_tmpmemctx );
+ if ( j1 ) {
+ ctrls[ 0 ] = (LDAPControl *)&ctrls[ n + j1 + j2 + 1 ];
+ *ctrls[ 0 ] = c[ 0 ];
+ for ( i = 1; i < j1; i++ ) {
+ ctrls[ i ] = &ctrls[ 0 ][ i ];
+ *ctrls[ i ] = c[ i ];
+ }
+ }
+
+ i = 0;
+ if ( op->o_ctrls ) {
+ LDAPControl *proxyauthz = ldap_control_find(
+ LDAP_CONTROL_PROXY_AUTHZ, op->o_ctrls, NULL );
+
+ for ( i = 0; op->o_ctrls[ i ]; i++ ) {
+ /* Only replace it if we generated one */
+ if ( j1 && proxyauthz && proxyauthz == op->o_ctrls[ i ] ) {
+ /* Frontend has already checked only one is present */
+ assert( skipped == 0 );
+ skipped++;
+ continue;
+ }
+ ctrls[ i + j1 - skipped ] = op->o_ctrls[ i ];
+ }
+ }
+
+ n += j1 - skipped;
+ if ( j2 ) {
+ ctrls[ n ] = (LDAPControl *)&ctrls[ n + j2 + 1 ] + j1;
+ *ctrls[ n ] = c[ j1 ];
+ for ( i = 1; i < j2; i++ ) {
+ ctrls[ n + i ] = &ctrls[ n ][ i ];
+ *ctrls[ n + i ] = c[ i ];
+ }
+ }
+
+ ctrls[ n + j2 ] = NULL;
+
+done:;
+ if ( ctrls == NULL ) {
+ ctrls = op->o_ctrls;
+ }
+
+ *pctrls = ctrls;
+
+ return rs->sr_err;
+}
+
+
+/*
+ * asyncmeta_dobind_init()
+ *
+ * initiates bind for a candidate target
+ */
+meta_search_candidate_t
+asyncmeta_dobind_init(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+ SlapReply *candidates = bc->candidates;
+ a_metainfo_t *mi = ( a_metainfo_t * )mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ struct berval binddn = msc->msc_bound_ndn,
+ cred = msc->msc_cred;
+ int method;
+
+ int rc;
+ ber_int_t msgid;
+
+ meta_search_candidate_t retcode;
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_dobind_init[%d] msc %p\n",
+ op->o_log_prefix, candidate, msc );
+
+ if ( mc->mc_authz_target == META_BOUND_ALL ) {
+ return META_SEARCH_CANDIDATE;
+ }
+
+ if ( slapd_shutdown ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ return META_SEARCH_ERR;
+ }
+
+ retcode = META_SEARCH_BINDING;
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+ /* already bound (or anonymous) */
+
+#ifdef DEBUG_205
+ char buf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+ int bound = 0;
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ bound = 1;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p%s DN=\"%s\"\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld, bound ? " bound" : " anonymous",
+ bound == 0 ? "" : msc->msc_bound_ndn.bv_val );
+#endif /* DEBUG_205 */
+
+ retcode = META_SEARCH_CANDIDATE;
+
+ } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) {
+ /* another thread is binding the target for this conn; wait */
+
+#ifdef DEBUG_205
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p needbind\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+ retcode = META_SEARCH_NEED_BIND;
+ } else {
+ /* we'll need to bind the target for this conn */
+
+#ifdef DEBUG_205
+
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p binding\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ if ( msc->msc_ld == NULL ) {
+ /* for some reason (e.g. because formerly in "binding"
+ * state, with eventual connection expiration or invalidation)
+ * it was not initialized as expected */
+
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p ld=NULL\n",
+ op->o_log_prefix, candidate, (void *)mc );
+
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( msc->msc_ld != NULL );
+ break;
+
+ case LDAP_SERVER_DOWN:
+ case LDAP_UNAVAILABLE:
+ goto down;
+
+ default:
+ goto other;
+ }
+ }
+
+ LDAP_BACK_CONN_BINDING_SET( msc );
+ }
+
+ if ( retcode != META_SEARCH_BINDING ) {
+ return retcode;
+ }
+
+ if ( op->o_conn != NULL &&
+ !op->o_do_not_cache &&
+ ( BER_BVISNULL( &msc->msc_bound_ndn ) ||
+ BER_BVISEMPTY( &msc->msc_bound_ndn ) ||
+ ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) )
+ {
+ rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, LDAP_BACK_DONTSEND, &binddn, &cred, &method );
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ break;
+ case LDAP_UNAVAILABLE:
+ goto down;
+ default:
+ goto other;
+ }
+
+ /* NOTE: we copy things here, even if bind didn't succeed yet,
+ * because the connection is not shared until bind is over */
+ if ( !BER_BVISNULL( &binddn ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &binddn );
+ if ( META_BACK_TGT_SAVECRED( mt ) && !BER_BVISNULL( &cred ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &cred );
+ }
+ }
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) ) {
+ /* apparently, idassert was configured with SASL bind,
+ * so bind occurred inside meta_back_proxy_authz_cred() */
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ return META_SEARCH_CANDIDATE;
+ }
+
+ /* paranoid */
+ switch ( method ) {
+ case LDAP_AUTH_NONE:
+ case LDAP_AUTH_SIMPLE:
+ /* do a simple bind with binddn, cred */
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ assert( msc->msc_ld != NULL );
+
+ if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) {
+ /* bind anonymously? */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p: "
+ "non-empty dn with empty cred; binding anonymously\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ cred = slap_empty_bv;
+
+ } else if ( BER_BVISEMPTY( &binddn ) && !BER_BVISEMPTY( &cred ) ) {
+ /* error */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_dobind_init[%d] mc=%p: "
+ "empty dn with non-empty cred: error\n",
+ op->o_log_prefix, candidate, (void *)mc );
+ rc = LDAP_OTHER;
+ goto other;
+ }
+retry_bind:
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init sending bind msc: %p\n",
+ time_buf, msc );
+ }
+ rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred,
+ NULL, NULL, &msgid );
+ ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rc );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init rc=%d msc: %p\n",
+ time_buf, rc, msc );
+ }
+ if ( LogTest( LDAP_DEBUG_TRACE )) {
+ ber_socket_t s;
+ char sockname[LDAP_IPADDRLEN];
+ struct berval sockbv = BER_BVC( sockname );
+ Sockaddr addr;
+ socklen_t len = sizeof( addr );
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ getsockname( s, &addr.sa_addr, &len );
+ ldap_pvt_sockaddrstr( &addr, &sockbv );
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_dobind_init msc %p ld %p ldr %p fd %d addr %s\n",
+ op->o_log_prefix, msc, msc->msc_ld, msc->msc_ldr, s, sockname );
+ }
+
+ if (rc == LDAP_SERVER_DOWN ) {
+ goto down;
+ } else if (rc == LDAP_BUSY) {
+ if (rs->sr_text == NULL) {
+ rs->sr_text = "Unable to establish LDAP connection to target within the specified network timeout.";
+ }
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ goto other;
+ }
+ /* mark as need bind so it gets send when the bind response is received */
+ candidates[ candidate ].sr_msgid = META_MSGID_NEED_BIND;
+ asyncmeta_set_msc_time(msc);
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_dobind_init[%d] mc=%p ld=%p rc=%d\n",
+ op->o_log_prefix, candidate, (void *)mc,
+ (void *)mc->mc_conns[candidate].msc_ld, rc );
+#endif /* DEBUG_205 */
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ assert( msgid >= 0 );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_init sending bind success msc: %p\n",
+ time_buf, msc );
+ }
+ META_BINDING_SET( &candidates[ candidate ] );
+ rs->sr_err = LDAP_SUCCESS;
+ msc->msc_binding_time = slap_get_time();
+ return META_SEARCH_BINDING;
+
+ case LDAP_X_CONNECTING:
+ /* must retry, same conn */
+ candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ goto retry_bind;
+
+ case LDAP_SERVER_DOWN:
+down:;
+ retcode = META_SEARCH_ERR;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ if (rs->sr_text == NULL) {
+ rs->sr_text = "Unable to bind to remote target - target down or unavailable";
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ break;
+
+ /* fall thru */
+
+ default:
+other:;
+ rs->sr_err = rc;
+ rc = slap_map_api2result( rs );
+ candidates[ candidate ].sr_err = rc;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ retcode = META_SEARCH_ERR;
+
+ } else {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ break;
+ }
+
+ return retcode;
+}
+
+
+
+
+meta_search_candidate_t
+asyncmeta_dobind_init_with_retry(Operation *op, SlapReply *rs, bm_context_t *bc, a_metaconn_t *mc, int candidate)
+{
+
+ int rc;
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+
+ if (META_BACK_CONN_INVALID(msc) || (LDAP_BACK_CONN_BINDING( msc ) && msc->msc_binding_time > 0
+ && (msc->msc_binding_time + mt->mt_timeout[ SLAP_OP_BIND ]) < slap_get_time())) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ), "called from %s:%d", __FILE__, __LINE__ );
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, buf);
+
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ }
+
+ if ( LDAP_BACK_CONN_ISBOUND( msc ) || LDAP_BACK_CONN_ISANON( msc ) ) {
+ if ( mc->pending_ops > 1 ) {
+ asyncmeta_send_all_pending_ops( mc, candidate, op->o_threadctx, 1 );
+ }
+ return META_SEARCH_CANDIDATE;
+ }
+
+retry_dobind:
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = asyncmeta_dobind_init(op, rs, bc, mc, candidate);
+ if (rs->sr_err != LDAP_UNAVAILABLE && rs->sr_err != LDAP_BUSY) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+ } else if (bc->nretries[candidate] == 0) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ snprintf( buf, sizeof( buf ), "called from %s:%d", __FILE__, __LINE__ );
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, buf);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+ }
+ /* need to retry */
+ bc->nretries[candidate]--;
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ /* this lock is required; however,
+ * it's invoked only when logging is on */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_dobind_init_with_retry[%d]: retrying URI=\"%s\" DN=\"%s\".\n",
+ op->o_log_prefix, candidate, mt->mt_uri,
+ BER_BVISNULL(&msc->msc_bound_ndn) ? "" : msc->msc_bound_ndn.bv_val );
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ }
+
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ rc = asyncmeta_init_one_conn( op, rs, mc, candidate,
+ LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 );
+
+ if (rs->sr_err != LDAP_SUCCESS) {
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return META_SEARCH_ERR;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ goto retry_dobind;
+ return rc;
+}
diff --git a/servers/slapd/back-asyncmeta/candidates.c b/servers/slapd/back-asyncmeta/candidates.c
new file mode 100644
index 0000000..5f3b228
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/candidates.c
@@ -0,0 +1,239 @@
+/* candidates.c - candidate targets selection and processing for
+ * back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The meta-directory has one suffix, called <suffix>.
+ * It handles a pool of target servers, each with a branch suffix
+ * of the form <branch X>,<suffix>, where <branch X> may be empty.
+ *
+ * When the meta-directory receives a request with a request DN that belongs
+ * to a branch, the corresponding target is invoked. When the request DN
+ * does not belong to a specific branch, all the targets that
+ * are compatible with the request DN are selected as candidates, and
+ * the request is spawned to all the candidate targets
+ *
+ * A request is characterized by a request DN. The following cases are
+ * handled:
+ * - the request DN is the suffix: <dn> == <suffix>,
+ * all the targets are candidates (search ...)
+ * - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or
+ * - the request DN is a subtree of a branch suffix:
+ * <dn> == <rdn>,<branch X>,<suffix>,
+ * the target is the only candidate.
+ *
+ * A possible extension will include the handling of multiple suffixes
+ */
+
+static a_metasubtree_t *
+asyncmeta_subtree_match( a_metatarget_t *mt, struct berval *ndn, int scope )
+{
+ a_metasubtree_t *ms = mt->mt_subtree;
+
+ for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) ) {
+ return ms;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( ndn, &ms->ms_dn ) &&
+ ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) )
+ {
+ return ms;
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* NOTE: cannot handle scope */
+ if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) {
+ return ms;
+ }
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * returns 1 if suffix is candidate for dn, otherwise 0
+ *
+ * Note: this function should never be called if dn is the <suffix>.
+ */
+int
+asyncmeta_is_candidate(
+ a_metatarget_t *mt,
+ struct berval *ndn,
+ int scope )
+{
+ struct berval rdn;
+ int d = ndn->bv_len - mt->mt_nsuffix.bv_len;
+
+ if ( d >= 0 ) {
+ if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ /*
+ * | match | exclude |
+ * +---------+---------+-------------------+
+ * | T | T | not candidate |
+ * | F | T | continue checking |
+ * +---------+---------+-------------------+
+ * | T | F | candidate |
+ * | F | F | not candidate |
+ * +---------+---------+-------------------+
+ */
+
+ if ( mt->mt_subtree ) {
+ int match = ( asyncmeta_subtree_match( mt, ndn, scope ) != NULL );
+
+ if ( !mt->mt_subtree_exclude ) {
+ return match ? META_CANDIDATE : META_NOT_CANDIDATE;
+ }
+
+ if ( match /* && mt->mt_subtree_exclude */ ) {
+ return META_NOT_CANDIDATE;
+ }
+ }
+
+ switch ( mt->mt_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ default:
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ if ( d > 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_ONELEVEL:
+ if ( d > 0 ) {
+ rdn.bv_val = ndn->bv_val;
+ rdn.bv_len = (ber_len_t)d - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ }
+ break;
+
+ /* nearly useless; not allowed by config */
+ case LDAP_SCOPE_BASE:
+ if ( d == 0 ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+
+ } else /* if ( d < 0 ) */ {
+ if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) {
+ return META_NOT_CANDIDATE;
+ }
+
+ switch ( scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ /*
+ * suffix longer than dn, but common part matches
+ */
+ return META_CANDIDATE;
+
+ case LDAP_SCOPE_ONELEVEL:
+ rdn.bv_val = mt->mt_nsuffix.bv_val;
+ rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn ) ) {
+ return META_CANDIDATE;
+ }
+ break;
+ }
+ }
+
+ return META_NOT_CANDIDATE;
+}
+
+/*
+ * meta_back_select_unique_candidate
+ *
+ * returns the index of the candidate in case it is unique, otherwise
+ * META_TARGET_NONE if none matches, or
+ * META_TARGET_MULTIPLE if more than one matches
+ * Note: ndn MUST be normalized.
+ */
+int
+asyncmeta_select_unique_candidate(
+ a_metainfo_t *mi,
+ struct berval *ndn )
+{
+ int i, candidate = META_TARGET_NONE;
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( asyncmeta_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) {
+ if ( candidate == META_TARGET_NONE ) {
+ candidate = i;
+
+ }
+ }
+ }
+
+ return candidate;
+}
+
+/*
+ * asyncmeta_clear_unused_candidates
+ *
+ * clears all candidates except candidate
+ */
+int
+asyncmeta_clear_unused_candidates(
+ Operation *op,
+ int candidate,
+ a_metaconn_t *mc,
+ SlapReply *candidates)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int i;
+
+ for ( i = 0; i < mi->mi_ntargets; ++i ) {
+ if ( i == candidate ) {
+ continue;
+ }
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/compare.c b/servers/slapd/back-asyncmeta/compare.c
new file mode 100644
index 0000000..8f56719
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/compare.c
@@ -0,0 +1,312 @@
+/* compare.c - compare exop handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_compare_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval c_attr = op->orc_ava->aa_desc->ad_cname;
+ struct berval mdn = BER_BVNULL;
+ struct berval mapped_value = op->orc_ava->aa_value;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value );
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root,&ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_compare_req( msc->msc_ld, mdn.bv_val, c_attr.bv_val, &mapped_value,
+ ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_compare_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_COMPARE,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send compare request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) {
+ op->o_tmpfree( mapped_value.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_compare_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_compare( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_compare: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug( asyncmeta_debug, "==> asyncmeta_back_compare[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_COMPARE ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_compare_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/config.c b/servers/slapd/back-asyncmeta/config.c
new file mode 100644
index 0000000..e4cc5ea
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/config.c
@@ -0,0 +1,2536 @@
+/* config.c - configuration parsing for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldif.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+#ifdef LDAP_DEVEL
+#define SLAP_AUTH_DN 1
+#endif
+
+static ConfigDriver asyncmeta_back_cf_gen;
+static ConfigLDAPadd asyncmeta_ldadd;
+static ConfigCfAdd asyncmeta_cfadd;
+
+/* Three sets of enums:
+ * 1) attrs that are only valid in the base config
+ * 2) attrs that are valid in base or target
+ * 3) attrs that are only valid in a target
+ */
+
+/* Base attrs */
+enum {
+ LDAP_BACK_CFG_DNCACHE_TTL = 1,
+ LDAP_BACK_CFG_IDLE_TIMEOUT,
+ LDAP_BACK_CFG_ONERR,
+ LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ LDAP_BACK_CFG_CONNPOOLMAX,
+ LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+ LDAP_BACK_CFG_MAX_PENDING_OPS,
+ LDAP_BACK_CFG_MAX_TARGET_CONNS,
+ LDAP_BACK_CFG_LAST_BASE,
+};
+
+/* Base or target */
+enum {
+ LDAP_BACK_CFG_BIND_TIMEOUT = LDAP_BACK_CFG_LAST_BASE,
+ LDAP_BACK_CFG_CANCEL,
+ LDAP_BACK_CFG_CHASE,
+ LDAP_BACK_CFG_CLIENT_PR,
+ LDAP_BACK_CFG_DEFAULT_T,
+ LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ LDAP_BACK_CFG_NOREFS,
+ LDAP_BACK_CFG_NOUNDEFFILTER,
+ LDAP_BACK_CFG_NRETRIES,
+ LDAP_BACK_CFG_QUARANTINE,
+ LDAP_BACK_CFG_REBIND,
+ LDAP_BACK_CFG_TIMEOUT,
+ LDAP_BACK_CFG_VERSION,
+ LDAP_BACK_CFG_ST_REQUEST,
+ LDAP_BACK_CFG_T_F,
+ LDAP_BACK_CFG_TLS,
+ LDAP_BACK_CFG_LAST_BOTH
+};
+
+/* Target attrs */
+enum {
+ LDAP_BACK_CFG_URI = LDAP_BACK_CFG_LAST_BOTH,
+ LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ LDAP_BACK_CFG_IDASSERT_BIND,
+ LDAP_BACK_CFG_SUFFIXM,
+ LDAP_BACK_CFG_SUBTREE_EX,
+ LDAP_BACK_CFG_SUBTREE_IN,
+ LDAP_BACK_CFG_KEEPALIVE,
+ LDAP_BACK_CFG_FILTER,
+ LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ LDAP_BACK_CFG_LAST
+};
+
+static ConfigTable a_metacfg[] = {
+ { "uri", "uri", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_URI,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:0.14 "
+ "NAME 'olcDbURI' "
+ "DESC 'URI (list) for remote DSA' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "tls", "what", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TLS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.1 "
+ "NAME 'olcDbStartTLS' "
+ "DESC 'StartTLS' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-bind", "args", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.7 "
+ "NAME 'olcDbIDAssertBind' "
+ "DESC 'Remote Identity Assertion administrative identity auth bind configuration' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idassert-authzFrom", "authzRule", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_AUTHZFROM,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.9 "
+ "NAME 'olcDbIDAssertAuthzFrom' "
+ "DESC 'Remote Identity Assertion authz rules' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "X-ORDERED 'VALUES' )",
+ NULL, NULL },
+ { "rebind-as-user", "true|FALSE", 1, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_REBIND,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.10 "
+ "NAME 'olcDbRebindAsUser' "
+ "DESC 'Rebind as user' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "chase-referrals", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_CHASE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.11 "
+ "NAME 'olcDbChaseReferrals' "
+ "DESC 'Chase referrals' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "t-f-support", "true|FALSE|discover", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_T_F,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.12 "
+ "NAME 'olcDbTFSupport' "
+ "DESC 'Absolute filters support' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "timeout", "timeout(list)", 2, 0, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.14 "
+ "NAME 'olcDbTimeout' "
+ "DESC 'Per-operation timeouts' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "idle-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_IDLE_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.15 "
+ "NAME 'olcDbIdleTimeout' "
+ "DESC 'connection idle timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "network-timeout", "timeout", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.17 "
+ "NAME 'olcDbNetworkTimeout' "
+ "DESC 'connection network timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "protocol-version", "version", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_VERSION,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.18 "
+ "NAME 'olcDbProtocolVersion' "
+ "DESC 'protocol version' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "cancel", "ABANDON|ignore|exop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CANCEL,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.20 "
+ "NAME 'olcDbCancel' "
+ "DESC 'abandon/ignore/exop operations when appropriate' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "quarantine", "retrylist", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_QUARANTINE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.21 "
+ "NAME 'olcDbQuarantine' "
+ "DESC 'Quarantine database if connection fails and retry according to rule' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "conn-pool-max", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.23 "
+ "NAME 'olcDbConnectionPoolMax' "
+ "DESC 'Max size of privileged connections pool' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ { "session-tracking-request", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_ST_REQUEST,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.24 "
+ "NAME 'olcDbSessionTrackingRequest' "
+ "DESC 'Add session tracking control to proxied requests' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+ { "norefs", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOREFS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.25 "
+ "NAME 'olcDbNoRefs' "
+ "DESC 'Do not return search reference responses' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "noundeffilter", "true|FALSE", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_NOUNDEFFILTER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.26 "
+ "NAME 'olcDbNoUndefFilter' "
+ "DESC 'Do not propagate undefined search filters' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "suffixmassage", "local> <remote", 2, 3, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.117 "
+ "NAME 'olcDbSuffixMassage' "
+ "DESC 'DN suffix massage' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "subtree-exclude", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.103 "
+ "NAME 'olcDbSubtreeExclude' "
+ "DESC 'DN of subtree to exclude from target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "subtree-include", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.104 "
+ "NAME 'olcDbSubtreeInclude' "
+ "DESC 'DN of subtree to include in target' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+ { "default-target", "[none|<target ID>]", 1, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.105 "
+ "NAME 'olcDbDefaultTarget' "
+ "DESC 'Specify the default target' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "dncache-ttl", "ttl", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_DNCACHE_TTL,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.106 "
+ "NAME 'olcDbDnCacheTtl' "
+ "DESC 'dncache ttl' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "bind-timeout", "microseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_ULONG|LDAP_BACK_CFG_BIND_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.107 "
+ "NAME 'olcDbBindTimeout' "
+ "DESC 'bind timeout' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "onerr", "CONTINUE|report|stop", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_ONERR,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.108 "
+ "NAME 'olcDbOnErr' "
+ "DESC 'error handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "pseudoroot-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.109 "
+ "NAME 'olcDbPseudoRootBindDefer' "
+ "DESC 'error handling' "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "root-bind-defer", "TRUE|false", 2, 2, 0,
+ ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER,
+ asyncmeta_back_cf_gen, NULL, NULL, NULL },
+ { "nretries", "NEVER|forever|<number>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_NRETRIES,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.110 "
+ "NAME 'olcDbNretries' "
+ "DESC 'retry handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+ { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.111 "
+ "NAME 'olcDbClientPr' "
+ "DESC 'PagedResults handling' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "", "", 0, 0, 0, ARG_IGNORED,
+ NULL, "( OLcfgDbAt:3.116 NAME 'olcAsyncMetaSub' "
+ "DESC 'Placeholder to name a Target entry' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE X-ORDERED 'SIBLINGS' )", NULL, NULL },
+
+ { "keepalive", "keepalive", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_KEEPALIVE,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.29 "
+ "NAME 'olcDbKeepalive' "
+ "DESC 'TCP keepalive' "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "tcp-user-timeout", "milliseconds", 2, 2, 0,
+ ARG_MAGIC|ARG_UINT|LDAP_BACK_CFG_TCP_USER_TIMEOUT,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.30 "
+ "NAME 'olcDbTcpUserTimeout' "
+ "DESC 'TCP User Timeout' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "filter", "pattern", 2, 2, 0,
+ ARG_MAGIC|LDAP_BACK_CFG_FILTER,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.112 "
+ "NAME 'olcDbFilter' "
+ "DESC 'Filter regex pattern to include in target' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )",
+ NULL, NULL },
+
+ { "max-pending-ops", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_PENDING_OPS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.113 "
+ "NAME 'olcDbMaxPendingOps' "
+ "DESC 'Maximum number of pending operations' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "max-target-conns", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TARGET_CONNS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.114 "
+ "NAME 'olcDbMaxTargetConns' "
+ "DESC 'Maximum number of open connections per target' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { "max-timeout-ops", "<n>", 2, 2, 0,
+ ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TIMEOUT_OPS,
+ asyncmeta_back_cf_gen, "( OLcfgDbAt:3.115 "
+ "NAME 'olcDbMaxTimeoutOps' "
+ "DESC 'Maximum number of consecutive timeout operations after which the connection is reset' "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+#define ST_ATTR "$ olcDbSessionTrackingRequest "
+#else
+#define ST_ATTR ""
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+#define COMMON_ATTRS \
+ "$ olcDbBindTimeout " \
+ "$ olcDbCancel " \
+ "$ olcDbChaseReferrals " \
+ "$ olcDbClientPr " \
+ "$ olcDbDefaultTarget " \
+ "$ olcDbNetworkTimeout " \
+ "$ olcDbNoRefs " \
+ "$ olcDbNoUndefFilter " \
+ "$ olcDbNretries " \
+ "$ olcDbProtocolVersion " \
+ "$ olcDbQuarantine " \
+ "$ olcDbRebindAsUser " \
+ ST_ATTR \
+ "$ olcDbStartTLS " \
+ "$ olcDbTFSupport "
+
+static ConfigOCs a_metaocs[] = {
+ { "( OLcfgDbOc:3.4 "
+ "NAME 'olcAsyncMetaConfig' "
+ "DESC 'Asyncmeta backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MAY ( olcDbDnCacheTtl "
+ "$ olcDbIdleTimeout "
+ "$ olcDbOnErr "
+ "$ olcDbPseudoRootBindDefer "
+ "$ olcDbConnectionPoolMax "
+ "$ olcDbMaxTimeoutOps"
+ "$ olcDbMaxPendingOps "
+ "$ olcDbMaxTargetConns"
+ /* defaults, may be overridden per-target */
+ COMMON_ATTRS
+ ") )",
+ Cft_Database, a_metacfg, NULL, asyncmeta_cfadd },
+ { "( OLcfgDbOc:3.5 "
+ "NAME 'olcAsyncMetaTargetConfig' "
+ "DESC 'Asyncmeta target configuration' "
+ "SUP olcConfig STRUCTURAL "
+ "MUST ( olcAsyncMetaSub $ olcDbURI ) "
+ "MAY ( olcDbIDAssertAuthzFrom "
+ "$ olcDbIDAssertBind "
+ "$ olcDbSuffixMassage "
+ "$ olcDbSubtreeExclude "
+ "$ olcDbSubtreeInclude "
+ "$ olcDbTimeout "
+ "$ olcDbKeepalive "
+ "$ olcDbFilter "
+ "$ olcDbTcpUserTimeout "
+
+ /* defaults may be inherited */
+ COMMON_ATTRS
+ ") )",
+ Cft_Misc, a_metacfg, asyncmeta_ldadd },
+ { NULL, 0, NULL }
+};
+
+static int
+asyncmeta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c )
+{
+ a_metainfo_t *mi;
+
+ if ( p->ce_type != Cft_Database || !p->ce_be ||
+ p->ce_be->be_cf_ocs != a_metaocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ c->be = p->ce_be;
+ mi = ( a_metainfo_t * )c->be->be_private;
+
+ if ( asyncmeta_db_has_pending_ops ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify a working database" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ struct berval bv;
+ int i;
+
+ bv.bv_val = c->cr_msg;
+ for ( i=0; i<mi->mi_ntargets; i++ ) {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "olcAsyncMetaSub=" SLAP_X_ORDERED_FMT "uri", i );
+ c->ca_private = mi->mi_targets[i];
+ c->valx = i;
+ config_build_entry( op, rs, p->e_private, c,
+ &bv, &a_metaocs[1], NULL );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+asyncmeta_back_new_target(
+ a_metatarget_t **mtp,
+ a_metainfo_t *mi )
+{
+ a_metatarget_t *mt;
+
+ *mtp = NULL;
+ int i;
+
+ assert ( mi != NULL );
+ mt = ch_calloc( sizeof( a_metatarget_t ), 1 );
+
+ ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex );
+
+ mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY;
+ mt->mt_idassert_authmethod = LDAP_AUTH_NONE;
+ mt->mt_idassert_tls = SB_TLS_DEFAULT;
+ /* by default, use proxyAuthz control on each operation */
+ mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE;
+
+ *mtp = mt;
+
+ for ( i = 0; i < mi->mi_num_conns; i++ ) {
+ a_metaconn_t *mc = &mi->mi_conns[i];
+ mc->mc_conns = ch_realloc( mc->mc_conns, sizeof( a_metasingleconn_t ) * mi->mi_ntargets);
+ memset( &(mc->mc_conns[mi->mi_ntargets-1]), 0, sizeof( a_metasingleconn_t ) );
+ }
+ /* If this is the first target, start the timeout loop */
+ if ( mi->mi_ntargets == 1 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 1,
+ asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", mi->mi_suffix.bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ return 0;
+}
+
+/* suffixmassage config */
+static int
+asyncmeta_suffixm_config(
+ ConfigArgs *c,
+ int argc,
+ char **argv,
+ a_metatarget_t *mt
+)
+{
+ BackendDB *tmp_bd;
+ struct berval dn, nvnc, pvnc, nrnc, prnc;
+ int j;
+
+ /*
+ * syntax:
+ *
+ * suffixmassage <local suffix> <remote suffix>
+ *
+ * the <local suffix> field must be defined as a valid suffix
+ * (or suffixAlias?) for the current database;
+ * the <remote suffix> shouldn't have already been
+ * defined as a valid suffix or suffixAlias for the
+ * current server
+ */
+
+ ber_str2bv( argv[ 1 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" is invalid",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &nvnc, &c->be->be_nsuffix[ 0 ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "suffix \"%s\" must be within the database naming context",
+ argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ ber_str2bv( argv[ 2 ], 0, 0, &dn );
+ if ( dnPrettyNormal( NULL, &dn, &prnc, &nrnc, NULL ) != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "massaged suffix \"%s\" is invalid",
+ argv[2] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ free( pvnc.bv_val );
+ free( nvnc.bv_val );
+ return 1;
+ }
+
+ tmp_bd = select_backend( &nrnc, 0 );
+ if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: warning: <massaged suffix> \"%s\" resolves to this database, in "
+ "\"suffixMassage <suffix> <massaged suffix>\"\n",
+ c->log, prnc.bv_val );
+ }
+
+ mt->mt_lsuffixm = pvnc;
+ mt->mt_rsuffixm = prnc;
+
+ free( nvnc.bv_val );
+ free( nrnc.bv_val );
+
+ return 0;
+}
+
+int
+asyncmeta_subtree_free( a_metasubtree_t *ms )
+{
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ber_memfree( ms->ms_dn.bv_val );
+ break;
+
+ case META_ST_REGEX:
+ regfree( &ms->ms_regex );
+ ber_memfree( ms->ms_regex_pattern.bv_val );
+ break;
+
+ default:
+ return -1;
+ }
+
+ ch_free( ms );
+ return 0;
+}
+
+int
+asyncmeta_subtree_destroy( a_metasubtree_t *ms )
+{
+ if ( ms->ms_next ) {
+ asyncmeta_subtree_destroy( ms->ms_next );
+ }
+
+ return asyncmeta_subtree_free( ms );
+}
+
+static void
+asyncmeta_filter_free( metafilter_t *mf )
+{
+ regfree( &mf->mf_regex );
+ ber_memfree( mf->mf_regex_pattern.bv_val );
+ ch_free( mf );
+}
+
+void
+asyncmeta_filter_destroy( metafilter_t *mf )
+{
+ if ( mf->mf_next )
+ asyncmeta_filter_destroy( mf->mf_next );
+ asyncmeta_filter_free( mf );
+}
+
+static struct berval st_styles[] = {
+ BER_BVC("subtree"),
+ BER_BVC("children"),
+ BER_BVC("regex")
+};
+
+static int
+asyncmeta_subtree_unparse(
+ ConfigArgs *c,
+ a_metatarget_t *mt )
+{
+ a_metasubtree_t *ms;
+ struct berval bv, *style;
+
+ if ( !mt->mt_subtree )
+ return 1;
+
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude )
+ return 1;
+
+ bv.bv_val = c->cr_msg;
+ for ( ms=mt->mt_subtree; ms; ms=ms->ms_next ) {
+ if (ms->ms_type == META_ST_SUBTREE)
+ style = &st_styles[0];
+ else if ( ms->ms_type == META_ST_SUBORDINATE )
+ style = &st_styles[1];
+ else if ( ms->ms_type == META_ST_REGEX )
+ style = &st_styles[2];
+ else {
+ assert(0);
+ continue;
+ }
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "dn.%s:%s", style->bv_val, ms->ms_dn.bv_val );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ return 0;
+}
+
+static int
+asyncmeta_subtree_config(
+ a_metatarget_t *mt,
+ ConfigArgs *c )
+{
+ meta_st_t type = META_ST_SUBTREE;
+ char *pattern;
+ struct berval ndn = BER_BVNULL;
+ a_metasubtree_t *ms = NULL;
+
+ if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) {
+ if ( mt->mt_subtree && !mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" );
+ return 1;
+ }
+
+ mt->mt_subtree_exclude = 1;
+
+ } else {
+ if ( mt->mt_subtree && mt->mt_subtree_exclude ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" );
+ return 1;
+ }
+ }
+
+ pattern = c->argv[1];
+ if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) {
+ char *style;
+
+ pattern = &pattern[STRLENOF( "dn")];
+
+ if ( pattern[0] == '.' ) {
+ style = &pattern[1];
+
+ if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "subtree" )];
+
+ } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) {
+ type = META_ST_SUBORDINATE;
+ pattern = &style[STRLENOF( "children" )];
+
+ } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) {
+ type = META_ST_SUBTREE;
+ pattern = &style[STRLENOF( "sub" )];
+
+ } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) {
+ type = META_ST_REGEX;
+ pattern = &style[STRLENOF( "regex" )];
+
+ } else {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" );
+ return 1;
+ }
+ }
+
+ if ( pattern[0] != ':' ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" );
+ return 1;
+ }
+ pattern++;
+ }
+
+ switch ( type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE: {
+ struct berval dn;
+
+ ber_str2bv( pattern, 0, 0, &dn );
+ if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL )
+ != LDAP_SUCCESS )
+ {
+ snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern );
+ return 1;
+ }
+
+ if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) {
+ snprintf( c->cr_msg, sizeof(c->cr_msg),
+ "DN=\"%s\" is not a subtree of target \"%s\"",
+ pattern, mt->mt_nsuffix.bv_val );
+ ber_memfree( ndn.bv_val );
+ return( 1 );
+ }
+ } break;
+
+ default:
+ /* silence warnings */
+ break;
+ }
+
+ ms = ch_calloc( sizeof( a_metasubtree_t ), 1 );
+ ms->ms_type = type;
+
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ ms->ms_dn = ndn;
+ break;
+
+ case META_ST_REGEX: {
+ int rc;
+
+ rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE );
+ if ( rc != 0 ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+
+ regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) );
+
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ pattern, regerr );
+ ch_free( ms );
+ return 1;
+ }
+ ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern );
+ } break;
+ }
+
+ if ( mt->mt_subtree == NULL ) {
+ mt->mt_subtree = ms;
+
+ } else {
+ a_metasubtree_t **msp;
+
+ for ( msp = &mt->mt_subtree; *msp; ) {
+ switch ( ms->ms_type ) {
+ case META_ST_SUBTREE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_SUBORDINATE:
+ if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) {
+ a_metasubtree_t *tmp = *msp;
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n",
+ c->log, pattern, (*msp)->ms_dn.bv_val );
+ *msp = (*msp)->ms_next;
+ tmp->ms_next = NULL;
+ asyncmeta_subtree_destroy( tmp );
+ continue;
+
+ } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n",
+ c->log, (*msp)->ms_dn.bv_val, pattern );
+ asyncmeta_subtree_destroy( ms );
+ ms = NULL;
+ return( 0 );
+ }
+ break;
+
+ case META_ST_REGEX:
+ if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n",
+ c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val );
+ }
+ break;
+ }
+ break;
+
+ case META_ST_REGEX:
+ switch ( (*msp)->ms_type ) {
+ case META_ST_SUBTREE:
+ case META_ST_SUBORDINATE:
+ if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n",
+ c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val );
+ }
+ break;
+
+ case META_ST_REGEX:
+ /* no check possible */
+ break;
+ }
+ break;
+ }
+
+ msp = &(*msp)->ms_next;
+ }
+
+ *msp = ms;
+ }
+
+ return 0;
+}
+
+static slap_verbmasks idassert_mode[] = {
+ { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF },
+ { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS },
+ { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT },
+ { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks tls_mode[] = {
+ { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK },
+ { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS },
+ { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK },
+ { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS },
+ { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS },
+ { BER_BVC( "none" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks t_f_mode[] = {
+ { BER_BVC( "yes" ), LDAP_BACK_F_T_F },
+ { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER },
+ { BER_BVC( "no" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks cancel_mode[] = {
+ { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE },
+ { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP },
+ { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER },
+ { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON },
+ { BER_BVNULL, 0 }
+};
+
+static slap_verbmasks onerr_mode[] = {
+ { BER_BVC( "stop" ), META_BACK_F_ONERR_STOP },
+ { BER_BVC( "report" ), META_BACK_F_ONERR_REPORT },
+ { BER_BVC( "continue" ), LDAP_BACK_F_NONE },
+ { BER_BVNULL, 0 }
+};
+
+/* see enum in slap.h */
+static slap_cf_aux_table timeout_table[] = {
+ { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL },
+ /* unbind makes no sense */
+ { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL },
+ { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL },
+ /* abandon makes little sense */
+#if 0 /* not implemented yet */
+ { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL },
+#endif
+ { BER_BVNULL, 0, 0, 0, NULL }
+};
+
+static int
+asyncmeta_cf_cleanup( ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ a_metatarget_t *mt = c->ca_private;
+
+ return asyncmeta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg ));
+}
+
+static int
+asyncmeta_back_cf_gen( ConfigArgs *c )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private;
+ a_metatarget_t *mt = NULL;
+ a_metacommon_t *mc = NULL;
+
+ int i, rc = 0;
+
+ assert( mi != NULL );
+
+ if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) {
+ if ( !mi )
+ return 1;
+
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ mc = &mt->mt_mc;
+ }
+ }
+
+ if ( c->op != SLAP_CONFIG_EMIT && asyncmeta_db_has_pending_ops ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify a working database" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ struct berval bv = BER_BVNULL;
+
+ switch( c->type ) {
+ /* Base attrs */
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) {
+ return 1;
+ } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl );
+ ber_str2bv( buf, 0, 0, &bv );
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ if ( mi->mi_idle_timeout == 0 ) {
+ return 1;
+ } else {
+ char buf[ SLAP_TEXT_BUFLEN ];
+
+ lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout );
+ ber_str2bv( buf, 0, 0, &bv );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv );
+ if ( BER_BVISNULL( &bv )) {
+ rc = 1;
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi );
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ c->value_int = mi->mi_conn_priv_max;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ if ( mc->mc_bind_timeout.tv_sec == 0 &&
+ mc->mc_bind_timeout.tv_usec == 0 ) {
+ return 1;
+ } else {
+ c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL +
+ mc->mc_bind_timeout.tv_usec;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CANCEL: {
+ slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2;
+
+ if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ mask &= ~LDAP_BACK_F_CANCEL_EXOP;
+ }
+ enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ case LDAP_BACK_CFG_CHASE:
+ c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc);
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) {
+ return 1;
+ } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) {
+ BER_BVSTR( &bv, "accept-unsolicited" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE )
+ return 1;
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ if ( mc->mc_network_timeout == 0 ) {
+ return 1;
+ }
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%ld",
+ mc->mc_network_timeout );
+ bv.bv_val = c->cr_msg;
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ c->value_int = META_BACK_CMN_NOREFS(mc);
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc);
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ if ( mc->mc_nretries == META_RETRY_FOREVER ) {
+ BER_BVSTR( &bv, "forever" );
+ } else if ( mc->mc_nretries == META_RETRY_NEVER ) {
+ BER_BVSTR( &bv, "never" );
+ } else {
+ bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d",
+ mc->mc_nretries );
+ bv.bv_val = c->cr_msg;
+ }
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( !META_BACK_CMN_QUARANTINE( mc )) {
+ rc = 1;
+ break;
+ }
+ rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv );
+ if ( rc == 0 ) {
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ c->value_int = META_BACK_CMN_SAVECRED(mc);
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ if ( mc->mc_timeout[ i ] != META_BACK_CFG_DEFAULT_OPS_TIMEOUT ) {
+ break;
+ }
+ }
+
+ if ( i == SLAP_OP_LAST ) {
+ return 1;
+ }
+
+ BER_BVZERO( &bv );
+ slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table );
+
+ if ( BER_BVISNULL( &bv ) ) {
+ return 1;
+ }
+
+ for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bv.bv_len -= i;
+ AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ],
+ bv.bv_len + 1 );
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( mc->mc_version == 0 )
+ return 1;
+ c->value_int = mc->mc_version;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ c->value_int = META_BACK_CMN_ST_REQUEST( mc );
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv );
+ if ( BER_BVISNULL( &bv ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS: {
+ struct berval bc = BER_BVNULL, bv2;
+
+ if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) {
+ rc = 1;
+ break;
+ }
+ enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv );
+ assert( !BER_BVISNULL( &bv ) );
+
+ if ( mt ) {
+ bindconf_tls_unparse( &mt->mt_tls, &bc );
+ }
+
+ if ( !BER_BVISEMPTY( &bc )) {
+ bv2.bv_len = bv.bv_len + bc.bv_len + 1;
+ bv2.bv_val = ch_malloc( bv2.bv_len + 1 );
+ strcpy( bv2.bv_val, bv.bv_val );
+ bv2.bv_val[bv.bv_len] = ' ';
+ strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val );
+ ber_memfree( bc.bv_val );
+ ber_bvarray_add( &c->rvalue_vals, &bv2 );
+ } else {
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ } break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI: {
+ char *p2, *p1 = strchr( mt->mt_uri, ' ' );
+ bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ p2 = bv.bv_val;
+ *p2++ = '"';
+ if ( p1 ) {
+ p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri );
+ } else {
+ p2 = lutil_strcopy( p2, mt->mt_uri );
+ }
+ *p2++ = '/';
+ p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val );
+ *p2++ = '"';
+ if ( p1 ) {
+ strcpy( p2, p1 );
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ } break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+ int i;
+ struct berval bv = BER_BVNULL;
+ char buf[SLAP_TEXT_BUFLEN];
+
+ bvp = &mt->mt_idassert_authz;
+ if ( *bvp == NULL ) {
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ {
+ BER_BVSTR( &bv, "*" );
+ value_add_one( &c->rvalue_vals, &bv );
+
+ } else {
+ rc = 1;
+ }
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) {
+ char *ptr;
+ int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i );
+ bv.bv_len = ((*bvp)[ i ]).bv_len + len;
+ bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ ptr = lutil_strcopy( ptr, buf );
+ ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len );
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ if ( bv.bv_val ) {
+ ber_memfree( bv.bv_val );
+ }
+ break;
+ }
+
+ case LDAP_BACK_CFG_IDASSERT_BIND: {
+ int i;
+ struct berval bc = BER_BVNULL;
+ char *ptr;
+
+ if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) {
+ return 1;
+ } else {
+ ber_len_t len;
+
+ switch ( mt->mt_idassert_mode ) {
+ case LDAP_BACK_IDASSERT_OTHERID:
+ case LDAP_BACK_IDASSERT_OTHERDN:
+ break;
+
+ default: {
+ struct berval mode = BER_BVNULL;
+
+ enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode );
+ if ( BER_BVISNULL( &mode ) ) {
+ /* there's something wrong... */
+ assert( 0 );
+ rc = 1;
+
+ } else {
+ bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ ptr = lutil_strcopy( bv.bv_val, "mode=" );
+ ptr = lutil_strcopy( ptr, mode.bv_val );
+ }
+ break;
+ }
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) {
+ len = bv.bv_len + STRLENOF( "authz=native" );
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ (void)lutil_strcopy( ptr, "authz=native" );
+ }
+
+ len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" );
+ /* flags */
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ len += STRLENOF( " " );
+ }
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ ptr = &bv.bv_val[ bv.bv_len ];
+
+ if ( !BER_BVISEMPTY( &bv ) ) {
+ ptr = lutil_strcopy( ptr, " " );
+ }
+
+ ptr = lutil_strcopy( ptr, "flags=" );
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) {
+ ptr = lutil_strcopy( ptr, "prescriptive" );
+ } else {
+ ptr = lutil_strcopy( ptr, "non-prescriptive" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) {
+ ptr = lutil_strcopy( ptr, ",override" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" );
+
+ } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) {
+ ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" );
+ }
+
+ if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-critical" );
+
+ } else {
+ ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" );
+ }
+
+#ifdef SLAP_AUTH_DN
+ switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) {
+ case LDAP_BACK_AUTH_DN_AUTHZID:
+ ptr = lutil_strcopy( ptr, ",dn-authzid" );
+ break;
+
+ case LDAP_BACK_AUTH_DN_WHOAMI:
+ ptr = lutil_strcopy( ptr, ",dn-whoami" );
+ break;
+
+ default:
+#if 0 /* implicit */
+ ptr = lutil_strcopy( ptr, ",dn-none" );
+#endif
+ break;
+ }
+#endif
+
+ bv.bv_len = ( ptr - bv.bv_val );
+ /* end-of-flags */
+ }
+
+ bindconf_unparse( &mt->mt_idassert.si_bc, &bc );
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ ber_len_t len = bv.bv_len + bc.bv_len;
+
+ bv.bv_val = ch_realloc( bv.bv_val, len + 1 );
+
+ assert( bc.bv_val[ 0 ] == ' ' );
+
+ ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val );
+ free( bc.bv_val );
+ bv.bv_len = ptr - bv.bv_val;
+
+ } else {
+ for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ )
+ /* count spaces */ ;
+
+ if ( i ) {
+ bc.bv_len -= i;
+ AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 );
+ }
+
+ bv = bc;
+ }
+
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+
+ break;
+ }
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ if ( mt->mt_lsuffixm.bv_val ) {
+ struct berval bv;
+ char *ptr;
+ bv.bv_len = mt->mt_lsuffixm.bv_len + 2 + 1 + mt->mt_rsuffixm.bv_len + 2;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = bv.bv_val;
+ *ptr++ = '"';
+ ptr = lutil_strcopy(ptr, mt->mt_lsuffixm.bv_val);
+ ptr = lutil_strcopy(ptr, "\" \"");
+ ptr = lutil_strcopy(ptr, mt->mt_rsuffixm.bv_val);
+ *ptr++ = '"';
+ *ptr = '\0';
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ rc = 0;
+ } else
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ rc = asyncmeta_subtree_unparse( c, mt );
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( mt->mt_filter == NULL ) {
+ rc = 1;
+ } else {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next )
+ value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern );
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ c->value_int = mi->mi_max_pending_ops;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ c->value_int = mi->mi_max_target_conns;
+ break;
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ c->value_int = mi->mi_max_timeout_ops;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ char buf[AC_LINE_MAX];
+ bv.bv_len = AC_LINE_MAX;
+ bv.bv_val = &buf[0];
+ slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1);
+ value_add_one( &c->rvalue_vals, &bv );
+ break;
+ }
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ c->value_uint = mt->mt_tls.sb_tcp_user_timeout;
+ break;
+
+ default:
+ rc = 1;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) {
+
+ switch( c->type ) {
+ /* Base attrs */
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+ break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT:
+ mi->mi_idle_timeout = 0;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN;
+ break;
+
+ /* common attrs */
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_bind_timeout.tv_sec = 0;
+ mc->mc_bind_timeout.tv_usec = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_network_timeout = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES:
+ mc->mc_nretries = META_RETRY_DEFAULT;
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ if ( META_BACK_CMN_QUARANTINE( mc )) {
+ mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine );
+ mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE;
+ if ( mc == &mt->mt_mc ) {
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ mt->mt_isquarantined = 0;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ mc->mc_timeout[ i ] = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mc->mc_version = 0;
+ }
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_T_F:
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ if ( mt )
+ bindconf_free( &mt->mt_tls );
+ break;
+
+ /* target attrs */
+ case LDAP_BACK_CFG_URI:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( mt->mt_uri ) {
+ ch_free( mt->mt_uri );
+ mt->mt_uri = NULL;
+ }
+ }
+ /* FIXME: should have a way to close all cached
+ * connections associated with this target.
+ */
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: {
+ BerVarray *bvp;
+
+ bvp = &mt->mt_idassert_authz;
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( c->valx < 0 ) {
+ if ( *bvp != NULL ) {
+ ber_bvarray_free( *bvp );
+ *bvp = NULL;
+ }
+
+ } else {
+ if ( *bvp == NULL ) {
+ rc = 1;
+ break;
+ }
+
+ for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ )
+ ;
+
+ if ( i >= c->valx ) {
+ rc = 1;
+ break;
+ }
+ ber_memfree( ((*bvp)[ c->valx ]).bv_val );
+ for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) {
+ (*bvp)[ i ] = (*bvp)[ i + 1 ];
+ }
+ BER_BVZERO( &((*bvp)[ i ]) );
+ }
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ bindconf_free( &mt->mt_idassert.si_bc );
+ memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) );
+ }
+ break;
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ if ( mt->mt_lsuffixm.bv_val ) {
+ ch_free( mt->mt_lsuffixm.bv_val );
+ ch_free( mt->mt_rsuffixm.bv_val );
+ BER_BVZERO( &mt->mt_lsuffixm );
+ BER_BVZERO( &mt->mt_rsuffixm );
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* can only be one of exclude or include */
+ if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) {
+ rc = 1;
+ break;
+ }
+ if ( c->valx < 0 ) {
+ asyncmeta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ } else {
+ a_metasubtree_t *ms, **mprev;
+ for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = ms->ms_next;
+ asyncmeta_subtree_free( ms );
+ break;
+ }
+ i++;
+ mprev = &ms->ms_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER:
+ if ( c->valx < 0 ) {
+ asyncmeta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ } else {
+ metafilter_t *mf, **mprev;
+ for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) {
+ if ( i == c->valx ) {
+ *mprev = mf->mf_next;
+ asyncmeta_filter_free( mf );
+ break;
+ }
+ i++;
+ mprev = &mf->mf_next;
+ }
+ if ( i != c->valx )
+ rc = 1;
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ mi->mi_max_pending_ops = 0;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-target-conns cannot be modified at runtime" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ break;
+
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ mi->mi_max_timeout_ops = 0;
+ break;
+
+ case LDAP_BACK_CFG_KEEPALIVE:
+ if ( asyncmeta_db_has_mscs ( mi ) > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "cannot modify this attribute if there are established target connections" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ rc = 1;
+ } else {
+ mt->mt_tls.sb_keepalive.sk_idle = 0;
+ mt->mt_tls.sb_keepalive.sk_probes = 0;
+ mt->mt_tls.sb_keepalive.sk_interval = 0;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = 0;
+ break;
+
+ default:
+ rc = 1;
+ break;
+ }
+
+ return rc;
+ }
+
+ if ( c->op == SLAP_CONFIG_ADD ) {
+ if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) {
+ /* exclude CFG_URI from this check */
+ if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) {
+ if ( !mi->mi_ntargets ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ if ( mi->mi_ntargets ) {
+ mt = mi->mi_targets[ mi->mi_ntargets-1 ];
+ mc = &mt->mt_mc;
+ } else {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ }
+ }
+ } else {
+ if ( c->table == Cft_Database ) {
+ mt = NULL;
+ mc = &mi->mi_mc;
+ } else {
+ mt = c->ca_private;
+ if ( mt )
+ mc = &mt->mt_mc;
+ else
+ mc = NULL;
+ }
+ }
+
+ switch( c->type ) {
+ case LDAP_BACK_CFG_URI: {
+ LDAPURLDesc *ludp;
+ struct berval dn;
+ int j;
+
+ char **uris = NULL;
+
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "the suffix must be defined before any target" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ i = mi->mi_ntargets++;
+
+ mi->mi_targets = ( a_metatarget_t ** )ch_realloc( mi->mi_targets,
+ sizeof( a_metatarget_t * ) * mi->mi_ntargets );
+ if ( mi->mi_targets == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "out of memory while storing server name"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( asyncmeta_back_new_target( &mi->mi_targets[ i ], mi ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to init server"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mt = mi->mi_targets[ i ];
+
+ mt->mt_rebind_f = mi->mi_rebind_f;
+ mt->mt_urllist_f = mi->mi_urllist_f;
+ mt->mt_urllist_p = mt;
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ mt->mt_mc = mi->mi_mc;
+
+ for ( j = 1; j < c->argc; j++ ) {
+ char **tmpuris = ldap_str2charray( c->argv[ j ], "\t" );
+
+ if ( tmpuris == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URIs #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( j == 1 ) {
+ uris = tmpuris;
+
+ } else {
+ ldap_charray_merge( &uris, tmpuris );
+ ldap_charray_free( tmpuris );
+ }
+ }
+
+ for ( j = 0; uris[ j ] != NULL; j++ ) {
+ char *tmpuri = NULL;
+
+ /*
+ * uri MUST be legal!
+ */
+ if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t",
+ LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS
+ || ludp->lud_next != NULL )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse URI #%d"
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ j-1, c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ if ( j == 0 ) {
+
+ /*
+ * uri MUST have the <dn> part!
+ */
+ if ( ludp->lud_dn == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "missing <naming context> "
+ " in \"%s <protocol>://<server>[:port]/<naming context>\"",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return 1;
+ }
+
+ /*
+ * copies and stores uri and suffix
+ */
+ ber_str2bv( ludp->lud_dn, 0, 0, &dn );
+ rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix,
+ &mt->mt_nsuffix, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "target DN is invalid \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ ludp->lud_dn[ 0 ] = '\0';
+
+ switch ( ludp->lud_scope ) {
+ case LDAP_SCOPE_DEFAULT:
+ mt->mt_scope = LDAP_SCOPE_SUBTREE;
+ break;
+
+ case LDAP_SCOPE_SUBTREE:
+ case LDAP_SCOPE_SUBORDINATE:
+ mt->mt_scope = ludp->lud_scope;
+ break;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid scope for target \"%s\"",
+ c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+
+ } else {
+ /* check all, to apply the scope check on the first one */
+ if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "multiple URIs must have no DN part" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_free_urllist( ludp );
+ ldap_charray_free( uris );
+ return( 1 );
+
+ }
+ }
+
+ tmpuri = ldap_url_list2urls( ludp );
+ ldap_free_urllist( ludp );
+ if ( tmpuri == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ ldap_charray_free( uris );
+ return( 1 );
+ }
+ ldap_memfree( uris[ j ] );
+ uris[ j ] = tmpuri;
+ }
+
+ mt->mt_uri = ldap_charray2str( uris, " " );
+ ldap_charray_free( uris );
+ if ( mt->mt_uri == NULL) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ /*
+ * uri MUST be a branch of suffix!
+ */
+ for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) {
+ if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) {
+ break;
+ }
+ }
+
+ if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "<naming context> of URI must be within the naming context of this database." );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ c->ca_private = mt;
+ config_push_cleanup( c, asyncmeta_cf_cleanup );
+ } break;
+ case LDAP_BACK_CFG_SUBTREE_EX:
+ case LDAP_BACK_CFG_SUBTREE_IN:
+ /* subtree-exclude */
+ if ( asyncmeta_subtree_config( mt, c )) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ break;
+
+ case LDAP_BACK_CFG_FILTER: {
+ metafilter_t *mf, **m2;
+ mf = ch_malloc( sizeof( metafilter_t ));
+ rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED );
+ if ( rc ) {
+ char regerr[ SLAP_TEXT_BUFLEN ];
+ regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) );
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "regular expression \"%s\" bad because of %s",
+ c->argv[1], regerr );
+ ch_free( mf );
+ return 1;
+ }
+ ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern );
+ for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next )
+ ;
+ *m2 = mf;
+ } break;
+ case LDAP_BACK_CFG_MAX_PENDING_OPS:
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-pending-ops invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_pending_ops = c->value_int;
+ break;
+ case LDAP_BACK_CFG_MAX_TARGET_CONNS:
+ {
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-target-conns invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_target_conns = c->value_int;
+ }
+ break;
+ case LDAP_BACK_CFG_MAX_TIMEOUT_OPS:
+ if (c->value_int < 0) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "max-timeout-ops invalid value %d",
+ c->value_int);
+ return 1;
+ }
+ mi->mi_max_timeout_ops = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_DEFAULT_T:
+ /* default target directive */
+ i = mi->mi_ntargets - 1;
+
+ if ( c->argc == 1 ) {
+ if ( i < 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" alone must be inside a \"uri\" directive",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_defaulttarget = i;
+
+ } else {
+ if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) {
+ if ( i >= 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s none\" should go before uri definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ }
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+
+ } else {
+
+ if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0
+ || mi->mi_defaulttarget < 0
+ || mi->mi_defaulttarget >= i - 1 )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "illegal target number %d",
+ mi->mi_defaulttarget );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_DNCACHE_TTL:
+ /* ttl of dn cache */
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) {
+ mi->mi_cache.ttl = META_DNCACHE_DISABLED;
+
+ } else {
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse dncache ttl \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_cache.ttl = (time_t)t;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NETWORK_TIMEOUT: {
+ /* network timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse network timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_network_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_IDLE_TIMEOUT: {
+ /* idle timeout when connecting to ldap servers */
+ unsigned long t;
+
+ if ( lutil_parse_time( c->argv[ 1 ], &t ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse idle timeout \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+
+ }
+ mi->mi_idle_timeout = (time_t)t;
+ } break;
+
+ case LDAP_BACK_CFG_BIND_TIMEOUT:
+ /* bind timeout when connecting to ldap servers */
+ mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000;
+ mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000;
+ break;
+
+ case LDAP_BACK_CFG_REBIND:
+ /* save bind creds for referral rebinds? */
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_SAVECRED;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_SAVECRED;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CHASE:
+ if ( c->argc == 1 || c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_TLS:
+ i = verb_to_mask( c->argv[1], tls_mode );
+ if ( BER_BVISNULL( &tls_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK;
+ mc->mc_flags |= tls_mode[i].mask;
+
+ if ( c->argc > 2 ) {
+ if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "need \"uri\" directive first" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( i = 2; i < c->argc; i++ ) {
+ if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls ))
+ return 1;
+ }
+ bindconf_tls_defaults( &mt->mt_tls );
+ }
+ break;
+
+ case LDAP_BACK_CFG_T_F:
+ i = verb_to_mask( c->argv[1], t_f_mode );
+ if ( BER_BVISNULL( &t_f_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2;
+ mc->mc_flags |= t_f_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_ONERR:
+ /* onerr? */
+ i = verb_to_mask( c->argv[1], onerr_mode );
+ if ( BER_BVISNULL( &onerr_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_flags &= ~META_BACK_F_ONERR_MASK;
+ mi->mi_flags |= onerr_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER:
+ /* bind-defer? */
+ if ( c->argc == 1 || c->value_int ) {
+ mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND;
+ } else {
+ mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND;
+ }
+ break;
+
+ case LDAP_BACK_CFG_CONNPOOLMAX:
+ /* privileged connections pool max size ? */
+ if ( mi->mi_ntargets > 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "\"%s\" must appear before target definitions",
+ c->argv[0] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+
+ if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN
+ || c->value_int > LDAP_BACK_CONN_PRIV_MAX )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "invalid max size " "of privileged "
+ "connections pool \"%s\" "
+ "in \"conn-pool-max <n> "
+ "(must be between %d and %d)\"",
+ c->argv[ 1 ],
+ LDAP_BACK_CONN_PRIV_MIN,
+ LDAP_BACK_CONN_PRIV_MAX );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mi->mi_conn_priv_max = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_CANCEL:
+ i = verb_to_mask( c->argv[1], cancel_mode );
+ if ( BER_BVISNULL( &cancel_mode[i].word ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s unknown argument \"%s\"",
+ c->argv[0], c->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2;
+ mc->mc_flags |= cancel_mode[i].mask;
+ break;
+
+ case LDAP_BACK_CFG_TIMEOUT:
+ for ( i = 1; i < c->argc; i++ ) {
+ if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) {
+ int j;
+ unsigned u;
+
+ if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ for ( j = 0; j < SLAP_OP_LAST; j++ ) {
+ mc->mc_timeout[ j ] = u;
+ }
+
+ continue;
+ }
+
+ if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg),
+ "unable to parse timeout \"%s\"",
+ c->argv[ i ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_BIND:
+ /* idassert-bind */
+ rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_IDASSERT_AUTHZFROM:
+ /* idassert-authzFrom */
+ rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert );
+ break;
+
+ case LDAP_BACK_CFG_QUARANTINE:
+ /* quarantine */
+ if ( META_BACK_CMN_QUARANTINE( mc ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "quarantine already defined" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ if ( mt ) {
+ mc->mc_quarantine.ri_interval = NULL;
+ mc->mc_quarantine.ri_num = NULL;
+ if ( !META_BACK_QUARANTINE( mi ) ) {
+ ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex );
+ }
+ }
+
+ if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) {
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+
+ mc->mc_flags |= LDAP_BACK_F_QUARANTINE;
+ break;
+
+#ifdef SLAP_CONTROL_X_SESSION_TRACKING
+ case LDAP_BACK_CFG_ST_REQUEST:
+ /* session tracking request */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_ST_REQUEST;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST;
+ }
+ break;
+#endif /* SLAP_CONTROL_X_SESSION_TRACKING */
+
+ case LDAP_BACK_CFG_SUFFIXM:
+ rc = asyncmeta_suffixm_config( c, c->argc, c->argv, mt );
+ break;
+
+ case LDAP_BACK_CFG_NRETRIES: {
+ int nretries = META_RETRY_UNDEFINED;
+
+ if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) {
+ nretries = META_RETRY_FOREVER;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) {
+ nretries = META_RETRY_NEVER;
+
+ } else {
+ if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse nretries {never|forever|<retries>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ }
+
+ mc->mc_nretries = nretries;
+ } break;
+
+ case LDAP_BACK_CFG_VERSION:
+ if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unsupported protocol version \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return 1;
+ }
+ mc->mc_version = c->value_int;
+ break;
+
+ case LDAP_BACK_CFG_NOREFS:
+ /* do not return search references */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOREFS;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOREFS;
+ }
+ break;
+
+ case LDAP_BACK_CFG_NOUNDEFFILTER:
+ /* do not propagate undefined search filters */
+ if ( c->value_int ) {
+ mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER;
+ } else {
+ mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER;
+ }
+ break;
+
+#ifdef SLAPD_META_CLIENT_PR
+ case LDAP_BACK_CFG_CLIENT_PR:
+ if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED;
+
+ } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) {
+ mc->mc_ps = META_CLIENT_PR_DISABLE;
+
+ } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"",
+ c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg );
+ return( 1 );
+ }
+ break;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ case LDAP_BACK_CFG_KEEPALIVE: {
+ struct berval bv;
+ ber_str2bv( c->argv[1], 0, 1, &bv );
+ slap_keepalive_parse( &bv, &mt->mt_tls.sb_keepalive, 0, 0, 0 );
+ }
+ break;
+
+ case LDAP_BACK_CFG_TCP_USER_TIMEOUT:
+ mt->mt_tls.sb_tcp_user_timeout = c->value_uint;
+ break;
+
+ /* anything else */
+ default:
+ return SLAP_CONF_UNKNOWN;
+ }
+
+ return rc;
+}
+
+int
+asyncmeta_back_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ /* Make sure we don't exceed the bits reserved for userland */
+ config_check_userland( LDAP_BACK_CFG_LAST );
+
+ bi->bi_cf_ocs = a_metaocs;
+
+ rc = config_register_schema( a_metacfg, a_metaocs );
+ if ( rc ) {
+ return rc;
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/conn.c b/servers/slapd/back-asyncmeta/conn.c
new file mode 100644
index 0000000..19c100e
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/conn.c
@@ -0,0 +1,1222 @@
+/* conn.c - handles connections to remote targets */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/errno.h>
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * asyncmeta_conn_alloc
+ *
+ * Allocates a connection structure, making room for all the referenced targets
+ */
+static a_metaconn_t *
+asyncmeta_conn_alloc(
+ a_metainfo_t *mi)
+{
+ a_metaconn_t *mc;
+ int ntargets = mi->mi_ntargets;
+
+ assert( ntargets > 0 );
+
+ /* malloc all in one */
+ mc = ( a_metaconn_t * )ch_calloc( 1, sizeof( a_metaconn_t ) + ntargets * sizeof( a_metasingleconn_t ));
+ if ( mc == NULL ) {
+ return NULL;
+ }
+
+ mc->mc_info = mi;
+ ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+ mc->mc_authz_target = META_BOUND_NONE;
+ mc->mc_conns = (a_metasingleconn_t *)(mc+1);
+ return mc;
+}
+
+/*
+ * asyncmeta_init_one_conn
+ *
+ * Initializes one connection
+ */
+int
+asyncmeta_init_one_conn(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int candidate,
+ int ispriv,
+ ldap_back_send_t sendok,
+ int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = NULL;
+ int version;
+ a_dncookie dc;
+ int isauthz = ( candidate == mc->mc_authz_target );
+ int do_return = 0;
+#ifdef HAVE_TLS
+ int is_ldaps = 0;
+ int do_start_tls = 0;
+#endif /* HAVE_TLS */
+
+ /* if the server is quarantined, and
+ * - the current interval did not expire yet, or
+ * - no more retries should occur,
+ * don't return the connection */
+ if ( mt->mt_isquarantined ) {
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+ int dont_retry = 0;
+
+ if ( mt->mt_quarantine.ri_interval ) {
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+ dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO );
+ if ( dont_retry ) {
+ dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL
+ || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] );
+ if ( !dont_retry ) {
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_init_one_conn[%d]: quarantine " "retry block #%d try #%d.\n",
+ op->o_log_prefix,
+ candidate, ri->ri_idx,
+ ri->ri_count );
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING;
+ }
+
+ }
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+ }
+
+ if ( dont_retry ) {
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Target is quarantined";
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: Target is quarantined\n",
+ op->o_log_prefix );
+ if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) {
+ send_ldap_result( op, rs );
+ }
+ return rs->sr_err;
+ }
+ }
+ msc = &mc->mc_conns[candidate];
+ /*
+ * Already init'ed
+ */
+ if ( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc ) )
+ {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_CREATING( msc )
+ || LDAP_BACK_CONN_BINDING( msc ) )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else if ( META_BACK_CONN_INITED( msc ) ) {
+ assert( msc->msc_ld != NULL );
+ rs->sr_err = LDAP_SUCCESS;
+ do_return = 1;
+
+ } else {
+ /*
+ * creating...
+ */
+ META_BACK_CONN_CREATING_SET( msc );
+ }
+
+ if ( do_return ) {
+ if ( rs->sr_err != LDAP_SUCCESS
+ && op->o_conn
+ && ( sendok & LDAP_BACK_SENDERR ) )
+ {
+ send_ldap_result( op, rs );
+ }
+
+ return rs->sr_err;
+ }
+
+ assert( msc->msc_ld == NULL );
+
+ /*
+ * Attempts to initialize the connection to the target ds
+ */
+ ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex );
+
+ rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri );
+#ifdef HAVE_TLS
+ is_ldaps = ldap_is_ldaps_url( mt->mt_uri );
+#endif /* HAVE_TLS */
+ ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: ldap_initialize failed err=%d\n",
+ op->o_log_prefix, rs->sr_err );
+ goto error_return;
+ }
+
+ ldap_set_option( msc->msc_ld, LDAP_OPT_KEEPCONN, LDAP_OPT_ON);
+
+ msc->msc_ldr = ldap_dup(msc->msc_ld);
+ if (!msc->msc_ldr) {
+ ldap_ld_free(msc->msc_ld, 0, NULL, NULL);
+ rs->sr_err = LDAP_NO_MEMORY;
+ goto error_return;
+ }
+
+ /*
+ * Set LDAP version. This will always succeed: If the client
+ * bound with a particular version, then so can we.
+ */
+ if ( mt->mt_version != 0 ) {
+ version = mt->mt_version;
+
+ } else if ( op->o_conn->c_protocol != 0 ) {
+ version = op->o_conn->c_protocol;
+
+ } else {
+ version = LDAP_VERSION3;
+ }
+ ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version );
+ ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p );
+
+ /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */
+ ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS,
+ META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF );
+
+ slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive);
+
+ if ( mt->mt_tls.sb_tcp_user_timeout > 0 ) {
+ ldap_set_option( msc->msc_ld, LDAP_OPT_TCP_USER_TIMEOUT,
+ &mt->mt_tls.sb_tcp_user_timeout );
+ }
+
+#ifdef HAVE_TLS
+ {
+ slap_bindconf *sb = NULL;
+
+ if ( ispriv ) {
+ sb = &mt->mt_idassert.si_bc;
+ } else {
+ sb = &mt->mt_tls;
+ }
+
+ bindconf_tls_set( sb, msc->msc_ld );
+
+ if ( !is_ldaps ) {
+ if ( META_BACK_TGT_USE_TLS( mt )
+ || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) )
+ {
+ do_start_tls = 1;
+ }
+ }
+ }
+
+ /* start TLS ("tls [try-]{start|propagate}" statement) */
+ if ( do_start_tls ) {
+#ifdef SLAP_STARTTLS_ASYNCHRONOUS
+ /*
+ * use asynchronous StartTLS; in case, chase referral
+ * FIXME: OpenLDAP does not return referral on StartTLS yet
+ */
+ int msgid;
+
+ rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ LDAPMessage *res = NULL;
+ int rc, nretries = mt->mt_nretries;
+ struct timeval tv;
+
+ LDAP_BACK_TV_SET( &tv );
+
+retry:;
+ rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res );
+ switch ( rc ) {
+ case -1:
+ rs->sr_err = LDAP_OTHER;
+ break;
+
+ case 0:
+ if ( nretries != 0 ) {
+ if ( nretries > 0 ) {
+ nretries--;
+ }
+ LDAP_BACK_TV_SET( &tv );
+ goto retry;
+ }
+ rs->sr_err = LDAP_OTHER;
+ break;
+
+ default:
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+ break;
+ }
+
+ if ( rc == LDAP_RES_EXTENDED ) {
+ struct berval *data = NULL;
+
+ /* NOTE: right now, data is unused, so don't get it */
+ rs->sr_err = ldap_parse_extended_result( msc->msc_ld,
+ res, NULL, NULL /* &data */ , 0 );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ int err;
+
+ /* FIXME: matched? referrals? response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ld,
+ res, &err, NULL, NULL, NULL, NULL, 1 );
+ res = NULL;
+
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+
+ rs->sr_err = err;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* FIXME: in case a referral
+ * is returned, should we try
+ * using it instead of the
+ * configured URI? */
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = ldap_install_tls( msc->msc_ld );
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ /* FIXME: LDAP_OPERATIONS_ERROR? */
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Unwilling to chase referral "
+ "returned by Start TLS exop";
+ }
+
+ if ( data ) {
+ ber_bvfree( data );
+ }
+ }
+
+ } else {
+ rs->sr_err = LDAP_OTHER;
+ }
+
+ if ( res != NULL ) {
+ ldap_msgfree( res );
+ }
+ }
+#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ /*
+ * use synchronous StartTLS
+ */
+ rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL );
+#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */
+ if (rs->sr_err != LDAP_SUCCESS) {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_init_one_conn: ldap_start_tls_s failed err=%d\n",
+ op->o_log_prefix, rs->sr_err );
+ }
+ /* if StartTLS is requested, only attempt it if the URL
+ * is not "ldaps://"; this may occur not only in case
+ * of misconfiguration, but also when used in the chain
+ * overlay, where the "uri" can be parsed out of a referral */
+ if ( rs->sr_err == LDAP_SERVER_DOWN
+ || ( rs->sr_err != LDAP_SUCCESS
+ && META_BACK_TGT_TLS_CRITICAL( mt ) ) )
+ {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY,
+ "### %s asyncmeta_init_one_conn(TLS) "
+ "ldap_unbind_ext[%d] ld=%p\n",
+ op->o_log_prefix, candidate,
+ (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ /* need to trash a failed Start TLS */
+ asyncmeta_clear_one_msc( op, mc, candidate, 1, __FUNCTION__ );
+ goto error_return;
+ }
+ }
+#endif /* HAVE_TLS */
+ /*
+ * Set the network timeout if set
+ */
+ if ( mt->mt_network_timeout != 0 ) {
+ struct timeval network_timeout;
+ network_timeout.tv_sec = 0;
+ network_timeout.tv_usec = mt->mt_network_timeout*1000;
+
+ ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT,
+ (void *)&network_timeout );
+ }
+
+ /*
+ * If the connection DN is not null, an attempt to rewrite it is made
+ */
+
+ if ( ispriv ) {
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN );
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0,
+ msc->msc_cred.bv_len );
+ }
+ ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd );
+ }
+ LDAP_BACK_CONN_ISIDASSERT_SET( msc );
+
+ } else {
+ ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv );
+ }
+
+ } else {
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ && isauthz )
+ {
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = NULL;
+ dc.to_from = MASSAGE_REQ;
+
+ /*
+ * Rewrite the bind dn if needed
+ */
+ asyncmeta_dn_massage( &dc, &op->o_conn->c_dn, &msc->msc_bound_ndn );
+
+ /* copy the DN if needed */
+ if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) {
+ ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn );
+ }
+ } else {
+ ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv );
+ }
+ }
+ assert( !BER_BVISNULL( &msc->msc_bound_ndn ) );
+
+error_return:;
+
+ if (msc != NULL) {
+ META_BACK_CONN_CREATING_CLEAR( msc );
+ }
+ if ( rs->sr_err == LDAP_SUCCESS && msc != NULL) {
+ META_BACK_CONN_INITED_SET( msc );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ rs->sr_err = slap_map_api2result( rs );
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ }
+ return rs->sr_err;
+}
+
+
+static int
+asyncmeta_get_candidate(
+ Operation *op,
+ SlapReply *rs,
+ struct berval *ndn )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ long candidate;
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ candidate = asyncmeta_select_unique_candidate( mi, ndn );
+
+ /*
+ * if any is found, inits the connection
+ */
+ if ( candidate == META_TARGET_NONE ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "No suitable candidate target found";
+
+ } else {
+ rs->sr_err = LDAP_SUCCESS;
+ }
+
+ return candidate;
+}
+
+
+/*
+ * asyncmeta_getconn
+ *
+ * Prepares the connection structure
+ *
+ * RATIONALE:
+ *
+ * - determine what DN is being requested:
+ *
+ * op requires candidate checks
+ *
+ * add unique parent of o_req_ndn
+ * bind unique^*[/all] o_req_ndn [no check]
+ * compare unique^+ o_req_ndn
+ * delete unique o_req_ndn
+ * modify unique o_req_ndn
+ * search any o_req_ndn
+ * modrdn unique[, unique] o_req_ndn[, orr_nnewSup]
+ *
+ * - for ops that require the candidate to be unique, in case of multiple
+ * occurrences an internal search with sizeLimit=1 is performed
+ * if a unique candidate can actually be determined. If none is found,
+ * the operation aborts; if multiple are found, the default target
+ * is used if defined and candidate; otherwise the operation aborts.
+ *
+ * *^note: actually, the bind operation is handled much like a search;
+ * i.e. the bind is broadcast to all candidate targets.
+ *
+ * +^note: actually, the compare operation is handled much like a search;
+ * i.e. the compare is broadcast to all candidate targets, while checking
+ * that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is
+ * returned.
+ */
+a_metaconn_t *
+asyncmeta_getconn(
+ Operation *op,
+ SlapReply *rs,
+ SlapReply *candidates,
+ int *candidate,
+ ldap_back_send_t sendok,
+ int alloc_new)
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metaconn_t *mc = NULL,
+ mc_curr = {{ 0 }};
+ int cached = META_TARGET_NONE,
+ i = META_TARGET_NONE,
+ err = LDAP_SUCCESS,
+ new_conn = 0,
+ ncandidates = 0;
+
+
+ meta_op_type op_type = META_OP_REQUIRE_SINGLE;
+ enum {
+ META_DNTYPE_ENTRY,
+ META_DNTYPE_PARENT,
+ META_DNTYPE_NEWPARENT
+ } dn_type = META_DNTYPE_ENTRY;
+ struct berval ndn = op->o_req_ndn,
+ pndn;
+
+ if (alloc_new > 0) {
+ mc = asyncmeta_conn_alloc(mi);
+ new_conn = 0;
+ } else {
+ mc = asyncmeta_get_next_mc(mi);
+ }
+
+ ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+ /* Internal searches are privileged and shared. So is root. */
+ if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) )
+ || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) )
+ || op->o_do_not_cache || be_isroot( op ) )
+ {
+ LDAP_BACK_CONN_ISPRIV_SET( &mc_curr );
+ LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op );
+
+ } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+
+ } else {
+ /* Explicit binds must not be shared */
+ if ( !BER_BVISEMPTY( &op->o_ndn )
+ || op->o_tag == LDAP_REQ_BIND
+ || SLAP_IS_AUTHZ_BACKEND( op ) )
+ {
+ //mc_curr.mc_conn = op->o_conn;
+
+ } else {
+ LDAP_BACK_CONN_ISANON_SET( &mc_curr );
+ LDAP_BACK_PCONN_ANON_SET( &mc_curr, op );
+ }
+ }
+
+ switch ( op->o_tag ) {
+ case LDAP_REQ_ADD:
+ /* if we go to selection, the entry must not exist,
+ * and we must be able to resolve the parent */
+ dn_type = META_DNTYPE_PARENT;
+ dnParent( &ndn, &pndn );
+ break;
+
+ case LDAP_REQ_MODRDN:
+ /* if nnewSuperior is not NULL, it must resolve
+ * to the same candidate as the req_ndn */
+ if ( op->orr_nnewSup ) {
+ dn_type = META_DNTYPE_NEWPARENT;
+ }
+ break;
+
+ case LDAP_REQ_BIND:
+ /* if bound as rootdn, the backend must bind to all targets
+ * with the administrative identity
+ * (unless pseoudoroot-bind-defer is TRUE) */
+ if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) {
+ op_type = META_OP_REQUIRE_ALL;
+ }
+ break;
+
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODIFY:
+ /* just a unique candidate */
+ break;
+
+ case LDAP_REQ_SEARCH:
+ /* allow multiple candidates for the searchBase */
+ op_type = META_OP_ALLOW_MULTIPLE;
+ break;
+
+ default:
+ /* right now, just break (exop?) */
+ break;
+ }
+
+ /*
+ * require all connections ...
+ */
+ if ( op_type == META_OP_REQUIRE_ALL ) {
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ candidates[ i ].sr_err = asyncmeta_init_one_conn( op,
+ rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) {
+ LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] );
+ }
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ err = candidates[ i ].sr_err;
+ continue;
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( alloc_new > 0) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ goto done;
+ }
+
+ /*
+ * looks in cache, if any
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ cached = i = asyncmeta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn );
+ }
+
+ if ( op_type == META_OP_REQUIRE_SINGLE ) {
+ int j;
+
+ for ( j = 0; j < mi->mi_ntargets; j++ ) {
+ META_CANDIDATE_RESET( &candidates[ j ] );
+ }
+
+ /*
+ * tries to get a unique candidate
+ * (takes care of default target)
+ */
+ if ( i == META_TARGET_NONE ) {
+ i = asyncmeta_get_candidate( op, rs, &ndn );
+
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) {
+ i = asyncmeta_get_candidate( op, rs, &pndn );
+ }
+
+ if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+ }
+
+ if ( dn_type == META_DNTYPE_NEWPARENT && asyncmeta_get_candidate( op, rs, op->orr_nnewSup ) != i )
+ {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "Cross-target rename not supported";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "==>asyncmeta__getconn: got target=%d for ndn=\"%s\" from cache\n",
+ i, op->o_req_ndn.bv_val );
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ /*
+ * Clear all other candidates
+ */
+ ( void )asyncmeta_clear_unused_candidates( op, i , mc, candidates);
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd. In case of error, asyncmeta_init_one_conn
+ * sends the appropriate result.
+ */
+ err = asyncmeta_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn );
+ if ( err != LDAP_SUCCESS ) {
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ META_CANDIDATE_RESET( &candidates[ i ] );
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( mc != NULL && alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+ }
+ return NULL;
+ }
+
+ candidates[ i ].sr_err = LDAP_SUCCESS;
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ if ( candidate ) {
+ *candidate = i;
+ }
+
+ /*
+ * if no unique candidate ...
+ */
+ } else {
+ if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISPRIV_SET( mc );
+
+ } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) {
+ LDAP_BACK_CONN_ISANON_SET( mc );
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ META_CANDIDATE_RESET( &candidates[ i ] );
+
+ if ( i == cached
+ || asyncmeta_is_candidate( mt, &op->o_req_ndn,
+ op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) )
+ {
+
+ /*
+ * The target is activated; if needed, it is
+ * also init'd
+ */
+ int lerr = asyncmeta_init_one_conn( op, rs, mc, i,
+ LDAP_BACK_CONN_ISPRIV( &mc_curr ),
+ LDAP_BACK_DONTSEND, !new_conn );
+ candidates[ i ].sr_err = lerr;
+ if ( lerr == LDAP_SUCCESS ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+ ncandidates++;
+
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d]\n",
+ op->o_log_prefix, i );
+
+ } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) {
+ META_CANDIDATE_SET( &candidates[ i ] );
+
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] %s\n",
+ op->o_log_prefix, i,
+ mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" );
+
+ } else {
+
+ /*
+ * FIXME: in case one target cannot
+ * be init'd, should the other ones
+ * be tried?
+ */
+ /* leave the target candidate, but record the error for later use */
+ err = lerr;
+
+ if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) {
+ Debug( LDAP_DEBUG_TRACE, "%s: asyncmeta_getconn[%d] quarantined err=%d\n",
+ op->o_log_prefix, i, lerr );
+
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s: asyncmeta_getconn[%d] failed err=%d\n",
+ op->o_log_prefix, i, lerr );
+ }
+
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ if ( alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+
+ }
+ return NULL;
+ }
+
+ continue;
+ }
+
+ }
+ }
+
+ if ( ncandidates == 0 ) {
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = "Unable to select valid candidates";
+ }
+
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) {
+ rs->sr_matched = mi->mi_suffix.bv_val;
+ }
+ send_ldap_result( op, rs );
+ rs->sr_matched = NULL;
+ }
+ if ( alloc_new > 0 ) {
+ asyncmeta_back_conn_free( mc );
+
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ return NULL;
+ }
+ }
+
+done:;
+ rs->sr_err = LDAP_SUCCESS;
+ rs->sr_text = NULL;
+
+ if ( new_conn ) {
+ if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) {
+ /*
+ * Err could be -1 in case a duplicate metaconn is inserted
+ */
+ switch ( err ) {
+ case 0:
+ break;
+ default:
+ LDAP_BACK_CONN_CACHED_CLEAR( mc );
+ if ( LogTest( LDAP_DEBUG_ANY ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_getconn: candidates=%d conn=%s insert failed\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ asyncmeta_back_conn_free( mc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "Proxy bind collision";
+ if ( sendok & LDAP_BACK_SENDERR ) {
+ send_ldap_result( op, rs );
+ }
+ return NULL;
+ }
+ }
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_getconn: candidates=%d conn=%s inserted\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+
+ } else {
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char buf[STRLENOF("4294967295U") + 1] = { 0 };
+ mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_getconn: candidates=%d conn=%s fetched\n",
+ op->o_log_prefix, ncandidates, buf );
+ }
+ }
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ return mc;
+}
+
+void
+asyncmeta_quarantine(
+ Operation *op,
+ a_metainfo_t *mi,
+ SlapReply *rs,
+ int candidate )
+{
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+
+ slap_retry_info_t *ri = &mt->mt_quarantine;
+
+ ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex );
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE ) {
+ time_t new_last = slap_get_time();
+
+ switch ( mt->mt_isquarantined ) {
+ case LDAP_BACK_FQ_NO:
+ if ( ri->ri_last == new_last ) {
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: enter.\n",
+ op->o_log_prefix, candidate );
+
+ ri->ri_idx = 0;
+ ri->ri_count = 0;
+ break;
+
+ case LDAP_BACK_FQ_RETRYING:
+ Debug(LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: block #%d try #%d failed.\n",
+ op->o_log_prefix, candidate, ri->ri_idx,
+ ri->ri_count );
+
+ ++ri->ri_count;
+ if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER
+ && ri->ri_count == ri->ri_num[ ri->ri_idx ] )
+ {
+ ri->ri_count = 0;
+ ++ri->ri_idx;
+ }
+ break;
+
+ default:
+ goto done;
+ }
+
+ mt->mt_isquarantined = LDAP_BACK_FQ_YES;
+ ri->ri_last = new_last;
+
+ } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_quarantine[%d]: exit.\n",
+ op->o_log_prefix, candidate );
+
+ if ( mi->mi_quarantine_f ) {
+ (void)mi->mi_quarantine_f( mi, candidate,
+ mi->mi_quarantine_p );
+ }
+
+ ri->ri_count = 0;
+ ri->ri_idx = 0;
+ mt->mt_isquarantined = LDAP_BACK_FQ_NO;
+ mt->mt_timeout_ops = 0;
+ }
+
+done:;
+ ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex );
+}
+
+a_metaconn_t *
+asyncmeta_get_next_mc( a_metainfo_t *mi )
+{
+ a_metaconn_t *mc = NULL;
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ if (mi->mi_next_conn >= mi->mi_num_conns-1) {
+ mi->mi_next_conn = 0;
+ } else {
+ mi->mi_next_conn++;
+ }
+
+ mc = &mi->mi_conns[mi->mi_next_conn];
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ return mc;
+}
+
+int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc)
+{
+ int i;
+ for (i = 0; i < mc->mc_info->mi_ntargets; i++) {
+ asyncmeta_start_one_listener(mc, candidates, bc, i);
+ }
+ return LDAP_SUCCESS;
+}
+
+int asyncmeta_start_one_listener(a_metaconn_t *mc,
+ SlapReply *candidates,
+ bm_context_t *bc,
+ int candidate)
+{
+ a_metasingleconn_t *msc;
+ ber_socket_t s;
+
+ msc = &mc->mc_conns[candidate];
+ if ( slapd_shutdown || !META_BACK_CONN_INITED( msc ) || msc->msc_ld == NULL
+ || META_BACK_CONN_INVALID(msc) || !META_IS_CANDIDATE( &candidates[ candidate ] )) {
+ return LDAP_SUCCESS;
+ }
+ bc->msgids[candidate] = candidates[candidate].sr_msgid;
+ if ( msc->conn == NULL) {
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ /* Todo a meaningful log pls */
+ return LDAP_OTHER;
+ }
+ msc->conn = connection_client_setup( s, asyncmeta_op_handle_result, mc );
+ }
+ connection_client_enable( msc->conn );
+ return LDAP_SUCCESS;
+}
+
+int
+asyncmeta_clear_one_msc(
+ Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller)
+{
+ a_metasingleconn_t *msc;
+ if (mc == NULL) {
+ return 0;
+ }
+ msc = &mc->mc_conns[candidate];
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] Resetting msc: %p, msc_ld: %p, "
+ "msc_bound_ndn: %s, msc->conn: %p, %s \n",
+ time_buf, msc, msc->msc_ld, msc->msc_bound_ndn.bv_val,
+ msc->conn, caller ? caller : "" );
+ }
+ msc->msc_mscflags = 0;
+ if (msc->conn) {
+ connection_client_stop( msc->conn );
+ msc->conn = NULL;
+ }
+
+ if ( msc->msc_ld != NULL ) {
+
+#ifdef DEBUG_205
+ Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_clear_one_msc ldap_unbind_ext[%d] ld=%p\n",
+ op ? op->o_log_prefix : "", candidate, (void *)msc->msc_ld );
+#endif /* DEBUG_205 */
+
+ ldap_unbind_ext( msc->msc_ld, NULL, NULL );
+ msc->msc_ld = NULL;
+ ldap_ld_free( msc->msc_ldr, 0, NULL, NULL );
+ msc->msc_ldr = NULL;
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) {
+ ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL );
+ BER_BVZERO( &msc->msc_bound_ndn );
+ }
+
+ if ( !BER_BVISNULL( &msc->msc_cred ) ) {
+ memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len );
+ ber_memfree_x( msc->msc_cred.bv_val, NULL );
+ BER_BVZERO( &msc->msc_cred );
+ }
+ msc->msc_time = 0;
+ msc->msc_binding_time = 0;
+ msc->msc_result_time = 0;
+ return 0;
+}
+
+void asyncmeta_get_timestamp(char *buf)
+{
+ struct timespec tp;
+ struct tm *ttm;
+ clock_gettime(CLOCK_REALTIME, &tp);
+ ttm = gmtime(&tp.tv_sec);
+ sprintf(buf, "%d:%d:%d.%ld", ttm->tm_hour, ttm->tm_min, ttm->tm_sec, tp.tv_nsec/1000);
+}
+
+int
+asyncmeta_reset_msc(
+ Operation *op,
+ a_metaconn_t *mc,
+ int candidate,
+ int unbind,
+ const char *caller)
+{
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug(asyncmeta_debug, "[%x] Will attempt to reset [%s] msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x %s\n",
+ (unsigned int)slap_get_time(), time_buf, msc,
+ (unsigned int)msc->msc_binding_time, msc->msc_mscflags, caller );
+ }
+ if (msc->msc_active <= 1 && mc->mc_active < 1) {
+ bm_context_t *om;
+ asyncmeta_clear_one_msc(NULL, mc, candidate, 0, caller);
+ /* set whatever's in the queue to invalid, so the timeout loop cleans it up,
+ * but do not invalidate the current op*/
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om->candidates[candidate].sr_msgid >= 0 && (om->op != op)) {
+ om->bc_invalid = 1;
+ }
+ }
+ return LDAP_SUCCESS;
+ } else {
+ META_BACK_CONN_INVALID_SET(msc);
+ Debug( asyncmeta_debug, "[%x] Failed to reset msc %p, msc_active=%d, mc_active=%d, %s\n",
+ (unsigned int)slap_get_time(), msc, msc->msc_active, mc->mc_active, caller );
+ }
+ return LDAP_OTHER;
+}
+
+
+void asyncmeta_log_msc(a_metasingleconn_t *msc)
+{
+ ber_socket_t s = 0;
+ if (msc->msc_ld) {
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ }
+ Debug( asyncmeta_debug, "msc: %p, msc_ld: %p, msc_ld socket: %d, "
+ "msc_bound_ndn: %s, msc->conn: %p\n", msc, msc->msc_ld,
+ (int)s, msc->msc_bound_ndn.bv_val, msc->conn );
+}
+
+void asyncmeta_log_conns(a_metainfo_t *mi)
+{
+ a_metaconn_t *mc;
+ int i, j;
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ mc = &mi->mi_conns[i];
+ Debug(asyncmeta_debug, "mc: %p, mc->pending_ops: %d\n", mc, mc->pending_ops);
+ for (j = 0; j < mi->mi_ntargets; j++ ) {
+ asyncmeta_log_msc(&mc->mc_conns[j]);
+ }
+
+ }
+}
+
+int
+asyncmeta_db_has_pending_ops(a_metainfo_t *mi)
+{
+ int i;
+ if (mi->mi_ntargets == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ if (mi->mi_conns[i].pending_ops > 0) {
+ return mi->mi_conns[i].pending_ops;
+ }
+ }
+
+ return 0;
+}
+
+
+int
+asyncmeta_db_has_mscs(a_metainfo_t *mi)
+{
+ int i, j;
+ if (mi->mi_ntargets == 0) {
+ return 0;
+ }
+
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ for (j = 0; j < mi->mi_ntargets; j++) {
+ if (mi->mi_conns[i].mc_conns[j].msc_ld != NULL ||
+ mi->mi_conns[i].mc_conns[j].msc_ldr != NULL ) {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/servers/slapd/back-asyncmeta/delete.c b/servers/slapd/back-asyncmeta/delete.c
new file mode 100644
index 0000000..b85d463
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/delete.c
@@ -0,0 +1,304 @@
+/* delete.c - delete request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
++ * This work was developed by Symas Corporation
++ * based on back-meta module for inclusion in OpenLDAP Software.
++ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_delete_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL;
+ a_dncookie dc;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ber = ldap_build_delete_req( msc->msc_ld, mdn.bv_val, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_delete_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_DELETE,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send delete request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_delete_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_delete( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_TRACE, "==> asyncmeta_back_delete: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_delete[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_DELETE ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_delete_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_delete: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/dncache.c b/servers/slapd/back-asyncmeta/dncache.c
new file mode 100644
index 0000000..a588290
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/dncache.c
@@ -0,0 +1,228 @@
+/* dncache.c - dn caching for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <ac/string.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+/*
+ * The dncache, at present, maps an entry to the target that holds it.
+ */
+
+typedef struct metadncacheentry_t {
+ struct berval dn;
+ int target;
+
+ time_t lastupdated;
+} metadncacheentry_t;
+
+/*
+ * asyncmeta_dncache_cmp
+ *
+ * compares two struct metadncacheentry; used by avl stuff
+ * FIXME: modify avl stuff to delete an entry based on cmp
+ * (e.g. when ttl expired?)
+ */
+int
+asyncmeta_dncache_cmp(
+ const void *c1,
+ const void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ber_bvcmp( &cc1->dn, &cc2->dn);
+}
+
+/*
+ * asyncmeta_dncache_dup
+ *
+ * returns -1 in case a duplicate struct metadncacheentry has been inserted;
+ * used by avl stuff
+ */
+int
+asyncmeta_dncache_dup(
+ void *c1,
+ void *c2 )
+{
+ metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1;
+ metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2;
+
+ /*
+ * case sensitive, because the dn MUST be normalized
+ */
+ return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0;
+}
+
+/*
+ * asyncmeta_dncache_get_target
+ *
+ * returns the target a dn belongs to, or -1 in case the dn is not
+ * in the cache
+ */
+int
+asyncmeta_dncache_get_target(
+ a_metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t tmp_entry,
+ *entry;
+ int target = META_TARGET_NONE;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+ if ( entry != NULL ) {
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl < 0 ) {
+ target = entry->target;
+
+ } else {
+ if ( entry->lastupdated+cache->ttl > slap_get_time() ) {
+ target = entry->target;
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return target;
+}
+
+/*
+ * asyncmeta_dncache_update_entry
+ *
+ * updates target and lastupdated of a struct metadncacheentry if exists,
+ * otherwise it gets created; returns -1 in case of error
+ */
+int
+asyncmeta_dncache_update_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn,
+ int target )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+ time_t curr_time = 0L;
+ int err = 0;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ /*
+ * if cache->ttl < 0, cache never expires;
+ * if cache->ttl = 0 no cache is used; shouldn't get here
+ * else, cache is used with ttl
+ */
+ if ( cache->ttl > 0 ) {
+ curr_time = slap_get_time();
+ }
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ( metadncacheentry_t * )ldap_avl_find( cache->tree,
+ ( caddr_t )&tmp_entry, asyncmeta_dncache_cmp );
+
+ if ( entry != NULL ) {
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ } else {
+ entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 );
+ if ( entry == NULL ) {
+ err = -1;
+ goto error_return;
+ }
+
+ entry->dn.bv_len = ndn->bv_len;
+ entry->dn.bv_val = (char *)&entry[ 1 ];
+ AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len );
+ entry->dn.bv_val[ ndn->bv_len ] = '\0';
+
+ entry->target = target;
+ entry->lastupdated = curr_time;
+
+ err = ldap_avl_insert( &cache->tree, ( caddr_t )entry,
+ asyncmeta_dncache_cmp, asyncmeta_dncache_dup );
+ }
+
+error_return:;
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ return err;
+}
+
+int
+asyncmeta_dncache_delete_entry(
+ a_metadncache_t *cache,
+ struct berval *ndn )
+{
+ metadncacheentry_t *entry,
+ tmp_entry;
+
+ assert( cache != NULL );
+ assert( ndn != NULL );
+
+ tmp_entry.dn = *ndn;
+
+ ldap_pvt_thread_mutex_lock( &cache->mutex );
+ entry = ldap_avl_delete( &cache->tree, ( caddr_t )&tmp_entry,
+ asyncmeta_dncache_cmp );
+ ldap_pvt_thread_mutex_unlock( &cache->mutex );
+
+ if ( entry != NULL ) {
+ asyncmeta_dncache_free( ( void * )entry );
+ }
+
+ return 0;
+}
+
+/*
+ * meta_dncache_free
+ *
+ * frees an entry
+ *
+ */
+void
+asyncmeta_dncache_free(
+ void *e )
+{
+ free( e );
+}
diff --git a/servers/slapd/back-asyncmeta/init.c b/servers/slapd/back-asyncmeta/init.c
new file mode 100644
index 0000000..2b43958
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/init.c
@@ -0,0 +1,475 @@
+/* init.c - initialization of a back-asyncmeta database */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+int asyncmeta_debug;
+
+int
+asyncmeta_back_open(
+ BackendInfo *bi )
+{
+ /* FIXME: need to remove the pagedResults, and likely more... */
+ bi->bi_controls = slap_known_controls;
+
+ return 0;
+}
+
+int
+asyncmeta_back_initialize(
+ BackendInfo *bi )
+{
+ int rc;
+ struct berval debugbv = BER_BVC("asyncmeta");
+
+ rc = slap_loglevel_get( &debugbv, &asyncmeta_debug );
+ if ( rc ) {
+ return rc;
+ }
+
+ bi->bi_flags =
+#if 0
+ /* this is not (yet) set essentially because back-meta does not
+ * directly support extended operations... */
+#ifdef LDAP_DYNAMIC_OBJECTS
+ /* this is set because all the support a proxy has to provide
+ * is the capability to forward the refresh exop, and to
+ * pass thru entries that contain the dynamicObject class
+ * and the entryTtl attribute */
+ SLAP_BFLAG_DYNAMIC |
+#endif /* LDAP_DYNAMIC_OBJECTS */
+#endif
+
+ /* back-meta recognizes RFC4525 increment;
+ * let the remote server complain, if needed (ITS#5912) */
+ SLAP_BFLAG_INCREMENT;
+
+ bi->bi_open = asyncmeta_back_open;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = asyncmeta_back_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = asyncmeta_back_db_open;
+ bi->bi_db_close = asyncmeta_back_db_close;
+ bi->bi_db_destroy = asyncmeta_back_db_destroy;
+
+ bi->bi_op_bind = asyncmeta_back_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = asyncmeta_back_search;
+ bi->bi_op_compare = asyncmeta_back_compare;
+ bi->bi_op_modify = asyncmeta_back_modify;
+ bi->bi_op_modrdn = asyncmeta_back_modrdn;
+ bi->bi_op_add = asyncmeta_back_add;
+ bi->bi_op_delete = asyncmeta_back_delete;
+ bi->bi_op_abandon = 0;
+
+ bi->bi_extended = 0;
+
+ bi->bi_chk_referrals = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0 /* asyncmeta_back_conn_destroy */;
+
+ return asyncmeta_back_init_cf( bi );
+}
+
+int
+asyncmeta_back_db_init(
+ Backend *be,
+ ConfigReply *cr)
+{
+ a_metainfo_t *mi;
+ int i;
+ BackendInfo *bi;
+
+ bi = backend_info( "ldap" );
+ if ( !bi || !bi->bi_extra ) {
+ Debug( LDAP_DEBUG_ANY,
+ "asyncmeta_back_db_init: needs back-ldap\n" );
+ return 1;
+ }
+
+ mi = ch_calloc( 1, sizeof( a_metainfo_t ) );
+ if ( mi == NULL ) {
+ return -1;
+ }
+
+ /* set default flags */
+ mi->mi_flags =
+ META_BACK_F_DEFER_ROOTDN_BIND
+ | META_BACK_F_PROXYAUTHZ_ALWAYS
+ | META_BACK_F_PROXYAUTHZ_ANON
+ | META_BACK_F_PROXYAUTHZ_NOANON;
+
+ /*
+ * At present the default is no default target;
+ * this may change
+ */
+ mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE;
+ mi->mi_bind_timeout.tv_sec = 0;
+ mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT;
+
+ mi->mi_rebind_f = asyncmeta_back_default_rebind;
+ mi->mi_urllist_f = asyncmeta_back_default_urllist;
+
+ ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex );
+
+ /* safe default */
+ mi->mi_nretries = META_RETRY_DEFAULT;
+ mi->mi_version = LDAP_VERSION3;
+
+ for ( i = 0; i < SLAP_OP_LAST; i++ ) {
+ mi->mi_timeout[ i ] = META_BACK_CFG_DEFAULT_OPS_TIMEOUT;
+ }
+
+ for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
+ mi->mi_conn_priv[ i ].mic_num = 0;
+ LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv );
+ }
+ mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT;
+
+ mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra;
+ ldap_pvt_thread_mutex_init( &mi->mi_mc_mutex);
+
+ be->be_private = mi;
+ be->be_cf_ocs = be->bd_info->bi_cf_ocs;
+
+ return 0;
+}
+
+int
+asyncmeta_target_finish(
+ a_metainfo_t *mi,
+ a_metatarget_t *mt,
+ const char *log,
+ char *msg,
+ size_t msize
+)
+{
+ slap_bindconf sb = { BER_BVNULL };
+ int rc;
+
+ ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri );
+ sb.sb_version = mt->mt_version;
+ sb.sb_method = LDAP_AUTH_SIMPLE;
+ BER_BVSTR( &sb.sb_binddn, "" );
+
+ if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedFeatures->ad_cname.bv_val,
+ LDAP_FEATURE_ABSOLUTE_FILTERS );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_T_F;
+ }
+ }
+
+ if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) {
+ rc = slap_discover_feature( &sb,
+ slap_schema.si_ad_supportedExtension->ad_cname.bv_val,
+ LDAP_EXOP_CANCEL );
+ if ( rc == LDAP_COMPARE_TRUE ) {
+ mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP;
+ }
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE )
+ || mt->mt_idassert_authz != NULL )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL )
+ && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ Debug(LDAP_DEBUG_ANY,
+ "%s: inconsistent idassert configuration " "(likely authz=\"*\" used with \"non-prescriptive\" flag) (target %s)\n",
+ log, mt->mt_uri );
+ return 1;
+ }
+
+ if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON;
+ }
+
+ if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) )
+ {
+ mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON;
+ }
+
+ return 0;
+}
+
+int
+asyncmeta_back_db_open(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi = (a_metainfo_t *)be->be_private;
+ char msg[SLAP_TEXT_BUFLEN];
+ int i;
+
+ if ( mi->mi_ntargets == 0 ) {
+
+ Debug( LDAP_DEBUG_ANY,
+ "asyncmeta_back_db_open: no targets defined\n" );
+ }
+
+ mi->mi_num_conns = 0;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+ if ( asyncmeta_target_finish( mi, mt,
+ "asyncmeta_back_db_open", msg, sizeof( msg ))) {
+ return 1;
+ }
+ }
+
+ mi->mi_num_conns = (mi->mi_max_target_conns == 0) ? META_BACK_CFG_MAX_TARGET_CONNS : mi->mi_max_target_conns;
+ assert(mi->mi_num_conns > 0);
+ mi->mi_conns = ch_calloc( mi->mi_num_conns, sizeof( a_metaconn_t ));
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ a_metaconn_t *mc = &mi->mi_conns[i];
+ ldap_pvt_thread_mutex_init( &mc->mc_om_mutex);
+ mc->mc_authz_target = META_BOUND_NONE;
+
+ if ( mi->mi_ntargets > 0 ) {
+ mc->mc_conns = ch_calloc( mi->mi_ntargets, sizeof( a_metasingleconn_t ));
+ } else {
+ mc->mc_conns = NULL;
+ }
+
+ mc->mc_info = mi;
+ LDAP_STAILQ_INIT( &mc->mc_om_list );
+ }
+
+ ber_dupbv ( &mi->mi_suffix, &be->be_suffix[0] );
+
+ if ( mi->mi_ntargets > 0 ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 1,
+ asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", mi->mi_suffix.bv_val );
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ }
+ return 0;
+}
+
+/*
+ * asyncmeta_back_conn_free()
+ *
+ * actually frees a connection; the reference count must be 0,
+ * and it must not (or no longer) be in the cache.
+ */
+void
+asyncmeta_back_conn_free(
+ void *v_mc )
+{
+ a_metaconn_t *mc = v_mc;
+
+ assert( mc != NULL );
+ ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+ free( mc );
+}
+
+static void
+asyncmeta_back_stop_miconns( a_metainfo_t *mi )
+{
+
+ /*Todo do any other mc cleanup here if necessary*/
+}
+
+static void
+asyncmeta_back_clear_miconns( a_metainfo_t *mi )
+{
+ int i, j;
+ a_metaconn_t *mc;
+ for (i = 0; i < mi->mi_num_conns; i++) {
+ mc = &mi->mi_conns[i];
+ /* todo clear the message queue */
+ for (j = 0; j < mi->mi_ntargets; j ++) {
+ asyncmeta_clear_one_msc(NULL, mc, j, 1, __FUNCTION__);
+ }
+ free(mc->mc_conns);
+ ldap_pvt_thread_mutex_destroy( &mc->mc_om_mutex );
+ }
+ free(mi->mi_conns);
+}
+
+static void
+asyncmeta_target_free(
+ a_metatarget_t *mt )
+{
+ if ( mt->mt_uri ) {
+ free( mt->mt_uri );
+ ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex );
+ }
+ if ( mt->mt_subtree ) {
+ asyncmeta_subtree_destroy( mt->mt_subtree );
+ mt->mt_subtree = NULL;
+ }
+ if ( mt->mt_filter ) {
+ asyncmeta_filter_destroy( mt->mt_filter );
+ mt->mt_filter = NULL;
+ }
+ if ( !BER_BVISNULL( &mt->mt_psuffix ) ) {
+ free( mt->mt_psuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) {
+ free( mt->mt_nsuffix.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_binddn ) ) {
+ free( mt->mt_binddn.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_bindpw ) ) {
+ free( mt->mt_bindpw.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) {
+ ch_free( mt->mt_idassert_authcID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) {
+ ch_free( mt->mt_idassert_authcDN.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) {
+ ch_free( mt->mt_idassert_passwd.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) {
+ ch_free( mt->mt_idassert_authzID.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) {
+ ch_free( mt->mt_idassert_sasl_mech.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) {
+ ch_free( mt->mt_idassert_sasl_realm.bv_val );
+ }
+ if ( mt->mt_idassert_authz != NULL ) {
+ ber_bvarray_free( mt->mt_idassert_authz );
+ }
+ if ( !BER_BVISNULL( &mt->mt_lsuffixm )) {
+ ch_free( mt->mt_lsuffixm.bv_val );
+ }
+ if ( !BER_BVISNULL( &mt->mt_rsuffixm )) {
+ ch_free( mt->mt_rsuffixm.bv_val );
+ }
+ free( mt );
+}
+
+int
+asyncmeta_back_db_close(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi;
+
+ if ( be->be_private ) {
+ mi = ( a_metainfo_t * )be->be_private;
+ if ( mi->mi_task != NULL ) {
+ ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+ if ( ldap_pvt_runqueue_isrunning( &slapd_rq, mi->mi_task )) {
+ ldap_pvt_runqueue_stoptask( &slapd_rq, mi->mi_task);
+ }
+ ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ mi->mi_task = NULL;
+ }
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ asyncmeta_back_stop_miconns( mi );
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ }
+ return 0;
+}
+
+int
+asyncmeta_back_db_destroy(
+ Backend *be,
+ ConfigReply *cr )
+{
+ a_metainfo_t *mi;
+
+ if ( be->be_private ) {
+ int i;
+
+ mi = ( a_metainfo_t * )be->be_private;
+ /*
+ * Destroy the per-target stuff (assuming there's at
+ * least one ...)
+ */
+ if ( mi->mi_targets != NULL ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ a_metatarget_t *mt = mi->mi_targets[ i ];
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num )
+ {
+ mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine );
+ }
+
+ ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex );
+ }
+
+ asyncmeta_target_free( mt );
+ }
+
+ free( mi->mi_targets );
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex );
+ if ( mi->mi_cache.tree ) {
+ ldap_avl_free( mi->mi_cache.tree, asyncmeta_dncache_free );
+ }
+
+ ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex );
+
+ if ( mi->mi_candidates != NULL ) {
+ ber_memfree_x( mi->mi_candidates, NULL );
+ }
+
+ if ( META_BACK_QUARANTINE( mi ) ) {
+ mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine );
+ }
+
+ ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
+ asyncmeta_back_clear_miconns(mi);
+ ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
+ ldap_pvt_thread_mutex_destroy( &mi->mi_mc_mutex );
+
+ free( be->be_private );
+ }
+ return 0;
+}
+
+#if SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( asyncmeta )
+
+#endif /* SLAPD_ASYNCMETA == SLAPD_MOD_DYNAMIC */
diff --git a/servers/slapd/back-asyncmeta/map.c b/servers/slapd/back-asyncmeta/map.c
new file mode 100644
index 0000000..66bb8b0
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/map.c
@@ -0,0 +1,224 @@
+/* map.c - ldap backend mapping routines */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+/* This is an altered version */
+/*
+ * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com>
+ *
+ * Permission is granted to anyone to use this software for any purpose
+ * on any computer system, and to alter it and redistribute it, subject
+ * to the following restrictions:
+ *
+ * 1. The author is not responsible for the consequences of use of this
+ * software, no matter how awful, even if they arise from flaws in it.
+ *
+ * 2. The origin of this software must not be misrepresented, either by
+ * explicit claim or by omission. Since few users ever read sources,
+ * credits should appear in the documentation.
+ *
+ * 3. Altered versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software. Since few users
+ * ever read sources, credits should appear in the documentation.
+ *
+ * 4. This notice may not be removed or altered.
+ *
+ *
+ *
+ * Copyright 2016, Symas Corporation
+ *
+ * This is based on the back-meta/map.c version by Pierangelo Masarati.
+ * The previously reported conditions apply to the modified code as well.
+ * Changes in the original code are highlighted where required.
+ * Credits for the original code go to the author, Howard Chu.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+void
+asyncmeta_referral_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals
+)
+{
+ int i, last;
+
+ assert( dc != NULL );
+ assert( a_vals != NULL );
+
+ for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ )
+ ;
+ last--;
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) {
+ struct berval dn,
+ olddn = BER_BVNULL;
+ int rc;
+ LDAPURLDesc *ludp;
+
+ rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp );
+ if ( rc != LDAP_URL_SUCCESS ) {
+ /* leave attr untouched if massage failed */
+ continue;
+ }
+
+ /* FIXME: URLs like "ldap:///dc=suffix" if passed
+ * thru ldap_url_parse() and ldap_url_desc2str()
+ * get rewritten as "ldap:///dc=suffix??base";
+ * we don't want this to occur... */
+ if ( ludp->lud_scope == LDAP_SCOPE_BASE ) {
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ }
+
+ ber_str2bv( ludp->lud_dn, 0, 0, &olddn );
+
+ asyncmeta_dn_massage( dc, &olddn, &dn );
+ /* leave attr untouched if massage did nothing */
+ if ( olddn.bv_val != dn.bv_val )
+ {
+ char *newurl;
+
+ ludp->lud_dn = dn.bv_val;
+ newurl = ldap_url_desc2str( ludp );
+ dc->op->o_tmpfree( dn.bv_val, dc->memctx );
+ if ( newurl )
+ {
+ /* FIXME: leave attr untouched
+ * even if ldap_url_desc2str failed...
+ */
+
+ ber_memfree_x( a_vals[ i ].bv_val, dc->op->o_tmpmemctx );
+ ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], dc->memctx );
+ ber_memfree( newurl );
+ ludp->lud_dn = olddn.bv_val;
+ }
+ }
+ ldap_free_urldesc( ludp );
+ }
+}
+
+void
+asyncmeta_dnattr_result_rewrite(
+ a_dncookie *dc,
+ BerVarray a_vals
+)
+{
+ struct berval bv;
+ int i;
+
+ assert( a_vals != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) {
+ asyncmeta_dn_massage( dc, &a_vals[i], &bv );
+ if ( bv.bv_val != a_vals[i].bv_val ) {
+ ber_memfree_x( a_vals[i].bv_val, dc->memctx );
+ a_vals[i] = bv;
+ }
+ }
+}
+
+/*
+ * asyncmeta_dn_massage
+ *
+ * Aliases the suffix.
+ */
+void
+asyncmeta_dn_massage(
+ a_dncookie *dc,
+ struct berval *odn,
+ struct berval *res
+)
+{
+ struct berval pretty = {0,NULL}, *dn = odn;
+ struct berval *osuff, *nsuff;
+ int diff;
+
+ assert( res );
+
+ BER_BVZERO(res);
+ if ( dn == NULL )
+ return;
+
+ /* no suffix massage configured */
+ if ( !dc->target->mt_lsuffixm.bv_val ) {
+ *res = *dn;
+ return;
+ }
+
+ if ( dc->to_from == MASSAGE_REQ ) {
+ osuff = &dc->target->mt_lsuffixm;
+ nsuff = &dc->target->mt_rsuffixm;
+ } else {
+ osuff = &dc->target->mt_rsuffixm;
+ nsuff = &dc->target->mt_lsuffixm;
+ /* DN from remote server may be in arbitrary form.
+ * Pretty it so we can parse reliably.
+ */
+ dnPretty( NULL, dn, &pretty, dc->op->o_tmpmemctx );
+ if (pretty.bv_val) dn = &pretty;
+ }
+
+ diff = dn->bv_len - osuff->bv_len;
+ /* DN is shorter than suffix - ignore */
+ if ( diff < 0 ) {
+ignore:
+ *res = *odn;
+ if (pretty.bv_val)
+ dc->op->o_tmpfree( pretty.bv_val, dc->op->o_tmpmemctx );
+ return;
+ }
+
+ /* DN longer than our suffix and doesn't match */
+ if ( osuff->bv_len != 0 && diff > 0 && !DN_SEPARATOR(dn->bv_val[diff-1]) )
+ goto ignore;
+
+ /* suffix is same length as ours, but doesn't match */
+ if ( strcasecmp( osuff->bv_val, &dn->bv_val[diff] ))
+ goto ignore;
+
+ /* if remote suffix is empty, remove or add the dn separator*/
+ if ( nsuff->bv_len == 0 ) {
+ diff--;
+ } else if ( osuff->bv_len == 0 ) {
+ diff++;
+ }
+
+
+ res->bv_len = diff + nsuff->bv_len;
+ res->bv_val = dc->op->o_tmpalloc( res->bv_len + 1, dc->memctx );
+ strncpy( res->bv_val, dn->bv_val, diff );
+ if ( osuff->bv_len == 0 )
+ res->bv_val[diff-1] = ',';
+ strcpy( &res->bv_val[diff], nsuff->bv_val );
+
+ if (pretty.bv_val)
+ dc->op->o_tmpfree( pretty.bv_val, dc->op->o_tmpmemctx );
+}
diff --git a/servers/slapd/back-asyncmeta/message_queue.c b/servers/slapd/back-asyncmeta/message_queue.c
new file mode 100644
index 0000000..29087c5
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/message_queue.c
@@ -0,0 +1,236 @@
+/* message_queue.c - routines to maintain the per-connection lists
+ * of pending operations */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+
+#include "lutil.h"
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "lutil.h"
+
+
+typedef struct listptr {
+ void *reserved;
+ struct listptr *next;
+} listptr;
+
+typedef struct listhead {
+ struct listptr *list;
+ int cnt;
+} listhead;
+
+#ifndef LH_MAX
+#define LH_MAX 16
+#endif
+
+static void asyncmeta_memctx_put(void *threadctx, void *memctx)
+{
+ slap_sl_mem_setctx(threadctx, NULL);
+ slap_sl_mem_destroy((void *)1, memctx);
+}
+
+int asyncmeta_new_bm_context(Operation *op,
+ SlapReply *rs,
+ bm_context_t **new_bc,
+ int ntargets,
+ a_metainfo_t *mi)
+{
+ int i;
+ *new_bc = op->o_tmpcalloc( 1, sizeof( bm_context_t ), op->o_tmpmemctx );
+
+ (*new_bc)->op = op;
+ (*new_bc)->copy_op = *op;
+ (*new_bc)->candidates = op->o_tmpcalloc(ntargets, sizeof(SlapReply),op->o_tmpmemctx);
+ (*new_bc)->msgids = op->o_tmpcalloc(ntargets, sizeof(int),op->o_tmpmemctx);
+ (*new_bc)->nretries = op->o_tmpcalloc(ntargets, sizeof(int),op->o_tmpmemctx);
+ (*new_bc)->c_peer_name = op->o_conn->c_peer_name;
+ (*new_bc)->is_root = be_isroot( op );
+
+ switch(op->o_tag) {
+ case LDAP_REQ_COMPARE:
+ {
+ AttributeAssertion *ava = op->o_tmpcalloc( 1, sizeof(AttributeAssertion), op->o_tmpmemctx );
+ *ava = *op->orc_ava;
+ op->orc_ava = ava;
+ }
+ break;
+ case LDAP_REQ_MODRDN:
+ if (op->orr_newSup != NULL) {
+ struct berval *bv = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ *bv = *op->orr_newSup;
+ op->orr_newSup = bv;
+ }
+
+ if (op->orr_nnewSup != NULL) {
+ struct berval *bv = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx );
+ *bv = *op->orr_nnewSup;
+ op->orr_nnewSup = bv;
+ }
+ break;
+ default:
+ break;
+ }
+ for (i = 0; i < ntargets; i++) {
+ (*new_bc)->msgids[i] = META_MSGID_UNDEFINED;
+ }
+ for (i = 0; i < ntargets; i++) {
+ (*new_bc)->nretries[i] = mi->mi_targets[i]->mt_nretries;
+ }
+ return LDAP_SUCCESS;
+}
+
+void asyncmeta_free_op(Operation *op)
+{
+ assert (op != NULL);
+ switch (op->o_tag) {
+ case LDAP_REQ_SEARCH:
+ break;
+ case LDAP_REQ_ADD:
+ if ( op->ora_modlist != NULL ) {
+ slap_mods_free(op->ora_modlist, 0 );
+ }
+
+ if ( op->ora_e != NULL ) {
+ entry_free( op->ora_e );
+ }
+
+ break;
+ case LDAP_REQ_MODIFY:
+ if ( op->orm_modlist != NULL ) {
+ slap_mods_free(op->orm_modlist, 1 );
+ }
+ break;
+ case LDAP_REQ_MODRDN:
+ if ( op->orr_modlist != NULL ) {
+ slap_mods_free(op->orr_modlist, 1 );
+ }
+ break;
+ case LDAP_REQ_COMPARE:
+ break;
+ case LDAP_REQ_DELETE:
+ break;
+ default:
+ Debug( LDAP_DEBUG_TRACE, "==> asyncmeta_free_op : other message type" );
+ }
+
+ connection_op_finish( op, 1 );
+ slap_op_free( op, op->o_threadctx );
+}
+
+
+
+
+void asyncmeta_clear_bm_context(bm_context_t *bc)
+{
+
+ Operation *op = bc->op;
+ void *thrctx, *memctx;
+ int i;
+
+ if ( bc->bc_mc && bc->bc_mc->mc_info ) {
+ for (i = 0; i < bc->bc_mc->mc_info->mi_ntargets; i++) {
+ if (bc->candidates[ i ].sr_text != NULL) {
+ ch_free( (char *)bc->candidates[ i ].sr_text );
+ bc->candidates[ i ].sr_text = NULL;
+ }
+ }
+ }
+
+ if (op->o_conn->c_conn_idx == -1)
+ return;
+ memctx = op->o_tmpmemctx;
+ thrctx = op->o_threadctx;
+ while (op->o_bd == bc->copy_op.o_bd)
+ ldap_pvt_thread_yield();
+ asyncmeta_free_op(op);
+ asyncmeta_memctx_put(thrctx, memctx);
+}
+
+int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug( LDAP_DEBUG_TRACE, "add_message_queue: mc %p, pending_ops %d, max_pending %d\n",
+ mc, mc->pending_ops, max_pending_ops );
+
+ assert(bc->bc_mc == NULL);
+ if (mc->pending_ops >= max_pending_ops) {
+ return LDAP_BUSY;
+ }
+ bc->bc_mc = mc;
+
+ slap_sl_mem_setctx(bc->op->o_threadctx, NULL);
+ LDAP_STAILQ_INSERT_TAIL( &mc->mc_om_list, bc, bc_next);
+ mc->pending_ops++;
+ return LDAP_SUCCESS;
+}
+
+
+void
+asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om == bc) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, om, bm_context_t, bc_next);
+ mc->pending_ops--;
+ break;
+ }
+ }
+ assert(om == bc);
+ assert(bc->bc_mc == mc);
+}
+
+
+bm_context_t *
+asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om->candidates[candidate].sr_msgid == msgid && !om->bc_invalid) {
+ break;
+ }
+ }
+ return om;
+}
+
+bm_context_t *
+asyncmeta_bc_in_queue(a_metaconn_t *mc, bm_context_t *bc)
+{
+ bm_context_t *om;
+ LDAP_STAILQ_FOREACH( om, &mc->mc_om_list, bc_next ) {
+ if (om == bc) {
+ return bc;
+ }
+ }
+ return NULL;
+}
diff --git a/servers/slapd/back-asyncmeta/meta_result.c b/servers/slapd/back-asyncmeta/meta_result.c
new file mode 100644
index 0000000..0ce279a
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/meta_result.c
@@ -0,0 +1,1825 @@
+/* meta_result.c - target responses processing */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+
+#include "slap.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+#include "ldap_rq.h"
+#include "../../../libraries/liblber/lber-int.h"
+
+static void
+asyncmeta_send_ldap_result(bm_context_t *bc, Operation *op, SlapReply *rs)
+{
+ if (bc->c_peer_name.bv_val == op->o_conn->c_peer_name.bv_val && !bc->op->o_abandon ) {
+ send_ldap_result(&bc->copy_op, rs);
+ bc->op->o_callback = bc->copy_op.o_callback;
+ bc->op->o_extra = bc->copy_op.o_extra;
+ bc->op->o_ctrls = bc->copy_op.o_ctrls;
+ }
+}
+
+static int
+asyncmeta_is_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int i;
+ SlapReply *candidates = bc->candidates;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+ if (candidates[ i ].sr_msgid != META_MSGID_IGNORE ||
+ candidates[ i ].sr_type != REP_RESULT) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+meta_search_candidate_t
+asyncmeta_dobind_result(
+ a_metaconn_t *mc,
+ int candidate,
+ SlapReply *bind_result,
+ LDAPMessage *res )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+
+ meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE;
+ int rc;
+
+ assert( msc->msc_ldr != NULL );
+
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%x] [%s] asyncmeta_dobind_result msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x\n ",
+ (unsigned int)slap_get_time(), time_buf, msc,
+ (unsigned int)msc->msc_binding_time, msc->msc_mscflags );
+ }
+ /* FIXME: matched? referrals? response controls? */
+ rc = ldap_parse_result( msc->msc_ldr, res,
+ &(bind_result->sr_err),
+ (char **)&(bind_result->sr_matched),
+ (char **)&(bind_result->sr_text),
+ NULL, NULL, 0 );
+
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug,
+ "[%s] asyncmeta_dobind_result error=%d msc: %p\n",
+ time_buf,bind_result->sr_err, msc );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ bind_result->sr_err = rc;
+ }
+ rc = slap_map_api2result( bind_result );
+
+ LDAP_BACK_CONN_BINDING_CLEAR( msc );
+ if ( rc != LDAP_SUCCESS ) {
+ bind_result->sr_err = rc;
+ } else {
+ /* FIXME: check if bound as idassert authcDN! */
+ if ( BER_BVISNULL( &msc->msc_bound_ndn )
+ || BER_BVISEMPTY( &msc->msc_bound_ndn ) )
+ {
+ LDAP_BACK_CONN_ISANON_SET( msc );
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_result anonymous msc: %p\n",
+ time_buf, msc );
+ }
+
+ } else {
+ if ( META_BACK_TGT_SAVECRED( mt ) &&
+ !BER_BVISNULL( &msc->msc_cred ) &&
+ !BER_BVISEMPTY( &msc->msc_cred ) )
+ {
+ ldap_set_rebind_proc( msc->msc_ldr, mt->mt_rebind_f, msc );
+ }
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_dobind_result success msc: %p\n",
+ time_buf, msc );
+ }
+ LDAP_BACK_CONN_ISBOUND_SET( msc );
+ }
+ retcode = META_SEARCH_CANDIDATE;
+ }
+ return retcode;
+}
+
+static int
+asyncmeta_send_entry(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ int target,
+ LDAPMessage *e )
+{
+ a_metainfo_t *mi = mc->mc_info;
+ struct berval a, mapped = BER_BVNULL;
+ int check_sorted_attrs = 0;
+ Entry ent = {0};
+ BerElement ber = *ldap_get_message_ber( e );
+ Attribute *attr, **attrp;
+ struct berval bdn,
+ dn = BER_BVNULL;
+ const char *text;
+ a_dncookie dc;
+ ber_len_t len;
+ int rc;
+ void *mem_mark;
+
+ mem_mark = slap_sl_mark( op->o_tmpmemctx );
+ ber_set_option( &ber, LBER_OPT_BER_MEMCTX, &op->o_tmpmemctx );
+
+ if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) {
+ return LDAP_OTHER;
+ }
+
+ if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) {
+ return LDAP_DECODING_ERROR;
+ }
+
+ /*
+ * Rewrite the dn of the result, if needed
+ */
+ dc.op = op;
+ dc.target = mi->mi_targets[ target ];
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REP;
+ asyncmeta_dn_massage( &dc, &bdn, &dn );
+
+ /*
+ * Note: this may fail if the target host(s) schema differs
+ * from the one known to the meta, and a DN with unknown
+ * attributes is returned.
+ *
+ * FIXME: should we log anything, or delegate to dnNormalize?
+ */
+ rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname,
+ op->o_tmpmemctx );
+ if ( dn.bv_val != bdn.bv_val ) {
+ op->o_tmpfree( dn.bv_val, op->o_tmpmemctx );
+ }
+ BER_BVZERO( &dn );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ "invalid DN syntax\n",
+ op->o_log_prefix, ent.e_name.bv_val );
+ rc = LDAP_INVALID_DN_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * cache dn
+ */
+ if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) {
+ ( void )asyncmeta_dncache_update_entry( &mi->mi_cache,
+ &ent.e_nname, target );
+ }
+
+ attrp = &ent.e_attrs;
+
+ while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) {
+ int last = 0;
+ slap_syntax_validate_func *validate;
+ slap_syntax_transform_func *pretty;
+
+ if ( ber_pvt_ber_remaining( &ber ) < 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ "unable to parse attr \"%s\".\n",
+ op->o_log_prefix, ent.e_name.bv_val, a.bv_val );
+
+ rc = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( ber_pvt_ber_remaining( &ber ) == 0 ) {
+ break;
+ }
+
+ attr = op->o_tmpcalloc( 1, sizeof(Attribute), op->o_tmpmemctx );
+ if ( slap_bv2ad( &a, &attr->a_desc, &text )
+ != LDAP_SUCCESS) {
+ if ( slap_bv2undef_ad( &a, &attr->a_desc, &text,
+ SLAP_AD_PROXIED ) != LDAP_SUCCESS )
+ {
+ Debug(LDAP_DEBUG_ANY,
+ "%s meta_send_entry(\"%s\"): " "slap_bv2undef_ad(%s): %s\n",
+ op->o_log_prefix, ent.e_name.bv_val,
+ mapped.bv_val, text );
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ continue;
+ }
+ }
+
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL )
+ check_sorted_attrs = 1;
+
+ /* no subschemaSubentry */
+ if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry
+ || attr->a_desc == slap_schema.si_ad_entryDN )
+ {
+
+ /*
+ * We eat target's subschemaSubentry because
+ * a search for this value is likely not
+ * to resolve to the appropriate backend;
+ * later, the local subschemaSubentry is
+ * added.
+ *
+ * We also eat entryDN because the frontend
+ * will reattach it without checking if already
+ * present...
+ */
+ ( void )ber_scanf( &ber, "x" /* [W] */ );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ continue;
+ }
+
+ if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR
+ || attr->a_vals == NULL )
+ {
+ attr->a_vals = (struct berval *)&slap_dummy_bv;
+
+ } else {
+ for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last )
+ ;
+ }
+ attr->a_numvals = last;
+
+ validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate;
+ pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( !validate && !pretty ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+
+ /*
+ * It is necessary to try to rewrite attributes with
+ * dn syntax because they might be used in ACLs as
+ * members of groups; since ACLs are applied to the
+ * rewritten stuff, no dn-based subecj clause could
+ * be used at the ldap backend side (see
+ * http://www.OpenLDAP.org/faq/data/cache/452.html)
+ * The problem can be overcome by moving the dn-based
+ * ACLs to the target directory server, and letting
+ * everything pass thru the ldap backend.
+ */
+ {
+ int i;
+
+ if ( attr->a_desc->ad_type->sat_syntax ==
+ slap_schema.si_syn_distinguishedName )
+ {
+ asyncmeta_dnattr_result_rewrite( &dc, attr->a_vals );
+
+ } else if ( attr->a_desc == slap_schema.si_ad_ref ) {
+ asyncmeta_referral_result_rewrite( &dc, attr->a_vals );
+
+ }
+
+ for ( i = 0; i < last; i++ ) {
+ struct berval pval;
+ int rc;
+
+ if ( pretty ) {
+ rc = ordered_value_pretty( attr->a_desc,
+ &attr->a_vals[i], &pval, op->o_tmpmemctx );
+
+ } else {
+ rc = ordered_value_validate( attr->a_desc,
+ &attr->a_vals[i], 0 );
+ }
+
+ if ( rc ) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ continue;
+ }
+
+ if ( pretty ) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ attr->a_vals[i] = pval;
+ }
+ }
+
+ if ( last == 0 && attr->a_vals != &slap_dummy_bv ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+ }
+
+ if ( last && attr->a_desc->ad_type->sat_equality &&
+ attr->a_desc->ad_type->sat_equality->smr_normalize )
+ {
+ int i;
+
+ attr->a_nvals = op->o_tmpalloc( ( last + 1 ) * sizeof( struct berval ), op->o_tmpmemctx );
+ for ( i = 0; i<last; i++ ) {
+ /* if normalizer fails, drop this value */
+ if ( ordered_value_normalize(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ attr->a_desc,
+ attr->a_desc->ad_type->sat_equality,
+ &attr->a_vals[i], &attr->a_nvals[i],
+ op->o_tmpmemctx )) {
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ if ( --last == i ) {
+ BER_BVZERO( &attr->a_vals[ i ] );
+ break;
+ }
+ attr->a_vals[i] = attr->a_vals[last];
+ BER_BVZERO( &attr->a_vals[last] );
+ i--;
+ }
+ }
+ BER_BVZERO( &attr->a_nvals[i] );
+ if ( last == 0 ) {
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ ber_bvarray_free_x( attr->a_nvals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ goto next_attr;
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+
+ attr->a_numvals = last;
+ *attrp = attr;
+ attrp = &attr->a_next;
+next_attr:;
+ }
+
+ /* Check for sorted attributes */
+ if ( check_sorted_attrs ) {
+ for ( attr = ent.e_attrs; attr; attr = attr->a_next ) {
+ if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) {
+ while ( attr->a_numvals > 1 ) {
+ int i;
+ int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx );
+ if ( rc != LDAP_TYPE_OR_VALUE_EXISTS )
+ break;
+
+ /* Strip duplicate values */
+ if ( attr->a_nvals != attr->a_vals )
+ ber_memfree_x( attr->a_nvals[i].bv_val, op->o_tmpmemctx );
+ ber_memfree_x( attr->a_vals[i].bv_val, op->o_tmpmemctx );
+ attr->a_numvals--;
+ if ( (unsigned)i < attr->a_numvals ) {
+ attr->a_vals[i] = attr->a_vals[attr->a_numvals];
+ if ( attr->a_nvals != attr->a_vals )
+ attr->a_nvals[i] = attr->a_nvals[attr->a_numvals];
+ }
+ BER_BVZERO(&attr->a_vals[attr->a_numvals]);
+ if ( attr->a_nvals != attr->a_vals )
+ BER_BVZERO(&attr->a_nvals[attr->a_numvals]);
+ }
+ attr->a_flags |= SLAP_ATTR_SORTED_VALS;
+ }
+ }
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_send_entry(\"%s\"): "
+ ".\n",
+ op->o_log_prefix, ent.e_name.bv_val );
+ ldap_get_entry_controls( mc->mc_conns[target].msc_ldr,
+ e, &rs->sr_ctrls );
+ rs->sr_entry = &ent;
+ rs->sr_attrs = op->ors_attrs;
+ rs->sr_operational_attrs = NULL;
+ rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags;
+ rs->sr_err = LDAP_SUCCESS;
+ rc = send_search_entry( op, rs );
+ switch ( rc ) {
+ case LDAP_UNAVAILABLE:
+ rc = LDAP_OTHER;
+ break;
+ }
+
+done:;
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+#if 0
+ while ( ent.e_attrs ) {
+ attr = ent.e_attrs;
+ ent.e_attrs = attr->a_next;
+ if ( attr->a_nvals != attr->a_vals )
+ ber_bvarray_free_x( attr->a_nvals, op->o_tmpmemctx );
+ ber_bvarray_free_x( attr->a_vals, op->o_tmpmemctx );
+ op->o_tmpfree( attr, op->o_tmpmemctx );
+ }
+ if (ent.e_name.bv_val != NULL) {
+ op->o_tmpfree( ent.e_name.bv_val, op->o_tmpmemctx );
+ }
+
+ if (ent.e_nname.bv_val != NULL) {
+ op->o_tmpfree( ent.e_nname.bv_val, op->o_tmpmemctx );
+ }
+ if (rs->sr_entry && rs->sr_entry != &ent) {
+ entry_free( rs->sr_entry );
+ }
+#endif
+ slap_sl_release( mem_mark, op->o_tmpmemctx );
+ rs->sr_entry = NULL;
+ rs->sr_attrs = NULL;
+ return rc;
+}
+
+static void
+asyncmeta_search_last_result(a_metaconn_t *mc, bm_context_t *bc, int candidate, int sres)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ Operation *op = bc->op;
+ SlapReply *rs = &bc->rs;
+ int i;
+ SlapReply *candidates = bc->candidates;
+ char *matched = NULL;
+
+ if ( bc->candidate_match > 0 ) {
+ struct berval pmatched = BER_BVNULL;
+
+ /* we use the first one */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_matched != NULL )
+ {
+ struct berval bv, pbv;
+ int rc;
+
+ /* if we got success, and this target
+ * returned noSuchObject, and its suffix
+ * is a superior of the searchBase,
+ * ignore the matchedDN */
+ if ( sres == LDAP_SUCCESS
+ && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT
+ && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len )
+ {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ continue;
+ }
+
+ ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv );
+ rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx );
+
+ if ( rc == LDAP_SUCCESS ) {
+
+ /* NOTE: if they all are superiors
+ * of the baseDN, the shorter is also
+ * superior of the longer... */
+ if ( pbv.bv_len > pmatched.bv_len ) {
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx );
+ }
+ pmatched = pbv;
+
+ } else {
+ op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx );
+ }
+ }
+
+ if ( candidates[ i ].sr_matched != NULL ) {
+ free( (char *)candidates[ i ].sr_matched );
+ candidates[ i ].sr_matched = NULL;
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &pmatched ) ) {
+ matched = pmatched.bv_val;
+ }
+
+ } else if ( sres == LDAP_NO_SUCH_OBJECT ) {
+ matched = mi->mi_suffix.bv_val;
+ }
+
+ /*
+ * In case we returned at least one entry, we return LDAP_SUCCESS
+ * otherwise, the latter error code we got
+ */
+
+ if ( sres == LDAP_SUCCESS ) {
+ if ( rs->sr_v2ref ) {
+ sres = LDAP_REFERRAL;
+ }
+
+ if ( META_BACK_ONERR_REPORT( mi ) ) {
+ /*
+ * Report errors, if any
+ *
+ * FIXME: we should handle error codes and return the more
+ * important/reasonable
+ */
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ if ( candidates[ i ].sr_err != LDAP_SUCCESS
+ && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT )
+ {
+ sres = candidates[ i ].sr_err;
+ break;
+ }
+ }
+ }
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_search_last_result(\"%d\"): "
+ ".\n",
+ op->o_log_prefix, candidate );
+ rs->sr_err = sres;
+ rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched );
+ rs->sr_text = ( sres == LDAP_SUCCESS ? NULL : candidates[candidate].sr_text );
+ rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL );
+ asyncmeta_send_ldap_result(bc, op, rs);
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+}
+
+static meta_search_candidate_t
+asyncmeta_send_pending_op(bm_context_t *bc, int candidate)
+{
+ meta_search_candidate_t retcode;
+ switch (bc->op->o_tag) {
+ case LDAP_REQ_SEARCH:
+ retcode = asyncmeta_back_search_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, NULL, 0 , 0);
+ break;
+ case LDAP_REQ_ADD:
+ retcode = asyncmeta_back_add_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_MODIFY:
+ retcode = asyncmeta_back_modify_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_MODRDN:
+ retcode = asyncmeta_back_modrdn_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_COMPARE:
+ retcode = asyncmeta_back_compare_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ case LDAP_REQ_DELETE:
+ retcode = asyncmeta_back_delete_start( &bc->copy_op, &bc->rs, bc->bc_mc, bc, candidate, 0);
+ break;
+ default:
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ }
+ return retcode;
+}
+
+
+meta_search_candidate_t
+asyncmeta_send_all_pending_ops(a_metaconn_t *mc, int candidate, void *ctx, int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ bm_context_t *bc, *onext;
+ a_metasingleconn_t *msc = &mc->mc_conns[candidate];
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+
+ msc->msc_active++;
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ meta_search_candidate_t ret;
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->candidates[candidate].sr_msgid == META_MSGID_NEED_BIND)
+ bc->candidates[candidate].sr_msgid = META_MSGID_GOT_BIND;
+ if (bc->candidates[candidate].sr_msgid != META_MSGID_GOT_BIND || bc->bc_active > 0 || bc->op->o_abandon > 0) {
+ continue;
+ }
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ bc->bc_active++;
+ ret = asyncmeta_send_pending_op(bc, candidate);
+ if (ret != META_SEARCH_CANDIDATE) {
+ bc->candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ bc->candidates[ candidate ].sr_type = REP_RESULT;
+ bc->candidates[ candidate ].sr_err = bc->rs.sr_err;
+ if (bc->op->o_tag != LDAP_REQ_SEARCH || (META_BACK_ONERR_STOP( mi )) ||
+ (asyncmeta_is_last_result(mc, bc, candidate) == 0)) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ asyncmeta_send_ldap_result(bc, bc->op, &bc->rs);
+ asyncmeta_clear_bm_context(bc);
+ }
+ } else {
+ bc->bc_active--;
+ }
+ }
+ msc->msc_active--;
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return META_SEARCH_CANDIDATE;
+}
+
+meta_search_candidate_t
+asyncmeta_return_bind_errors(a_metaconn_t *mc, int candidate, SlapReply *bind_result, void *ctx, int dolock)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ bm_context_t *bc, *onext;
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->candidates[candidate].sr_msgid != META_MSGID_NEED_BIND
+ || bc->bc_active > 0 || bc->op->o_abandon > 0) {
+ continue;
+ }
+ bc->candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ bc->candidates[ candidate ].sr_type = REP_RESULT;
+ bc->candidates[ candidate ].sr_err = bind_result->sr_err;
+ if (bc->op->o_tag != LDAP_REQ_SEARCH || (META_BACK_ONERR_STOP( mi )) ||
+ (asyncmeta_is_last_result(mc, bc, candidate) == 0)) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ bc->rs.sr_err = bind_result->sr_err;
+ bc->rs.sr_text = bind_result->sr_text;
+ mc->pending_ops--;
+ asyncmeta_send_ldap_result(bc, bc->op, &bc->rs);
+ asyncmeta_clear_bm_context(bc);
+ }
+ }
+
+ if ( dolock )
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return META_SEARCH_CANDIDATE;
+}
+
+static meta_search_candidate_t
+asyncmeta_handle_bind_result(LDAPMessage *msg, a_metaconn_t *mc, int candidate, void *ctx)
+{
+ meta_search_candidate_t retcode;
+ SlapReply bind_result = {0};
+ /* could modify the msc, safer to lock it */
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ retcode = asyncmeta_dobind_result( mc, candidate, &bind_result, msg );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if ( retcode == META_SEARCH_CANDIDATE ) {
+ /* send the remaining pending ops */
+ asyncmeta_send_all_pending_ops(mc, candidate, ctx, 1);
+ } else {
+ asyncmeta_return_bind_errors(mc, candidate, &bind_result, ctx, 1);
+ }
+ return retcode;
+}
+
+int
+asyncmeta_handle_search_msg(LDAPMessage *res, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi;
+ a_metatarget_t *mt;
+ a_metasingleconn_t *msc;
+ Operation *op = bc->op;
+ SlapReply *rs;
+ int i, rc = LDAP_SUCCESS, sres;
+ SlapReply *candidates;
+ char **references = NULL;
+ LDAPControl **ctrls = NULL;
+ a_dncookie dc;
+ LDAPMessage *msg;
+ ber_int_t id;
+
+ rs = &bc->rs;
+ mi = mc->mc_info;
+ mt = mi->mi_targets[ candidate ];
+ msc = &mc->mc_conns[ candidate ];
+ dc.op = op;
+ dc.target = mt;
+ dc.to_from = MASSAGE_REP;
+ id = ldap_msgid(res);
+
+
+ candidates = bc->candidates;
+ i = candidate;
+
+ while (res && !META_BACK_CONN_INVALID(msc)) {
+ for (msg = ldap_first_message(msc->msc_ldr, res); msg; msg = ldap_next_message(msc->msc_ldr, msg)) {
+ switch(ldap_msgtype(msg)) {
+ case LDAP_RES_SEARCH_ENTRY:
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p entry\n",
+ op->o_log_prefix, msc );
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ /* count entries returned by target */
+ candidates[ i ].sr_nentries++;
+ if (bc->c_peer_name.bv_val == op->o_conn->c_peer_name.bv_val && !op->o_abandon) {
+ rs->sr_err = asyncmeta_send_entry( &bc->copy_op, rs, mc, i, msg );
+ } else {
+ goto err_cleanup;
+ }
+ switch ( rs->sr_err ) {
+ case LDAP_SIZELIMIT_EXCEEDED:
+ asyncmeta_send_ldap_result(bc, op, rs);
+ rs->sr_err = LDAP_SUCCESS;
+ goto err_cleanup;
+ case LDAP_UNAVAILABLE:
+ rs->sr_err = LDAP_OTHER;
+ break;
+ default:
+ break;
+ }
+ bc->is_ok++;
+ break;
+
+ case LDAP_RES_SEARCH_REFERENCE:
+ if ( META_BACK_TGT_NOREFS( mt ) ) {
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ bc->is_ok++;
+ rc = ldap_parse_reference( msc->msc_ldr, msg,
+ &references, &rs->sr_ctrls, 0 );
+
+ if ( rc != LDAP_SUCCESS || references == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+
+ /* FIXME: merge all and return at the end */
+
+ {
+ int cnt;
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &rs->sr_ref[ cnt ] );
+ }
+
+ {
+ dc.memctx = op->o_tmpmemctx;
+ ( void )asyncmeta_referral_result_rewrite( &dc, rs->sr_ref );
+ }
+
+ if ( rs->sr_ref != NULL ) {
+ if (!BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) {
+ /* ignore return value by now */
+ ( void )send_search_reference( op, rs );
+ }
+
+ ber_bvarray_free_x( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+
+ /* cleanup */
+ if ( references ) {
+ ber_memvfree( (void **)references );
+ }
+
+ if ( rs->sr_ctrls ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ break;
+
+ case LDAP_RES_INTERMEDIATE:
+ if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) {
+ /* don't retry any more... */
+ candidates[ i ].sr_type = REP_RESULT;
+ }
+ bc->is_ok++;
+
+ /* FIXME: response controls
+ * are passed without checks */
+ rs->sr_err = ldap_parse_intermediate( msc->msc_ldr,
+ msg,
+ (char **)&rs->sr_rspoid,
+ &rs->sr_rspdata,
+ &rs->sr_ctrls,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_type = REP_RESULT;
+ rs->sr_err = LDAP_OTHER;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ goto err_cleanup;
+ }
+
+ slap_send_ldap_intermediate( op, rs );
+
+ if ( rs->sr_rspoid != NULL ) {
+ ber_memfree( (char *)rs->sr_rspoid );
+ rs->sr_rspoid = NULL;
+ }
+
+ if ( rs->sr_rspdata != NULL ) {
+ ber_bvfree( rs->sr_rspdata );
+ rs->sr_rspdata = NULL;
+ }
+
+ if ( rs->sr_ctrls != NULL ) {
+ ldap_controls_free( rs->sr_ctrls );
+ rs->sr_ctrls = NULL;
+ }
+ break;
+
+ case LDAP_RES_SEARCH_RESULT:
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p result\n",
+ op->o_log_prefix, msc );
+ candidates[ i ].sr_type = REP_RESULT;
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ /* NOTE: ignores response controls
+ * (and intermediate response controls
+ * as well, except for those with search
+ * references); this may not be correct,
+ * but if they're not ignored then
+ * back-meta would need to merge them
+ * consistently (think of pagedResults...)
+ */
+ /* FIXME: response controls? */
+ rs->sr_err = ldap_parse_result( msc->msc_ldr,
+ msg,
+ &candidates[ i ].sr_err,
+ (char **)&candidates[ i ].sr_matched,
+ (char **)&candidates[ i ].sr_text,
+ &references,
+ &ctrls /* &candidates[ i ].sr_ctrls (unused) */ ,
+ 0 );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ candidates[ i ].sr_err = rs->sr_err;
+ sres = slap_map_api2result( &candidates[ i ] );
+ candidates[ i ].sr_type = REP_RESULT;
+ goto finish;
+ }
+
+ rs->sr_err = candidates[ i ].sr_err;
+
+ /* massage matchedDN if need be */
+ if ( candidates[ i ].sr_matched != NULL ) {
+ struct berval match, mmatch;
+
+ ber_str2bv( candidates[ i ].sr_matched,
+ 0, 0, &match );
+ candidates[ i ].sr_matched = NULL;
+
+ dc.memctx = NULL;
+ asyncmeta_dn_massage( &dc, &match, &mmatch );
+ if ( mmatch.bv_val == match.bv_val ) {
+ candidates[ i ].sr_matched
+ = ch_strdup( mmatch.bv_val );
+
+ } else {
+ candidates[ i ].sr_matched = mmatch.bv_val;
+ }
+
+ bc->candidate_match++;
+ ldap_memfree( match.bv_val );
+ }
+
+ /* add references to array */
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( references != NULL
+ && references[ 0 ] != NULL
+ && references[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asncmeta_search_result[%d]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ } else {
+ BerVarray sr_ref;
+ int cnt;
+
+ for ( cnt = 0; references[ cnt ]; cnt++ )
+ ;
+
+ sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( cnt + 1 ),
+ op->o_tmpmemctx );
+
+ for ( cnt = 0; references[ cnt ]; cnt++ ) {
+ ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ BER_BVZERO( &sr_ref[ cnt ] );
+
+ dc.memctx = op->o_tmpmemctx;
+ ( void )asyncmeta_referral_result_rewrite( &dc, sr_ref );
+
+ if ( rs->sr_v2ref == NULL ) {
+ rs->sr_v2ref = sr_ref;
+
+ } else {
+ for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) {
+ ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ],
+ op->o_tmpmemctx );
+ }
+ ber_memfree_x( sr_ref, op->o_tmpmemctx );
+ }
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_search_result[%d]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ i, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ /* cleanup */
+ ber_memvfree( (void **)references );
+
+ sres = slap_map_api2result( rs );
+
+ if ( candidates[ i ].sr_err == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_search_result[%d] "
+ "match=\"%s\" err=%ld\n",
+ op->o_log_prefix, i,
+ candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+ (long) candidates[ i ].sr_err );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_search_result[%d] "
+ "match=\"%s\" err=%ld (%s)\n",
+ op->o_log_prefix, i,
+ candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "",
+ (long) candidates[ i ].sr_err, ldap_err2string( candidates[ i ].sr_err ) );
+ }
+
+ switch ( sres ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* is_ok is touched any time a valid
+ * (even intermediate) result is
+ * returned; as a consequence, if
+ * a candidate returns noSuchObject
+ * it is ignored and the candidate
+ * is simply demoted. */
+ if ( bc->is_ok ) {
+ sres = LDAP_SUCCESS;
+ }
+ break;
+
+ case LDAP_SUCCESS:
+ if ( ctrls != NULL && ctrls[0] != NULL ) {
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl *pr_c;
+
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL );
+ if ( pr_c != NULL ) {
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ ber_tag_t tag;
+ ber_int_t prsize;
+ struct berval prcookie;
+
+ /* unsolicited, do not accept */
+ if ( mt->mt_ps == 0 ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER );
+
+ tag = ber_scanf( ber, "{im}", &prsize, &prcookie );
+ if ( tag == LBER_ERROR ) {
+ rs->sr_err = LDAP_OTHER;
+ goto err_pr;
+ }
+
+ /* more pages? new search request */
+ if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) {
+ if ( mt->mt_ps > 0 ) {
+ /* ignore size if specified */
+ prsize = 0;
+
+ } else if ( prsize == 0 ) {
+ /* guess the page size from the entries returned so far */
+ prsize = candidates[ i ].sr_nentries;
+ }
+
+ candidates[ i ].sr_nentries = 0;
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_INTERMEDIATE;
+
+ assert( candidates[ i ].sr_matched == NULL );
+ assert( candidates[ i ].sr_text == NULL );
+ assert( candidates[ i ].sr_ref == NULL );
+
+ switch ( asyncmeta_back_search_start( &bc->copy_op, rs, mc, bc, i, &prcookie, prsize, 1 ) )
+ {
+ case META_SEARCH_CANDIDATE:
+ assert( candidates[ i ].sr_msgid >= 0 );
+ ldap_controls_free( ctrls );
+ // goto free_message;
+
+ case META_SEARCH_ERR:
+ case META_SEARCH_NEED_BIND:
+err_pr:;
+ candidates[ i ].sr_err = rs->sr_err;
+ candidates[ i ].sr_type = REP_RESULT;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_send_ldap_result(bc, op, rs);
+ ldap_controls_free( ctrls );
+ goto err_cleanup;
+ }
+ /* fallthru */
+
+ case META_SEARCH_NOT_CANDIDATE:
+ /* means that asyncmeta_back_search_start()
+ * failed but onerr == continue */
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_RESULT;
+ break;
+
+ default:
+ /* impossible */
+ assert( 0 );
+ break;
+ }
+ break;
+ }
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ ldap_controls_free( ctrls );
+ }
+ /* fallthru */
+
+ case LDAP_REFERRAL:
+ bc->is_ok++;
+ break;
+
+ case LDAP_SIZELIMIT_EXCEEDED:
+ /* if a target returned sizelimitExceeded
+ * and the entry count is equal to the
+ * proxy's limit, the target would have
+ * returned more, and the error must be
+ * propagated to the client; otherwise,
+ * the target enforced a limit lower
+ * than what requested by the proxy;
+ * ignore it */
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( rs->sr_nentries == op->ors_slimit
+ || META_BACK_ONERR_STOP( mi ) )
+ {
+ const char *save_text;
+got_err:
+ save_text = rs->sr_text;
+ rs->sr_text = candidates[ i ].sr_text;
+ asyncmeta_send_ldap_result(bc, op, rs);
+ if (candidates[ i ].sr_text != NULL) {
+ ch_free( (char *)candidates[ i ].sr_text );
+ candidates[ i ].sr_text = NULL;
+ }
+ rs->sr_text = save_text;
+ ldap_controls_free( ctrls );
+ goto err_cleanup;
+ }
+ break;
+
+ default:
+ candidates[ i ].sr_err = rs->sr_err;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ goto got_err;
+ }
+ break;
+ }
+ /* if this is the last result we will ever receive, send it back */
+ rc = rs->sr_err;
+ if (asyncmeta_is_last_result(mc, bc, i) == 0) {
+ Debug( LDAP_DEBUG_TRACE,
+ "%s asyncmeta_handle_search_msg: msc %p last result\n",
+ op->o_log_prefix, msc );
+ asyncmeta_search_last_result(mc, bc, i, sres);
+err_cleanup:
+ rc = rs->sr_err;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ ldap_msgfree(res);
+ return rc;
+ }
+finish:
+ break;
+
+ default:
+ continue;
+ }
+ }
+ ldap_msgfree(res);
+ res = NULL;
+ if (candidates[ i ].sr_type != REP_RESULT) {
+ struct timeval tv = {0};
+ rc = ldap_result( msc->msc_ldr, id, LDAP_MSG_RECEIVED, &tv, &res );
+ if (res != NULL) {
+ msc->msc_result_time = slap_get_time();
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ return rc;
+}
+
+/* handles the received result for add, modify, modrdn, compare and delete ops */
+
+int asyncmeta_handle_common_result(LDAPMessage *msg, a_metaconn_t *mc, bm_context_t *bc, int candidate)
+{
+ a_metainfo_t *mi;
+ a_metatarget_t *mt;
+ a_metasingleconn_t *msc;
+ const char *save_text = NULL,
+ *save_matched = NULL;
+ BerVarray save_ref = NULL;
+ LDAPControl **save_ctrls = NULL;
+ void *matched_ctx = NULL;
+
+ char *matched = NULL;
+ char *text = NULL;
+ char **refs = NULL;
+ LDAPControl **ctrls = NULL;
+ Operation *op;
+ SlapReply *rs;
+ int rc;
+
+ mi = mc->mc_info;
+ mt = mi->mi_targets[ candidate ];
+ msc = &mc->mc_conns[ candidate ];
+
+ op = bc->op;
+ rs = &bc->rs;
+ save_text = rs->sr_text,
+ save_matched = rs->sr_matched;
+ save_ref = rs->sr_ref;
+ save_ctrls = rs->sr_ctrls;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ rs->sr_ref = NULL;
+ rs->sr_ctrls = NULL;
+
+ /* only touch when activity actually took place... */
+ if ( mi->mi_idle_timeout != 0 ) {
+ asyncmeta_set_msc_time(msc);
+ }
+
+ rc = ldap_parse_result( msc->msc_ldr, msg, &rs->sr_err,
+ &matched, &text, &refs, &ctrls, 0 );
+
+ if ( rc == LDAP_SUCCESS ) {
+ rs->sr_text = text;
+ } else {
+ rs->sr_err = rc;
+ }
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* RFC 4511: referrals can only appear
+ * if result code is LDAP_REFERRAL */
+ if ( refs != NULL
+ && refs[ 0 ] != NULL
+ && refs[ 0 ][ 0 ] != '\0' )
+ {
+ if ( rs->sr_err != LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_handle_common_result[%d]: "
+ "got referrals with err=%d\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ } else {
+ int i;
+
+ for ( i = 0; refs[ i ] != NULL; i++ )
+ /* count */ ;
+ rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ),
+ op->o_tmpmemctx );
+ for ( i = 0; refs[ i ] != NULL; i++ ) {
+ ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] );
+ }
+ BER_BVZERO( &rs->sr_ref[ i ] );
+ }
+
+ } else if ( rs->sr_err == LDAP_REFERRAL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s asyncmeta_handle_common_result[%d]: "
+ "got err=%d with null "
+ "or empty referrals\n",
+ op->o_log_prefix,
+ candidate, rs->sr_err );
+
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+
+ if ( ctrls != NULL ) {
+ rs->sr_ctrls = ctrls;
+ }
+
+ /* if the error in the reply structure is not
+ * LDAP_SUCCESS, try to map it from client
+ * to server error */
+ if ( !LDAP_ERR_OK( rs->sr_err ) ) {
+ rs->sr_err = slap_map_api2result( rs );
+
+ /* internal ops ( op->o_conn == NULL )
+ * must not reply to client */
+ if ( op->o_conn && !op->o_do_not_cache && matched ) {
+
+ /* record the (massaged) matched
+ * DN into the reply structure */
+ rs->sr_matched = matched;
+ }
+ }
+
+ if ( META_BACK_TGT_QUARANTINE( mt ) ) {
+ asyncmeta_quarantine( op, mi, rs, candidate );
+ }
+
+ if ( matched != NULL ) {
+ struct berval dn, pdn;
+
+ ber_str2bv( matched, 0, 0, &dn );
+ if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+ ldap_memfree( matched );
+ matched_ctx = op->o_tmpmemctx;
+ matched = pdn.bv_val;
+ }
+ rs->sr_matched = matched;
+ }
+
+ if ( rs->sr_err == LDAP_UNAVAILABLE || rs->sr_err == LDAP_SERVER_DOWN ) {
+ if ( rs->sr_text == NULL ) {
+ rs->sr_text = "Target is unavailable";
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ if ( op->o_conn ) {
+ asyncmeta_send_ldap_result(bc, op, rs);
+ }
+
+ if ( matched ) {
+ op->o_tmpfree( (char *)rs->sr_matched, matched_ctx );
+ }
+ if ( text ) {
+ ldap_memfree( text );
+ }
+ if ( rs->sr_ref ) {
+ op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx );
+ rs->sr_ref = NULL;
+ }
+ if ( refs ) {
+ ber_memvfree( (void **)refs );
+ }
+ if ( ctrls ) {
+ assert( rs->sr_ctrls != NULL );
+ ldap_controls_free( ctrls );
+ }
+
+ rs->sr_text = save_text;
+ rs->sr_matched = save_matched;
+ rs->sr_ref = save_ref;
+ rs->sr_ctrls = save_ctrls;
+ rc = (LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return rc;
+}
+
+/* This takes care to clean out the outbound queue in case we have a read error
+ * sending back responses to the client */
+int
+asyncmeta_op_read_error(a_metaconn_t *mc, int candidate, int error, void* ctx)
+{
+ bm_context_t *bc, *onext;
+ int cleanup;
+ Operation *op;
+ SlapReply *rs;
+ SlapReply *candidates;
+
+ /* no outstanding ops, nothing to do but log */
+ Debug( LDAP_DEBUG_TRACE,
+ "asyncmeta_op_read_error: ldr=%p, err=%d\n",
+ mc->mc_conns[candidate].msc_ldr, error );
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ /*someone may be trying to write */
+ if (mc->mc_conns[candidate].msc_active <= 1) {
+ asyncmeta_clear_one_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ } else {
+ META_BACK_CONN_INVALID_SET(&mc->mc_conns[candidate]);
+ }
+
+ if (mc->pending_ops <= 0) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return LDAP_SUCCESS;
+ }
+
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ cleanup = 0;
+ candidates = bc->candidates;
+ /* was this op affected? */
+ if ( !META_IS_CANDIDATE( &candidates[ candidate ] ) )
+ continue;
+
+ if (bc->op->o_abandon) {
+ bc->bc_invalid = 1;
+ continue;
+ }
+
+ if (bc->bc_active > 0) {
+ bc->bc_invalid = 1;
+ continue;
+ }
+
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+
+ op = bc->op;
+ rs = &bc->rs;
+ switch (op->o_tag) {
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_MODRDN:
+ case LDAP_REQ_COMPARE:
+ case LDAP_REQ_DELETE:
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Read error on connection to target";
+ asyncmeta_send_ldap_result( bc, op, rs );
+ cleanup = 1;
+ break;
+ case LDAP_REQ_SEARCH:
+ {
+ a_metainfo_t *mi = mc->mc_info;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Read error on connection to target";
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ candidate ].sr_type = REP_RESULT;
+ if ( (META_BACK_ONERR_STOP( mi ) ||
+ asyncmeta_is_last_result(mc, bc, candidate)) && op->o_conn) {
+ asyncmeta_send_ldap_result( bc, op, rs );
+ cleanup = 1;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (cleanup) {
+ int j;
+ a_metainfo_t *mi = mc->mc_info;
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (j != candidate && bc->candidates[j].sr_msgid >= 0
+ && mc->mc_conns[j].msc_ld != NULL) {
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ }
+ }
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ asyncmeta_clear_bm_context(bc);
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return LDAP_SUCCESS;
+}
+
+void *
+asyncmeta_op_handle_result(void *ctx, void *arg)
+{
+ a_metaconn_t *mc = arg;
+ int i, j, rc, ntargets;
+ struct timeval tv = {0};
+ LDAPMessage *msg;
+ a_metasingleconn_t *msc;
+ bm_context_t *bc;
+ void *oldctx;
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = ++mc->mc_active;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (rc > 1)
+ return NULL;
+
+ ntargets = mc->mc_info->mi_ntargets;
+ i = ntargets;
+ oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0); /* get existing memctx */
+
+again:
+ for (j=0; j<ntargets; j++) {
+ i++;
+ if (i >= ntargets) i = 0;
+ msc = &mc->mc_conns[i];
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ if (!mc->mc_conns[i].msc_ldr ||
+ META_BACK_CONN_CREATING( &mc->mc_conns[i] ) ||
+ META_BACK_CONN_INVALID(&mc->mc_conns[i])) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ continue;
+ }
+
+ msc->msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ rc = ldap_result( mc->mc_conns[i].msc_ldr, LDAP_RES_ANY, LDAP_MSG_RECEIVED, &tv, &msg );
+ if (rc < 1) {
+ if (rc < 0) {
+ ldap_get_option( mc->mc_conns[i].msc_ldr, LDAP_OPT_ERROR_NUMBER, &rc);
+ META_BACK_CONN_INVALID_SET(&mc->mc_conns[i]);
+ asyncmeta_op_read_error(mc, i, rc, ctx);
+ }
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ continue;
+ }
+ rc = ldap_msgtype( msg );
+ if (rc == LDAP_RES_BIND) {
+ if ( LogTest( asyncmeta_debug ) ) {
+ char time_buf[ SLAP_TEXT_BUFLEN ];
+ asyncmeta_get_timestamp(time_buf);
+ Debug( asyncmeta_debug, "[%s] asyncmeta_op_handle_result received bind msgid=%d msc: %p\n",
+ time_buf, ldap_msgid(msg), msc );
+ }
+ asyncmeta_handle_bind_result(msg, mc, i, ctx);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ msc->msc_result_time = slap_get_time();
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (msg)
+ ldap_msgfree(msg);
+
+ continue;
+ }
+retry_bc:
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ bc = asyncmeta_find_message(ldap_msgid(msg), mc, i);
+/* The sender might not be yet done with the context. On error it might also remove it
+ * so it's best to try and find it again after a wait */
+ if (bc && bc->bc_active > 0) {
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ ldap_pvt_thread_yield();
+ goto retry_bc;
+ }
+ if (bc) {
+ bc->bc_active++;
+ }
+
+ msc->msc_result_time = slap_get_time();
+ msc->msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (!bc) {
+ Debug( asyncmeta_debug,
+ "asyncmeta_op_handle_result: Unable to find bc for msguid %d, msc: %p\n", ldap_msgid(msg), msc );
+ ldap_msgfree(msg);
+ continue;
+ }
+
+ /* set our memctx */
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+ if (bc->op->o_abandon) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ asyncmeta_drop_bc( mc, bc);
+ if ( bc->op->o_tag == LDAP_REQ_SEARCH ) {
+ int j;
+ for (j=0; j<ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *tmp_msc = &mc->mc_conns[j];
+ tmp_msc->msc_active++;
+ asyncmeta_back_cancel( mc, bc->op,
+ bc->candidates[ j ].sr_msgid, j );
+ tmp_msc->msc_active--;
+ }
+ }
+ }
+ asyncmeta_clear_bm_context(bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ if (msg)
+ ldap_msgfree(msg);
+ continue;
+ }
+
+ switch (rc) {
+ case LDAP_RES_SEARCH_ENTRY:
+ case LDAP_RES_SEARCH_REFERENCE:
+ case LDAP_RES_SEARCH_RESULT:
+ case LDAP_RES_INTERMEDIATE:
+ asyncmeta_handle_search_msg(msg, mc, bc, i);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ msg = NULL;
+ break;
+ case LDAP_RES_ADD:
+ case LDAP_RES_DELETE:
+ case LDAP_RES_MODDN:
+ case LDAP_RES_COMPARE:
+ case LDAP_RES_MODIFY:
+ rc = asyncmeta_handle_common_result(msg, mc, bc, i);
+ mc->mc_info->mi_targets[i]->mt_timeout_ops = 0;
+ break;
+ default:
+ {
+ Debug( asyncmeta_debug,
+ "asyncmeta_op_handle_result: "
+ "unrecognized response message tag=%d\n",
+ rc );
+
+ }
+ }
+ if (msg)
+ ldap_msgfree(msg);
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ rc = --mc->mc_active;
+ if (rc) {
+ i++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ goto again;
+ }
+ slap_sl_mem_setctx(ctx, oldctx);
+ if (mc->mc_conns) {
+ for (i=0; i<ntargets; i++) {
+ if (!slapd_shutdown && !META_BACK_CONN_INVALID(msc)
+ && mc->mc_conns[i].msc_ldr && mc->mc_conns[i].conn) {
+ connection_client_enable(mc->mc_conns[i].conn);
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ return NULL;
+}
+
+void asyncmeta_set_msc_time(a_metasingleconn_t *msc)
+{
+ msc->msc_time = slap_get_time();
+}
+
+void* asyncmeta_timeout_loop(void *ctx, void *arg)
+{
+ struct re_s* rtask = arg;
+ a_metainfo_t *mi = rtask->arg;
+ bm_context_t *bc, *onext;
+ time_t current_time = slap_get_time();
+ int i, j;
+ LDAP_STAILQ_HEAD(BCList, bm_context_t) timeout_list;
+ LDAP_STAILQ_INIT( &timeout_list );
+
+ Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] start at [%ld] \n", rtask, current_time );
+ void *oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0);
+ for (i=0; i<mi->mi_num_conns; i++) {
+ a_metaconn_t * mc= &mi->mi_conns[i];
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ if (bc->bc_active > 0) {
+ continue;
+ }
+
+ if (bc->op->o_abandon ) {
+ Operation *op = bc->op;
+
+ /* set our memctx */
+ op->o_threadctx = ctx;
+ op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, op->o_tmpmemctx);
+ operation_counter_init( op, ctx );
+
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ msc->msc_active++;
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ msc->msc_active--;
+ }
+ }
+ }
+ asyncmeta_clear_bm_context(bc);
+ continue;
+ }
+ if (bc->bc_invalid) {
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ LDAP_STAILQ_INSERT_TAIL( &timeout_list, bc, bc_next);
+ continue;
+ }
+
+ if (bc->timeout && bc->stoptime < current_time) {
+ Operation *op = bc->op;
+ LDAP_STAILQ_REMOVE(&mc->mc_om_list, bc, bm_context_t, bc_next);
+ mc->pending_ops--;
+ LDAP_STAILQ_INSERT_TAIL( &timeout_list, bc, bc_next);
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ asyncmeta_set_msc_time(msc);
+ if ( op->o_tag == LDAP_REQ_SEARCH ) {
+ msc->msc_active++;
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ msc->msc_active--;
+ }
+ }
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+
+ for (bc = LDAP_STAILQ_FIRST(&timeout_list); bc; bc = onext) {
+ Operation *op = bc->op;
+ SlapReply *rs = &bc->rs;
+ int timeout_err;
+ const char *timeout_text;
+
+ onext = LDAP_STAILQ_NEXT(bc, bc_next);
+ LDAP_STAILQ_REMOVE(&timeout_list, bc, bm_context_t, bc_next);
+ /* set our memctx */
+ bc->op->o_threadctx = ctx;
+ bc->op->o_tid = ldap_pvt_thread_pool_tid( ctx );
+ slap_sl_mem_setctx(ctx, bc->op->o_tmpmemctx);
+ operation_counter_init( bc->op, ctx );
+
+ if (bc->searchtime) {
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ } else {
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ }
+
+ if ( bc->bc_invalid ) {
+ timeout_text = "Operation is invalid - target connection has been reset";
+ } else {
+ a_metasingleconn_t *log_msc = &mc->mc_conns[0];
+ Debug( asyncmeta_debug,
+ "asyncmeta_timeout_loop:Timeout op %s loop[%p], "
+ "current_time:%ld, op->o_time:%ld msc: %p, "
+ "msc->msc_binding_time: %x, msc->msc_flags:%x \n",
+ bc->op->o_log_prefix, rtask, current_time, bc->op->o_time,
+ log_msc, (unsigned int)log_msc->msc_binding_time, log_msc->msc_mscflags );
+
+ if (bc->searchtime) {
+ timeout_text = NULL;
+ } else {
+ timeout_text = "Operation timed out";
+ }
+
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (bc->candidates[j].sr_msgid >= 0) {
+ a_metatarget_t *mt = mi->mi_targets[j];
+ if (!META_BACK_TGT_QUARANTINE( mt ) ||
+ bc->candidates[j].sr_type == REP_RESULT) {
+ continue;
+ }
+
+ if (mt->mt_isquarantined > LDAP_BACK_FQ_NO) {
+ timeout_err = LDAP_UNAVAILABLE;
+ } else {
+ mt->mt_timeout_ops++;
+ if ((mi->mi_max_timeout_ops > 0) &&
+ (mt->mt_timeout_ops > mi->mi_max_timeout_ops)) {
+ timeout_err = LDAP_UNAVAILABLE;
+ rs->sr_err = timeout_err;
+ if (mt->mt_isquarantined == LDAP_BACK_FQ_NO)
+ asyncmeta_quarantine(op, mi, rs, j);
+ }
+ }
+ }
+ }
+ }
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ if (!bc->op->o_abandon ) {
+ asyncmeta_send_ldap_result( bc, bc->op, &bc->rs );
+ }
+ asyncmeta_clear_bm_context(bc);
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
+ if (mi->mi_idle_timeout) {
+ for (j=0; j<mi->mi_ntargets; j++) {
+ a_metasingleconn_t *msc = &mc->mc_conns[j];
+ if ( msc->msc_active > 0 ) {
+ continue;
+ }
+ if (mc->pending_ops > 0) {
+ continue;
+ }
+ current_time = slap_get_time();
+ if (msc->msc_ld && msc->msc_time > 0 && msc->msc_time + mi->mi_idle_timeout < current_time) {
+ asyncmeta_clear_one_msc(NULL, mc, j, 1, __FUNCTION__);
+ }
+ }
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
+ }
+
+ slap_sl_mem_setctx(ctx, oldctx);
+ current_time = slap_get_time();
+ Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] stop at [%ld] \n", rtask, current_time );
+ 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_thread_mutex_unlock( &slapd_rq.rq_mutex );
+ return NULL;
+}
+
diff --git a/servers/slapd/back-asyncmeta/modify.c b/servers/slapd/back-asyncmeta/modify.c
new file mode 100644
index 0000000..14105ae
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/modify.c
@@ -0,0 +1,360 @@
+/* modify.c - modify request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/string.h>
+#include <ac/socket.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_modify_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ int i, isupdate, rc = 0;
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ LDAPMod **modv = NULL;
+ LDAPMod *mods = NULL;
+ struct berval mdn;
+ Modifications *ml;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+ LDAPControl **ctrls = NULL;
+
+ /*
+ * Rewrite the modify dn, if needed
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next )
+ ;
+
+ modv = op->o_tmpalloc( ( i + 1 )*sizeof( LDAPMod * ) + i*sizeof( LDAPMod ),
+ op->o_tmpmemctx );
+ if ( modv == NULL ) {
+ rs->sr_err = LDAP_OTHER;
+ retcode = META_SEARCH_ERR;
+ goto doreturn;
+ }
+ mods = (LDAPMod *)&modv[ i + 1 ];
+
+ isupdate = be_shadow_update( op );
+ for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) {
+ int j;
+
+ if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod )
+ {
+ continue;
+ }
+
+ modv[ i ] = &mods[ i ];
+ mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES;
+ mods[ i ].mod_type = ml->sml_desc->ad_cname.bv_val;
+
+ if ( ml->sml_values != NULL ) {
+ j = ml->sml_numvals;
+ mods[ i ].mod_bvalues =(struct berval **)op->o_tmpalloc( ( j + 1 ) *sizeof( struct berval * ), op->o_tmpmemctx );
+ for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) {
+ mods[ i ].mod_bvalues[ j ] = op->o_tmpalloc(sizeof( struct berval ), op->o_tmpmemctx );
+ if ( ml->sml_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName )
+ asyncmeta_dn_massage( &dc, &ml->sml_values[ j ], mods[ i ].mod_bvalues[ j ] );
+ else
+ *mods[ i ].mod_bvalues[ j ] = ml->sml_values[ j ];
+ }
+ mods[ i ].mod_bvalues[ j ] = NULL;
+
+ } else {
+ mods[ i ].mod_bvalues = NULL;
+ }
+
+ i++;
+ }
+ modv[ i ] = 0;
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ /* someone reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug , "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ber = ldap_build_modify_req( msc->msc_ld, mdn.bv_val, modv, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_modify_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODIFY,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ ldap_pvt_thread_yield();
+ retcode = META_SEARCH_NEED_BIND;
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send modify request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ op->o_tmpfree( modv, op->o_tmpmemctx );
+
+doreturn:;
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modify_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_modify( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modify: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_modify[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_MODIFY ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock(&mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock(&mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_modify_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modify: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/modrdn.c b/servers/slapd/back-asyncmeta/modrdn.c
new file mode 100644
index 0000000..6793686
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/modrdn.c
@@ -0,0 +1,375 @@
+/* modrdn.c - modrdn request handler for back-syncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+meta_search_candidate_t
+asyncmeta_back_modrdn_start(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ int do_lock)
+{
+ a_dncookie dc;
+ a_metainfo_t *mi = mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ struct berval mdn = BER_BVNULL,
+ mnewSuperior = BER_BVNULL,
+ newrdn = BER_BVNULL;
+ int rc = 0;
+ LDAPControl **ctrls = NULL;
+ meta_search_candidate_t retcode = META_SEARCH_CANDIDATE;
+ BerElement *ber = NULL;
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ SlapReply *candidates = bc->candidates;
+ ber_int_t msgid;
+
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+
+ if ( op->orr_newSup ) {
+
+ /*
+ * NOTE: the newParent, if defined, must be on the
+ * same target as the entry to be renamed. This check
+ * has been anticipated in meta_back_getconn()
+ */
+ /*
+ * FIXME: one possibility is to delete the entry
+ * from one target and add it to the other;
+ * unfortunately we'd need write access to both,
+ * which is nearly impossible; for administration
+ * needs, the rootdn of the metadirectory could
+ * be mapped to an administrative account on each
+ * target (the binddn?); we'll see.
+ */
+ /*
+ * NOTE: we need to port the identity assertion
+ * feature from back-ldap
+ */
+
+ /* needs LDAPv3 */
+ switch ( mt->mt_version ) {
+ case LDAP_VERSION3:
+ break;
+
+ case 0:
+ if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) {
+ break;
+ }
+ /* fall thru */
+
+ default:
+ /* op->o_protocol cannot be anything but LDAPv3,
+ * otherwise wouldn't be here */
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ /*
+ * Rewrite the new superior, if defined and required
+ */
+ asyncmeta_dn_massage( &dc, op->orr_newSup, &mnewSuperior );
+ }
+
+ /*
+ * Rewrite the modrdn dn, if required
+ */
+ asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn );
+
+ /* NOTE: we need to copy the newRDN in case it was formed
+ * from a DN by simply changing the length (ITS#5397) */
+ newrdn = op->orr_newrdn;
+ if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) {
+ ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx );
+ }
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+ /* someone might have reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ ber = ldap_build_moddn_req( msc->msc_ld, mdn.bv_val, newrdn.bv_val,
+ mnewSuperior.bv_val, op->orr_deleteoldrdn, ctrls, NULL, &msgid);
+
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_modrdn_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+ ber_socket_t s;
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_MODRDN,
+ mdn.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__ );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ /* fall though*/
+ default:
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send modrdn request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+
+ if ( mdn.bv_val != op->o_req_dn.bv_val ) {
+ op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &mnewSuperior )
+ && mnewSuperior.bv_val != op->orr_newSup->bv_val )
+ {
+ op->o_tmpfree( mnewSuperior.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( newrdn.bv_val != op->orr_newrdn.bv_val ) {
+ op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_modrdn_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_modrdn( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ a_metatarget_t *mt;
+ a_metaconn_t *mc;
+ int rc, candidate = -1;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ SlapReply *candidates;
+ time_t current_time = slap_get_time();
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+
+ Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_modrdn: %s\n",
+ op->o_req_dn.bv_val );
+
+ if (current_time > op->o_time) {
+ Debug(asyncmeta_debug, "==> asyncmeta_back_modrdn[%s]: o_time:[%ld], current time: [%ld]\n",
+ op->o_log_prefix, op->o_time, current_time );
+ }
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ mt = mi->mi_targets[ candidate ];
+ bc->timeout = mt->mt_timeout[ SLAP_OP_MODRDN ];
+ bc->retrying = LDAP_BACK_RETRYING;
+ bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying );
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ mc->mc_conns[candidate].msc_active++;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ goto finish;
+ }
+
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time()) {
+ int timeout_err;
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ rs->sr_err = timeout_err;
+ rs->sr_text = "Operation timed out before it was sent to target";
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the request */
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+
+ rc = asyncmeta_back_modrdn_start( op, rs, mc, bc, candidate, 1);
+ if (rc == META_SEARCH_ERR) {
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: NOT_CANDIDATE "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: BINDING "
+ "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]);
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_modrdn: ERR "
+ "cnd=\"%d\"\n", op->o_log_prefix, candidate );
+ asyncmeta_error_cleanup(op, rs, bc, mc, candidate);
+ goto finish;
+ default:
+ assert( 0 );
+ break;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ mc->mc_conns[candidate].msc_active--;
+ asyncmeta_start_one_listener(mc, candidates, bc, candidate);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+finish:
+ return rs->sr_err;
+}
diff --git a/servers/slapd/back-asyncmeta/proto-asyncmeta.h b/servers/slapd/back-asyncmeta/proto-asyncmeta.h
new file mode 100644
index 0000000..54041fa
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/proto-asyncmeta.h
@@ -0,0 +1,53 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#ifndef PROTO_ASYNCMETA_H
+#define PROTO_ASYNCMETA_H
+
+LDAP_BEGIN_DECL
+
+extern BI_init asyncmeta_back_initialize;
+
+extern BI_open asyncmeta_back_open;
+extern BI_close asyncmeta_back_close;
+extern BI_destroy asyncmeta_back_destroy;
+
+extern BI_db_init asyncmeta_back_db_init;
+extern BI_db_open asyncmeta_back_db_open;
+extern BI_db_destroy asyncmeta_back_db_destroy;
+extern BI_db_close asyncmeta_back_db_close;
+extern BI_db_config asyncmeta_back_db_config;
+
+extern BI_op_bind asyncmeta_back_bind;
+extern BI_op_search asyncmeta_back_search;
+extern BI_op_compare asyncmeta_back_compare;
+extern BI_op_modify asyncmeta_back_modify;
+extern BI_op_modrdn asyncmeta_back_modrdn;
+extern BI_op_add asyncmeta_back_add;
+extern BI_op_delete asyncmeta_back_delete;
+
+extern BI_connection_destroy asyncmeta_back_conn_destroy;
+
+int asyncmeta_back_init_cf( BackendInfo *bi );
+
+LDAP_END_DECL
+
+#endif /* PROTO_ASYNCMETA_H */
diff --git a/servers/slapd/back-asyncmeta/search.c b/servers/slapd/back-asyncmeta/search.c
new file mode 100644
index 0000000..ab32c48
--- /dev/null
+++ b/servers/slapd/back-asyncmeta/search.c
@@ -0,0 +1,970 @@
+/* search.c - search request handler for back-asyncmeta */
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2022 The OpenLDAP Foundation.
+ * Portions Copyright 2016 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+
+/* ACKNOWLEDGEMENTS:
+ * This work was developed by Symas Corporation
+ * based on back-meta module for inclusion in OpenLDAP Software.
+ * This work was sponsored by Ericsson. */
+
+#include "portable.h"
+
+#include <stdio.h>
+
+#include <ac/socket.h>
+#include <ac/string.h>
+#include <ac/time.h>
+#include "slap.h"
+#include "../../../libraries/liblber/lber-int.h"
+#include "../../../libraries/libldap/ldap-int.h"
+#include "lutil.h"
+#include "../back-ldap/back-ldap.h"
+#include "back-asyncmeta.h"
+
+static void
+asyncmeta_handle_onerr_stop(Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate)
+{
+ a_metainfo_t *mi = mc->mc_info;
+ int j;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ if (asyncmeta_bc_in_queue(mc,bc) == NULL || bc->bc_active > 1) {
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ return;
+ }
+ asyncmeta_drop_bc(mc, bc);
+ for (j=0; j<mi->mi_ntargets; j++) {
+ if (j != candidate && bc->candidates[j].sr_msgid >= 0
+ && mc->mc_conns[j].msc_ld != NULL && !META_BACK_CONN_CREATING( &mc->mc_conns[j] )) {
+ asyncmeta_back_cancel( mc, op,
+ bc->candidates[ j ].sr_msgid, j );
+ }
+ }
+ slap_sl_mem_setctx(op->o_threadctx, op->o_tmpmemctx);
+ operation_counter_init( op, op->o_threadctx );
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+}
+
+static int
+asyncmeta_int_filter2bv( a_dncookie *dc,
+ Filter *f,
+ struct berval *fstr )
+{
+ int i;
+ Filter *p;
+ struct berval atmp,
+ vtmp,
+ ntmp,
+ *tmp;
+ static struct berval
+ /* better than nothing... */
+ ber_bvfalse = BER_BVC( "(!(objectClass=*))" ),
+ ber_bvtf_false = BER_BVC( "(|)" ),
+ /* better than nothing... */
+ ber_bvtrue = BER_BVC( "(objectClass=*)" ),
+ ber_bvtf_true = BER_BVC( "(&)" ),
+ ber_bverror = BER_BVC( "(?=error)" ),
+ ber_bvunknown = BER_BVC( "(?=unknown)" ),
+ ber_bvnone = BER_BVC( "(?=none)" );
+ ber_len_t len;
+ void *memctx = dc->memctx;
+
+ assert( fstr != NULL );
+ BER_BVZERO( fstr );
+
+ if ( f == NULL ) {
+ ber_dupbv_x( fstr, &ber_bvnone, memctx );
+ return LDAP_OTHER;
+ }
+
+ switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) {
+ case LDAP_FILTER_EQUALITY:
+ if ( f->f_av_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) {
+ asyncmeta_dn_massage( dc, &f->f_av_value, &vtmp );
+ } else {
+ vtmp = f->f_av_value;
+ }
+
+ filter_escape_value_x( &vtmp, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_GE:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(>=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_LE:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(<=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ filter_escape_value_x( &f->f_av_value, &ntmp, memctx );
+ fstr->bv_len = f->f_av_desc->ad_cname.bv_len + ntmp.bv_len
+ + ( sizeof("(~=)") - 1 );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)",
+ f->f_av_desc->ad_cname.bv_val, ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ fstr->bv_len = f->f_sub_desc->ad_cname.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ f->f_sub_desc->ad_cname.bv_val );
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_initial, &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 2], ntmp.bv_len + 3,
+ /* "(attr=" */ "%s*)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) {
+ len = fstr->bv_len;
+ filter_escape_value_x( &f->f_sub_any[i], &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len + 1;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 1], ntmp.bv_len + 3,
+ /* "(attr=[init]*[any*]" */ "%s*)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ len = fstr->bv_len;
+
+ filter_escape_value_x( &f->f_sub_final, &ntmp, memctx );
+
+ fstr->bv_len += ntmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len - 1], ntmp.bv_len + 3,
+ /* "(attr=[init*][any*]" */ "%s)",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+
+ ber_memfree_x( ntmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ fstr->bv_len = f->f_desc->ad_cname.bv_len + ( STRLENOF( "(=*)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)",
+ f->f_desc->ad_cname.bv_val );
+ break;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR:
+ case LDAP_FILTER_NOT:
+ fstr->bv_len = STRLENOF( "(%)" );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 128, memctx ); /* FIXME: why 128? */
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)",
+ f->f_choice == LDAP_FILTER_AND ? '&' :
+ f->f_choice == LDAP_FILTER_OR ? '|' : '!' );
+
+ for ( p = f->f_list; p != NULL; p = p->f_next ) {
+ int rc;
+
+ len = fstr->bv_len;
+
+ rc = asyncmeta_int_filter2bv( dc, p, &vtmp );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ fstr->bv_len += vtmp.bv_len;
+ fstr->bv_val = dc->op->o_tmprealloc( fstr->bv_val, fstr->bv_len + 1, memctx );
+
+ snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2,
+ /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" );
+
+ ber_memfree_x( vtmp.bv_val, memctx );
+ }
+
+ break;
+
+ case LDAP_FILTER_EXT:
+ if ( f->f_mr_desc ) {
+ atmp = f->f_mr_desc->ad_cname;
+
+ } else {
+ BER_BVSTR( &atmp, "" );
+ }
+ filter_escape_value_x( &f->f_mr_value, &ntmp, memctx );
+
+ /* FIXME: cleanup (less ?: operators...) */
+ fstr->bv_len = atmp.bv_len +
+ ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) +
+ ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) +
+ ntmp.bv_len + ( STRLENOF( "(:=)" ) );
+ fstr->bv_val = dc->op->o_tmpalloc( fstr->bv_len + 1, memctx );
+
+ snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)",
+ atmp.bv_val,
+ f->f_mr_dnattrs ? ":dn" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "",
+ !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "",
+ ntmp.bv_len ? ntmp.bv_val : "" );
+ ber_memfree_x( ntmp.bv_val, memctx );
+ break;
+
+ case SLAPD_FILTER_COMPUTED:
+ switch ( f->f_result ) {
+ /* FIXME: treat UNDEFINED as FALSE */
+ case SLAPD_COMPARE_UNDEFINED:
+ if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) {
+ return LDAP_COMPARE_FALSE;
+ }
+ /* fallthru */
+
+ case LDAP_COMPARE_FALSE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_false;
+ break;
+ }
+ tmp = &ber_bvfalse;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+ if ( META_BACK_TGT_T_F( dc->target ) ) {
+ tmp = &ber_bvtf_true;
+ break;
+ }
+
+ tmp = &ber_bvtrue;
+ break;
+
+ default:
+ tmp = &ber_bverror;
+ break;
+ }
+
+ ber_dupbv_x( fstr, tmp, memctx );
+ break;
+
+ default:
+ ber_dupbv_x( fstr, &ber_bvunknown, memctx );
+ break;
+ }
+
+ return 0;
+}
+meta_search_candidate_t
+asyncmeta_back_search_start(
+ Operation *op,
+ SlapReply *rs,
+ a_metaconn_t *mc,
+ bm_context_t *bc,
+ int candidate,
+ struct berval *prcookie,
+ ber_int_t prsize,
+ int do_lock)
+{
+ SlapReply *candidates = bc->candidates;
+ a_metainfo_t *mi = ( a_metainfo_t * )mc->mc_info;
+ a_metatarget_t *mt = mi->mi_targets[ candidate ];
+ a_metasingleconn_t *msc = &mc->mc_conns[ candidate ];
+ a_dncookie dc;
+ struct berval realbase = op->o_req_dn;
+ char **attrs;
+ int realscope = op->ors_scope;
+ struct berval mbase = BER_BVNULL;
+ int rc;
+ struct berval filterbv = BER_BVNULL;
+ meta_search_candidate_t retcode;
+ int timelimit;
+ LDAPControl **ctrls = NULL;
+ BerElement *ber = NULL;
+ ber_int_t msgid;
+ ber_socket_t s = -1;
+#ifdef SLAPD_META_CLIENT_PR
+ LDAPControl **save_ctrls = NULL;
+#endif /* SLAPD_META_CLIENT_PR */
+
+ /* this should not happen; just in case... */
+ if ( msc->msc_ld == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "%s: asyncmeta_back_search_start candidate=%d ld=NULL%s.\n",
+ op->o_log_prefix, candidate,
+ META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" );
+ candidates[ candidate ].sr_err = LDAP_OTHER;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ return META_SEARCH_ERR;
+ }
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ return META_SEARCH_NOT_CANDIDATE;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "%s >>> asyncmeta_back_search_start: dn=%s filter=%s\n",
+ op->o_log_prefix, op->o_req_dn.bv_val, op->ors_filterstr.bv_val );
+ /*
+ * modifies the base according to the scope, if required
+ */
+ if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) {
+ switch ( op->ors_scope ) {
+ case LDAP_SCOPE_SUBTREE:
+ /*
+ * make the target suffix the new base
+ * FIXME: this is very forgiving, because
+ * "illegal" searchBases may be turned
+ * into the suffix of the target; however,
+ * the requested searchBase already passed
+ * thru the candidate analyzer...
+ */
+ if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) {
+ realbase = mt->mt_nsuffix;
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ }
+
+ } else {
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_ONELEVEL:
+ {
+ struct berval rdn = mt->mt_nsuffix;
+ rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
+ if ( dnIsOneLevelRDN( &rdn )
+ && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) )
+ {
+ /*
+ * if there is exactly one level,
+ * make the target suffix the new
+ * base, and make scope "base"
+ */
+ realbase = mt->mt_nsuffix;
+ if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) {
+ if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) {
+ realscope = LDAP_SCOPE_SUBORDINATE;
+ } else {
+ realscope = LDAP_SCOPE_SUBTREE;
+ }
+ } else {
+ realscope = LDAP_SCOPE_BASE;
+ }
+ break;
+ } /* else continue with the next case */
+ }
+
+ case LDAP_SCOPE_BASE:
+ /*
+ * this target is no longer candidate
+ */
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /* check filter expression */
+ if ( mt->mt_filter ) {
+ metafilter_t *mf;
+ for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) {
+ if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 )
+ break;
+ }
+ /* nothing matched, this target is no longer a candidate */
+ if ( !mf ) {
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto doreturn;
+ }
+ }
+
+ /*
+ * Rewrite the search base, if required
+ */
+ dc.op = op;
+ dc.target = mt;
+ dc.memctx = op->o_tmpmemctx;
+ dc.to_from = MASSAGE_REQ;
+ asyncmeta_dn_massage( &dc, &realbase, &mbase );
+
+ attrs = anlist2charray_x( op->ors_attrs, 0, op->o_tmpmemctx );
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT ) {
+ timelimit = op->ors_tlimit > 0 ? op->ors_tlimit : 1;
+ } else {
+ timelimit = -1; /* no limit */
+ }
+
+#ifdef SLAPD_META_CLIENT_PR
+ save_ctrls = op->o_ctrls;
+ {
+ LDAPControl *pr_c = NULL;
+ int i = 0, nc = 0;
+
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[i] != NULL; i++ );
+ nc = i;
+ pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL );
+ }
+
+ if ( pr_c != NULL ) nc--;
+ if ( mt->mt_ps > 0 || prcookie != NULL ) nc++;
+
+ if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) {
+ int src = 0, dst = 0;
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ struct berval val = BER_BVNULL;
+ ber_len_t len;
+
+ len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl );
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ struct berval nullcookie = BER_BVNULL;
+ ber_tag_t tag;
+
+ if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps;
+ if ( prcookie == NULL ) prcookie = &nullcookie;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+ tag = ber_printf( ber, "{iO}", prsize, prcookie );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ tag = ber_flatten2( ber, &val, 0 );
+ if ( tag == LBER_ERROR ) {
+ /* error */
+ (void) ber_free_buf( ber );
+ goto done_pr;
+ }
+
+ len += val.bv_len + 1;
+ }
+
+ op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx );
+ if ( save_ctrls ) {
+ for ( ; save_ctrls[ src ] != NULL; src++ ) {
+ if ( save_ctrls[ src ] != pr_c ) {
+ op->o_ctrls[ dst ] = save_ctrls[ src ];
+ dst++;
+ }
+ }
+ }
+
+ if ( mt->mt_ps > 0 || prcookie != NULL ) {
+ op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ];
+
+ op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ op->o_ctrls[ dst ]->ldctl_iscritical = 1;
+
+ op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ];
+ AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 );
+ op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len;
+ dst++;
+
+ (void)ber_free_buf( ber );
+ }
+
+ op->o_ctrls[ dst ] = NULL;
+ }
+done_pr:;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ asyncmeta_set_msc_time(msc);
+ ctrls = op->o_ctrls;
+
+ if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls )
+ != LDAP_SUCCESS )
+ {
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+
+ /*
+ * Starts the search
+ */
+ /* someone reset the connection */
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+ rc = asyncmeta_int_filter2bv( &dc, op->ors_filter, &filterbv );
+ if ( rc ) {
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ ber = ldap_build_search_req( msc->msc_ld,
+ mbase.bv_val, realscope, filterbv.bv_val,
+ attrs, op->ors_attrsonly,
+ ctrls, NULL, timelimit, op->ors_slimit, op->ors_deref,
+ &msgid );
+ if (!ber) {
+ Debug( asyncmeta_debug, "%s asyncmeta_back_search_start: Operation encoding failed with errno %d\n",
+ op->o_log_prefix, msc->msc_ld->ld_errno );
+ rs->sr_err = LDAP_OPERATIONS_ERROR;
+ rs->sr_text = "Failed to encode proxied request";
+ retcode = META_SEARCH_ERR;
+ goto done;
+ }
+
+ if (ber) {
+ struct timeval tv = {0, mt->mt_network_timeout*1000};
+
+ if (!( LDAP_BACK_CONN_ISBOUND( msc )
+ || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s );
+ if (s < 0) {
+ Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+ }
+
+ rc = ldap_int_poll( msc->msc_ld, s, &tv, 1);
+ if (rc < 0) {
+ Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ );
+ if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) {
+ rc = LDAP_SERVER_DOWN;
+ } else {
+ goto error_unavailable;
+ }
+ } else {
+ candidates[ candidate ].sr_msgid = msgid;
+ rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_SEARCH,
+ mbase.bv_val, ber, msgid );
+ if (rc == msgid)
+ rc = LDAP_SUCCESS;
+ else
+ rc = LDAP_SERVER_DOWN;
+ ber = NULL;
+ }
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ retcode = META_SEARCH_CANDIDATE;
+ asyncmeta_set_msc_time(msc);
+ goto done;
+
+ case LDAP_SERVER_DOWN:
+ /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */
+ if (do_lock > 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ );
+ goto error_unavailable;
+
+ default:
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ retcode = META_SEARCH_NOT_CANDIDATE;
+ goto done;
+ }
+ }
+
+error_unavailable:
+ if (ber)
+ ber_free(ber, 1);
+ switch (bc->nretries[candidate]) {
+ case -1: /* nretries = forever */
+ retcode = META_SEARCH_NEED_BIND;
+ ldap_pvt_thread_yield();
+ break;
+ case 0: /* no retries left */
+ candidates[ candidate ].sr_msgid = META_MSGID_IGNORE;
+ rs->sr_err = LDAP_UNAVAILABLE;
+ rs->sr_text = "Unable to send search request to target";
+ retcode = META_SEARCH_ERR;
+ break;
+ default: /* more retries left - try to rebind and go again */
+ retcode = META_SEARCH_NEED_BIND;
+ bc->nretries[candidate]--;
+ ldap_pvt_thread_yield();
+ break;
+ }
+done:;
+#if 0
+ (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls );
+#endif
+#ifdef SLAPD_META_CLIENT_PR
+ if ( save_ctrls != op->o_ctrls ) {
+ op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx );
+ op->o_ctrls = save_ctrls;
+ }
+#endif /* SLAPD_META_CLIENT_PR */
+
+ if ( mbase.bv_val != realbase.bv_val ) {
+ op->o_tmpfree( mbase.bv_val, op->o_tmpmemctx );
+ }
+
+doreturn:;
+ Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_search_start[%p] (fd %d)=%d\n", op->o_log_prefix, msc, s, candidates[candidate].sr_msgid );
+ return retcode;
+}
+
+int
+asyncmeta_back_search( Operation *op, SlapReply *rs )
+{
+ a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private;
+ time_t timeout = 0;
+ int rc = 0;
+ int ncandidates = 0, initial_candidates = 0;
+ long i;
+ SlapReply *candidates = NULL;
+ void *thrctx = op->o_threadctx;
+ bm_context_t *bc;
+ a_metaconn_t *mc;
+ int msc_decr = 0;
+ int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops;
+ int check_bind = 0;
+
+ rs_assert_ready( rs );
+ rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */
+
+ if ( mi->mi_ntargets == 0 ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "No targets are configured for this database";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ /*
+ * controls are set in ldap_back_dobind()
+ *
+ * FIXME: in case of values return filter, we might want
+ * to map attrs and maybe rewrite value
+ */
+
+ asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi );
+ if (bc == NULL) {
+ rs->sr_err = LDAP_OTHER;
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ candidates = bc->candidates;
+ mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_DONTSEND, 0);
+ if ( !mc || rs->sr_err != LDAP_SUCCESS) {
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ /*
+ * Inits searches
+ */
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ /* reset sr_msgid; it is used in most loops
+ * to check if that target is still to be considered */
+ candidates[i].sr_msgid = META_MSGID_UNDEFINED;
+ /* a target is marked as candidate by asyncmeta_getconn();
+ * if for any reason (an error, it's over or so) it is
+ * no longer active, sr_msgid is set to META_MSGID_IGNORE
+ * but it remains candidate, which means it has been active
+ * at some point during the operation. This allows to
+ * use its response code and more to compute the final
+ * response */
+ if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) {
+ continue;
+ }
+
+ candidates[ i ].sr_matched = NULL;
+ candidates[ i ].sr_text = NULL;
+ candidates[ i ].sr_ref = NULL;
+ candidates[ i ].sr_ctrls = NULL;
+ candidates[ i ].sr_nentries = 0;
+ candidates[ i ].sr_type = -1;
+
+ /* get largest timeout among candidates */
+ if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]
+ && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout )
+ {
+ timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ];
+ }
+ }
+
+ if ( op->ors_tlimit != SLAP_NO_LIMIT && (timeout == 0 || op->ors_tlimit < timeout)) {
+ bc->searchtime = 1;
+ bc->timeout = op->ors_tlimit;
+ } else {
+ bc->timeout = timeout;
+ }
+
+ bc->stoptime = op->o_time + bc->timeout;
+ bc->bc_active = 1;
+
+ if (mc->pending_ops >= max_pending_ops) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ return rs->sr_err;
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ rc = asyncmeta_add_message_queue(mc, bc);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active++;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+
+ if (rc != LDAP_SUCCESS) {
+ rs->sr_err = LDAP_BUSY;
+ rs->sr_text = "Maximum pending ops limit exceeded";
+ send_ldap_result(op, rs);
+ goto finish;
+ }
+
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( !META_IS_CANDIDATE( &candidates[ i ] )
+ || candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ continue;
+ }
+retry:
+ if (bc->timeout && bc->stoptime < slap_get_time() && META_BACK_ONERR_STOP( mi )) {
+ int timeout_err;
+ const char *timeout_text;
+ if (bc->searchtime) {
+ timeout_err = LDAP_TIMELIMIT_EXCEEDED;
+ timeout_text = NULL;
+ } else {
+ timeout_err = op->o_protocol >= LDAP_VERSION3 ?
+ LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER;
+ timeout_text = "Operation timed out before it was sent to target";
+ }
+ rs->sr_err = timeout_err;
+ rs->sr_text = timeout_text;
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+
+ }
+
+ if (op->o_abandon) {
+ rs->sr_err = SLAPD_ABANDON;
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+
+ rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, i);
+ switch (rc)
+ {
+ case META_SEARCH_CANDIDATE:
+ /* target is already bound, just send the search request */
+ ncandidates++;
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: IS_CANDIDATE "
+ "cnd=\"%ld\"\n", op->o_log_prefix, i );
+
+ rc = asyncmeta_back_search_start( op, rs, mc, bc, i, NULL, 0 , 1);
+ if (rc == META_SEARCH_ERR) {
+ META_CANDIDATE_CLEAR(&candidates[i]);
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ else {
+ continue;
+ }
+ } else if (rc == META_SEARCH_NEED_BIND) {
+ goto retry;
+ }
+ break;
+ case META_SEARCH_NOT_CANDIDATE:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: NOT_CANDIDATE "
+ "cnd=\"%ld\"\n", op->o_log_prefix, i );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ break;
+
+ case META_SEARCH_NEED_BIND:
+ case META_SEARCH_BINDING:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: BINDING "
+ "cnd=\"%ld\" mc %p msc %p\n", op->o_log_prefix, i , mc, &mc->mc_conns[i]);
+ check_bind++;
+ ncandidates++;
+ /* Todo add the context to the message queue but do not send the request
+ the receiver must send this when we are done binding */
+ /* question - how would do receiver know to which targets??? */
+ break;
+
+ case META_SEARCH_ERR:
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: SEARCH_ERR "
+ "cnd=\"%ldd\"\n", op->o_log_prefix, i );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ candidates[ i ].sr_type = REP_RESULT;
+
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ else {
+ continue;
+ }
+ break;
+
+ default:
+ assert( 0 );
+ break;
+ }
+ }
+
+ initial_candidates = ncandidates;
+
+ if ( LogTest( LDAP_DEBUG_TRACE ) ) {
+ char cnd[ SLAP_TEXT_BUFLEN ];
+ int c;
+
+ for ( c = 0; c < mi->mi_ntargets; c++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ c ] ) ) {
+ cnd[ c ] = '*';
+ } else {
+ cnd[ c ] = ' ';
+ }
+ }
+ cnd[ c ] = '\0';
+
+ Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_search: ncandidates=%d "
+ "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd );
+ }
+
+ if ( initial_candidates == 0 ) {
+ /* NOTE: here we are not sending any matchedDN;
+ * this is intended, because if the back-meta
+ * is serving this search request, but no valid
+ * candidate could be looked up, it means that
+ * there is a hole in the mapping of the targets
+ * and thus no knowledge of any remote superior
+ * is available */
+ Debug( LDAP_DEBUG_ANY, "%s asyncmeta_back_search: "
+ "base=\"%s\" scope=%d: "
+ "no candidate could be selected\n",
+ op->o_log_prefix, op->o_req_dn.bv_val,
+ op->ors_scope );
+
+ /* FIXME: we're sending the first error we encounter;
+ * maybe we should pick the worst... */
+ rc = LDAP_NO_SUCH_OBJECT;
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( META_IS_CANDIDATE( &candidates[ i ] )
+ && candidates[ i ].sr_err != LDAP_SUCCESS )
+ {
+ rc = candidates[ i ].sr_err;
+ break;
+ }
+ }
+ rs->sr_err = rc;
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ asyncmeta_drop_bc(mc, bc);
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ send_ldap_result(op, rs);
+ goto finish;
+ }
+
+ /* If we were processing many targets the result from a pending Bind
+ * on an earlier target may have arrived while we were sending to a
+ * later target. See if we can now send our pending request.
+ */
+ if ( check_bind ) {
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ if ( candidates[ i ].sr_msgid == META_MSGID_GOT_BIND ) {
+ rc = asyncmeta_back_search_start( op, rs, mc, bc, i, NULL, 0, 1 );
+ if ( rc == META_SEARCH_ERR ) {
+ META_CANDIDATE_CLEAR( &candidates[i] );
+ candidates[ i ].sr_msgid = META_MSGID_IGNORE;
+ if ( META_BACK_ONERR_STOP( mi ) ) {
+ asyncmeta_handle_onerr_stop(op,rs,mc,bc,i);
+ goto finish;
+ }
+ }
+ }
+ }
+ }
+
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active--;
+ }
+ msc_decr = 1;
+
+ asyncmeta_start_listeners(mc, candidates, bc);
+ bc->bc_active--;
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ rs->sr_err = SLAPD_ASYNCOP;
+
+finish:
+ /* we ended up straight here due to error and need to reset the msc_active*/
+ if (msc_decr == 0) {
+ ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex);
+ for ( i = 0; i < mi->mi_ntargets; i++ ) {
+ mc->mc_conns[i].msc_active--;
+ }
+ ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex);
+ }
+ return rs->sr_err;
+}