diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:35:32 +0000 |
commit | 5ea77a75dd2d2158401331879f3c8f47940a732c (patch) | |
tree | d89dc06e9f4850a900f161e25f84e922c4f86cc8 /servers/slapd/back-asyncmeta | |
parent | Initial commit. (diff) | |
download | openldap-5ea77a75dd2d2158401331879f3c8f47940a732c.tar.xz openldap-5ea77a75dd2d2158401331879f3c8f47940a732c.zip |
Adding upstream version 2.5.13+dfsg.upstream/2.5.13+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servers/slapd/back-asyncmeta')
-rw-r--r-- | servers/slapd/back-asyncmeta/Makefile.in | 50 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/add.c | 363 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/back-asyncmeta.h | 782 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/bind.c | 1730 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/candidates.c | 239 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/compare.c | 304 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/config.c | 2443 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/conn.c | 1184 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/delete.c | 297 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/dncache.c | 228 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/init.c | 468 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/map.c | 214 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/message_queue.c | 236 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/meta_result.c | 1825 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/modify.c | 357 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/modrdn.c | 367 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/proto-asyncmeta.h | 53 | ||||
-rw-r--r-- | servers/slapd/back-asyncmeta/search.c | 963 |
18 files changed, 12103 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..1f194ed --- /dev/null +++ b/servers/slapd/back-asyncmeta/add.c @@ -0,0 +1,363 @@ +/* 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 ); + } + + 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..f3ce06e --- /dev/null +++ b/servers/slapd/back-asyncmeta/back-asyncmeta.h @@ -0,0 +1,782 @@ +/* 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); + +/* 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..e290350 --- /dev/null +++ b/servers/slapd/back-asyncmeta/bind.c @@ -0,0 +1,1730 @@ +/* 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; + + candidates = op->o_tmpcalloc(mi->mi_ntargets, sizeof(SlapReply),op->o_tmpmemctx); + 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; + } + + /* 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..1349cac --- /dev/null +++ b/servers/slapd/back-asyncmeta/compare.c @@ -0,0 +1,304 @@ +/* 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 ); + } + 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..849ac01 --- /dev/null +++ b/servers/slapd/back-asyncmeta/config.c @@ -0,0 +1,2443 @@ +/* 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 ) +{ + 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; + 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_metatarget_t *mt; + + *mtp = 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; + + 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 ) { + 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: + 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: + 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: + 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 ( 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 ( 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: + bindconf_free( &mt->mt_idassert.si_bc ); + memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) ); + break; + + case LDAP_BACK_CFG_SUFFIXM: + 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: + mi->mi_max_target_conns = 0; + break; + + case LDAP_BACK_CFG_MAX_TIMEOUT_OPS: + mi->mi_max_timeout_ops = 0; + break; + + case LDAP_BACK_CFG_KEEPALIVE: + 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 ] ) != 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..986d1ca --- /dev/null +++ b/servers/slapd/back-asyncmeta/conn.c @@ -0,0 +1,1184 @@ +/* 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]); + } + + } +} diff --git a/servers/slapd/back-asyncmeta/delete.c b/servers/slapd/back-asyncmeta/delete.c new file mode 100644 index 0000000..b91b1a5 --- /dev/null +++ b/servers/slapd/back-asyncmeta/delete.c @@ -0,0 +1,297 @@ +/* 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 ); + } + + 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..5324907 --- /dev/null +++ b/servers/slapd/back-asyncmeta/init.c @@ -0,0 +1,468 @@ +/* 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 ) { + /* Dynamically added, nothing to check here until + * some targets get added + */ + if ( slapMode & SLAP_SERVER_RUNNING ) + return 0; + + Debug( LDAP_DEBUG_ANY, + "asyncmeta_back_db_open: no targets defined\n" ); + return 1; + } + 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; + mc->mc_conns = ch_calloc( mi->mi_ntargets, sizeof( a_metasingleconn_t )); + mc->mc_info = mi; + LDAP_STAILQ_INIT( &mc->mc_om_list ); + } + mi->mi_suffix = be->be_suffix[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..b811708 --- /dev/null +++ b/servers/slapd/back-asyncmeta/map.c @@ -0,0 +1,214 @@ +/* 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 ( 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; + + 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 ); + 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..f2e9c86 --- /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 ); + 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..a70bae8 --- /dev/null +++ b/servers/slapd/back-asyncmeta/modify.c @@ -0,0 +1,357 @@ +/* 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 ) + ; + if (i > 0) { + mods = op->o_tmpalloc( sizeof( LDAPMod )*i, op->o_tmpmemctx ); + } + + if ( mods == NULL ) { + rs->sr_err = LDAP_OTHER; + retcode = META_SEARCH_ERR; + goto doreturn; + } + modv = ( LDAPMod ** )op->o_tmpalloc( ( i + 1 )*sizeof( LDAPMod * ), op->o_tmpmemctx ); + if ( modv == NULL ) { + rs->sr_err = LDAP_OTHER; + retcode = META_SEARCH_ERR; + goto doreturn; + } + + 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 ); + } + +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 ); + } + + 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..03dee11 --- /dev/null +++ b/servers/slapd/back-asyncmeta/modrdn.c @@ -0,0 +1,367 @@ +/* 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 ); + } + 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..0b0db82 --- /dev/null +++ b/servers/slapd/back-asyncmeta/search.c @@ -0,0 +1,963 @@ +/* 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 */ + + /* + * 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; +} |