From b527294153be3b79563c82c66102adc0004736c0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 19:54:12 +0200 Subject: Adding upstream version 2.6.7+dfsg. Signed-off-by: Daniel Baumann --- servers/slapd/back-asyncmeta/Makefile.in | 50 + servers/slapd/back-asyncmeta/add.c | 370 ++++ servers/slapd/back-asyncmeta/back-asyncmeta.h | 788 ++++++++ servers/slapd/back-asyncmeta/bind.c | 1739 ++++++++++++++++ servers/slapd/back-asyncmeta/candidates.c | 239 +++ servers/slapd/back-asyncmeta/compare.c | 312 +++ servers/slapd/back-asyncmeta/config.c | 2536 ++++++++++++++++++++++++ servers/slapd/back-asyncmeta/conn.c | 1222 ++++++++++++ servers/slapd/back-asyncmeta/delete.c | 304 +++ servers/slapd/back-asyncmeta/dncache.c | 228 +++ servers/slapd/back-asyncmeta/init.c | 475 +++++ servers/slapd/back-asyncmeta/map.c | 224 +++ servers/slapd/back-asyncmeta/message_queue.c | 236 +++ servers/slapd/back-asyncmeta/meta_result.c | 1825 +++++++++++++++++ servers/slapd/back-asyncmeta/modify.c | 360 ++++ servers/slapd/back-asyncmeta/modrdn.c | 375 ++++ servers/slapd/back-asyncmeta/proto-asyncmeta.h | 53 + servers/slapd/back-asyncmeta/search.c | 970 +++++++++ 18 files changed, 12306 insertions(+) create mode 100644 servers/slapd/back-asyncmeta/Makefile.in create mode 100644 servers/slapd/back-asyncmeta/add.c create mode 100644 servers/slapd/back-asyncmeta/back-asyncmeta.h create mode 100644 servers/slapd/back-asyncmeta/bind.c create mode 100644 servers/slapd/back-asyncmeta/candidates.c create mode 100644 servers/slapd/back-asyncmeta/compare.c create mode 100644 servers/slapd/back-asyncmeta/config.c create mode 100644 servers/slapd/back-asyncmeta/conn.c create mode 100644 servers/slapd/back-asyncmeta/delete.c create mode 100644 servers/slapd/back-asyncmeta/dncache.c create mode 100644 servers/slapd/back-asyncmeta/init.c create mode 100644 servers/slapd/back-asyncmeta/map.c create mode 100644 servers/slapd/back-asyncmeta/message_queue.c create mode 100644 servers/slapd/back-asyncmeta/meta_result.c create mode 100644 servers/slapd/back-asyncmeta/modify.c create mode 100644 servers/slapd/back-asyncmeta/modrdn.c create mode 100644 servers/slapd/back-asyncmeta/proto-asyncmeta.h create mode 100644 servers/slapd/back-asyncmeta/search.c (limited to 'servers/slapd/back-asyncmeta') 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 . +## +## 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 +## . +## +## ACKNOWLEDGEMENTS: +## This work was developed by Symas Corporation +## based on back-meta module for inclusion in OpenLDAP Software. +## This work was sponsored by Ericsson + +SRCS = init.c config.c search.c message_queue.c bind.c add.c compare.c \ + delete.c modify.c modrdn.c map.c \ + conn.c candidates.c dncache.c meta_result.c +OBJS = init.lo config.lo search.lo message_queue.lo bind.lo add.lo compare.lo \ + delete.lo modify.lo modrdn.lo map.lo \ + conn.lo candidates.lo dncache.lo meta_result.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-asyncmeta" +BUILD_MOD = @BUILD_ASYNCMETA@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_ASYNCMETA@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(LIBS) $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_asyncmeta + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ diff --git a/servers/slapd/back-asyncmeta/add.c b/servers/slapd/back-asyncmeta/add.c new file mode 100644 index 0000000..55277ee --- /dev/null +++ b/servers/slapd/back-asyncmeta/add.c @@ -0,0 +1,370 @@ +/* add.c - add request handler for back-asyncmeta */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +/* 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 + +#include +#include +#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; ja_numvals; j++) { + attrs[ i ]->mod_bvalues[ j ] = op->o_tmpalloc( sizeof( struct berval ), op->o_tmpmemctx ); + if ( a->a_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + asyncmeta_dn_massage( &dc, &a->a_vals[ j ], attrs[ i ]->mod_bvalues[ j ] ); + else + *attrs[ i ]->mod_bvalues[ j ] = a->a_vals[ j ]; + } + + attrs[ i ]->mod_bvalues[ j ] = NULL; + i++; + } + attrs[ i ] = NULL; + + asyncmeta_set_msc_time(msc); + + ctrls = op->o_ctrls; + if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root, &ctrls ) != LDAP_SUCCESS ) + { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_ERR; + goto done; + } + /* someone might have reset the connection */ + if (!( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + + ber = ldap_build_add_req( msc->msc_ld, mdn.bv_val, attrs, ctrls, NULL, &msgid); + if (!ber) { + Debug( asyncmeta_debug, "%s asyncmeta_back_add_start: Operation encoding failed with errno %d\n", + op->o_log_prefix, msc->msc_ld->ld_errno ); + rs->sr_err = LDAP_OPERATIONS_ERROR; + rs->sr_text = "Failed to encode proxied request"; + retcode = META_SEARCH_ERR; + goto done; + } + + if (ber) { + struct timeval tv = {0, mt->mt_network_timeout*1000}; + ber_socket_t s; + if (!( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s ); + if (s < 0) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + + rc = ldap_int_poll( msc->msc_ld, s, &tv, 1); + if (rc < 0) { + Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ ); + if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) { + rc = LDAP_SERVER_DOWN; + } else { + goto error_unavailable; + } + } else { + candidates[ candidate ].sr_msgid = msgid; + rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_ADD, + mdn.bv_val, ber, msgid ); + if (rc == msgid) + rc = LDAP_SUCCESS; + else + rc = LDAP_SERVER_DOWN; + ber = NULL; + } + + switch ( rc ) { + case LDAP_SUCCESS: + retcode = META_SEARCH_CANDIDATE; + asyncmeta_set_msc_time(msc); + goto done; + + case LDAP_SERVER_DOWN: + /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */ + if (do_lock > 0) { + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__); + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + } + /* fall though*/ + default: + Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + } + +error_unavailable: + if (ber) + ber_free(ber, 1); + switch (bc->nretries[candidate]) { + case -1: /* nretries = forever */ + ldap_pvt_thread_yield(); + retcode = META_SEARCH_NEED_BIND; + break; + case 0: /* no retries left */ + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Unable to send add request to target"; + retcode = META_SEARCH_ERR; + break; + default: /* more retries left - try to rebind and go again */ + retcode = META_SEARCH_NEED_BIND; + bc->nretries[candidate]--; + ldap_pvt_thread_yield(); + break; + } + +done: + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_add_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid ); + return retcode; +} + + +int +asyncmeta_back_add( Operation *op, SlapReply *rs ) +{ + a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private; + a_metatarget_t *mt; + a_metaconn_t *mc; + int rc, candidate = -1; + void *thrctx = op->o_threadctx; + bm_context_t *bc; + SlapReply *candidates; + time_t current_time = slap_get_time(); + int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops; + + Debug(LDAP_DEBUG_TRACE, "==> asyncmeta_back_add: %s\n", + op->o_req_dn.bv_val ); + + if (current_time > op->o_time) { + Debug(asyncmeta_debug, "==> asyncmeta_back_add[%s]: o_time:[%ld], current time: [%ld]\n", + op->o_log_prefix, op->o_time, current_time ); + } + + if ( mi->mi_ntargets == 0 ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "No targets are configured for this database"; + send_ldap_result(op, rs); + return rs->sr_err; + } + + asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi ); + if (bc == NULL) { + rs->sr_err = LDAP_OTHER; + send_ldap_result(op, rs); + return rs->sr_err; + } + + candidates = bc->candidates; + mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0); + if ( !mc || rs->sr_err != LDAP_SUCCESS) { + send_ldap_result(op, rs); + return rs->sr_err; + } + + mt = mi->mi_targets[ candidate ]; + bc->timeout = mt->mt_timeout[ SLAP_OP_ADD ]; + bc->retrying = LDAP_BACK_RETRYING; + bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying ); + bc->stoptime = op->o_time + bc->timeout; + bc->bc_active = 1; + + if (mc->pending_ops >= max_pending_ops) { + rs->sr_err = LDAP_BUSY; + rs->sr_text = "Maximum pending ops limit exceeded"; + send_ldap_result(op, rs); + return rs->sr_err; + } + + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + rc = asyncmeta_add_message_queue(mc, bc); + mc->mc_conns[candidate].msc_active++; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + + if (rc != LDAP_SUCCESS) { + rs->sr_err = LDAP_BUSY; + rs->sr_text = "Maximum pending ops limit exceeded"; + send_ldap_result(op, rs); + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + mc->mc_conns[candidate].msc_active--; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + goto finish; + } + +retry: + current_time = slap_get_time(); + if (bc->timeout && bc->stoptime < current_time) { + int timeout_err; + timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + rs->sr_err = timeout_err; + rs->sr_text = "Operation timed out before it was sent to target"; + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + } + + rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate); + switch (rc) + { + case META_SEARCH_CANDIDATE: + /* target is already bound, just send the request */ + Debug(LDAP_DEBUG_TRACE , "%s asyncmeta_back_add: " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + + rc = asyncmeta_back_add_start( op, rs, mc, bc, candidate, 1); + if (rc == META_SEARCH_ERR) { + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + + } else if (rc == META_SEARCH_NEED_BIND) { + goto retry; + } + break; + case META_SEARCH_NOT_CANDIDATE: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: NOT_CANDIDATE " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + + case META_SEARCH_NEED_BIND: + case META_SEARCH_BINDING: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: BINDING " + "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]); + /* add the context to the message queue but do not send the request + the receiver must send this when we are done binding */ + break; + + case META_SEARCH_ERR: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_add: ERR " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + default: + assert( 0 ); + break; + } + + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + mc->mc_conns[candidate].msc_active--; + asyncmeta_start_one_listener(mc, candidates, bc, candidate); + bc->bc_active--; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + rs->sr_err = SLAPD_ASYNCOP; +finish: + return rs->sr_err; +} diff --git a/servers/slapd/back-asyncmeta/back-asyncmeta.h b/servers/slapd/back-asyncmeta/back-asyncmeta.h new file mode 100644 index 0000000..a5ae6cd --- /dev/null +++ b/servers/slapd/back-asyncmeta/back-asyncmeta.h @@ -0,0 +1,788 @@ +/* back-asyncmeta.h - main header file for back-asyncmeta module */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +/* 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 */ +#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP +#define META_BACK_F_ONERR_REPORT (0x02000000U) +#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT) +#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U) +#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */ + +#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP ) +#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT ) +#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) ) + +#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND ) +#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS ) +#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON ) +#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON ) + +#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE ) + + time_t mi_idle_timeout; + struct re_s *mi_task; + + a_metacommon_t mi_mc; + ldap_extra_t *mi_ldap_extra; + + int mi_max_timeout_ops; + int mi_max_pending_ops; + int mi_max_target_conns; + /* mutex for access to the connection structures */ + ldap_pvt_thread_mutex_t mi_mc_mutex; + int mi_num_conns; + int mi_next_conn; + a_metaconn_t *mi_conns; + + struct berval mi_suffix; +} a_metainfo_t; + +typedef enum meta_op_type { + META_OP_ALLOW_MULTIPLE = 0, + META_OP_REQUIRE_SINGLE, + META_OP_REQUIRE_ALL +} meta_op_type; + +/* Whatever context asyncmeta_dn_massage needs... */ +typedef struct a_dncookie { + Operation *op; + struct a_metatarget_t *target; + void *memctx; + int to_from; +} a_dncookie; + + +#define MASSAGE_REQ 0 +#define MASSAGE_REP 1 + +extern void +asyncmeta_dn_massage(a_dncookie *dc, struct berval *dn, + struct berval *res); + +extern void +asyncmeta_filter_map_rewrite( + a_dncookie *dc, + Filter *f, + struct berval *fstr ); + +extern void +asyncmeta_back_referral_result_rewrite( + a_dncookie *dc, + BerVarray a_vals ); + +extern a_metaconn_t * +asyncmeta_getconn( + Operation *op, + SlapReply *rs, + SlapReply *candidates, + int *candidate, + ldap_back_send_t sendok, + int alloc_new); + + +extern int +asyncmeta_init_one_conn( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + int candidate, + int ispriv, + ldap_back_send_t sendok, + int dolock ); + +extern void +asyncmeta_quarantine( + Operation *op, + a_metainfo_t *mi, + SlapReply *rs, + int candidate ); + +extern int +asyncmeta_proxy_authz_cred( + a_metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ); + +extern int +asyncmeta_controls_add( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + int candidate, + int isroot, + LDAPControl ***pctrls ); + +extern int +asyncmeta_LTX_init_module( + int argc, + char *argv[] ); + +/* + * Candidate stuff + */ +extern int +asyncmeta_is_candidate( + a_metatarget_t *mt, + struct berval *ndn, + int scope ); + +extern int +asyncmeta_select_unique_candidate( + a_metainfo_t *mi, + struct berval *ndn ); + +extern int +asyncmeta_clear_unused_candidates( + Operation *op, + int candidate, + a_metaconn_t *mc, + SlapReply *candidates); + +/* + * Dn cache stuff (experimental) + */ +extern int +asyncmeta_dncache_cmp( + const void *c1, + const void *c2 ); + +extern int +asyncmeta_dncache_dup( + void *c1, + void *c2 ); + +#define META_TARGET_NONE (-1) +#define META_TARGET_MULTIPLE (-2) +extern int +asyncmeta_dncache_get_target( + a_metadncache_t *cache, + struct berval *ndn ); + +extern int +meta_dncache_update_entry( + a_metadncache_t *cache, + struct berval *ndn, + int target ); + +extern int +asyncmeta_dncache_delete_entry( + a_metadncache_t *cache, + struct berval *ndn ); + +extern void +asyncmeta_dncache_free( void *entry ); + +extern int +asyncmeta_subtree_destroy( a_metasubtree_t *ms ); + +extern void +asyncmeta_filter_destroy( metafilter_t *mf ); + +extern int +asyncmeta_target_finish( a_metainfo_t *mi, a_metatarget_t *mt, + const char *log, char *msg, size_t msize +); + + +extern LDAP_REBIND_PROC asyncmeta_back_default_rebind; +extern LDAP_URLLIST_PROC asyncmeta_back_default_urllist; + +/* IGNORE means that target does not (no longer) participate + * in the search; + * NOTREADY means the search on that target has not been initialized yet + */ +#define META_MSGID_IGNORE (-1) +#define META_MSGID_NEED_BIND (-2) +#define META_MSGID_CONNECTING (-3) +#define META_MSGID_UNDEFINED (-4) +#define META_MSGID_GOT_BIND (-5) + +typedef enum meta_search_candidate_t { + META_SEARCH_UNDEFINED = -2, + META_SEARCH_ERR = -1, + META_SEARCH_NOT_CANDIDATE, + META_SEARCH_CANDIDATE, + META_SEARCH_BINDING, + META_SEARCH_NEED_BIND, + META_SEARCH_CONNECTING +} meta_search_candidate_t; + +Operation* asyncmeta_copy_op(Operation *op); +void asyncmeta_clear_bm_context(bm_context_t *bc); + +int asyncmeta_add_message_queue(a_metaconn_t *mc, bm_context_t *bc); +void asyncmeta_drop_bc(a_metaconn_t *mc, bm_context_t *bc); +void asyncmeta_drop_bc_from_fconn(bm_context_t *bc); + +bm_context_t * +asyncmeta_find_message(ber_int_t msgid, a_metaconn_t *mc, int candidate); + +void* asyncmeta_op_handle_result(void *ctx, void *arg); +int asyncmeta_back_cleanup( Operation *op, SlapReply *rs, bm_context_t *bm ); + +int +asyncmeta_clear_one_msc( + Operation *op, + a_metaconn_t *msc, + int candidate, + int unbind, + const char * caller); + +a_metaconn_t * +asyncmeta_get_next_mc( a_metainfo_t *mi ); + +void* asyncmeta_timeout_loop(void *ctx, void *arg); + +int +asyncmeta_start_timeout_loop(a_metatarget_t *mt, a_metainfo_t *mi); + +void asyncmeta_set_msc_time(a_metasingleconn_t *msc); + +int asyncmeta_back_cancel( + a_metaconn_t *mc, + Operation *op, + ber_int_t msgid, + int candidate ); + +void +asyncmeta_send_result(bm_context_t* bc, int error, char *text); + +int asyncmeta_new_bm_context(Operation *op, + SlapReply *rs, + bm_context_t **new_bc, + int ntargets, + a_metainfo_t *mi); + +int asyncmeta_start_listeners(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc); +int asyncmeta_start_one_listener(a_metaconn_t *mc, SlapReply *candidates, bm_context_t *bc, int candidate); + +meta_search_candidate_t +asyncmeta_back_search_start( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + struct berval *prcookie, + ber_int_t prsize, + int do_lock); + +meta_search_candidate_t +asyncmeta_dobind_init( + Operation *op, + SlapReply *rs, + bm_context_t *bc, + a_metaconn_t *mc, + int candidate); + +meta_search_candidate_t +asyncmeta_dobind_init_with_retry( + Operation *op, + SlapReply *rs, + bm_context_t *bc, + a_metaconn_t *mc, + int candidate); + +meta_search_candidate_t +asyncmeta_back_add_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock); +meta_search_candidate_t +asyncmeta_back_modify_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock); + +meta_search_candidate_t +asyncmeta_back_modrdn_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock); +meta_search_candidate_t +asyncmeta_back_delete_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock); + +meta_search_candidate_t +asyncmeta_back_compare_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock); + +bm_context_t * +asyncmeta_bc_in_queue(a_metaconn_t *mc, + bm_context_t *bc); + +int +asyncmeta_error_cleanup(Operation *op, + SlapReply *rs, + bm_context_t *bc, + a_metaconn_t *mc, + int candidate); + +int +asyncmeta_reset_msc(Operation *op, + a_metaconn_t *mc, + int candidate, + int unbind, + const char *caller); + + +void +asyncmeta_back_conn_free( + void *v_mc ); + +void asyncmeta_log_msc(a_metasingleconn_t *msc); +void asyncmeta_log_conns(a_metainfo_t *mi); + +void asyncmeta_get_timestamp(char *buf); + +int +asyncmeta_dncache_update_entry(a_metadncache_t *cache, + struct berval *ndn, + int target ); + +void +asyncmeta_dnattr_result_rewrite(a_dncookie *dc, + BerVarray a_vals); + +void +asyncmeta_referral_result_rewrite(a_dncookie *dc, + BerVarray a_vals); + +meta_search_candidate_t +asyncmeta_send_all_pending_ops(a_metaconn_t *mc, + int candidate, + void *ctx, + int dolock); +meta_search_candidate_t +asyncmeta_return_bind_errors(a_metaconn_t *mc, + int candidate, + SlapReply *bind_result, + void *ctx, + int dolock); + +int +asyncmeta_db_has_pending_ops(a_metainfo_t *mi); + +int +asyncmeta_db_has_mscs(a_metainfo_t *mi); + +/* The the maximum time in seconds after a result has been received on a connection, + * after which it can be reset if a sender error occurs. Should this be configurable? */ +#define META_BACK_RESULT_INTERVAL (2) + +extern int asyncmeta_debug; + +LDAP_END_DECL + +#endif /* SLAPD_ASYNCMETA_H */ diff --git a/servers/slapd/back-asyncmeta/bind.c b/servers/slapd/back-asyncmeta/bind.c new file mode 100644 index 0000000..78c0e57 --- /dev/null +++ b/servers/slapd/back-asyncmeta/bind.c @@ -0,0 +1,1739 @@ +/* bind.c - bind request handler functions for binding + * to remote targets for back-asyncmeta */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +/* 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 + +#include +#include +#include +#include "slap.h" +#include "../../../libraries/libldap/ldap-int.h" + +#define AVL_INTERNAL +#include "../back-ldap/back-ldap.h" +#include "back-asyncmeta.h" +#include "lutil_ldap.h" + +#define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ "2.16.840.1.113730.3.4.12" + +static int +asyncmeta_proxy_authz_bind( + a_metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ); + +static int +asyncmeta_single_bind( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + int candidate ); + +int +asyncmeta_back_bind( Operation *op, SlapReply *rs ) +{ + a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private; + a_metaconn_t *mc = NULL; + + int rc = LDAP_OTHER, + i, + gotit = 0, + isroot = 0; + + SlapReply *candidates = NULL; + + rs->sr_err = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, "%s asyncmeta_back_bind: dn=\"%s\".\n", + op->o_log_prefix, op->o_req_dn.bv_val ); + + /* the test on the bind method should be superfluous */ + switch ( be_rootdn_bind( op, rs ) ) { + case LDAP_SUCCESS: + if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) { + /* frontend will return success */ + return rs->sr_err; + } + + isroot = 1; + /* fallthru */ + + case SLAP_CB_CONTINUE: + break; + + default: + /* be_rootdn_bind() sent result */ + return rs->sr_err; + } + + + if ( mi->mi_ntargets == 0 ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "No targets are configured for this database"; + send_ldap_result(op, rs); + return rs->sr_err; + } + + candidates = op->o_tmpcalloc(mi->mi_ntargets, sizeof(SlapReply),op->o_tmpmemctx); + + /* we need asyncmeta_getconn() not send result even on error, + * because we want to intercept the error and make it + * invalidCredentials */ + mc = asyncmeta_getconn( op, rs, candidates, NULL, LDAP_BACK_BIND_DONTSEND, 1 ); + if ( !mc ) { + Debug(LDAP_DEBUG_ANY, + "%s asyncmeta_back_bind: no target " "for dn \"%s\" (%d%s%s).\n", + op->o_log_prefix, op->o_req_dn.bv_val, + rs->sr_err, rs->sr_text ? ". " : "", + rs->sr_text ? rs->sr_text : "" ); + + /* FIXME: there might be cases where we don't want + * to map the error onto invalidCredentials */ + switch ( rs->sr_err ) { + case LDAP_NO_SUCH_OBJECT: + case LDAP_UNWILLING_TO_PERFORM: + rs->sr_err = LDAP_INVALID_CREDENTIALS; + rs->sr_text = NULL; + break; + } + send_ldap_result( op, rs ); + return rs->sr_err; + } + + /* + * Each target is scanned ... + */ + mc->mc_authz_target = META_BOUND_NONE; + for ( i = 0; i < mi->mi_ntargets; i++ ) { + a_metatarget_t *mt = mi->mi_targets[ i ]; + int lerr; + + /* + * Skip non-candidates + */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( gotit == 0 ) { + /* set rc to LDAP_SUCCESS only if at least + * one candidate has been tried */ + rc = LDAP_SUCCESS; + gotit = 1; + + } else if ( !isroot ) { + /* + * A bind operation is expected to have + * ONE CANDIDATE ONLY! + */ + Debug( LDAP_DEBUG_ANY, + "### %s asyncmeta_back_bind: more than one" + " candidate selected...\n", + op->o_log_prefix ); + } + + if ( isroot ) { + if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE + || BER_BVISNULL( &mt->mt_idassert_authcDN ) ) + { + a_metasingleconn_t *msc = &mc->mc_conns[ i ]; + + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ch_free( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + /* destroy sensitive data */ + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + ch_free( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + + continue; + } + + + (void)asyncmeta_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 ); + lerr = rs->sr_err; + + } else { + lerr = asyncmeta_single_bind( op, rs, mc, i ); + } + + if ( lerr != LDAP_SUCCESS ) { + rc = rs->sr_err = lerr; + + /* FIXME: in some cases (e.g. unavailable) + * do not assume it's not candidate; rather + * mark this as an error to be eventually + * reported to client */ + META_CANDIDATE_CLEAR( &candidates[ i ] ); + break; + } + } + + if ( mc != NULL ) { + for ( i = 0; i < mi->mi_ntargets; i++ ) { + a_metasingleconn_t *msc = &mc->mc_conns[ i ]; + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ch_free( msc->msc_bound_ndn.bv_val ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + /* destroy sensitive data */ + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + ch_free( msc->msc_cred.bv_val ); + } + } + asyncmeta_back_conn_free( mc ); + } + + /* + * rc is LDAP_SUCCESS if at least one bind succeeded, + * err is the last error that occurred during a bind; + * if at least (and at most?) one bind succeeds, fine. + */ + if ( rc != LDAP_SUCCESS ) { + + /* + * deal with bind failure ... + */ + + /* + * no target was found within the naming context, + * so bind must fail with invalid credentials + */ + if ( rs->sr_err == LDAP_SUCCESS && gotit == 0 ) { + rs->sr_err = LDAP_INVALID_CREDENTIALS; + } else { + rs->sr_err = slap_map_api2result( rs ); + } + send_ldap_result( op, rs ); + return rs->sr_err; + + } + return LDAP_SUCCESS; +} + +static int +asyncmeta_bind_op_result( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + int candidate, + int msgid, + ldap_back_send_t sendok, + int dolock ) +{ + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + LDAPMessage *res; + struct timeval tv; + int rc; + int nretries = mt->mt_nretries; + + Debug( LDAP_DEBUG_TRACE, + ">>> %s asyncmeta_bind_op_result[%d]\n", + op->o_log_prefix, candidate ); + + /* make sure this is clean */ + assert( rs->sr_ctrls == NULL ); + + if ( rs->sr_err == LDAP_SUCCESS ) { + time_t stoptime = (time_t)(-1), + timeout; + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + slap_op_t opidx = slap_req2op( op->o_tag ); + + /* since timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( opidx == LDAP_REQ_SEARCH ) { + if ( op->ors_tlimit <= 0 ) { + timeout = 0; + + } else { + timeout = op->ors_tlimit; + timeout_err = LDAP_TIMELIMIT_EXCEEDED; + timeout_text = NULL; + } + + } else { + timeout = mt->mt_timeout[ opidx ]; + } + + /* better than nothing :) */ + if ( timeout == 0 ) { + if ( mi->mi_idle_timeout ) { + timeout = mi->mi_idle_timeout; + + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + + /* + * handle response!!! + */ +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( nretries != META_RETRY_NEVER + || ( timeout && slap_get_time() <= stoptime ) ) + { + ldap_pvt_thread_yield(); + if ( nretries > 0 ) { + nretries--; + } + tv = mt->mt_bind_timeout; + goto retry; + } + + /* don't let anyone else use this handler, + * because there's a pending bind that will not + * be acknowledged */ + assert( LDAP_BACK_CONN_BINDING( msc ) ); + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "### %s asyncmeta_bind_op_result ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + + case -1: + ldap_get_option( msc->msc_ld, LDAP_OPT_ERROR_NUMBER, + &rs->sr_err ); + + Debug( LDAP_DEBUG_ANY, + "### %s asyncmeta_bind_op_result[%d]: err=%d (%s) nretries=%d.\n", + op->o_log_prefix, candidate, rs->sr_err, + ldap_err2string(rs->sr_err), nretries ); + break; + + default: + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < op->o_time ) { + msc->msc_time = op->o_time; + } + + /* FIXME: matched? referrals? response controls? */ + rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err, + NULL, NULL, NULL, NULL, 1 ); + if ( rc != LDAP_SUCCESS ) { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + break; + } + } + + rs->sr_err = slap_map_api2result( rs ); + Debug( LDAP_DEBUG_TRACE, + "<<< %s asyncmeta_bind_op_result[%d] err=%d\n", + op->o_log_prefix, candidate, rs->sr_err ); + + return rs->sr_err; +} + +/* + * asyncmeta_single_bind + * + * attempts to perform a bind with creds + */ +static int +asyncmeta_single_bind( + Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + int candidate ) +{ + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + struct berval mdn = BER_BVNULL; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int msgid; + a_dncookie dc; + struct berval save_o_dn; + int save_o_do_not_cache; + LDAPControl **ctrls = NULL; + + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ch_free( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + /* destroy sensitive data */ + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ch_free( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + + /* + * Rewrite the bind dn if needed + */ + dc.op = op; + dc.target = mt; + dc.memctx = op->o_tmpmemctx; + dc.to_from = MASSAGE_REQ; + + asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ); + + /* don't add proxyAuthz; set the bindDN */ + save_o_dn = op->o_dn; + save_o_do_not_cache = op->o_do_not_cache; + op->o_do_not_cache = 1; + op->o_dn = op->o_req_dn; + + ctrls = op->o_ctrls; + rs->sr_err = asyncmeta_controls_add( op, rs, mc, candidate, be_isroot(op), &ctrls ); + op->o_dn = save_o_dn; + op->o_do_not_cache = save_o_do_not_cache; + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + /* FIXME: this fixes the bind problem right now; we need + * to use the asynchronous version to get the "matched" + * and more in case of failure ... */ + /* FIXME: should we check if at least some of the op->o_ctrls + * can/should be passed? */ + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, mdn.bv_val, + LDAP_SASL_SIMPLE, &op->orb_cred, + ctrls, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, LDAP_BACK_DONTSEND, 1 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto return_results; + } + + /* If defined, proxyAuthz will be used also when + * back-ldap is the authorizing backend; for this + * purpose, a successful bind is followed by a + * bind with the configured identity assertion */ + /* NOTE: use with care */ + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + asyncmeta_proxy_authz_bind( mc, candidate, op, rs, LDAP_BACK_SENDERR, 1 ); + if ( !LDAP_BACK_CONN_ISBOUND( msc ) ) { + goto return_results; + } + goto cache_refresh; + } + + ber_bvreplace( &msc->msc_bound_ndn, &op->o_req_ndn ); + LDAP_BACK_CONN_ISBOUND_SET( msc ); + mc->mc_authz_target = candidate; + + if ( META_BACK_TGT_SAVECRED( mt ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &op->orb_cred ); + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + +cache_refresh:; + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED + && !BER_BVISEMPTY( &op->o_req_ndn ) ) + { + ( void )asyncmeta_dncache_update_entry( &mi->mi_cache, + &op->o_req_ndn, candidate ); + } + +return_results:; + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx ); + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + asyncmeta_quarantine( op, mi, rs, candidate ); + } + ldap_unbind_ext( msc->msc_ld, NULL, NULL ); + msc->msc_ld = NULL; + ldap_ld_free( msc->msc_ldr, 0, NULL, NULL ); + msc->msc_ldr = NULL; + return rs->sr_err; +} + + +/* + * asyncmeta_back_default_rebind + * + * This is a callback used for chasing referrals using the same + * credentials as the original user on this session. + */ +int +asyncmeta_back_default_rebind( + LDAP *ld, + LDAP_CONST char *url, + ber_tag_t request, + ber_int_t msgid, + void *params ) +{ + a_metasingleconn_t *msc = ( a_metasingleconn_t * )params; + + return ldap_sasl_bind_s( ld, msc->msc_bound_ndn.bv_val, + LDAP_SASL_SIMPLE, &msc->msc_cred, + NULL, NULL, NULL ); +} + +/* + * meta_back_default_urllist + * + * This is a callback used for mucking with the urllist + */ +int +asyncmeta_back_default_urllist( + LDAP *ld, + LDAPURLDesc **urllist, + LDAPURLDesc **url, + void *params ) +{ + a_metatarget_t *mt = (a_metatarget_t *)params; + LDAPURLDesc **urltail; + + if ( urllist == url ) { + return LDAP_SUCCESS; + } + + for ( urltail = &(*url)->lud_next; *urltail; urltail = &(*urltail)->lud_next ) + /* count */ ; + + *urltail = *urllist; + *urllist = *url; + *url = NULL; + + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + if ( mt->mt_uri ) { + ch_free( mt->mt_uri ); + } + + ldap_get_option( ld, LDAP_OPT_URI, (void *)&mt->mt_uri ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + return LDAP_SUCCESS; +} + +int +asyncmeta_back_cancel( + a_metaconn_t *mc, + Operation *op, + ber_int_t msgid, + int candidate ) +{ + + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + int rc = LDAP_OTHER; + struct timeval tv = { 0, 0 }; + ber_socket_t s; + + Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n", + op->o_log_prefix, candidate, msgid ); + + if (!( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) { + Debug( LDAP_DEBUG_TRACE, ">>> %s asyncmeta_back_cancel[%d] msgid=%d\n already reset", + op->o_log_prefix, candidate, msgid ); + return LDAP_SUCCESS; + } + + ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s ); + if (s < 0) { + return rc; + } + rc = ldap_int_poll( msc->msc_ld, s, &tv, 1); + if (rc < 0) { + rc = LDAP_SERVER_DOWN; + return rc; + } + /* default behavior */ + if ( META_BACK_TGT_ABANDON( mt ) ) { + rc = ldap_abandon_ext( msc->msc_ld, msgid, NULL, NULL ); + + } else if ( META_BACK_TGT_IGNORE( mt ) ) { + rc = ldap_pvt_discard( msc->msc_ld, msgid ); + + } else if ( META_BACK_TGT_CANCEL( mt ) ) { + rc = ldap_cancel_s( msc->msc_ld, msgid, NULL, NULL ); + + } else { + assert( 0 ); + } + + Debug( LDAP_DEBUG_TRACE, "<<< %s asyncmeta_back_cancel[%d] err=%d\n", + op->o_log_prefix, candidate, rc ); + + return rc; +} + + + +/* + * asyncmeta_back_proxy_authz_cred() + * + * prepares credentials & method for meta_back_proxy_authz_bind(); + * or, if method is SASL, performs the SASL bind directly. + */ +int +asyncmeta_back_proxy_authz_cred( + a_metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ) +{ + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval ndn; + int dobind = 0; + struct timeval old_tv = {0, 0}; + struct timeval bind_tv = { mt->mt_timeout[ SLAP_OP_BIND ], 0}; + /* don't proxyAuthz if protocol is not LDAPv3 */ + switch ( mt->mt_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + + if ( op->o_tag == LDAP_REQ_BIND ) { + ndn = op->o_req_ndn; + + } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; + + } else { + ndn = op->o_ndn; + } + rs->sr_err = LDAP_SUCCESS; + + /* + * FIXME: we need to let clients use proxyAuthz + * otherwise we cannot do symmetric pools of servers; + * we have to live with the fact that a user can + * authorize itself as any ID that is allowed + * by the authzTo directive of the "proxyauthzdn". + */ + /* + * NOTE: current Proxy Authorization specification + * and implementation do not allow proxy authorization + * control to be provided with Bind requests + */ + /* + * if no bind took place yet, but the connection is bound + * and the "proxyauthzdn" is set, then bind as + * "proxyauthzdn" and explicitly add the proxyAuthz + * control to every operation with the dn bound + * to the connection as control value. + */ + + /* bind as proxyauthzdn only if no idassert mode + * is requested, or if the client's identity + * is authorized */ + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + if ( !BER_BVISNULL( &ndn ) && !BER_BVISEMPTY( &ndn ) ) { + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) && !BER_BVISEMPTY( &mt->mt_idassert_authcDN ) ) + { + *binddn = mt->mt_idassert_authcDN; + *bindcred = mt->mt_idassert_passwd; + dobind = 1; + } + } + break; + + default: + /* NOTE: rootdn can always idassert */ + if ( BER_BVISNULL( &ndn ) + && mt->mt_idassert_authz == NULL + && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + rs->sr_err = LDAP_INAPPROPRIATE_AUTH; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + + } + + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + + } else if ( mt->mt_idassert_authz && !be_isroot( op ) ) { + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + + } else { + authcDN = ndn; + } + rs->sr_err = slap_sasl_matches( op, mt->mt_idassert_authz, + &authcDN, &authcDN ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + + rs->sr_err = LDAP_SUCCESS; + *binddn = slap_empty_bv; + *bindcred = slap_empty_bv; + break; + } + } + + *binddn = mt->mt_idassert_authcDN; + *bindcred = mt->mt_idassert_passwd; + dobind = 1; + break; + } + + if ( dobind && mt->mt_idassert_authmethod == LDAP_AUTH_SASL ) { +#ifdef HAVE_CYRUS_SASL + void *defaults = NULL; + struct berval authzID = BER_BVNULL; + int freeauthz = 0; + + /* if SASL supports native authz, prepare for it */ + if ( ( !op->o_do_not_cache || !op->o_is_auth_check ) && + ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + authzID = mt->mt_idassert_authzID; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + BER_BVSTR( &authzID, "dn:" ); + break; + + case LDAP_BACK_IDASSERT_SELF: + if ( BER_BVISNULL( &ndn ) ) { + /* connection is not authc'd, so don't idassert */ + BER_BVSTR( &authzID, "dn:" ); + break; + } + authzID.bv_len = STRLENOF( "dn:" ) + ndn.bv_len; + authzID.bv_val = slap_sl_malloc( authzID.bv_len + 1, op->o_tmpmemctx ); + AC_MEMCPY( authzID.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( authzID.bv_val + STRLENOF( "dn:" ), + ndn.bv_val, ndn.bv_len + 1 ); + freeauthz = 1; + break; + + default: + break; + } + } + + if ( mt->mt_idassert_secprops != NULL ) { + rs->sr_err = ldap_set_option( msc->msc_ld, + LDAP_OPT_X_SASL_SECPROPS, + (void *)mt->mt_idassert_secprops ); + + if ( rs->sr_err != LDAP_OPT_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + } + + ldap_get_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv); + + if (mt->mt_timeout[ SLAP_OP_BIND ] > 0 ) { + rs->sr_err = ldap_set_option( msc->msc_ld, + LDAP_OPT_TIMEOUT, + (void *)&bind_tv ); + + if ( rs->sr_err != LDAP_OPT_SUCCESS ) { + rs->sr_err = LDAP_OTHER; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + goto done; + } + } + defaults = lutil_sasl_defaults( msc->msc_ld, + mt->mt_idassert_sasl_mech.bv_val, + mt->mt_idassert_sasl_realm.bv_val, + mt->mt_idassert_authcID.bv_val, + mt->mt_idassert_passwd.bv_val, + authzID.bv_val ); + if ( defaults == NULL ) { + rs->sr_err = LDAP_OTHER; + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + goto done; + } + + rs->sr_err = ldap_sasl_interactive_bind_s( msc->msc_ld, binddn->bv_val, + mt->mt_idassert_sasl_mech.bv_val, NULL, NULL, + LDAP_SASL_QUIET, lutil_sasl_interact, + defaults ); + + /* restore the old timeout just in case */ + ldap_set_option( msc->msc_ld, LDAP_OPT_TIMEOUT, (void *)&old_tv ); + + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( LogTest( asyncmeta_debug ) ) { + char time_buf[ SLAP_TEXT_BUFLEN ]; + asyncmeta_get_timestamp(time_buf); + Debug( asyncmeta_debug, "[%s] asyncmeta_back_proxy_authz_cred failed bind msc: %p\n", + time_buf, msc ); + } + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + + } else { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } + + lutil_sasl_freedefs( defaults ); + if ( freeauthz ) { + slap_sl_free( authzID.bv_val, op->o_tmpmemctx ); + } + + goto done; +#endif /* HAVE_CYRUS_SASL */ + } + + *method = mt->mt_idassert_authmethod; + switch ( mt->mt_idassert_authmethod ) { + case LDAP_AUTH_NONE: + BER_BVSTR( binddn, "" ); + BER_BVSTR( bindcred, "" ); + /* fallthru */ + + case LDAP_AUTH_SIMPLE: + break; + + default: + /* unsupported! */ + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + rs->sr_err = LDAP_AUTH_METHOD_NOT_SUPPORTED; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + break; + } + +done:; + + if ( !BER_BVISEMPTY( binddn ) ) { + LDAP_BACK_CONN_ISIDASSERT_SET( msc ); + } + + return rs->sr_err; +} + +static int +asyncmeta_proxy_authz_bind( + a_metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ) +{ + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval binddn = BER_BVC( "" ), + cred = BER_BVC( "" ); + int method = LDAP_AUTH_NONE, + rc; + + rc = asyncmeta_back_proxy_authz_cred( mc, candidate, op, rs, sendok, &binddn, &cred, &method ); + if ( rc == LDAP_SUCCESS && !LDAP_BACK_CONN_ISBOUND( msc ) ) { + int msgid; + + switch ( method ) { + case LDAP_AUTH_NONE: + case LDAP_AUTH_SIMPLE: + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, + binddn.bv_val, LDAP_SASL_SIMPLE, + &cred, NULL, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + rc = asyncmeta_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock ); + if ( rc == LDAP_SUCCESS ) { + /* set rebind stuff in case of successful proxyAuthz bind, + * so that referral chasing is attempted using the right + * identity */ + LDAP_BACK_CONN_ISBOUND_SET( msc ); + ber_bvreplace( &msc->msc_bound_ndn, &binddn ); + + if ( META_BACK_TGT_SAVECRED( mt ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &cred ); + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + } + break; + + default: + assert( 0 ); + break; + } + } + + return LDAP_BACK_CONN_ISBOUND( msc ); +} + + +static int +asyncmeta_back_proxy_authz_ctrl(Operation *op, + SlapReply *rs, + struct berval *bound_ndn, + int version, + int isroot, + slap_idassert_t *si, + LDAPControl *ctrl ) +{ + slap_idassert_mode_t mode; + struct berval assertedID, + ndn; + + rs->sr_err = SLAP_CB_CONTINUE; + + /* FIXME: SASL/EXTERNAL over ldapi:// doesn't honor the authcID, + * but if it is not set this test fails. We need a different + * means to detect if idassert is enabled */ + if ( ( BER_BVISNULL( &si->si_bc.sb_authcId ) || BER_BVISEMPTY( &si->si_bc.sb_authcId ) ) + && ( BER_BVISNULL( &si->si_bc.sb_binddn ) || BER_BVISEMPTY( &si->si_bc.sb_binddn ) ) + && BER_BVISNULL( &si->si_bc.sb_saslmech ) ) + { + goto done; + } + + if ( !op->o_conn || op->o_do_not_cache || ( isroot ) ) { + goto done; + } + + if ( op->o_tag == LDAP_REQ_BIND ) { + ndn = op->o_req_ndn; + +#if 0 + } else if ( !BER_BVISNULL( &op->o_conn->c_ndn ) ) { + ndn = op->o_conn->c_ndn; +#endif + } else { + ndn = op->o_ndn; + } + + if ( si->si_mode == LDAP_BACK_IDASSERT_LEGACY ) { + if ( op->o_proxy_authz ) { + /* + * FIXME: we do not want to perform proxyAuthz + * on behalf of the client, because this would + * be performed with "proxyauthzdn" privileges. + * + * This might actually be too strict, since + * the "proxyauthzdn" authzTo, and each entry's + * authzFrom attributes may be crafted + * to avoid unwanted proxyAuthz to take place. + */ +#if 0 + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; +#endif + goto done; + } + + if ( !BER_BVISNULL( bound_ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &ndn ) ) { + goto done; + } + + if ( BER_BVISNULL( &si->si_bc.sb_binddn ) ) { + goto done; + } + + } else if ( si->si_bc.sb_method == LDAP_AUTH_SASL ) { + if ( ( si->si_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) ) + { + /* already asserted in SASL via native authz */ + goto done; + } + + } else if ( si->si_authz && !isroot ) { + int rc; + struct berval authcDN; + + if ( BER_BVISNULL( &ndn ) ) { + authcDN = slap_empty_bv; + } else { + authcDN = ndn; + } + rc = slap_sasl_matches( op, si->si_authz, + &authcDN, &authcDN ); + if ( rc != LDAP_SUCCESS ) { + if ( si->si_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + /* ndn is not authorized + * to use idassert */ + rs->sr_err = rc; + } + goto done; + } + } + + if ( op->o_proxy_authz ) { + /* + * FIXME: we can: + * 1) ignore the already set proxyAuthz control + * 2) leave it in place, and don't set ours + * 3) add both + * 4) reject the operation + * + * option (4) is very drastic + * option (3) will make the remote server reject + * the operation, thus being equivalent to (4) + * option (2) will likely break the idassert + * assumptions, so we cannot accept it; + * option (1) means that we are contradicting + * the client's request. + * + * I think (4) is the only correct choice. + */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "proxyAuthz not allowed within namingContext"; + } + + if ( op->o_is_auth_check ) { + mode = LDAP_BACK_IDASSERT_NOASSERT; + + } else { + mode = si->si_mode; + } + + switch ( mode ) { + case LDAP_BACK_IDASSERT_LEGACY: + /* original behavior: + * assert the client's identity */ + case LDAP_BACK_IDASSERT_SELF: + assertedID = ndn; + break; + + case LDAP_BACK_IDASSERT_ANONYMOUS: + /* assert "anonymous" */ + assertedID = slap_empty_bv; + break; + + case LDAP_BACK_IDASSERT_NOASSERT: + /* don't assert; bind as proxyauthzdn */ + goto done; + + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + /* assert idassert DN */ + assertedID = si->si_bc.sb_authzId; + break; + + default: + assert( 0 ); + } + + /* if we got here, "" is allowed to proxyAuthz */ + if ( BER_BVISNULL( &assertedID ) ) { + assertedID = slap_empty_bv; + } + + /* don't idassert the bound DN (ITS#4497) */ + if ( dn_match( &assertedID, bound_ndn ) ) { + goto done; + } + + ctrl->ldctl_oid = LDAP_CONTROL_PROXY_AUTHZ; + ctrl->ldctl_iscritical = ( ( si->si_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) == LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ); + + switch ( si->si_mode ) { + /* already in u:ID or dn:DN form */ + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + ber_dupbv_x( &ctrl->ldctl_value, &assertedID, op->o_tmpmemctx ); + rs->sr_err = LDAP_SUCCESS; + break; + + /* needs the dn: prefix */ + default: + ctrl->ldctl_value.bv_len = assertedID.bv_len + STRLENOF( "dn:" ); + ctrl->ldctl_value.bv_val = op->o_tmpalloc( ctrl->ldctl_value.bv_len + 1, + op->o_tmpmemctx ); + AC_MEMCPY( ctrl->ldctl_value.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &ctrl->ldctl_value.bv_val[ STRLENOF( "dn:" ) ], + assertedID.bv_val, assertedID.bv_len + 1 ); + rs->sr_err = LDAP_SUCCESS; + break; + } + + /* Older versions of 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 . + * + * 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 + * . + */ + +/* 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 +#include "ac/string.h" + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-asyncmeta.h" + +/* + * The meta-directory has one suffix, called . + * It handles a pool of target servers, each with a branch suffix + * of the form ,, where 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: == , + * all the targets are candidates (search ...) + * - the request DN is a branch suffix: == ,, or + * - the request DN is a subtree of a branch 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 . + */ +int +asyncmeta_is_candidate( + a_metatarget_t *mt, + struct berval *ndn, + int scope ) +{ + struct berval rdn; + int d = ndn->bv_len - mt->mt_nsuffix.bv_len; + + if ( d >= 0 ) { + if ( !dnIsSuffix( ndn, &mt->mt_nsuffix ) ) { + return META_NOT_CANDIDATE; + } + + /* + * | match | exclude | + * +---------+---------+-------------------+ + * | T | T | not candidate | + * | F | T | continue checking | + * +---------+---------+-------------------+ + * | T | F | candidate | + * | F | F | not candidate | + * +---------+---------+-------------------+ + */ + + if ( mt->mt_subtree ) { + int match = ( asyncmeta_subtree_match( mt, ndn, scope ) != NULL ); + + if ( !mt->mt_subtree_exclude ) { + return match ? META_CANDIDATE : META_NOT_CANDIDATE; + } + + if ( match /* && mt->mt_subtree_exclude */ ) { + return META_NOT_CANDIDATE; + } + } + + switch ( mt->mt_scope ) { + case LDAP_SCOPE_SUBTREE: + default: + return META_CANDIDATE; + + case LDAP_SCOPE_SUBORDINATE: + if ( d > 0 ) { + return META_CANDIDATE; + } + break; + + /* nearly useless; not allowed by config */ + case LDAP_SCOPE_ONELEVEL: + if ( d > 0 ) { + rdn.bv_val = ndn->bv_val; + rdn.bv_len = (ber_len_t)d - STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) ) { + return META_CANDIDATE; + } + } + break; + + /* nearly useless; not allowed by config */ + case LDAP_SCOPE_BASE: + if ( d == 0 ) { + return META_CANDIDATE; + } + break; + } + + } else /* if ( d < 0 ) */ { + if ( !dnIsSuffix( &mt->mt_nsuffix, ndn ) ) { + return META_NOT_CANDIDATE; + } + + switch ( scope ) { + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + /* + * suffix longer than dn, but common part matches + */ + return META_CANDIDATE; + + case LDAP_SCOPE_ONELEVEL: + rdn.bv_val = mt->mt_nsuffix.bv_val; + rdn.bv_len = (ber_len_t)(-d) - STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) ) { + return META_CANDIDATE; + } + break; + } + } + + return META_NOT_CANDIDATE; +} + +/* + * meta_back_select_unique_candidate + * + * returns the index of the candidate in case it is unique, otherwise + * META_TARGET_NONE if none matches, or + * META_TARGET_MULTIPLE if more than one matches + * Note: ndn MUST be normalized. + */ +int +asyncmeta_select_unique_candidate( + a_metainfo_t *mi, + struct berval *ndn ) +{ + int i, candidate = META_TARGET_NONE; + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + a_metatarget_t *mt = mi->mi_targets[ i ]; + + if ( asyncmeta_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) { + if ( candidate == META_TARGET_NONE ) { + candidate = i; + + } + } + } + + return candidate; +} + +/* + * asyncmeta_clear_unused_candidates + * + * clears all candidates except candidate + */ +int +asyncmeta_clear_unused_candidates( + Operation *op, + int candidate, + a_metaconn_t *mc, + SlapReply *candidates) +{ + a_metainfo_t *mi = mc->mc_info; + int i; + + for ( i = 0; i < mi->mi_ntargets; ++i ) { + if ( i == candidate ) { + continue; + } + META_CANDIDATE_RESET( &candidates[ i ] ); + } + + return 0; +} diff --git a/servers/slapd/back-asyncmeta/compare.c b/servers/slapd/back-asyncmeta/compare.c new file mode 100644 index 0000000..8f56719 --- /dev/null +++ b/servers/slapd/back-asyncmeta/compare.c @@ -0,0 +1,312 @@ +/* compare.c - compare exop handler for back-asyncmeta */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +/* 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 + +#include +#include +#include "slap.h" +#include "../../../libraries/liblber/lber-int.h" +#include "../../../libraries/libldap/ldap-int.h" +#include "../back-ldap/back-ldap.h" +#include "back-asyncmeta.h" + +meta_search_candidate_t +asyncmeta_back_compare_start(Operation *op, + SlapReply *rs, + a_metaconn_t *mc, + bm_context_t *bc, + int candidate, + int do_lock) +{ + a_dncookie dc; + a_metainfo_t *mi = mc->mc_info; + a_metatarget_t *mt = mi->mi_targets[ candidate ]; + struct berval c_attr = op->orc_ava->aa_desc->ad_cname; + struct berval mdn = BER_BVNULL; + struct berval mapped_value = op->orc_ava->aa_value; + int rc = 0; + LDAPControl **ctrls = NULL; + meta_search_candidate_t retcode = META_SEARCH_CANDIDATE; + BerElement *ber = NULL; + a_metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + SlapReply *candidates = bc->candidates; + ber_int_t msgid; + + dc.op = op; + dc.target = mt; + dc.memctx = op->o_tmpmemctx; + dc.to_from = MASSAGE_REQ; + + asyncmeta_dn_massage( &dc, &op->o_req_dn, &mdn ); + + if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + asyncmeta_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value ); + + asyncmeta_set_msc_time(msc); + ctrls = op->o_ctrls; + if ( asyncmeta_controls_add( op, rs, mc, candidate, bc->is_root,&ctrls ) != LDAP_SUCCESS ) + { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_ERR; + goto done; + } + /* someone might have reset the connection */ + if (!( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + + ber = ldap_build_compare_req( msc->msc_ld, mdn.bv_val, c_attr.bv_val, &mapped_value, + ctrls, NULL, &msgid); + + if (!ber) { + Debug( asyncmeta_debug, "%s asyncmeta_back_compare_start: Operation encoding failed with errno %d\n", + op->o_log_prefix, msc->msc_ld->ld_errno ); + rs->sr_err = LDAP_OPERATIONS_ERROR; + rs->sr_text = "Failed to encode proxied request"; + retcode = META_SEARCH_ERR; + goto done; + } + + if (ber) { + struct timeval tv = {0, mt->mt_network_timeout*1000}; + ber_socket_t s; + if (!( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc )) || msc->msc_ld == NULL ) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + + ldap_get_option( msc->msc_ld, LDAP_OPT_DESC, &s ); + if (s < 0) { + Debug( asyncmeta_debug, "msc %p not initialized at %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + rc = ldap_int_poll( msc->msc_ld, s, &tv, 1); + if (rc < 0) { + Debug( asyncmeta_debug, "msc %p not writable within network timeout %s:%d\n", msc, __FILE__, __LINE__ ); + if ((msc->msc_result_time + META_BACK_RESULT_INTERVAL) < slap_get_time()) { + rc = LDAP_SERVER_DOWN; + } else { + goto error_unavailable; + } + } else { + candidates[ candidate ].sr_msgid = msgid; + rc = ldap_send_initial_request( msc->msc_ld, LDAP_REQ_COMPARE, + mdn.bv_val, ber, msgid ); + if (rc == msgid) + rc = LDAP_SUCCESS; + else + rc = LDAP_SERVER_DOWN; + ber = NULL; + } + + switch ( rc ) { + case LDAP_SUCCESS: + retcode = META_SEARCH_CANDIDATE; + asyncmeta_set_msc_time(msc); + goto done; + + case LDAP_SERVER_DOWN: + /* do not lock if called from asyncmeta_handle_bind_result. Also do not reset the connection */ + if (do_lock > 0) { + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + asyncmeta_reset_msc(NULL, mc, candidate, 0, __FUNCTION__); + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + } + /* fall though*/ + default: + Debug( asyncmeta_debug, "msc %p ldap_send_initial_request failed. %s:%d\n", msc, __FILE__, __LINE__ ); + goto error_unavailable; + } + } + +error_unavailable: + if (ber) + ber_free(ber, 1); + switch (bc->nretries[candidate]) { + case -1: /* nretries = forever */ + retcode = META_SEARCH_NEED_BIND; + break; + case 0: /* no retries left */ + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Unable to send compare request to target"; + retcode = META_SEARCH_ERR; + break; + default: /* more retries left - try to rebind and go again */ + retcode = META_SEARCH_NEED_BIND; + bc->nretries[candidate]--; + break; + } +done: + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) { + op->o_tmpfree( mapped_value.bv_val, op->o_tmpmemctx ); + } + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + op->o_tmpfree( mdn.bv_val, op->o_tmpmemctx ); + } + + Debug( LDAP_DEBUG_TRACE, "%s <<< asyncmeta_back_compare_start[%p]=%d\n", op->o_log_prefix, msc, candidates[candidate].sr_msgid ); + return retcode; +} + +int +asyncmeta_back_compare( Operation *op, SlapReply *rs ) +{ + a_metainfo_t *mi = ( a_metainfo_t * )op->o_bd->be_private; + a_metatarget_t *mt; + a_metaconn_t *mc; + int rc, candidate = -1; + void *thrctx = op->o_threadctx; + bm_context_t *bc; + SlapReply *candidates; + time_t current_time = slap_get_time(); + int max_pending_ops = (mi->mi_max_pending_ops == 0) ? META_BACK_CFG_MAX_PENDING_OPS : mi->mi_max_pending_ops; + + Debug(LDAP_DEBUG_ARGS, "==> asyncmeta_back_compare: %s\n", + op->o_req_dn.bv_val ); + + if (current_time > op->o_time) { + Debug( asyncmeta_debug, "==> asyncmeta_back_compare[%s]: o_time:[%ld], current time: [%ld]\n", + op->o_log_prefix, op->o_time, current_time ); + } + + if ( mi->mi_ntargets == 0 ) { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "No targets are configured for this database"; + send_ldap_result(op, rs); + return rs->sr_err; + } + + asyncmeta_new_bm_context(op, rs, &bc, mi->mi_ntargets, mi ); + if (bc == NULL) { + rs->sr_err = LDAP_OTHER; + send_ldap_result(op, rs); + return rs->sr_err; + } + + candidates = bc->candidates; + mc = asyncmeta_getconn( op, rs, candidates, &candidate, LDAP_BACK_DONTSEND, 0); + if ( !mc || rs->sr_err != LDAP_SUCCESS) { + send_ldap_result(op, rs); + return rs->sr_err; + } + + mt = mi->mi_targets[ candidate ]; + bc->timeout = mt->mt_timeout[ SLAP_OP_COMPARE ]; + bc->retrying = LDAP_BACK_RETRYING; + bc->sendok = ( LDAP_BACK_SENDRESULT | bc->retrying ); + bc->stoptime = op->o_time + bc->timeout; + bc->bc_active = 1; + + if (mc->pending_ops >= max_pending_ops) { + rs->sr_err = LDAP_BUSY; + rs->sr_text = "Maximum pending ops limit exceeded"; + send_ldap_result(op, rs); + return rs->sr_err; + } + + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + rc = asyncmeta_add_message_queue(mc, bc); + mc->mc_conns[candidate].msc_active++; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + + if (rc != LDAP_SUCCESS) { + rs->sr_err = LDAP_BUSY; + rs->sr_text = "Maximum pending ops limit exceeded"; + send_ldap_result(op, rs); + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + mc->mc_conns[candidate].msc_active--; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + goto finish; + } + +retry: + if (bc->timeout && bc->stoptime < slap_get_time()) { + int timeout_err; + timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + rs->sr_err = timeout_err; + rs->sr_text = "Operation timed out before it was sent to target"; + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + } + + rc = asyncmeta_dobind_init_with_retry(op, rs, bc, mc, candidate); + switch (rc) + { + case META_SEARCH_CANDIDATE: + /* target is already bound, just send the request */ + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + + rc = asyncmeta_back_compare_start( op, rs, mc, bc, candidate, 1); + if (rc == META_SEARCH_ERR) { + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + + } else if (rc == META_SEARCH_NEED_BIND) { + goto retry; + } + break; + case META_SEARCH_NOT_CANDIDATE: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: NOT_CANDIDATE " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + + case META_SEARCH_NEED_BIND: + case META_SEARCH_BINDING: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: BINDING " + "cnd=\"%d\" %p\n", op->o_log_prefix, candidate , &mc->mc_conns[candidate]); + /* Todo add the context to the message queue but do not send the request + the receiver must send this when we are done binding */ + /* question - how would do receiver know to which targets??? */ + break; + + case META_SEARCH_ERR: + Debug( LDAP_DEBUG_TRACE, "%s asyncmeta_back_compare: ERR " + "cnd=\"%d\"\n", op->o_log_prefix, candidate ); + asyncmeta_error_cleanup(op, rs, bc, mc, candidate); + goto finish; + default: + assert( 0 ); + break; + } + + ldap_pvt_thread_mutex_lock( &mc->mc_om_mutex); + mc->mc_conns[candidate].msc_active--; + asyncmeta_start_one_listener(mc, candidates, bc, candidate); + bc->bc_active--; + ldap_pvt_thread_mutex_unlock( &mc->mc_om_mutex); + rs->sr_err = SLAPD_ASYNCOP; +finish: + return rs->sr_err; +} diff --git a/servers/slapd/back-asyncmeta/config.c b/servers/slapd/back-asyncmeta/config.c new file mode 100644 index 0000000..e4cc5ea --- /dev/null +++ b/servers/slapd/back-asyncmeta/config.c @@ -0,0 +1,2536 @@ +/* config.c - configuration parsing for back-asyncmeta */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software . + * + * 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 + * . + */ + +/* 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 +#include + +#include +#include + +#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", "", 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> ]", 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|", 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|", 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", "", 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", "", 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", "", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_MAX_TIMEOUT_OPS, + asyncmeta_back_cf_gen, "( OLcfgDbAt:3.115 " + "NAME 'olcDbMaxTimeoutOps' " + "DESC 'Maximum number of consecutive timeout operations after which the connection is reset' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + + { NULL, NULL, 0, 0, 0, ARG_IGNORED, + NULL, NULL, NULL, NULL } +}; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING +#define ST_ATTR "$ olcDbSessionTrackingRequest " +#else +#define ST_ATTR "" +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + +#define COMMON_ATTRS \ + "$ olcDbBindTimeout " \ + "$ olcDbCancel " \ + "$ olcDbChaseReferrals " \ + "$ olcDbClientPr " \ + "$ olcDbDefaultTarget " \ + "$ olcDbNetworkTimeout " \ + "$ olcDbNoRefs " \ + "$ olcDbNoUndefFilter " \ + "$ olcDbNretries " \ + "$ olcDbProtocolVersion " \ + "$ olcDbQuarantine " \ + "$ olcDbRebindAsUser " \ + ST_ATTR \ + "$ olcDbStartTLS " \ + "$ olcDbTFSupport " + +static ConfigOCs a_metaocs[] = { + { "( OLcfgDbOc:3.4 " + "NAME 'olcAsyncMetaConfig' " + "DESC 'Asyncmeta backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbDnCacheTtl " + "$ olcDbIdleTimeout " + "$ olcDbOnErr " + "$ olcDbPseudoRootBindDefer " + "$ olcDbConnectionPoolMax " + "$ olcDbMaxTimeoutOps" + "$ olcDbMaxPendingOps " + "$ olcDbMaxTargetConns" + /* defaults, may be overridden per-target */ + COMMON_ATTRS + ") )", + Cft_Database, a_metacfg, NULL, asyncmeta_cfadd }, + { "( OLcfgDbOc:3.5 " + "NAME 'olcAsyncMetaTargetConfig' " + "DESC 'Asyncmeta target configuration' " + "SUP olcConfig STRUCTURAL " + "MUST ( olcAsyncMetaSub $ olcDbURI ) " + "MAY ( olcDbIDAssertAuthzFrom " + "$ olcDbIDAssertBind " + "$ olcDbSuffixMassage " + "$ olcDbSubtreeExclude " + "$ olcDbSubtreeInclude " + "$ olcDbTimeout " + "$ olcDbKeepalive " + "$ olcDbFilter " + "$ olcDbTcpUserTimeout " + + /* defaults may be inherited */ + COMMON_ATTRS + ") )", + Cft_Misc, a_metacfg, asyncmeta_ldadd }, + { NULL, 0, NULL } +}; + +static int +asyncmeta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c ) +{ + a_metainfo_t *mi; + + if ( p->ce_type != Cft_Database || !p->ce_be || + p->ce_be->be_cf_ocs != a_metaocs ) + return LDAP_CONSTRAINT_VIOLATION; + + c->be = p->ce_be; + mi = ( a_metainfo_t * )c->be->be_private; + + if ( asyncmeta_db_has_pending_ops ( mi ) > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "cannot modify a working database" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg ); + return 1; + } + return LDAP_SUCCESS; +} + +static int +asyncmeta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c ) +{ + a_metainfo_t *mi = ( a_metainfo_t * )c->be->be_private; + struct berval bv; + int i; + + bv.bv_val = c->cr_msg; + for ( i=0; imi_ntargets; i++ ) { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "olcAsyncMetaSub=" SLAP_X_ORDERED_FMT "uri", i ); + c->ca_private = mi->mi_targets[i]; + c->valx = i; + config_build_entry( op, rs, p->e_private, c, + &bv, &a_metaocs[1], NULL ); + } + + return LDAP_SUCCESS; +} + +static int +asyncmeta_back_new_target( + a_metatarget_t **mtp, + a_metainfo_t *mi ) +{ + a_metatarget_t *mt; + + *mtp = NULL; + int i; + + assert ( mi != NULL ); + mt = ch_calloc( sizeof( a_metatarget_t ), 1 ); + + ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex ); + + mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY; + mt->mt_idassert_authmethod = LDAP_AUTH_NONE; + mt->mt_idassert_tls = SB_TLS_DEFAULT; + /* by default, use proxyAuthz control on each operation */ + mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE; + + *mtp = mt; + + for ( i = 0; i < mi->mi_num_conns; i++ ) { + a_metaconn_t *mc = &mi->mi_conns[i]; + mc->mc_conns = ch_realloc( mc->mc_conns, sizeof( a_metasingleconn_t ) * mi->mi_ntargets); + memset( &(mc->mc_conns[mi->mi_ntargets-1]), 0, sizeof( a_metasingleconn_t ) ); + } + /* If this is the first target, start the timeout loop */ + if ( mi->mi_ntargets == 1 ) { + ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex ); + mi->mi_task = ldap_pvt_runqueue_insert( &slapd_rq, 1, + asyncmeta_timeout_loop, mi, "asyncmeta_timeout_loop", mi->mi_suffix.bv_val ); + ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex ); + } + return 0; +} + +/* suffixmassage config */ +static int +asyncmeta_suffixm_config( + ConfigArgs *c, + int argc, + char **argv, + a_metatarget_t *mt +) +{ + BackendDB *tmp_bd; + struct berval dn, nvnc, pvnc, nrnc, prnc; + int j; + + /* + * syntax: + * + * suffixmassage + * + * the field must be defined as a valid suffix + * (or suffixAlias?) for the current database; + * the 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: \"%s\" resolves to this database, in " + "\"suffixMassage \"\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.