/* meta_result.c - target responses processing */
/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2016-2026 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;
}

static int
asyncmeta_handle_search_msg(LDAPMessage *msg, 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;
	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(msg);

	candidates = bc->candidates;
	i = candidate;
	{
		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(msg);
				return rc;
			}
finish:
			break;

		default:
			break;
		}
	}
	ldap_msgfree(msg);
	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( mc->mc_info->mi_targets[candidate],
			&mc->mc_conns[candidate], __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, first_active = 0;
	struct timeval	tv = {0};
	LDAPMessage     *msg;
	a_metasingleconn_t *msc;
	bm_context_t *bc;
	void *oldctx;

/* exit if the database is disabled, this will let timeout_loop
 * do it's job faster */
	if ( mc->mc_info->mi_disabled > 0 )
		return NULL;

	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:
	j = first_active;
	first_active = ntargets;
	for (j; 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_ONE, &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;
		}
		if ( j < first_active ) first_active = j;
		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;
		}
		/* unsolicited msg */
		if ( ldap_msgid(msg) == 0 && rc == LDAP_RES_EXTENDED ) {
			char		*retoid = NULL;
			struct berval	*retdata = NULL;
			if ( LogTest( asyncmeta_debug ) ) {
				char	time_buf[ SLAP_TEXT_BUFLEN ];
				asyncmeta_get_timestamp(time_buf);
				Debug( asyncmeta_debug, "[%s] asyncmeta_op_handle_result received exop msc: %p\n",
				      time_buf, msc );
			}
			rc = ldap_parse_extended_result( msc->msc_ldr, msg, &retoid, &retdata, 0 );

			if ( rc == LDAP_SUCCESS ) {
				if ( retoid && strcmp( LDAP_NOTICE_OF_DISCONNECTION, retoid ) == 0) {
					if ( LogTest( asyncmeta_debug ) ) {
						char	time_buf[ SLAP_TEXT_BUFLEN ];
						asyncmeta_get_timestamp(time_buf);
						Debug( asyncmeta_debug, "[%s] asyncmeta_op_handle_result received NOD msc: %p\n",
							   time_buf, msc );
					}
					ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex );
					META_BACK_CONN_CLOSING_SET( msc );
					msc->msc_active--;
					ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
				}
			}

			if ( retoid )
				ber_memfree( retoid );
			if ( retdata )
				ber_memfree( retdata );
			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);
	}

	if ( first_active < ntargets ) goto again;
	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 );
		first_active = 0;
		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();
}

static void
asyncmeta_update_msc_pending_ops( a_metaconn_t *mc,
									   bm_context_t *bc)
{
	int i;
	a_metainfo_t *mi = mc->mc_info;
	for (i=0; i<mi->mi_ntargets; i++) {
		if ( !META_IS_CANDIDATE( &bc->candidates[ i ] ) ||
			bc->candidates[i].sr_msgid == META_MSGID_IGNORE ||
			bc->candidates[i].sr_type == REP_RESULT) {
			continue;
		}
		mc->mc_conns[i].msc_pending_ops++;
	}
}

static void
asyncmeta_reset_msc_pending_ops( a_metaconn_t *mc )
{
	int i;
	a_metainfo_t *mi = mc->mc_info;
	for (i=0; i<mi->mi_ntargets; i++) {
		mc->mc_conns[i].msc_pending_ops = 0;
	}
}

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();
	time_t conn_ttl;
	time_t conn_reset_interval;
	int i, j;
	LDAP_STAILQ_HEAD(BCList, bm_context_t) timeout_list;
	LDAP_STAILQ_INIT( &timeout_list );
	a_metasingleconn_t* oldest[mi->mi_ntargets];

	Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] start at [%lld] \n", rtask, (long long)current_time );
	if ( mi->mi_disabled > 0 && asyncmeta_db_has_pending_ops( mi ) == 0 ) {
		Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] database disabled, clearing connections [%ld] \n", rtask, current_time );
		ldap_pvt_thread_mutex_lock( &mi->mi_mc_mutex );
		asyncmeta_back_clear_miconns( mi );
		mi->mi_task = NULL;
		ldap_pvt_thread_mutex_unlock( &mi->mi_mc_mutex );
		return NULL;
	}
	void *oldctx = slap_sl_mem_create(SLAP_SLAB_SIZE, SLAP_SLAB_STACK, ctx, 0);

	for (i=0; i<mi->mi_ntargets; i++ ) {
		oldest[i] = NULL;
	}
	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 );
		asyncmeta_reset_msc_pending_ops(mc);
		for (bc = LDAP_STAILQ_FIRST(&mc->mc_om_list); bc; bc = onext) {
			onext = LDAP_STAILQ_NEXT(bc, bc_next);
			if (bc->bc_active > 0) {
				asyncmeta_update_msc_pending_ops( mc, bc);
				continue;
			}

			if (mi->mi_disabled > 0) {
				bc->bc_invalid = 1;
			}

			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 &&
							 !META_BACK_CONN_CLOSING(msc) ) {
							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 &&
							 !META_BACK_CONN_CLOSING(msc) ) {
							msc->msc_active++;
							asyncmeta_back_cancel( mc, op,
									       bc->candidates[ j ].sr_msgid, j );
							msc->msc_active--;
						}
					}
				}
			}
			asyncmeta_update_msc_pending_ops( mc, bc);
		}
		for (j=0; j<mi->mi_ntargets; j++) {
			a_metasingleconn_t *msc = &mc->mc_conns[j];
			if ( META_BACK_CONN_CLOSING(msc) &&
				 msc->msc_pending_ops == 0 &&
				 msc->msc_active <= 0 ) {
				asyncmeta_clear_one_msc (mi->mi_targets[j], msc, __FUNCTION__ );
			}
		}

		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:%lld, op->o_time:%lld msc: %p, "
				       "msc->msc_binding_time: %x, msc->msc_flags:%x \n",
				       bc->op->o_log_prefix, rtask, (long long)current_time, (long long)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(mi->mi_targets[j], msc,  __FUNCTION__);
				}
			}
		}
		/* find the oldest connection to reset */
		for (j=0; j<mi->mi_ntargets; j++) {
			a_metasingleconn_t *msc = &mc->mc_conns[j];
			if ( msc->msc_established_time > 0 &&
				 ( oldest[j] == NULL || oldest[j]->msc_established_time > msc->msc_established_time ) ) {
				oldest[j] = msc;
			}
		}
		ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex );
	}

	current_time = slap_get_time();
	for (j=0; j<mi->mi_ntargets; j++) {
		a_metasingleconn_t *msc = oldest[j];
		a_metatarget_t *mt = mi->mi_targets[j];
		conn_ttl = (mt->mt_conn_ttl > 0)?mt->mt_conn_ttl:mi->mi_conn_ttl;
		conn_reset_interval = (mt->mt_conn_reset_interval > 0) ?
			mt->mt_conn_reset_interval:mi->mi_conn_reset_interval;

		if ( conn_ttl || conn_reset_interval ) {
			if ( (current_time - mt->msc_reset_time) <= conn_reset_interval )
				continue;
			if ( msc != NULL && msc->msc_established_time
				 && (msc->msc_established_time + conn_ttl <= current_time) ) {
				ldap_pvt_thread_mutex_lock( &msc->mc->mc_om_mutex );
				META_BACK_CONN_CLOSING_SET( msc );
				ldap_pvt_thread_mutex_unlock( &msc->mc->mc_om_mutex );
			}
		}
	}
	slap_sl_mem_setctx(ctx, oldctx);
	current_time = slap_get_time();
	Debug( asyncmeta_debug, "asyncmeta_timeout_loop[%p] stop at [%lld] \n", rtask, (long long)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;
}

