diff options
Diffstat (limited to '')
-rw-r--r-- | servers/slapd/back-meta/Makefile.in | 45 | ||||
-rw-r--r-- | servers/slapd/back-meta/add.c | 211 | ||||
-rw-r--r-- | servers/slapd/back-meta/back-meta.h | 705 | ||||
-rw-r--r-- | servers/slapd/back-meta/bind.c | 1771 | ||||
-rw-r--r-- | servers/slapd/back-meta/candidates.c | 284 | ||||
-rw-r--r-- | servers/slapd/back-meta/compare.c | 154 | ||||
-rw-r--r-- | servers/slapd/back-meta/config.c | 3385 | ||||
-rw-r--r-- | servers/slapd/back-meta/conn.c | 1910 | ||||
-rw-r--r-- | servers/slapd/back-meta/delete.c | 103 | ||||
-rw-r--r-- | servers/slapd/back-meta/dncache.c | 235 | ||||
-rw-r--r-- | servers/slapd/back-meta/init.c | 475 | ||||
-rw-r--r-- | servers/slapd/back-meta/map.c | 877 | ||||
-rw-r--r-- | servers/slapd/back-meta/modify.c | 221 | ||||
-rw-r--r-- | servers/slapd/back-meta/modrdn.c | 177 | ||||
-rw-r--r-- | servers/slapd/back-meta/proto-meta.h | 54 | ||||
-rw-r--r-- | servers/slapd/back-meta/search.c | 2465 | ||||
-rw-r--r-- | servers/slapd/back-meta/suffixmassage.c | 193 | ||||
-rw-r--r-- | servers/slapd/back-meta/unbind.c | 89 |
18 files changed, 13354 insertions, 0 deletions
diff --git a/servers/slapd/back-meta/Makefile.in b/servers/slapd/back-meta/Makefile.in new file mode 100644 index 0000000..12974fb --- /dev/null +++ b/servers/slapd/back-meta/Makefile.in @@ -0,0 +1,45 @@ +# Makefile.in for back-meta +# $OpenLDAP$ +## This work is part of OpenLDAP Software <http://www.openldap.org/>. +## +## Copyright 1998-2021 The OpenLDAP Foundation. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## <http://www.OpenLDAP.org/license.html>. + +SRCS = init.c config.c search.c bind.c unbind.c add.c compare.c \ + delete.c modify.c modrdn.c suffixmassage.c map.c \ + conn.c candidates.c dncache.c +OBJS = init.lo config.lo search.lo bind.lo unbind.lo add.lo compare.lo \ + delete.lo modify.lo modrdn.lo suffixmassage.lo map.lo \ + conn.lo candidates.lo dncache.lo + +LDAP_INCDIR= ../../../include +LDAP_LIBDIR= ../../../libraries + +BUILD_OPT = "--enable-meta" +BUILD_MOD = @BUILD_META@ + +mod_DEFS = -DSLAPD_IMPORT +MOD_DEFS = $(@BUILD_META@_DEFS) + +shared_LDAP_LIBS = $(LDAP_LIBLDAP_R_LA) $(LDAP_LIBLBER_LA) +NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) +UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) + +LIBBASE = back_meta + +XINCPATH = -I.. -I$(srcdir)/.. +XDEFS = $(MODULES_CPPFLAGS) + +all-local-lib: ../.backend + +../.backend: lib$(LIBBASE).a + @touch $@ + diff --git a/servers/slapd/back-meta/add.c b/servers/slapd/back-meta/add.c new file mode 100644 index 0000000..745bc1e --- /dev/null +++ b/servers/slapd/back-meta/add.c @@ -0,0 +1,211 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_add( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int i, candidate = -1; + int isupdate; + Attribute *a; + LDAPMod **attrs; + struct berval mdn = BER_BVNULL, mapped; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + Debug(LDAP_DEBUG_ARGS, "==> meta_back_add: %s\n", + op->o_req_dn.bv_val, 0, 0 ); + + /* + * get the current connection + */ + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the add dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "addDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto done; + } + + /* 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 = ch_malloc( sizeof( LDAPMod * )*i ); + + dc.ctx = "addAttrDN"; + isupdate = be_shadow_update( op ); + for ( i = 0, a = op->ora_e->e_attrs; a; a = a->a_next ) { + int j, is_oc = 0; + + if ( !isupdate && !get_relax( op ) && a->a_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + if ( a->a_desc == slap_schema.si_ad_objectClass + || a->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + mapped = a->a_desc->ad_cname; + + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &a->a_desc->ad_cname, &mapped, BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { + continue; + } + } + + attrs[ i ] = ch_malloc( sizeof( LDAPMod ) ); + if ( attrs[ i ] == NULL ) { + continue; + } + attrs[ i ]->mod_op = LDAP_MOD_BVALUES; + attrs[ i ]->mod_type = mapped.bv_val; + + if ( is_oc ) { + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) + ; + + attrs[ i ]->mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); ) { + struct ldapmapping *mapping; + + ldap_back_mapping( &mt->mt_rwmap.rwm_oc, + &a->a_vals[ j ], &mapping, BACKLDAP_MAP ); + + if ( mapping == NULL ) { + if ( mt->mt_rwmap.rwm_oc.drop_missing ) { + continue; + } + attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ]; + + } else { + attrs[ i ]->mod_bvalues[ j ] = &mapping->dst; + } + j++; + } + attrs[ i ]->mod_bvalues[ j ] = NULL; + + } else { + /* + * FIXME: dn-valued attrs should be rewritten + * to allow their use in ACLs at the back-ldap + * level. + */ + if ( a->a_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + (void)ldap_dnattr_rewrite( &dc, a->a_vals ); + if ( a->a_vals == NULL ) { + continue; + } + } + + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) + ; + + attrs[ i ]->mod_bvalues = ch_malloc( ( j + 1 ) * sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &a->a_vals[ j ] ); j++ ) { + attrs[ i ]->mod_bvalues[ j ] = &a->a_vals[ j ]; + } + attrs[ i ]->mod_bvalues[ j ] = NULL; + } + i++; + } + attrs[ i ] = NULL; + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_add_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + attrs, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_ADD ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + for ( --i; i >= 0; --i ) { + free( attrs[ i ]->mod_bvalues ); + free( attrs[ i ] ); + } + free( attrs ); + if ( mdn.bv_val != op->ora_e->e_dn ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + +done:; + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/back-meta.h b/servers/slapd/back-meta/back-meta.h new file mode 100644 index 0000000..087111d --- /dev/null +++ b/servers/slapd/back-meta/back-meta.h @@ -0,0 +1,705 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef SLAPD_LDAP_H +#error "include servers/slapd/back-ldap/back-ldap.h before this file!" +#endif /* SLAPD_LDAP_H */ + +#ifndef SLAPD_META_H +#define SLAPD_META_H + +#ifdef LDAP_DEVEL +#define SLAPD_META_CLIENT_PR 1 +#endif /* LDAP_DEVEL */ + +#include "proto-meta.h" + +/* String rewrite library */ +#include "rewrite.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 */ + +/* from back-ldap.h before rwm removal */ +struct ldapmap { + int drop_missing; + + Avlnode *map; + Avlnode *remap; +}; + +struct ldapmapping { + struct berval src; + struct berval dst; +}; + +struct ldaprwmap { + /* + * DN rewriting + */ +#ifdef ENABLE_REWRITE + struct rewrite_info *rwm_rw; +#else /* !ENABLE_REWRITE */ + /* some time the suffix massaging without librewrite + * will be disabled */ + BerVarray rwm_suffix_massage; +#endif /* !ENABLE_REWRITE */ + BerVarray rwm_bva_rewrite; + + /* + * Attribute/objectClass mapping + */ + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + BerVarray rwm_bva_map; +}; + +/* Whatever context ldap_back_dn_massage needs... */ +typedef struct dncookie { + struct metatarget_t *target; + +#ifdef ENABLE_REWRITE + Connection *conn; + char *ctx; + SlapReply *rs; +#else + int normalized; + int tofrom; +#endif +} dncookie; + +int ldap_back_dn_massage(dncookie *dc, struct berval *dn, + struct berval *res); + +extern int ldap_back_conn_cmp( const void *c1, const void *c2); +extern int ldap_back_conn_dup( void *c1, void *c2 ); +extern void ldap_back_conn_free( void *c ); + +/* attributeType/objectClass mapping */ +int mapping_cmp (const void *, const void *); +int mapping_dup (void *, void *); + +void ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping ** ); +int ldap_back_mapping ( struct ldapmap *map, struct berval *s, + struct ldapmapping **m, int remap ); +void ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *m, + int remap ); +#define BACKLDAP_MAP 0 +#define BACKLDAP_REMAP 1 +char * +ldap_back_map_filter( + struct ldapmap *at_map, + struct ldapmap *oc_map, + struct berval *f, + int remap ); + +int +ldap_back_map_attrs( + Operation *op, + struct ldapmap *at_map, + AttributeName *a, + int remap, + char ***mapped_attrs ); + +extern int +ldap_back_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ); + +/* suffix massaging by means of librewrite */ +#ifdef ENABLE_REWRITE +extern int +suffix_massage_config( struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc ); +#endif /* ENABLE_REWRITE */ +extern int +ldap_back_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals, + void *memctx ); +extern int +ldap_dnattr_rewrite( + dncookie *dc, + BerVarray a_vals ); +extern int +ldap_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals ); + +/* (end of) from back-ldap.h before rwm removal */ + +/* + * 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_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)) + +struct metainfo_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 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; + time_t msc_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 +} metasingleconn_t; + +typedef struct 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(metaconn_t) mc_q; + + /* NOTE: msc_mscflags is used to recycle the #define + * in metasingleconn_t */ + unsigned msc_mscflags; + + /* + * 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 metainfo_t *mc_info; + + /* supersedes the connection stuff */ + metasingleconn_t mc_conns[ 1 ]; + /* NOTE: mc_conns must be last, because + * the required number of conns is malloc'ed + * in one block with the metaconn_t structure */ +} 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 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 metasubtree_t *ms_next; +} metasubtree_t; + +typedef struct metafilter_t { + struct metafilter_t *mf_next; + struct berval mf_regex_pattern; + regex_t mf_regex; +} metafilter_t; + +typedef struct 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 (10) + + 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 ]; +} metacommon_t; + +typedef struct 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; + 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_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 + + struct ldaprwmap mt_rwmap; + + sig_atomic_t mt_isquarantined; + ldap_pvt_thread_mutex_t mt_quarantine_mutex; + + 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 ) + + slap_mask_t mt_rep_flags; + +} metatarget_t; + +typedef struct 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 */ +} metadncache_t; + +typedef struct metacandidates_t { + int mc_ntargets; + SlapReply *mc_candidates; +} metacandidates_t; + +/* + * Hook to allow mucking with metainfo_t/metatarget_t when quarantine is over + */ +typedef int (*meta_back_quarantine_f)( struct metainfo_t *, int target, void * ); + +typedef struct 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 + + metatarget_t **mi_targets; + metacandidates_t *mi_candidates; + + LDAP_REBIND_PROC *mi_rebind_f; + LDAP_URLLIST_PROC *mi_urllist_f; + + metadncache_t mi_cache; + + /* cached connections; + * special conns are in tailq rather than in tree */ + ldap_avl_info_t mi_conninfo; + struct { + int mic_num; + LDAP_TAILQ_HEAD(mc_conn_priv_q, metaconn_t) mic_priv; + } mi_conn_priv[ LDAP_BACK_PCONN_LAST ]; + int mi_conn_priv_max; + + /* NOTE: quarantine uses the connection mutex */ + meta_back_quarantine_f mi_quarantine_f; + void *mi_quarantine_p; + +#define li_flags mi_flags +/* uses flags as defined in <back-ldap/back-ldap.h> */ +#define META_BACK_F_ONERR_STOP LDAP_BACK_F_ONERR_STOP +#define META_BACK_F_ONERR_REPORT (0x02000000U) +#define META_BACK_F_ONERR_MASK (META_BACK_F_ONERR_STOP|META_BACK_F_ONERR_REPORT) +#define META_BACK_F_DEFER_ROOTDN_BIND (0x04000000U) +#define META_BACK_F_PROXYAUTHZ_ALWAYS (0x08000000U) /* users always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_ANON (0x10000000U) /* anonymous always proxyauthz */ +#define META_BACK_F_PROXYAUTHZ_NOANON (0x20000000U) /* anonymous remains anonymous */ + +#define META_BACK_ONERR_STOP(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_STOP ) +#define META_BACK_ONERR_REPORT(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_REPORT ) +#define META_BACK_ONERR_CONTINUE(mi) ( !LDAP_BACK_ISSET( (mi), META_BACK_F_ONERR_MASK ) ) + +#define META_BACK_DEFER_ROOTDN_BIND(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_DEFER_ROOTDN_BIND ) +#define META_BACK_PROXYAUTHZ_ALWAYS(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ALWAYS ) +#define META_BACK_PROXYAUTHZ_ANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_ANON ) +#define META_BACK_PROXYAUTHZ_NOANON(mi) LDAP_BACK_ISSET( (mi), META_BACK_F_PROXYAUTHZ_NOANON ) + +#define META_BACK_QUARANTINE(mi) LDAP_BACK_ISSET( (mi), LDAP_BACK_F_QUARANTINE ) + + time_t mi_conn_ttl; + time_t mi_idle_timeout; + + metacommon_t mi_mc; + ldap_extra_t *mi_ldap_extra; + +} metainfo_t; + +typedef enum meta_op_type { + META_OP_ALLOW_MULTIPLE = 0, + META_OP_REQUIRE_SINGLE, + META_OP_REQUIRE_ALL +} meta_op_type; + +SlapReply * +meta_back_candidates_get( Operation *op ); + +extern metaconn_t * +meta_back_getconn( + Operation *op, + SlapReply *rs, + int *candidate, + ldap_back_send_t sendok ); + +extern void +meta_back_release_conn_lock( + metainfo_t *mi, + metaconn_t *mc, + int dolock ); +#define meta_back_release_conn(mi, mc) meta_back_release_conn_lock( (mi), (mc), 1 ) + +extern int +meta_back_retry( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok ); + +extern void +meta_back_conn_free( + void *v_mc ); + +#if META_BACK_PRINT_CONNTREE > 0 +extern void +meta_back_print_conntree( + metainfo_t *mi, + char *msg ); +#endif + +extern int +meta_back_init_one_conn( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int ispriv, + ldap_back_send_t sendok, + int dolock ); + +extern void +meta_back_quarantine( + Operation *op, + SlapReply *rs, + int candidate ); + +extern int +meta_back_dobind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + ldap_back_send_t sendok ); + +extern int +meta_back_single_dobind( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok, + int retries, + int dolock ); + +extern int +meta_back_proxy_authz_cred( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ); + +extern int +meta_back_cancel( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + int candidate, + ldap_back_send_t sendok ); + +extern int +meta_back_op_result( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + int candidate, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ); + +extern int +meta_back_controls_add( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + LDAPControl ***pctrls ); + +extern int +back_meta_LTX_init_module( + int argc, + char *argv[] ); + +extern int +meta_back_conn_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_back_conndn_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_back_conndn_dup( + void *c1, + void *c2 ); + +/* + * Candidate stuff + */ +extern int +meta_back_is_candidate( + metatarget_t *mt, + struct berval *ndn, + int scope ); + +extern int +meta_back_select_unique_candidate( + metainfo_t *mi, + struct berval *ndn ); + +extern int +meta_clear_unused_candidates( + Operation *op, + int candidate ); + +extern int +meta_clear_one_candidate( + Operation *op, + metaconn_t *mc, + int candidate ); + +/* + * Dn cache stuff (experimental) + */ +extern int +meta_dncache_cmp( + const void *c1, + const void *c2 ); + +extern int +meta_dncache_dup( + void *c1, + void *c2 ); + +#define META_TARGET_NONE (-1) +#define META_TARGET_MULTIPLE (-2) +extern int +meta_dncache_get_target( + metadncache_t *cache, + struct berval *ndn ); + +extern int +meta_dncache_update_entry( + metadncache_t *cache, + struct berval *ndn, + int target ); + +extern int +meta_dncache_delete_entry( + metadncache_t *cache, + struct berval *ndn ); + +extern void +meta_dncache_free( void *entry ); + +extern void +meta_back_map_free( struct ldapmap *lm ); + +extern int +meta_subtree_destroy( metasubtree_t *ms ); + +extern void +meta_filter_destroy( metafilter_t *mf ); + +extern int +meta_target_finish( metainfo_t *mi, metatarget_t *mt, + const char *log, char *msg, size_t msize +); + +extern LDAP_REBIND_PROC meta_back_default_rebind; +extern LDAP_URLLIST_PROC meta_back_default_urllist; + +LDAP_END_DECL + +#endif /* SLAPD_META_H */ + diff --git a/servers/slapd/back-meta/bind.c b/servers/slapd/back-meta/bind.c new file mode 100644 index 0000000..5b87e89 --- /dev/null +++ b/servers/slapd/back-meta/bind.c @@ -0,0 +1,1771 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + + +#define AVL_INTERNAL +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#include "lutil_ldap.h" + +static int +meta_back_proxy_authz_bind( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ); + +static int +meta_back_single_bind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate ); + +int +meta_back_bind( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc = NULL; + + int rc = LDAP_OTHER, + i, + gotit = 0, + isroot = 0; + + SlapReply *candidates; + + rs->sr_err = LDAP_SUCCESS; + + Debug( LDAP_DEBUG_ARGS, "%s meta_back_bind: dn=\"%s\".\n", + op->o_log_prefix, op->o_req_dn.bv_val, 0 ); + + /* the test on the bind method should be superfluous */ + switch ( be_rootdn_bind( op, rs ) ) { + case LDAP_SUCCESS: + if ( META_BACK_DEFER_ROOTDN_BIND( mi ) ) { + /* frontend will return success */ + return rs->sr_err; + } + + isroot = 1; + /* fallthru */ + + case SLAP_CB_CONTINUE: + break; + + default: + /* be_rootdn_bind() sent result */ + return rs->sr_err; + } + + /* we need meta_back_getconn() not send result even on error, + * because we want to intercept the error and make it + * invalidCredentials */ + mc = meta_back_getconn( op, rs, NULL, LDAP_BACK_BIND_DONTSEND ); + if ( !mc ) { + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_bind: no target " + "for dn \"%s\" (%d%s%s).", + op->o_req_dn.bv_val, rs->sr_err, + rs->sr_text ? ". " : "", + rs->sr_text ? rs->sr_text : "" ); + Debug( LDAP_DEBUG_ANY, + "%s %s\n", + op->o_log_prefix, buf, 0 ); + } + + /* 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; + } + + candidates = meta_back_candidates_get( op ); + + /* + * Each target is scanned ... + */ + mc->mc_authz_target = META_BOUND_NONE; + for ( i = 0; i < mi->mi_ntargets; i++ ) { + 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 meta_back_bind: more than one" + " candidate selected...\n", + op->o_log_prefix, 0, 0 ); + } + + if ( isroot ) { + if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE + || BER_BVISNULL( &mt->mt_idassert_authcDN ) ) + { + metasingleconn_t *msc = &mc->mc_conns[ i ]; + + /* skip the target if no pseudorootdn is provided */ + 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)meta_back_proxy_authz_bind( mc, i, op, rs, LDAP_BACK_DONTSEND, 1 ); + lerr = rs->sr_err; + + } else { + lerr = meta_back_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; + } + } + + /* must re-insert if local DN changed as result of bind */ + if ( rc == LDAP_SUCCESS ) { + if ( isroot ) { + mc->mc_authz_target = META_BOUND_ALL; + } + + if ( !LDAP_BACK_PCONN_ISPRIV( mc ) + && !dn_match( &op->o_req_ndn, &mc->mc_local_ndn ) ) + { + int lerr; + + /* wait for all other ops to release the connection */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + assert( mc->mc_refcnt == 1 ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_bind" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* delete all cached connections with the current connection */ + if ( LDAP_BACK_SINGLECONN( mi ) ) { + metaconn_t *tmpmc; + + while ( ( tmpmc = avl_delete( &mi->mi_conninfo.lai_tree, (caddr_t)mc, meta_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( mc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_bind: destroying conn %lu (refcnt=%u)\n", + mc->mc_conn->c_connid, mc->mc_refcnt, 0 ); + + if ( tmpmc->mc_refcnt != 0 ) { + /* taint it */ + LDAP_BACK_CONN_TAINTED_SET( tmpmc ); + + } else { + /* + * Needs a test because the handler may be corrupted, + * and calling ldap_unbind on a corrupted header results + * in a segmentation fault + */ + meta_back_conn_free( tmpmc ); + } + } + } + + ber_bvreplace( &mc->mc_local_ndn, &op->o_req_ndn ); + lerr = avl_insert( &mi->mi_conninfo.lai_tree, (caddr_t)mc, + meta_back_conndn_cmp, meta_back_conndn_dup ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_bind" ); +#endif /* META_BACK_PRINT_CONNTREE */ + if ( lerr == 0 ) { +#if 0 + /* NOTE: a connection cannot be privileged + * and be in the avl tree at the same time + */ + if ( isroot ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + LDAP_BACK_PCONN_SET( mc, op ); + } +#endif + LDAP_BACK_CONN_CACHED_SET( mc ); + + } else { + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + } + + if ( mc != NULL ) { + meta_back_release_conn( mi, 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 +meta_back_bind_op_result( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int msgid, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + LDAPMessage *res; + struct timeval tv; + int rc; + int nretries = mt->mt_nretries; + char buf[ SLAP_TEXT_BUFLEN ]; + + Debug( LDAP_DEBUG_TRACE, + ">>> %s meta_back_bind_op_result[%d]\n", + op->o_log_prefix, candidate, 0 ); + + /* 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; + + } else if ( mi->mi_conn_ttl ) { + timeout = mi->mi_conn_ttl; + } + } + + 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 */ + if ( dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + assert( LDAP_BACK_CONN_BINDING( msc ) ); + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "### %s meta_back_bind_op_result ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + meta_clear_one_candidate( op, mc, candidate ); + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + 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 ); + + snprintf( buf, sizeof( buf ), + "err=%d (%s) nretries=%d", + rs->sr_err, ldap_err2string( rs->sr_err ), nretries ); + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_bind_op_result[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + 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 meta_back_bind_op_result[%d] err=%d\n", + op->o_log_prefix, candidate, rs->sr_err ); + + return rs->sr_err; +} + +/* + * meta_back_single_bind + * + * attempts to perform a bind with creds + */ +static int +meta_back_single_bind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + struct berval mdn = BER_BVNULL; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int msgid; + 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.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "bindDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + rs->sr_text = "DN rewrite error"; + rs->sr_err = LDAP_OTHER; + return rs->sr_err; + } + + /* 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 = meta_back_controls_add( op, rs, mc, candidate, &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 ); + + meta_back_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 ) { + meta_back_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 )meta_dncache_update_entry( &mi->mi_cache, + &op->o_req_ndn, candidate ); + } + +return_results:; + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + return rs->sr_err; +} + +/* + * meta_back_single_dobind + */ +int +meta_back_single_dobind( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok, + int nretries, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int msgid; + + assert( !LDAP_BACK_CONN_ISBOUND( msc ) ); + + /* NOTE: this obsoletes pseudorootdn */ + if ( op->o_conn != NULL && + !op->o_do_not_cache && + ( BER_BVISNULL( &msc->msc_bound_ndn ) || + BER_BVISEMPTY( &msc->msc_bound_ndn ) || + ( LDAP_BACK_CONN_ISPRIV( mc ) && dn_match( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ) ) || + ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) ) ) + { + (void)meta_back_proxy_authz_bind( mc, candidate, op, rs, sendok, dolock ); + + } else { + char *binddn = ""; + struct berval cred = BER_BVC( "" ); + + /* use credentials if available */ + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) + && !BER_BVISNULL( &msc->msc_cred ) ) + { + binddn = msc->msc_bound_ndn.bv_val; + cred = msc->msc_cred; + } + + /* FIXME: should we check if at least some of the op->o_ctrls + * can/should be passed? */ + if(!dolock) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + for (;;) { + rs->sr_err = ldap_sasl_bind( msc->msc_ld, + binddn, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, &msgid ); + if ( rs->sr_err != LDAP_X_CONNECTING ) { + break; + } + ldap_pvt_thread_yield(); + } + + if(!dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + rs->sr_err = meta_back_bind_op_result( op, rs, mc, candidate, msgid, sendok, dolock ); + + /* if bind succeeded, but anonymous, clear msc_bound_ndn */ + if ( rs->sr_err != LDAP_SUCCESS || binddn[0] == '\0' ) { + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree( msc->msc_bound_ndn.bv_val ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree( msc->msc_cred.bv_val ); + BER_BVZERO( &msc->msc_cred ); + } + } + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + return rs->sr_err; +} + +/* + * meta_back_dobind + */ +int +meta_back_dobind( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + + int bound = 0, + i, + isroot = 0; + + SlapReply *candidates; + + if ( be_isroot( op ) ) { + isroot = 1; + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind: conn=%s%s\n", + op->o_log_prefix, buf, + isroot ? " (isroot)" : "" ); + } + + /* + * all the targets are bound as pseudoroot + */ + if ( mc->mc_authz_target == META_BOUND_ALL ) { + bound = 1; + goto done; + } + + candidates = meta_back_candidates_get( op ); + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + metasingleconn_t *msc = &mc->mc_conns[ i ]; + int rc; + + /* + * Not a candidate + */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + assert( msc->msc_ld != NULL ); + + /* + * If the target is already bound it is skipped + */ + +retry_binding:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_ISBOUND( msc ) + || ( LDAP_BACK_CONN_ISANON( msc ) + && mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) ) + { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ++bound; + continue; + + } else if ( META_BACK_CONN_CREATING( msc ) || LDAP_BACK_CONN_BINDING( msc ) ) + { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_binding; + + } + + LDAP_BACK_CONN_BINDING_SET( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + rc = meta_back_single_dobind( op, rs, &mc, i, + LDAP_BACK_DONTSEND, mt->mt_nretries, 1 ); + /* + * NOTE: meta_back_single_dobind() already retries; + * in case of failure, it resets mc... + */ + if ( rc != LDAP_SUCCESS ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + if ( mc == NULL ) { + /* meta_back_single_dobind() already sent + * response and released connection */ + goto send_err; + } + + + if ( rc == LDAP_UNAVAILABLE ) { + /* FIXME: meta_back_retry() already re-calls + * meta_back_single_dobind() */ + if ( meta_back_retry( op, rs, &mc, i, sendok ) ) { + goto retry_ok; + } + + if ( mc != NULL ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + meta_back_release_conn_lock( mi, mc, 0 ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + return 0; + } + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + snprintf( buf, sizeof( buf ), + "meta_back_dobind[%d]: (%s) err=%d (%s).", + i, isroot ? op->o_bd->be_rootdn.bv_val : "anonymous", + rc, ldap_err2string( rc ) ); + Debug( LDAP_DEBUG_ANY, + "%s %s\n", + op->o_log_prefix, buf, 0 ); + + /* + * null cred bind should always succeed + * as anonymous, so a failure means + * the target is no longer candidate possibly + * due to technical reasons (remote host down?) + * so better clear the handle + */ + /* leave the target candidate, but record the error for later use */ + candidates[ i ].sr_err = rc; + if ( META_BACK_ONERR_STOP( mi ) ) { + bound = 0; + goto done; + } + + continue; + } /* else */ + +retry_ok:; + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind[%d]: " + "(%s)\n", + op->o_log_prefix, i, + isroot ? op->o_bd->be_rootdn.bv_val : "anonymous" ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( isroot ) { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } else { + LDAP_BACK_CONN_ISANON_SET( msc ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ++bound; + } + +done:; + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_dobind: conn=%s bound=%d\n", + op->o_log_prefix, buf, bound ); + } + + if ( bound == 0 ) { + meta_back_release_conn( mi, mc ); + +send_err:; + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_BUSY; + } + send_ldap_result( op, rs ); + } + + return 0; + } + + return ( bound > 0 ); +} + +/* + * meta_back_default_rebind + * + * This is a callback used for chasing referrals using the same + * credentials as the original user on this session. + */ +int +meta_back_default_rebind( + LDAP *ld, + LDAP_CONST char *url, + ber_tag_t request, + ber_int_t msgid, + void *params ) +{ + metasingleconn_t *msc = ( 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 +meta_back_default_urllist( + LDAP *ld, + LDAPURLDesc **urllist, + LDAPURLDesc **url, + void *params ) +{ + metatarget_t *mt = (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 +meta_back_cancel( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + ber_int_t msgid, + int candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + int rc = LDAP_OTHER; + + Debug( LDAP_DEBUG_TRACE, ">>> %s meta_back_cancel[%d] msgid=%d\n", + op->o_log_prefix, candidate, msgid ); + + /* 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 meta_back_cancel[%d] err=%d\n", + op->o_log_prefix, candidate, rc ); + + return rc; +} + + + +/* + * FIXME: error return must be handled in a cleaner way ... + */ +int +meta_back_op_result( + metaconn_t *mc, + Operation *op, + SlapReply *rs, + int candidate, + ber_int_t msgid, + time_t timeout, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + + const char *save_text = rs->sr_text, + *save_matched = rs->sr_matched; + BerVarray save_ref = rs->sr_ref; + LDAPControl **save_ctrls = rs->sr_ctrls; + void *matched_ctx = NULL; + + char *matched = NULL; + char *text = NULL; + char **refs = NULL; + LDAPControl **ctrls = NULL; + + assert( mc != NULL ); + + rs->sr_text = NULL; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + rs->sr_ctrls = NULL; + + if ( candidate != META_TARGET_NONE ) { + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + if ( LDAP_ERR_OK( rs->sr_err ) ) { + int rc; + struct timeval tv; + LDAPMessage *res = NULL; + time_t stoptime = (time_t)(-1); + int timeout_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + const char *timeout_text = "Operation timed out"; + + /* if timeout is not specified, compute and use + * the one specific to the ongoing operation */ + if ( timeout == (time_t)(-1) ) { + slap_op_t opidx = slap_req2op( op->o_tag ); + + if ( opidx == SLAP_OP_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; + + } else if ( mi->mi_conn_ttl ) { + timeout = mi->mi_conn_ttl; + } + } + + if ( timeout ) { + stoptime = op->o_time + timeout; + } + + LDAP_BACK_TV_SET( &tv ); + +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case 0: + if ( timeout && slap_get_time() > stoptime ) { + (void)meta_back_cancel( mc, op, rs, msgid, candidate, sendok ); + rs->sr_err = timeout_err; + rs->sr_text = timeout_text; + break; + } + + LDAP_BACK_TV_SET( &tv ); + ldap_pvt_thread_yield(); + goto retry; + + case -1: + ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, + &rs->sr_err ); + break; + + + /* otherwise get the result; if it is not + * LDAP_SUCCESS, record it in the reply + * structure (this includes + * LDAP_COMPARE_{TRUE|FALSE}) */ + 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; + } + + rc = ldap_parse_result( msc->msc_ld, res, &rs->sr_err, + &matched, &text, &refs, &ctrls, 1 ); + res = NULL; + if ( rc == LDAP_SUCCESS ) { + rs->sr_text = text; + } else { + rs->sr_err = rc; + } + rs->sr_err = slap_map_api2result( rs ); + + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( refs != NULL + && refs[ 0 ] != NULL + && refs[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_op_result[%d]: " + "got referrals with err=%d\n", + op->o_log_prefix, + candidate, rs->sr_err ); + + } else { + int i; + + for ( i = 0; refs[ i ] != NULL; i++ ) + /* count */ ; + rs->sr_ref = op->o_tmpalloc( sizeof( struct berval ) * ( i + 1 ), + op->o_tmpmemctx ); + for ( i = 0; refs[ i ] != NULL; i++ ) { + ber_str2bv( refs[ i ], 0, 0, &rs->sr_ref[ i ] ); + } + BER_BVZERO( &rs->sr_ref[ i ] ); + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_op_result[%d]: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + candidate, rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + if ( ctrls != NULL ) { + rs->sr_ctrls = ctrls; + } + } + + assert( res == NULL ); + } + + /* if the error in the reply structure is not + * LDAP_SUCCESS, try to map it from client + * to server error */ + if ( !LDAP_ERR_OK( rs->sr_err ) ) { + rs->sr_err = slap_map_api2result( rs ); + + /* internal ops ( op->o_conn == NULL ) + * must not reply to client */ + if ( op->o_conn && !op->o_do_not_cache && matched ) { + + /* record the (massaged) matched + * DN into the reply structure */ + rs->sr_matched = matched; + } + } + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + } else { + int i, + err = rs->sr_err; + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metasingleconn_t *msc = &mc->mc_conns[ i ]; + char *xtext = NULL; + char *xmatched = NULL; + + if ( msc->msc_ld == NULL ) { + continue; + } + + rs->sr_err = LDAP_SUCCESS; + + ldap_get_option( msc->msc_ld, LDAP_OPT_RESULT_CODE, &rs->sr_err ); + if ( rs->sr_err != LDAP_SUCCESS ) { + /* + * better check the type of error. In some cases + * (search ?) it might be better to return a + * success if at least one of the targets gave + * positive result ... + */ + ldap_get_option( msc->msc_ld, + LDAP_OPT_DIAGNOSTIC_MESSAGE, &xtext ); + if ( xtext != NULL && xtext [ 0 ] == '\0' ) { + ldap_memfree( xtext ); + xtext = NULL; + } + + ldap_get_option( msc->msc_ld, + LDAP_OPT_MATCHED_DN, &xmatched ); + if ( xmatched != NULL && xmatched[ 0 ] == '\0' ) { + ldap_memfree( xmatched ); + xmatched = NULL; + } + + rs->sr_err = slap_map_api2result( rs ); + + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_op_result[%d] " + "err=%d text=\"%s\" matched=\"%s\"", + i, rs->sr_err, + ( xtext ? xtext : "" ), + ( xmatched ? xmatched : "" ) ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + /* + * FIXME: need to rewrite "match" (need rwinfo) + */ + switch ( rs->sr_err ) { + default: + err = rs->sr_err; + if ( xtext != NULL ) { + if ( text ) { + ldap_memfree( text ); + } + text = xtext; + xtext = NULL; + } + if ( xmatched != NULL ) { + if ( matched ) { + ldap_memfree( matched ); + } + matched = xmatched; + xmatched = NULL; + } + break; + } + + if ( xtext ) { + ldap_memfree( xtext ); + } + + if ( xmatched ) { + ldap_memfree( xmatched ); + } + } + + if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) { + meta_back_quarantine( op, rs, i ); + } + } + + if ( err != LDAP_SUCCESS ) { + rs->sr_err = err; + } + } + + if ( matched != NULL ) { + struct berval dn, pdn; + + ber_str2bv( matched, 0, 0, &dn ); + if ( dnPretty( NULL, &dn, &pdn, op->o_tmpmemctx ) == LDAP_SUCCESS ) { + ldap_memfree( matched ); + matched_ctx = op->o_tmpmemctx; + matched = pdn.bv_val; + } + rs->sr_matched = matched; + } + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + if ( !( sendok & LDAP_BACK_RETRYING ) ) { + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + if ( rs->sr_text == NULL ) rs->sr_text = "Proxy operation retry failed"; + send_ldap_result( op, rs ); + } + } + + } else if ( op->o_conn && + ( ( ( sendok & LDAP_BACK_SENDOK ) && LDAP_ERR_OK( rs->sr_err ) ) + || ( ( sendok & LDAP_BACK_SENDERR ) && !LDAP_ERR_OK( rs->sr_err ) ) ) ) + { + send_ldap_result( op, rs ); + } + if ( matched ) { + op->o_tmpfree( (char *)rs->sr_matched, matched_ctx ); + } + if ( text ) { + ldap_memfree( text ); + } + if ( rs->sr_ref ) { + op->o_tmpfree( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + if ( refs ) { + ber_memvfree( (void **)refs ); + } + if ( ctrls ) { + assert( rs->sr_ctrls != NULL ); + ldap_controls_free( ctrls ); + } + + rs->sr_text = save_text; + rs->sr_matched = save_matched; + rs->sr_ref = save_ref; + rs->sr_ctrls = save_ctrls; + + return( LDAP_ERR_OK( rs->sr_err ) ? LDAP_SUCCESS : rs->sr_err ); +} + +/* + * meta_back_proxy_authz_cred() + * + * prepares credentials & method for meta_back_proxy_authz_bind(); + * or, if method is SASL, performs the SASL bind directly. + */ +int +meta_back_proxy_authz_cred( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + struct berval *binddn, + struct berval *bindcred, + int *method ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval ndn; + int dobind = 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; + } + } + + 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 ); + + rs->sr_err = slap_map_api2result( rs ); + if ( rs->sr_err != LDAP_SUCCESS ) { + 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 +meta_back_proxy_authz_bind( + metaconn_t *mc, + int candidate, + Operation *op, + SlapReply *rs, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + struct berval binddn = BER_BVC( "" ), + cred = BER_BVC( "" ); + int method = LDAP_AUTH_NONE, + rc; + + rc = meta_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: + + if(!dolock) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + 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(); + } + + if(!dolock) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + rc = meta_back_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 ); +} + +/* + * 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 +meta_back_controls_add( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + LDAPControl ***pctrls ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + 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; + + *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 ( mi->mi_ldap_extra->proxy_authz_ctrl( op, rs, &msc->msc_bound_ndn, + mt->mt_version, &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 ) { + for ( i = 0; op->o_ctrls[ i ]; i++ ) { + ctrls[ i + j1 ] = op->o_ctrls[ i ]; + } + } + + n += j1; + 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; +} + diff --git a/servers/slapd/back-meta/candidates.c b/servers/slapd/back-meta/candidates.c new file mode 100644 index 0000000..98f0a36 --- /dev/null +++ b/servers/slapd/back-meta/candidates.c @@ -0,0 +1,284 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include "ac/string.h" + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * The meta-directory has one suffix, called <suffix>. + * It handles a pool of target servers, each with a branch suffix + * of the form <branch X>,<suffix>, where <branch X> may be empty. + * + * When the meta-directory receives a request with a request DN that belongs + * to a branch, the corresponding target is invoked. When the request DN + * does not belong to a specific branch, all the targets that + * are compatible with the request DN are selected as candidates, and + * the request is spawned to all the candidate targets + * + * A request is characterized by a request DN. The following cases are + * handled: + * - the request DN is the suffix: <dn> == <suffix>, + * all the targets are candidates (search ...) + * - the request DN is a branch suffix: <dn> == <branch X>,<suffix>, or + * - the request DN is a subtree of a branch suffix: + * <dn> == <rdn>,<branch X>,<suffix>, + * the target is the only candidate. + * + * A possible extension will include the handling of multiple suffixes + */ + +static metasubtree_t * +meta_subtree_match( metatarget_t *mt, struct berval *ndn, int scope ) +{ + metasubtree_t *ms = mt->mt_subtree; + + for ( ms = mt->mt_subtree; ms; ms = ms->ms_next ) { + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( ndn, &ms->ms_dn ) ) { + return ms; + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( ndn, &ms->ms_dn ) && + ( ndn->bv_len > ms->ms_dn.bv_len || scope != LDAP_SCOPE_BASE ) ) + { + return ms; + } + break; + + case META_ST_REGEX: + /* NOTE: cannot handle scope */ + if ( regexec( &ms->ms_regex, ndn->bv_val, 0, NULL, 0 ) == 0 ) { + return ms; + } + break; + } + } + + return NULL; +} + +/* + * returns 1 if suffix is candidate for dn, otherwise 0 + * + * Note: this function should never be called if dn is the <suffix>. + */ +int +meta_back_is_candidate( + 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 = ( meta_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 +meta_back_select_unique_candidate( + metainfo_t *mi, + struct berval *ndn ) +{ + int i, candidate = META_TARGET_NONE; + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( meta_back_is_candidate( mt, ndn, LDAP_SCOPE_BASE ) ) { + if ( candidate == META_TARGET_NONE ) { + candidate = i; + + } else { + return META_TARGET_MULTIPLE; + } + } + } + + return candidate; +} + +/* + * meta_clear_unused_candidates + * + * clears all candidates except candidate + */ +int +meta_clear_unused_candidates( + Operation *op, + int candidate ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + int i; + SlapReply *candidates = meta_back_candidates_get( op ); + + for ( i = 0; i < mi->mi_ntargets; ++i ) { + if ( i == candidate ) { + continue; + } + META_CANDIDATE_RESET( &candidates[ i ] ); + } + + return 0; +} + +/* + * meta_clear_one_candidate + * + * clears the selected candidate + */ +int +meta_clear_one_candidate( + Operation *op, + metaconn_t *mc, + int candidate ) +{ + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + if ( msc->msc_ld != NULL ) { + +#ifdef DEBUG_205 + char buf[ BUFSIZ ]; + + snprintf( buf, sizeof( buf ), "meta_clear_one_candidate ldap_unbind_ext[%d] mc=%p ld=%p", + candidate, (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s %s\n", + op ? op->o_log_prefix : "", buf, 0 ); +#endif /* DEBUG_205 */ + + ldap_unbind_ext( msc->msc_ld, NULL, NULL ); + msc->msc_ld = NULL; + } + + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree_x( msc->msc_cred.bv_val, NULL ); + BER_BVZERO( &msc->msc_cred ); + } + + msc->msc_mscflags = 0; + + return 0; +} + diff --git a/servers/slapd/back-meta/compare.c b/servers/slapd/back-meta/compare.c new file mode 100644 index 0000000..b2c584f --- /dev/null +++ b/servers/slapd/back-meta/compare.c @@ -0,0 +1,154 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_compare( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int rc = 0; + int candidate = -1; + struct berval mdn = BER_BVNULL; + dncookie dc; + struct berval mapped_attr = op->orc_ava->aa_desc->ad_cname; + struct berval mapped_value = op->orc_ava->aa_value; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the modify dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "compareDN"; + + switch ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + case LDAP_UNWILLING_TO_PERFORM: + rc = 1; + goto cleanup; + + default: + break; + } + + /* + * if attr is objectClass, try to remap the value + */ + if ( op->orc_ava->aa_desc == slap_schema.si_ad_objectClass ) { + ldap_back_map( &mt->mt_rwmap.rwm_oc, + &op->orc_ava->aa_value, + &mapped_value, BACKLDAP_MAP ); + + if ( BER_BVISNULL( &mapped_value ) || BER_BVISEMPTY( &mapped_value ) ) { + goto cleanup; + } + + /* + * else try to remap the attribute + */ + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &op->orc_ava->aa_desc->ad_cname, + &mapped_attr, BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped_attr ) || BER_BVISEMPTY( &mapped_attr ) ) { + goto cleanup; + } + + if ( op->orc_ava->aa_desc->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + { + dc.ctx = "compareAttrDN"; + + switch ( ldap_back_dn_massage( &dc, &op->orc_ava->aa_value, &mapped_value ) ) + { + case LDAP_UNWILLING_TO_PERFORM: + rc = 1; + goto cleanup; + + default: + break; + } + } + } + +retry:; + ctrls = op->o_ctrls; + rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_compare_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + mapped_attr.bv_val, &mapped_value, + ctrls, NULL, &msgid ); + + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_COMPARE ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + } + + if ( op->orc_ava->aa_value.bv_val != mapped_value.bv_val ) { + free( mapped_value.bv_val ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/config.c b/servers/slapd/back-meta/config.c new file mode 100644 index 0000000..03bc762 --- /dev/null +++ b/servers/slapd/back-meta/config.c @@ -0,0 +1,3385 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ctype.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "lutil.h" +#include "ldif.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#ifdef LDAP_DEVEL +#define SLAP_AUTH_DN 1 +#endif + +static ConfigDriver meta_back_cf_gen; +static ConfigLDAPadd meta_ldadd; +static ConfigCfAdd meta_cfadd; + +static int ldap_back_map_config( + ConfigArgs *c, + struct ldapmap *oc_map, + struct ldapmap *at_map ); + +/* 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_CONN_TTL = 1, + LDAP_BACK_CFG_DNCACHE_TTL, + LDAP_BACK_CFG_IDLE_TIMEOUT, + LDAP_BACK_CFG_ONERR, + LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER, + LDAP_BACK_CFG_SINGLECONN, + LDAP_BACK_CFG_USETEMP, + LDAP_BACK_CFG_CONNPOOLMAX, + 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_ACL_AUTHCDN, + LDAP_BACK_CFG_ACL_PASSWD, + LDAP_BACK_CFG_IDASSERT_AUTHZFROM, + LDAP_BACK_CFG_IDASSERT_BIND, + LDAP_BACK_CFG_REWRITE, + LDAP_BACK_CFG_SUFFIXM, + LDAP_BACK_CFG_MAP, + LDAP_BACK_CFG_SUBTREE_EX, + LDAP_BACK_CFG_SUBTREE_IN, + LDAP_BACK_CFG_PSEUDOROOTDN, + LDAP_BACK_CFG_PSEUDOROOTPW, + LDAP_BACK_CFG_KEEPALIVE, + LDAP_BACK_CFG_FILTER, + + LDAP_BACK_CFG_LAST +}; + +static ConfigTable metacfg[] = { + { "uri", "uri", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_URI, + meta_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, + meta_back_cf_gen, "( OLcfgDbAt:3.1 " + "NAME 'olcDbStartTLS' " + "DESC 'StartTLS' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "acl-authcDN", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + meta_back_cf_gen, "( OLcfgDbAt:3.2 " + "NAME 'olcDbACLAuthcDn' " + "DESC 'Remote ACL administrative identity' " + "OBSOLETE " + "SYNTAX OMsDN " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-authcDN" */ + { "binddn", "DN", 2, 2, 0, + ARG_DN|ARG_MAGIC|LDAP_BACK_CFG_ACL_AUTHCDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "acl-passwd", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + meta_back_cf_gen, "( OLcfgDbAt:3.3 " + "NAME 'olcDbACLPasswd' " + "DESC 'Remote ACL administrative identity credentials' " + "OBSOLETE " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + /* deprecated, will be removed; aliases "acl-passwd" */ + { "bindpw", "cred", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_ACL_PASSWD, + meta_back_cf_gen, NULL, NULL, NULL }, + { "idassert-bind", "args", 2, 0, 0, + ARG_MAGIC|LDAP_BACK_CFG_IDASSERT_BIND, + meta_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, + meta_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, + meta_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, + meta_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, + meta_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, + meta_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, + meta_back_cf_gen, "( OLcfgDbAt:3.15 " + "NAME 'olcDbIdleTimeout' " + "DESC 'connection idle timeout' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-ttl", "ttl", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CONN_TTL, + meta_back_cf_gen, "( OLcfgDbAt:3.16 " + "NAME 'olcDbConnTtl' " + "DESC 'connection ttl' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "network-timeout", "timeout", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NETWORK_TIMEOUT, + meta_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, + meta_back_cf_gen, "( OLcfgDbAt:3.18 " + "NAME 'olcDbProtocolVersion' " + "DESC 'protocol version' " + "SYNTAX OMsInteger " + "SINGLE-VALUE )", + NULL, NULL }, + { "single-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_SINGLECONN, + meta_back_cf_gen, "( OLcfgDbAt:3.19 " + "NAME 'olcDbSingleConn' " + "DESC 'cache a single connection per identity' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "cancel", "ABANDON|ignore|exop", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CANCEL, + meta_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, + meta_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 }, + { "use-temporary-conn", "true|FALSE", 2, 2, 0, + ARG_MAGIC|ARG_ON_OFF|LDAP_BACK_CFG_USETEMP, + meta_back_cf_gen, "( OLcfgDbAt:3.22 " + "NAME 'olcDbUseTemporaryConn' " + "DESC 'Use temporary connections if the cached one is busy' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + { "conn-pool-max", "<n>", 2, 2, 0, + ARG_MAGIC|ARG_INT|LDAP_BACK_CFG_CONNPOOLMAX, + meta_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, + meta_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, + meta_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, + meta_back_cf_gen, "( OLcfgDbAt:3.26 " + "NAME 'olcDbNoUndefFilter' " + "DESC 'Do not propagate undefined search filters' " + "SYNTAX OMsBoolean " + "SINGLE-VALUE )", + NULL, NULL }, + + { "rewrite", "arglist", 2, 0, STRLENOF( "rewrite" ), + ARG_MAGIC|LDAP_BACK_CFG_REWRITE, + meta_back_cf_gen, "( OLcfgDbAt:3.101 " + "NAME 'olcDbRewrite' " + "DESC 'DN rewriting rules' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + { "suffixmassage", "virtual> <real", 2, 3, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUFFIXM, + meta_back_cf_gen, NULL, NULL, NULL }, + + { "map", "attribute|objectClass> [*|<local>] *|<remote", 3, 4, 0, + ARG_MAGIC|LDAP_BACK_CFG_MAP, + meta_back_cf_gen, "( OLcfgDbAt:3.102 " + "NAME 'olcDbMap' " + "DESC 'Map attribute and objectclass names' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString " + "X-ORDERED 'VALUES' )", + NULL, NULL }, + + { "subtree-exclude", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_EX, + meta_back_cf_gen, "( OLcfgDbAt:3.103 " + "NAME 'olcDbSubtreeExclude' " + "DESC 'DN of subtree to exclude from target' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL }, + { "subtree-include", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_SUBTREE_IN, + meta_back_cf_gen, "( OLcfgDbAt:3.104 " + "NAME 'olcDbSubtreeInclude' " + "DESC 'DN of subtree to include in target' " + "EQUALITY caseIgnoreMatch " + "SYNTAX OMsDirectoryString )", + NULL, NULL }, + { "default-target", "[none|<target ID>]", 1, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_DEFAULT_T, + meta_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, + meta_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, + meta_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, + meta_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, + meta_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, + meta_back_cf_gen, NULL, NULL, NULL }, + { "pseudorootdn", "dn", 2, 2, 0, + ARG_MAGIC|ARG_DN|LDAP_BACK_CFG_PSEUDOROOTDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "pseudorootpw", "password", 2, 2, 0, + ARG_MAGIC|ARG_STRING|LDAP_BACK_CFG_PSEUDOROOTDN, + meta_back_cf_gen, NULL, NULL, NULL }, + { "nretries", "NEVER|forever|<number>", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_NRETRIES, + meta_back_cf_gen, "( OLcfgDbAt:3.110 " + "NAME 'olcDbNretries' " + "DESC 'retry handling' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + { "client-pr", "accept-unsolicited|disable|<size>", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_CLIENT_PR, + meta_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.100 NAME 'olcMetaSub' " + "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, + meta_back_cf_gen, "( OLcfgDbAt:3.29 " + "NAME 'olcDbKeepalive' " + "DESC 'TCP keepalive' " + "SYNTAX OMsDirectoryString " + "SINGLE-VALUE )", + NULL, NULL }, + + { "filter", "pattern", 2, 2, 0, + ARG_MAGIC|LDAP_BACK_CFG_FILTER, + meta_back_cf_gen, "( OLcfgDbAt:3.112 " + "NAME 'olcDbFilter' " + "DESC 'Filter regex pattern to include in target' " + "EQUALITY caseExactMatch " + "SYNTAX OMsDirectoryString )", + 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 metaocs[] = { + { "( OLcfgDbOc:3.2 " + "NAME 'olcMetaConfig' " + "DESC 'Meta backend configuration' " + "SUP olcDatabaseConfig " + "MAY ( olcDbConnTtl " + "$ olcDbDnCacheTtl " + "$ olcDbIdleTimeout " + "$ olcDbOnErr " + "$ olcDbPseudoRootBindDefer " + "$ olcDbSingleConn " + "$ olcDbUseTemporaryConn " + "$ olcDbConnectionPoolMax " + + /* defaults, may be overridden per-target */ + COMMON_ATTRS + ") )", + Cft_Database, metacfg, NULL, meta_cfadd }, + { "( OLcfgDbOc:3.3 " + "NAME 'olcMetaTargetConfig' " + "DESC 'Meta target configuration' " + "SUP olcConfig STRUCTURAL " + "MUST ( olcMetaSub $ olcDbURI ) " + "MAY ( olcDbACLAuthcDn " + "$ olcDbACLPasswd " + "$ olcDbIDAssertAuthzFrom " + "$ olcDbIDAssertBind " + "$ olcDbMap " + "$ olcDbRewrite " + "$ olcDbSubtreeExclude " + "$ olcDbSubtreeInclude " + "$ olcDbTimeout " + "$ olcDbKeepalive " + "$ olcDbFilter " + + /* defaults may be inherited */ + COMMON_ATTRS + ") )", + Cft_Misc, metacfg, meta_ldadd }, + { NULL, 0, NULL } +}; + +static int +meta_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *c ) +{ + if ( p->ce_type != Cft_Database || !p->ce_be || + p->ce_be->be_cf_ocs != metaocs ) + return LDAP_CONSTRAINT_VIOLATION; + + c->be = p->ce_be; + return LDAP_SUCCESS; +} + +static int +meta_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + struct berval bv; + int i; + + bv.bv_val = c->cr_msg; + for ( i=0; i<mi->mi_ntargets; i++ ) { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), + "olcMetaSub=" 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, &metaocs[1], NULL ); + } + + return LDAP_SUCCESS; +} + +static int +meta_rwi_init( struct rewrite_info **rwm_rw ) +{ + char *rargv[ 3 ]; + + *rwm_rw = rewrite_info_init( REWRITE_MODE_USE_DEFAULT ); + if ( *rwm_rw == NULL ) { + return -1; + } + /* + * the filter rewrite as a string must be disabled + * by default; it can be re-enabled by adding rules; + * this creates an empty rewriteContext + */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchFilter"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( *rwm_rw, "<suffix massage>", 1, 2, rargv ); + + return 0; +} + +static int +meta_back_new_target( + metatarget_t **mtp ) +{ + metatarget_t *mt; + + *mtp = NULL; + + mt = ch_calloc( sizeof( metatarget_t ), 1 ); + + if ( meta_rwi_init( &mt->mt_rwmap.rwm_rw )) { + ch_free( mt ); + return -1; + } + + ldap_pvt_thread_mutex_init( &mt->mt_uri_mutex ); + + mt->mt_idassert_mode = LDAP_BACK_IDASSERT_LEGACY; + mt->mt_idassert_authmethod = LDAP_AUTH_NONE; + mt->mt_idassert_tls = SB_TLS_DEFAULT; + + /* by default, use proxyAuthz control on each operation */ + mt->mt_idassert_flags = LDAP_BACK_AUTH_PRESCRIPTIVE; + + *mtp = mt; + + return 0; +} + +/* Validation for suffixmassage_config */ +static int +meta_suffixm_config( + ConfigArgs *c, + int argc, + char **argv, + metatarget_t *mt +) +{ + BackendDB *tmp_bd; + struct berval dn, nvnc, pvnc, nrnc, prnc; + int j, rc; + + /* + * syntax: + * + * suffixmassage <suffix> <massaged suffix> + * + * the <suffix> field must be defined as a valid suffix + * (or suffixAlias?) for the current database; + * the <massaged suffix> shouldn't have already been + * defined as a valid suffix or suffixAlias for the + * current server + */ + + ber_str2bv( argv[ 1 ], 0, 0, &dn ); + if ( dnPrettyNormal( NULL, &dn, &pvnc, &nvnc, NULL ) != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "suffix \"%s\" is invalid", + argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + 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, 0 ); + 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, 0 ); + free( pvnc.bv_val ); + free( nvnc.bv_val ); + return 1; + } + + tmp_bd = select_backend( &nrnc, 0 ); + if ( tmp_bd != NULL && tmp_bd->be_private == c->be->be_private ) { + Debug( LDAP_DEBUG_ANY, + "%s: warning: <massaged suffix> \"%s\" resolves to this database, in " + "\"suffixMassage <suffix> <massaged suffix>\"\n", + c->log, prnc.bv_val, 0 ); + } + + /* + * The suffix massaging is emulated by means of the + * rewrite capabilities + */ + rc = suffix_massage_config( mt->mt_rwmap.rwm_rw, + &pvnc, &nvnc, &prnc, &nrnc ); + + free( pvnc.bv_val ); + free( nvnc.bv_val ); + free( prnc.bv_val ); + free( nrnc.bv_val ); + + return rc; +} + +static int +slap_bv_x_ordered_unparse( BerVarray in, BerVarray *out ) +{ + int i; + BerVarray bva = NULL; + char ibuf[32], *ptr; + struct berval idx; + + assert( in != NULL ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) + /* count'em */ ; + + if ( i == 0 ) { + return 1; + } + + idx.bv_val = ibuf; + + bva = ch_malloc( ( i + 1 ) * sizeof(struct berval) ); + BER_BVZERO( &bva[ 0 ] ); + + for ( i = 0; !BER_BVISNULL( &in[i] ); i++ ) { + idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), SLAP_X_ORDERED_FMT, i ); + if ( idx.bv_len >= sizeof( ibuf ) ) { + ber_bvarray_free( bva ); + return 1; + } + + bva[i].bv_len = idx.bv_len + in[i].bv_len; + bva[i].bv_val = ch_malloc( bva[i].bv_len + 1 ); + ptr = lutil_strcopy( bva[i].bv_val, ibuf ); + ptr = lutil_strcopy( ptr, in[i].bv_val ); + *ptr = '\0'; + BER_BVZERO( &bva[ i + 1 ] ); + } + + *out = bva; + return 0; +} + +int +meta_subtree_free( 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 +meta_subtree_destroy( metasubtree_t *ms ) +{ + if ( ms->ms_next ) { + meta_subtree_destroy( ms->ms_next ); + } + + return meta_subtree_free( ms ); +} + +static void +meta_filter_free( metafilter_t *mf ) +{ + regfree( &mf->mf_regex ); + ber_memfree( mf->mf_regex_pattern.bv_val ); + ch_free( mf ); +} + +void +meta_filter_destroy( metafilter_t *mf ) +{ + if ( mf->mf_next ) + meta_filter_destroy( mf->mf_next ); + meta_filter_free( mf ); +} + +static struct berval st_styles[] = { + BER_BVC("subtree"), + BER_BVC("children"), + BER_BVC("regex") +}; + +static int +meta_subtree_unparse( + ConfigArgs *c, + metatarget_t *mt ) +{ + 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 +meta_subtree_config( + metatarget_t *mt, + ConfigArgs *c ) +{ + meta_st_t type = META_ST_SUBTREE; + char *pattern; + struct berval ndn = BER_BVNULL; + metasubtree_t *ms = NULL; + + if ( c->type == LDAP_BACK_CFG_SUBTREE_EX ) { + if ( mt->mt_subtree && !mt->mt_subtree_exclude ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"subtree-exclude\" incompatible with previous \"subtree-include\" directives" ); + return 1; + } + + mt->mt_subtree_exclude = 1; + + } else { + if ( mt->mt_subtree && mt->mt_subtree_exclude ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "\"subtree-include\" incompatible with previous \"subtree-exclude\" directives" ); + return 1; + } + } + + pattern = c->argv[1]; + if ( strncasecmp( pattern, "dn", STRLENOF( "dn" ) ) == 0 ) { + char *style; + + pattern = &pattern[STRLENOF( "dn")]; + + if ( pattern[0] == '.' ) { + style = &pattern[1]; + + if ( strncasecmp( style, "subtree", STRLENOF( "subtree" ) ) == 0 ) { + type = META_ST_SUBTREE; + pattern = &style[STRLENOF( "subtree" )]; + + } else if ( strncasecmp( style, "children", STRLENOF( "children" ) ) == 0 ) { + type = META_ST_SUBORDINATE; + pattern = &style[STRLENOF( "children" )]; + + } else if ( strncasecmp( style, "sub", STRLENOF( "sub" ) ) == 0 ) { + type = META_ST_SUBTREE; + pattern = &style[STRLENOF( "sub" )]; + + } else if ( strncasecmp( style, "regex", STRLENOF( "regex" ) ) == 0 ) { + type = META_ST_REGEX; + pattern = &style[STRLENOF( "regex" )]; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), "unknown style in \"dn.<style>\"" ); + return 1; + } + } + + if ( pattern[0] != ':' ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), "missing colon after \"dn.<style>\"" ); + return 1; + } + pattern++; + } + + switch ( type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: { + struct berval dn; + + ber_str2bv( pattern, 0, 0, &dn ); + if ( dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL ) + != LDAP_SUCCESS ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), "DN=\"%s\" is invalid", pattern ); + return 1; + } + + if ( !dnIsSuffix( &ndn, &mt->mt_nsuffix ) ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "DN=\"%s\" is not a subtree of target \"%s\"", + pattern, mt->mt_nsuffix.bv_val ); + ber_memfree( ndn.bv_val ); + return( 1 ); + } + } break; + + default: + /* silence warnings */ + break; + } + + ms = ch_calloc( sizeof( metasubtree_t ), 1 ); + ms->ms_type = type; + + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: + ms->ms_dn = ndn; + break; + + case META_ST_REGEX: { + int rc; + + rc = regcomp( &ms->ms_regex, pattern, REG_EXTENDED|REG_ICASE ); + if ( rc != 0 ) { + char regerr[ SLAP_TEXT_BUFLEN ]; + + regerror( rc, &ms->ms_regex, regerr, sizeof(regerr) ); + + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "regular expression \"%s\" bad because of %s", + pattern, regerr ); + ch_free( ms ); + return 1; + } + ber_str2bv( pattern, 0, 1, &ms->ms_regex_pattern ); + } break; + } + + if ( mt->mt_subtree == NULL ) { + mt->mt_subtree = ms; + + } else { + metasubtree_t **msp; + + for ( msp = &mt->mt_subtree; *msp; ) { + switch ( ms->ms_type ) { + case META_ST_SUBTREE: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_REGEX: + if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n", + c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val ); + } + break; + } + break; + + case META_ST_SUBORDINATE: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.subtree:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) && ms->ms_dn.bv_len > (*msp)->ms_dn.bv_len ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.subtree:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_SUBORDINATE: + if ( dnIsSuffix( &(*msp)->ms_dn, &ms->ms_dn ) ) { + metasubtree_t *tmp = *msp; + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" is contained in rule \"dn.children:%s\" (replaced)\n", + c->log, pattern, (*msp)->ms_dn.bv_val ); + *msp = (*msp)->ms_next; + tmp->ms_next = NULL; + meta_subtree_destroy( tmp ); + continue; + + } else if ( dnIsSuffix( &ms->ms_dn, &(*msp)->ms_dn ) ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.children:%s\" contains rule \"dn.children:%s\" (ignored)\n", + c->log, (*msp)->ms_dn.bv_val, pattern ); + meta_subtree_destroy( ms ); + ms = NULL; + return( 0 ); + } + break; + + case META_ST_REGEX: + if ( regexec( &(*msp)->ms_regex, ms->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.regex:%s\" may contain rule \"dn.subtree:%s\"\n", + c->log, (*msp)->ms_regex_pattern.bv_val, ms->ms_dn.bv_val ); + } + break; + } + break; + + case META_ST_REGEX: + switch ( (*msp)->ms_type ) { + case META_ST_SUBTREE: + case META_ST_SUBORDINATE: + if ( regexec( &ms->ms_regex, (*msp)->ms_dn.bv_val, 0, NULL, 0 ) == 0 ) { + Debug( LDAP_DEBUG_CONFIG, + "%s: previous rule \"dn.subtree:%s\" may be contained in rule \"dn.regex:%s\"\n", + c->log, (*msp)->ms_dn.bv_val, ms->ms_regex_pattern.bv_val ); + } + break; + + case META_ST_REGEX: + /* no check possible */ + break; + } + break; + } + + msp = &(*msp)->ms_next; + } + + *msp = ms; + } + + return 0; +} + +static slap_verbmasks idassert_mode[] = { + { BER_BVC("self"), LDAP_BACK_IDASSERT_SELF }, + { BER_BVC("anonymous"), LDAP_BACK_IDASSERT_ANONYMOUS }, + { BER_BVC("none"), LDAP_BACK_IDASSERT_NOASSERT }, + { BER_BVC("legacy"), LDAP_BACK_IDASSERT_LEGACY }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks tls_mode[] = { + { BER_BVC( "propagate" ), LDAP_BACK_F_TLS_PROPAGATE_MASK }, + { BER_BVC( "try-propagate" ), LDAP_BACK_F_PROPAGATE_TLS }, + { BER_BVC( "start" ), LDAP_BACK_F_TLS_USE_MASK }, + { BER_BVC( "try-start" ), LDAP_BACK_F_USE_TLS }, + { BER_BVC( "ldaps" ), LDAP_BACK_F_TLS_LDAPS }, + { BER_BVC( "none" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks t_f_mode[] = { + { BER_BVC( "yes" ), LDAP_BACK_F_T_F }, + { BER_BVC( "discover" ), LDAP_BACK_F_T_F_DISCOVER }, + { BER_BVC( "no" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks cancel_mode[] = { + { BER_BVC( "ignore" ), LDAP_BACK_F_CANCEL_IGNORE }, + { BER_BVC( "exop" ), LDAP_BACK_F_CANCEL_EXOP }, + { BER_BVC( "exop-discover" ), LDAP_BACK_F_CANCEL_EXOP_DISCOVER }, + { BER_BVC( "abandon" ), LDAP_BACK_F_CANCEL_ABANDON }, + { BER_BVNULL, 0 } +}; + +static slap_verbmasks onerr_mode[] = { + { BER_BVC( "stop" ), META_BACK_F_ONERR_STOP }, + { BER_BVC( "report" ), META_BACK_F_ONERR_REPORT }, + { BER_BVC( "continue" ), LDAP_BACK_F_NONE }, + { BER_BVNULL, 0 } +}; + +/* see enum in slap.h */ +static slap_cf_aux_table timeout_table[] = { + { BER_BVC("bind="), SLAP_OP_BIND * sizeof( time_t ), 'u', 0, NULL }, + /* unbind makes no sense */ + { BER_BVC("add="), SLAP_OP_ADD * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("delete="), SLAP_OP_DELETE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modrdn="), SLAP_OP_MODRDN * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("modify="), SLAP_OP_MODIFY * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("compare="), SLAP_OP_COMPARE * sizeof( time_t ), 'u', 0, NULL }, + { BER_BVC("search="), SLAP_OP_SEARCH * sizeof( time_t ), 'u', 0, NULL }, + /* abandon makes little sense */ +#if 0 /* not implemented yet */ + { BER_BVC("extended="), SLAP_OP_EXTENDED * sizeof( time_t ), 'u', 0, NULL }, +#endif + { BER_BVNULL, 0, 0, 0, NULL } +}; + +static int +meta_cf_cleanup( ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + metatarget_t *mt = c->ca_private; + + return meta_target_finish( mi, mt, c->log, c->cr_msg, sizeof( c->cr_msg )); +} + +static int +meta_back_cf_gen( ConfigArgs *c ) +{ + metainfo_t *mi = ( metainfo_t * )c->be->be_private; + metatarget_t *mt; + metacommon_t *mc; + + int i, rc = 0; + + assert( mi != NULL ); + + if ( c->op == SLAP_CONFIG_EMIT || c->op == LDAP_MOD_DELETE ) { + if ( !mi ) + return 1; + + if ( c->table == Cft_Database ) { + mt = NULL; + mc = &mi->mi_mc; + } else { + mt = c->ca_private; + mc = &mt->mt_mc; + } + } + + if ( c->op == SLAP_CONFIG_EMIT ) { + struct berval bv = BER_BVNULL; + + switch( c->type ) { + /* Base attrs */ + case LDAP_BACK_CFG_CONN_TTL: + if ( mi->mi_conn_ttl == 0 ) { + return 1; + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_conn_ttl ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + if ( mi->mi_cache.ttl == META_DNCACHE_DISABLED ) { + return 1; + } else if ( mi->mi_cache.ttl == META_DNCACHE_FOREVER ) { + BER_BVSTR( &bv, "forever" ); + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_cache.ttl ); + ber_str2bv( buf, 0, 0, &bv ); + } + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + if ( mi->mi_idle_timeout == 0 ) { + return 1; + } else { + char buf[ SLAP_TEXT_BUFLEN ]; + + lutil_unparse_time( buf, sizeof( buf ), mi->mi_idle_timeout ); + ber_str2bv( buf, 0, 0, &bv ); + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_ONERR: + enum_to_verb( onerr_mode, mi->mi_flags & META_BACK_F_ONERR_MASK, &bv ); + if ( BER_BVISNULL( &bv )) { + rc = 1; + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + c->value_int = META_BACK_DEFER_ROOTDN_BIND( mi ); + break; + + case LDAP_BACK_CFG_SINGLECONN: + c->value_int = LDAP_BACK_SINGLECONN( mi ); + break; + + case LDAP_BACK_CFG_USETEMP: + c->value_int = LDAP_BACK_USE_TEMPORARIES( mi ); + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + c->value_int = mi->mi_conn_priv_max; + break; + + /* common attrs */ + case LDAP_BACK_CFG_BIND_TIMEOUT: + if ( mc->mc_bind_timeout.tv_sec == 0 && + mc->mc_bind_timeout.tv_usec == 0 ) { + return 1; + } else { + c->value_ulong = mc->mc_bind_timeout.tv_sec * 1000000UL + + mc->mc_bind_timeout.tv_usec; + } + break; + + case LDAP_BACK_CFG_CANCEL: { + slap_mask_t mask = LDAP_BACK_F_CANCEL_MASK2; + + if ( mt && META_BACK_TGT_CANCEL_DISCOVER( mt ) ) { + mask &= ~LDAP_BACK_F_CANCEL_EXOP; + } + enum_to_verb( cancel_mode, (mc->mc_flags & mask), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + case LDAP_BACK_CFG_CHASE: + c->value_int = META_BACK_CMN_CHASE_REFERRALS(mc); + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + if ( mc->mc_ps == META_CLIENT_PR_DISABLE ) { + return 1; + } else if ( mc->mc_ps == META_CLIENT_PR_ACCEPT_UNSOLICITED ) { + BER_BVSTR( &bv, "accept-unsolicited" ); + } else { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mc->mc_ps ); + bv.bv_val = c->cr_msg; + } + value_add_one( &c->rvalue_vals, &bv ); + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_DEFAULT_T: + if ( mt || mi->mi_defaulttarget == META_DEFAULT_TARGET_NONE ) + return 1; + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", mi->mi_defaulttarget ); + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + if ( mc->mc_network_timeout == 0 ) { + return 1; + } + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%ld", + mc->mc_network_timeout ); + bv.bv_val = c->cr_msg; + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_NOREFS: + c->value_int = META_BACK_CMN_NOREFS(mc); + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + c->value_int = META_BACK_CMN_NOUNDEFFILTER(mc); + break; + + case LDAP_BACK_CFG_NRETRIES: + if ( mc->mc_nretries == META_RETRY_FOREVER ) { + BER_BVSTR( &bv, "forever" ); + } else if ( mc->mc_nretries == META_RETRY_NEVER ) { + BER_BVSTR( &bv, "never" ); + } else { + bv.bv_len = snprintf( c->cr_msg, sizeof(c->cr_msg), "%d", + mc->mc_nretries ); + bv.bv_val = c->cr_msg; + } + value_add_one( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( !META_BACK_CMN_QUARANTINE( mc )) { + rc = 1; + break; + } + rc = mi->mi_ldap_extra->retry_info_unparse( &mc->mc_quarantine, &bv ); + if ( rc == 0 ) { + ber_bvarray_add( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_REBIND: + c->value_int = META_BACK_CMN_SAVECRED(mc); + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + if ( mc->mc_timeout[ i ] != 0 ) { + break; + } + } + + if ( i == SLAP_OP_LAST ) { + return 1; + } + + BER_BVZERO( &bv ); + slap_cf_aux_table_unparse( mc->mc_timeout, &bv, timeout_table ); + + if ( BER_BVISNULL( &bv ) ) { + return 1; + } + + for ( i = 0; isspace( (unsigned char) bv.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bv.bv_len -= i; + AC_MEMCPY( bv.bv_val, &bv.bv_val[ i ], + bv.bv_len + 1 ); + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + break; + + case LDAP_BACK_CFG_VERSION: + if ( mc->mc_version == 0 ) + return 1; + c->value_int = mc->mc_version; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + c->value_int = META_BACK_CMN_ST_REQUEST( mc ); + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_T_F: + enum_to_verb( t_f_mode, (mc->mc_flags & LDAP_BACK_F_T_F_MASK2), &bv ); + if ( BER_BVISNULL( &bv ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + break; + + case LDAP_BACK_CFG_TLS: { + struct berval bc = BER_BVNULL, bv2; + + if (( mc->mc_flags & LDAP_BACK_F_TLS_MASK ) == LDAP_BACK_F_NONE ) { + rc = 1; + break; + } + enum_to_verb( tls_mode, ( mc->mc_flags & LDAP_BACK_F_TLS_MASK ), &bv ); + assert( !BER_BVISNULL( &bv ) ); + + if ( mt ) { + bindconf_tls_unparse( &mt->mt_tls, &bc ); + } + + if ( !BER_BVISEMPTY( &bc )) { + bv2.bv_len = bv.bv_len + bc.bv_len + 1; + bv2.bv_val = ch_malloc( bv2.bv_len + 1 ); + strcpy( bv2.bv_val, bv.bv_val ); + bv2.bv_val[bv.bv_len] = ' '; + strcpy( &bv2.bv_val[bv.bv_len + 1], bc.bv_val ); + ber_memfree( bc.bv_val ); + ber_bvarray_add( &c->rvalue_vals, &bv2 ); + } else { + value_add_one( &c->rvalue_vals, &bv ); + } + } break; + + /* target attrs */ + case LDAP_BACK_CFG_URI: { + char *p2, *p1 = strchr( mt->mt_uri, ' ' ); + bv.bv_len = strlen( mt->mt_uri ) + 3 + mt->mt_psuffix.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + p2 = bv.bv_val; + *p2++ = '"'; + if ( p1 ) { + p2 = lutil_strncopy( p2, mt->mt_uri, p1 - mt->mt_uri ); + } else { + p2 = lutil_strcopy( p2, mt->mt_uri ); + } + *p2++ = '/'; + p2 = lutil_strcopy( p2, mt->mt_psuffix.bv_val ); + *p2++ = '"'; + if ( p1 ) { + strcpy( p2, p1 ); + } + ber_bvarray_add( &c->rvalue_vals, &bv ); + } break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + case LDAP_BACK_CFG_ACL_PASSWD: + /* FIXME no point here, there is no code implementing + * their features. Was this supposed to implement + * acl-bind like back-ldap? + */ + rc = 1; + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: { + BerVarray *bvp; + int i; + struct berval bv = BER_BVNULL; + char buf[SLAP_TEXT_BUFLEN]; + + bvp = &mt->mt_idassert_authz; + if ( *bvp == NULL ) { + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) + { + BER_BVSTR( &bv, "*" ); + value_add_one( &c->rvalue_vals, &bv ); + + } else { + rc = 1; + } + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) { + char *ptr; + int len = snprintf( buf, sizeof( buf ), SLAP_X_ORDERED_FMT, i ); + bv.bv_len = ((*bvp)[ i ]).bv_len + len; + bv.bv_val = ber_memrealloc( bv.bv_val, bv.bv_len + 1 ); + ptr = bv.bv_val; + ptr = lutil_strcopy( ptr, buf ); + ptr = lutil_strncopy( ptr, ((*bvp)[ i ]).bv_val, ((*bvp)[ i ]).bv_len ); + value_add_one( &c->rvalue_vals, &bv ); + } + if ( bv.bv_val ) { + ber_memfree( bv.bv_val ); + } + break; + } + + case LDAP_BACK_CFG_IDASSERT_BIND: { + int i; + struct berval bc = BER_BVNULL; + char *ptr; + + if ( mt->mt_idassert_authmethod == LDAP_AUTH_NONE ) { + return 1; + } else { + ber_len_t len; + + switch ( mt->mt_idassert_mode ) { + case LDAP_BACK_IDASSERT_OTHERID: + case LDAP_BACK_IDASSERT_OTHERDN: + break; + + default: { + struct berval mode = BER_BVNULL; + + enum_to_verb( idassert_mode, mt->mt_idassert_mode, &mode ); + if ( BER_BVISNULL( &mode ) ) { + /* there's something wrong... */ + assert( 0 ); + rc = 1; + + } else { + bv.bv_len = STRLENOF( "mode=" ) + mode.bv_len; + bv.bv_val = ch_malloc( bv.bv_len + 1 ); + + ptr = lutil_strcopy( bv.bv_val, "mode=" ); + ptr = lutil_strcopy( ptr, mode.bv_val ); + } + break; + } + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_NATIVE_AUTHZ ) { + len = bv.bv_len + STRLENOF( "authz=native" ); + + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + (void)lutil_strcopy( ptr, "authz=native" ); + } + + len = bv.bv_len + STRLENOF( "flags=non-prescriptive,override,obsolete-encoding-workaround,proxy-authz-non-critical,dn-authzid" ); + /* flags */ + if ( !BER_BVISEMPTY( &bv ) ) { + len += STRLENOF( " " ); + } + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + ptr = &bv.bv_val[ bv.bv_len ]; + + if ( !BER_BVISEMPTY( &bv ) ) { + ptr = lutil_strcopy( ptr, " " ); + } + + ptr = lutil_strcopy( ptr, "flags=" ); + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) { + ptr = lutil_strcopy( ptr, "prescriptive" ); + } else { + ptr = lutil_strcopy( ptr, "non-prescriptive" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) { + ptr = lutil_strcopy( ptr, ",override" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_PROXY_AUTHZ ) { + ptr = lutil_strcopy( ptr, ",obsolete-proxy-authz" ); + + } else if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_OBSOLETE_ENCODING_WORKAROUND ) { + ptr = lutil_strcopy( ptr, ",obsolete-encoding-workaround" ); + } + + if ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PROXYAUTHZ_CRITICAL ) { + ptr = lutil_strcopy( ptr, ",proxy-authz-critical" ); + + } else { + ptr = lutil_strcopy( ptr, ",proxy-authz-non-critical" ); + } + +#ifdef SLAP_AUTH_DN + switch ( mt->mt_idassert_flags & LDAP_BACK_AUTH_DN_MASK ) { + case LDAP_BACK_AUTH_DN_AUTHZID: + ptr = lutil_strcopy( ptr, ",dn-authzid" ); + break; + + case LDAP_BACK_AUTH_DN_WHOAMI: + ptr = lutil_strcopy( ptr, ",dn-whoami" ); + break; + + default: +#if 0 /* implicit */ + ptr = lutil_strcopy( ptr, ",dn-none" ); +#endif + break; + } +#endif + + bv.bv_len = ( ptr - bv.bv_val ); + /* end-of-flags */ + } + + bindconf_unparse( &mt->mt_idassert.si_bc, &bc ); + + if ( !BER_BVISNULL( &bv ) ) { + ber_len_t len = bv.bv_len + bc.bv_len; + + bv.bv_val = ch_realloc( bv.bv_val, len + 1 ); + + assert( bc.bv_val[ 0 ] == ' ' ); + + ptr = lutil_strcopy( &bv.bv_val[ bv.bv_len ], bc.bv_val ); + free( bc.bv_val ); + bv.bv_len = ptr - bv.bv_val; + + } else { + for ( i = 0; isspace( (unsigned char) bc.bv_val[ i ] ); i++ ) + /* count spaces */ ; + + if ( i ) { + bc.bv_len -= i; + AC_MEMCPY( bc.bv_val, &bc.bv_val[ i ], bc.bv_len + 1 ); + } + + bv = bc; + } + + ber_bvarray_add( &c->rvalue_vals, &bv ); + + break; + } + + case LDAP_BACK_CFG_SUFFIXM: /* unused */ + case LDAP_BACK_CFG_REWRITE: + if ( mt->mt_rwmap.rwm_bva_rewrite == NULL ) { + rc = 1; + } else { + rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_rewrite, &c->rvalue_vals ); + } + break; + + case LDAP_BACK_CFG_MAP: + if ( mt->mt_rwmap.rwm_bva_map == NULL ) { + rc = 1; + } else { + rc = slap_bv_x_ordered_unparse( mt->mt_rwmap.rwm_bva_map, &c->rvalue_vals ); + } + break; + + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + rc = meta_subtree_unparse( c, mt ); + break; + + case LDAP_BACK_CFG_FILTER: + if ( mt->mt_filter == NULL ) { + rc = 1; + } else { + metafilter_t *mf; + for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) + value_add_one( &c->rvalue_vals, &mf->mf_regex_pattern ); + } + break; + + /* replaced by idassert */ + case LDAP_BACK_CFG_PSEUDOROOTDN: + case LDAP_BACK_CFG_PSEUDOROOTPW: + rc = 1; + break; + + case LDAP_BACK_CFG_KEEPALIVE: { + struct berval bv; + char buf[AC_LINE_MAX]; + bv.bv_len = AC_LINE_MAX; + bv.bv_val = &buf[0]; + slap_keepalive_parse(&bv, &mt->mt_tls.sb_keepalive, 0, 0, 1); + value_add_one( &c->rvalue_vals, &bv ); + break; + } + + default: + rc = 1; + } + return rc; + } else if ( c->op == LDAP_MOD_DELETE ) { + switch( c->type ) { + /* Base attrs */ + case LDAP_BACK_CFG_CONN_TTL: + mi->mi_conn_ttl = 0; + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + mi->mi_cache.ttl = META_DNCACHE_DISABLED; + break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: + mi->mi_idle_timeout = 0; + break; + + case LDAP_BACK_CFG_ONERR: + mi->mi_flags &= ~META_BACK_F_ONERR_MASK; + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND; + break; + + case LDAP_BACK_CFG_SINGLECONN: + mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN; + break; + + case LDAP_BACK_CFG_USETEMP: + mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_MIN; + break; + + /* common attrs */ + case LDAP_BACK_CFG_BIND_TIMEOUT: + mc->mc_bind_timeout.tv_sec = 0; + mc->mc_bind_timeout.tv_usec = 0; + break; + + case LDAP_BACK_CFG_CANCEL: + mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + break; + + case LDAP_BACK_CFG_CHASE: + mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + mc->mc_ps = META_CLIENT_PR_DISABLE; + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_DEFAULT_T: + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: + mc->mc_network_timeout = 0; + break; + + case LDAP_BACK_CFG_NOREFS: + mc->mc_flags &= ~LDAP_BACK_F_NOREFS; + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + break; + + case LDAP_BACK_CFG_NRETRIES: + mc->mc_nretries = META_RETRY_DEFAULT; + break; + + case LDAP_BACK_CFG_QUARANTINE: + if ( META_BACK_CMN_QUARANTINE( mc )) { + mi->mi_ldap_extra->retry_info_destroy( &mc->mc_quarantine ); + mc->mc_flags &= ~LDAP_BACK_F_QUARANTINE; + if ( mc == &mt->mt_mc ) { + ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex ); + mt->mt_isquarantined = 0; + } + } + break; + + case LDAP_BACK_CFG_REBIND: + mc->mc_flags &= ~LDAP_BACK_F_SAVECRED; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 0; i < SLAP_OP_LAST; i++ ) { + mc->mc_timeout[ i ] = 0; + } + break; + + case LDAP_BACK_CFG_VERSION: + mc->mc_version = 0; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST; + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_T_F: + mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2; + break; + + case LDAP_BACK_CFG_TLS: + mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK; + if ( mt ) + bindconf_free( &mt->mt_tls ); + break; + + /* target attrs */ + case LDAP_BACK_CFG_URI: + if ( mt->mt_uri ) { + ch_free( mt->mt_uri ); + mt->mt_uri = NULL; + } + /* FIXME: should have a way to close all cached + * connections associated with this target. + */ + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: { + BerVarray *bvp; + + bvp = &mt->mt_idassert_authz; + if ( c->valx < 0 ) { + if ( *bvp != NULL ) { + ber_bvarray_free( *bvp ); + *bvp = NULL; + } + + } else { + if ( *bvp == NULL ) { + rc = 1; + break; + } + + for ( i = 0; !BER_BVISNULL( &((*bvp)[ i ]) ); i++ ) + ; + + if ( i >= c->valx ) { + rc = 1; + break; + } + ber_memfree( ((*bvp)[ c->valx ]).bv_val ); + for ( i = c->valx; !BER_BVISNULL( &((*bvp)[ i + 1 ]) ); i++ ) { + (*bvp)[ i ] = (*bvp)[ i + 1 ]; + } + BER_BVZERO( &((*bvp)[ i ]) ); + } + } break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + bindconf_free( &mt->mt_idassert.si_bc ); + memset( &mt->mt_idassert, 0, sizeof( slap_idassert_t ) ); + break; + + case LDAP_BACK_CFG_SUFFIXM: /* unused */ + case LDAP_BACK_CFG_REWRITE: + { + if ( c->valx >= 0 ) { + int i; + + for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ ); + + if ( c->valx >= i ) { + rc = 1; + break; + } + + ber_memfree( mt->mt_rwmap.rwm_bva_rewrite[ c->valx ].bv_val ); + for ( i = c->valx; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ] ); i++ ) + { + mt->mt_rwmap.rwm_bva_rewrite[ i ] = mt->mt_rwmap.rwm_bva_rewrite[ i + 1 ]; + } + BER_BVZERO( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); + + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + assert( mt->mt_rwmap.rwm_rw == NULL ); + + rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + + for ( i = 0; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ i ] ); i++ ) + { + ConfigArgs ca = { 0 }; + + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + init_config_argv( &ca ); + config_parse_ldif( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, ca.argv ); + } + + + ch_free( ca.tline ); + ch_free( ca.argv ); + + assert( rc == 0 ); + } + + } else if ( mt->mt_rwmap.rwm_rw != NULL ) { + if ( mt->mt_rwmap.rwm_bva_rewrite ) { + ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite ); + mt->mt_rwmap.rwm_bva_rewrite = NULL; + } + if ( mt->mt_rwmap.rwm_rw ) + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + + meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + } + } + break; + + case LDAP_BACK_CFG_MAP: + if ( mt->mt_rwmap.rwm_bva_map ) { + ber_bvarray_free( mt->mt_rwmap.rwm_bva_map ); + mt->mt_rwmap.rwm_bva_map = NULL; + } + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + mt->mt_rwmap.rwm_oc.drop_missing = 0; + mt->mt_rwmap.rwm_at.drop_missing = 0; + break; + + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + /* can only be one of exclude or include */ + if (( c->type == LDAP_BACK_CFG_SUBTREE_EX ) ^ mt->mt_subtree_exclude ) { + rc = 1; + break; + } + if ( c->valx < 0 ) { + meta_subtree_destroy( mt->mt_subtree ); + mt->mt_subtree = NULL; + } else { + metasubtree_t *ms, **mprev; + for (i=0, mprev = &mt->mt_subtree, ms = *mprev; ms; ms = *mprev) { + if ( i == c->valx ) { + *mprev = ms->ms_next; + meta_subtree_free( ms ); + break; + } + i++; + mprev = &ms->ms_next; + } + if ( i != c->valx ) + rc = 1; + } + break; + + case LDAP_BACK_CFG_FILTER: + if ( c->valx < 0 ) { + meta_filter_destroy( mt->mt_filter ); + mt->mt_filter = NULL; + } else { + metafilter_t *mf, **mprev; + for (i=0, mprev = &mt->mt_filter, mf = *mprev; mf; mf = *mprev) { + if ( i == c->valx ) { + *mprev = mf->mf_next; + meta_filter_free( mf ); + break; + } + i++; + mprev = &mf->mf_next; + } + if ( i != c->valx ) + rc = 1; + } + break; + + case LDAP_BACK_CFG_KEEPALIVE: + mt->mt_tls.sb_keepalive.sk_idle = 0; + mt->mt_tls.sb_keepalive.sk_probes = 0; + mt->mt_tls.sb_keepalive.sk_interval = 0; + break; + + default: + rc = 1; + break; + } + + return rc; + } + + if ( c->op == SLAP_CONFIG_ADD ) { + if ( c->type >= LDAP_BACK_CFG_LAST_BASE ) { + /* exclude CFG_URI from this check */ + if ( c->type > LDAP_BACK_CFG_LAST_BOTH ) { + if ( !mi->mi_ntargets ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need \"uri\" directive first" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + if ( mi->mi_ntargets ) { + mt = mi->mi_targets[ mi->mi_ntargets-1 ]; + mc = &mt->mt_mc; + } else { + mt = NULL; + mc = &mi->mi_mc; + } + } + } else { + if ( c->table == Cft_Database ) { + mt = NULL; + mc = &mi->mi_mc; + } else { + mt = c->ca_private; + if ( mt ) + mc = &mt->mt_mc; + else + mc = NULL; + } + } + + switch( c->type ) { + case LDAP_BACK_CFG_URI: { + LDAPURLDesc *ludp; + struct berval dn; + int j; + + char **uris = NULL; + + if ( c->be->be_nsuffix == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "the suffix must be defined before any target" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + i = mi->mi_ntargets++; + + mi->mi_targets = ( metatarget_t ** )ch_realloc( mi->mi_targets, + sizeof( metatarget_t * ) * mi->mi_ntargets ); + if ( mi->mi_targets == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "out of memory while storing server name" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( meta_back_new_target( &mi->mi_targets[ i ] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to init server" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mt = mi->mi_targets[ i ]; + + mt->mt_rebind_f = mi->mi_rebind_f; + mt->mt_urllist_f = mi->mi_urllist_f; + mt->mt_urllist_p = mt; + + if ( META_BACK_QUARANTINE( mi ) ) { + ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex ); + } + mt->mt_mc = mi->mi_mc; + + for ( j = 1; j < c->argc; j++ ) { + char **tmpuris = ldap_str2charray( c->argv[ j ], "\t" ); + + if ( tmpuris == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse URIs #%d" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + j-1, c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( j == 1 ) { + uris = tmpuris; + + } else { + ldap_charray_merge( &uris, tmpuris ); + ldap_charray_free( tmpuris ); + } + } + + for ( j = 0; uris[ j ] != NULL; j++ ) { + char *tmpuri = NULL; + + /* + * uri MUST be legal! + */ + if ( ldap_url_parselist_ext( &ludp, uris[ j ], "\t", + LDAP_PVT_URL_PARSE_NONE ) != LDAP_SUCCESS + || ludp->lud_next != NULL ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse URI #%d" + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + j-1, c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_charray_free( uris ); + return 1; + } + + if ( j == 0 ) { + + /* + * uri MUST have the <dn> part! + */ + if ( ludp->lud_dn == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "missing <naming context> " + " in \"%s <protocol>://<server>[:port]/<naming context>\"", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return 1; + } + + /* + * copies and stores uri and suffix + */ + ber_str2bv( ludp->lud_dn, 0, 0, &dn ); + rc = dnPrettyNormal( NULL, &dn, &mt->mt_psuffix, + &mt->mt_nsuffix, NULL ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "target DN is invalid \"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + } + + ludp->lud_dn[ 0 ] = '\0'; + + switch ( ludp->lud_scope ) { + case LDAP_SCOPE_DEFAULT: + mt->mt_scope = LDAP_SCOPE_SUBTREE; + break; + + case LDAP_SCOPE_SUBTREE: + case LDAP_SCOPE_SUBORDINATE: + mt->mt_scope = ludp->lud_scope; + break; + + default: + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid scope for target \"%s\"", + c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + } + + } else { + /* check all, to apply the scope check on the first one */ + if ( ludp->lud_dn != NULL && ludp->lud_dn[ 0 ] != '\0' ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "multiple URIs must have no DN part" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_free_urllist( ludp ); + ldap_charray_free( uris ); + return( 1 ); + + } + } + + tmpuri = ldap_url_list2urls( ludp ); + ldap_free_urllist( ludp ); + if ( tmpuri == NULL ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + ldap_charray_free( uris ); + return( 1 ); + } + ldap_memfree( uris[ j ] ); + uris[ j ] = tmpuri; + } + + mt->mt_uri = ldap_charray2str( uris, " " ); + ldap_charray_free( uris ); + if ( mt->mt_uri == NULL) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), "no memory?" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + /* + * uri MUST be a branch of suffix! + */ + for ( j = 0; !BER_BVISNULL( &c->be->be_nsuffix[ j ] ); j++ ) { + if ( dnIsSuffix( &mt->mt_nsuffix, &c->be->be_nsuffix[ j ] ) ) { + break; + } + } + + if ( BER_BVISNULL( &c->be->be_nsuffix[ j ] ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "<naming context> of URI must be within the naming context of this database." ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + c->ca_private = mt; + c->cleanup = meta_cf_cleanup; + } break; + case LDAP_BACK_CFG_SUBTREE_EX: + case LDAP_BACK_CFG_SUBTREE_IN: + /* subtree-exclude */ + if ( meta_subtree_config( mt, c )) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + break; + + case LDAP_BACK_CFG_FILTER: { + metafilter_t *mf, **m2; + mf = ch_calloc( 1, sizeof( metafilter_t )); + rc = regcomp( &mf->mf_regex, c->argv[1], REG_EXTENDED ); + if ( rc ) { + char regerr[ SLAP_TEXT_BUFLEN ]; + regerror( rc, &mf->mf_regex, regerr, sizeof(regerr) ); + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "regular expression \"%s\" bad because of %s", + c->argv[1], regerr ); + ch_free( mf ); + return 1; + } + ber_str2bv( c->argv[1], 0, 1, &mf->mf_regex_pattern ); + for ( m2 = &mt->mt_filter; *m2; m2 = &(*m2)->mf_next ) + ; + *m2 = mf; + } break; + + case LDAP_BACK_CFG_DEFAULT_T: + /* default target directive */ + i = mi->mi_ntargets - 1; + + if ( c->argc == 1 ) { + if ( i < 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" alone must be inside a \"uri\" directive", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_defaulttarget = i; + + } else { + if ( strcasecmp( c->argv[ 1 ], "none" ) == 0 ) { + if ( i >= 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s none\" should go before uri definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + } + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + + } else { + + if ( lutil_atoi( &mi->mi_defaulttarget, c->argv[ 1 ] ) != 0 + || mi->mi_defaulttarget < 0 + || mi->mi_defaulttarget >= i - 1 ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "illegal target number %d", + mi->mi_defaulttarget ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + } + break; + + case LDAP_BACK_CFG_DNCACHE_TTL: + /* ttl of dn cache */ + if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) { + mi->mi_cache.ttl = META_DNCACHE_FOREVER; + + } else if ( strcasecmp( c->argv[ 1 ], "disabled" ) == 0 ) { + mi->mi_cache.ttl = META_DNCACHE_DISABLED; + + } else { + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse dncache ttl \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_cache.ttl = (time_t)t; + } + break; + + case LDAP_BACK_CFG_NETWORK_TIMEOUT: { + /* network timeout when connecting to ldap servers */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse network timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_network_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_IDLE_TIMEOUT: { + /* idle timeout when connecting to ldap servers */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse idle timeout \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + } + mi->mi_idle_timeout = (time_t)t; + } break; + + case LDAP_BACK_CFG_CONN_TTL: { + /* conn ttl */ + unsigned long t; + + if ( lutil_parse_time( c->argv[ 1 ], &t ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse conn ttl \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + + } + mi->mi_conn_ttl = (time_t)t; + } break; + + case LDAP_BACK_CFG_BIND_TIMEOUT: + /* bind timeout when connecting to ldap servers */ + mc->mc_bind_timeout.tv_sec = c->value_ulong/1000000; + mc->mc_bind_timeout.tv_usec = c->value_ulong%1000000; + break; + + case LDAP_BACK_CFG_ACL_AUTHCDN: + /* name to use for meta_back_group */ + if ( strcasecmp( c->argv[ 0 ], "binddn" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s: " + "\"binddn\" statement is deprecated; " + "use \"acl-authcDN\" instead\n", + c->log, 0, 0 ); + /* FIXME: some day we'll need to throw an error */ + } + + ber_memfree_x( c->value_dn.bv_val, NULL ); + mt->mt_binddn = c->value_ndn; + BER_BVZERO( &c->value_dn ); + BER_BVZERO( &c->value_ndn ); + break; + + case LDAP_BACK_CFG_ACL_PASSWD: + /* password to use for meta_back_group */ + if ( strcasecmp( c->argv[ 0 ], "bindpw" ) == 0 ) { + Debug( LDAP_DEBUG_ANY, "%s " + "\"bindpw\" statement is deprecated; " + "use \"acl-passwd\" instead\n", + c->log, 0, 0 ); + /* FIXME: some day we'll need to throw an error */ + } + + ber_str2bv( c->argv[ 1 ], 0L, 1, &mt->mt_bindpw ); + break; + + case LDAP_BACK_CFG_REBIND: + /* save bind creds for referral rebinds? */ + if ( c->argc == 1 || c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_SAVECRED; + } else { + mc->mc_flags &= ~LDAP_BACK_F_SAVECRED; + } + break; + + case LDAP_BACK_CFG_CHASE: + if ( c->argc == 1 || c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_CHASE_REFERRALS; + } else { + mc->mc_flags &= ~LDAP_BACK_F_CHASE_REFERRALS; + } + break; + + case LDAP_BACK_CFG_TLS: + i = verb_to_mask( c->argv[1], tls_mode ); + if ( BER_BVISNULL( &tls_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_TLS_MASK; + mc->mc_flags |= tls_mode[i].mask; + + if ( c->argc > 2 ) { + if ( c->op == SLAP_CONFIG_ADD && mi->mi_ntargets == 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "need \"uri\" directive first" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( i = 2; i < c->argc; i++ ) { + if ( bindconf_tls_parse( c->argv[i], &mt->mt_tls )) + return 1; + } + bindconf_tls_defaults( &mt->mt_tls ); + } + break; + + case LDAP_BACK_CFG_T_F: + i = verb_to_mask( c->argv[1], t_f_mode ); + if ( BER_BVISNULL( &t_f_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_T_F_MASK2; + mc->mc_flags |= t_f_mode[i].mask; + break; + + case LDAP_BACK_CFG_ONERR: + /* onerr? */ + i = verb_to_mask( c->argv[1], onerr_mode ); + if ( BER_BVISNULL( &onerr_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_flags &= ~META_BACK_F_ONERR_MASK; + mi->mi_flags |= onerr_mode[i].mask; + break; + + case LDAP_BACK_CFG_PSEUDOROOT_BIND_DEFER: + /* bind-defer? */ + if ( c->argc == 1 || c->value_int ) { + mi->mi_flags |= META_BACK_F_DEFER_ROOTDN_BIND; + } else { + mi->mi_flags &= ~META_BACK_F_DEFER_ROOTDN_BIND; + } + break; + + case LDAP_BACK_CFG_SINGLECONN: + /* single-conn? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( c->value_int ) { + mi->mi_flags |= LDAP_BACK_F_SINGLECONN; + } else { + mi->mi_flags &= ~LDAP_BACK_F_SINGLECONN; + } + break; + + case LDAP_BACK_CFG_USETEMP: + /* use-temporaries? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + if ( c->value_int ) { + mi->mi_flags |= LDAP_BACK_F_USE_TEMPORARIES; + } else { + mi->mi_flags &= ~LDAP_BACK_F_USE_TEMPORARIES; + } + break; + + case LDAP_BACK_CFG_CONNPOOLMAX: + /* privileged connections pool max size ? */ + if ( mi->mi_ntargets > 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "\"%s\" must appear before target definitions", + c->argv[0] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + + if ( c->value_int < LDAP_BACK_CONN_PRIV_MIN + || c->value_int > LDAP_BACK_CONN_PRIV_MAX ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "invalid max size " "of privileged " + "connections pool \"%s\" " + "in \"conn-pool-max <n> " + "(must be between %d and %d)\"", + c->argv[ 1 ], + LDAP_BACK_CONN_PRIV_MIN, + LDAP_BACK_CONN_PRIV_MAX ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mi->mi_conn_priv_max = c->value_int; + break; + + case LDAP_BACK_CFG_CANCEL: + i = verb_to_mask( c->argv[1], cancel_mode ); + if ( BER_BVISNULL( &cancel_mode[i].word ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_flags &= ~LDAP_BACK_F_CANCEL_MASK2; + mc->mc_flags |= cancel_mode[i].mask; + break; + + case LDAP_BACK_CFG_TIMEOUT: + for ( i = 1; i < c->argc; i++ ) { + if ( isdigit( (unsigned char) c->argv[ i ][ 0 ] ) ) { + int j; + unsigned u; + + if ( lutil_atoux( &u, c->argv[ i ], 0 ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + for ( j = 0; j < SLAP_OP_LAST; j++ ) { + mc->mc_timeout[ j ] = u; + } + + continue; + } + + if ( slap_cf_aux_table_parse( c->argv[ i ], mc->mc_timeout, timeout_table, "slapd-meta timeout" ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg), + "unable to parse timeout \"%s\"", + c->argv[ i ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + break; + + case LDAP_BACK_CFG_PSEUDOROOTDN: + /* name to use as pseudo-root dn */ + /* + * exact replacement: + * + +idassert-bind bindmethod=simple + binddn=<pseudorootdn> + credentials=<pseudorootpw> + mode=none + flags=non-prescriptive +idassert-authzFrom "dn:<rootdn>" + + * so that only when authc'd as <rootdn> the proxying occurs + * rebinding as the <pseudorootdn> without proxyAuthz. + */ + + Debug( LDAP_DEBUG_ANY, + "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; " + "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n", + c->log, 0, 0 ); + + { + char binddn[ SLAP_TEXT_BUFLEN ]; + char *cargv[] = { + "idassert-bind", + "bindmethod=simple", + NULL, + "mode=none", + "flags=non-prescriptive", + NULL + }; + char **oargv; + int oargc; + int cargc = 5; + int rc; + + + if ( BER_BVISNULL( &c->be->be_rootndn ) ) { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"rootdn\" must be defined first.\n", + c->log, 0, 0 ); + return 1; + } + + if ( sizeof( binddn ) <= (unsigned) snprintf( binddn, + sizeof( binddn ), "binddn=%s", c->argv[ 1 ] )) + { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootdn\" too long.\n", + c->log, 0, 0 ); + return 1; + } + cargv[ 2 ] = binddn; + + oargv = c->argv; + oargc = c->argc; + c->argv = cargv; + c->argc = cargc; + rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert ); + c->argv = oargv; + c->argc = oargc; + if ( rc == 0 ) { + struct berval bv; + + if ( mt->mt_idassert_authz != NULL ) { + Debug( LDAP_DEBUG_ANY, "%s: \"idassert-authzFrom\" already defined (discarded).\n", + c->log, 0, 0 ); + ber_bvarray_free( mt->mt_idassert_authz ); + mt->mt_idassert_authz = NULL; + } + + assert( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ); + + bv.bv_len = STRLENOF( "dn:" ) + c->be->be_rootndn.bv_len; + bv.bv_val = ber_memalloc( bv.bv_len + 1 ); + AC_MEMCPY( bv.bv_val, "dn:", STRLENOF( "dn:" ) ); + AC_MEMCPY( &bv.bv_val[ STRLENOF( "dn:" ) ], c->be->be_rootndn.bv_val, c->be->be_rootndn.bv_len + 1 ); + + ber_bvarray_add( &mt->mt_idassert_authz, &bv ); + } + + return rc; + } + break; + + case LDAP_BACK_CFG_PSEUDOROOTPW: + /* password to use as pseudo-root */ + Debug( LDAP_DEBUG_ANY, + "%s: \"pseudorootdn\", \"pseudorootpw\" are no longer supported; " + "use \"idassert-bind\" and \"idassert-authzFrom\" instead.\n", + c->log, 0, 0 ); + + if ( BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + Debug( LDAP_DEBUG_ANY, "%s: \"pseudorootpw\": \"pseudorootdn\" must be defined first.\n", + c->log, 0, 0 ); + return 1; + } + + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + memset( mt->mt_idassert_passwd.bv_val, 0, + mt->mt_idassert_passwd.bv_len ); + ber_memfree( mt->mt_idassert_passwd.bv_val ); + } + ber_str2bv( c->argv[ 1 ], 0, 1, &mt->mt_idassert_passwd ); + break; + + case LDAP_BACK_CFG_IDASSERT_BIND: + /* idassert-bind */ + rc = mi->mi_ldap_extra->idassert_parse( c, &mt->mt_idassert ); + break; + + case LDAP_BACK_CFG_IDASSERT_AUTHZFROM: + /* idassert-authzFrom */ + rc = mi->mi_ldap_extra->idassert_authzfrom_parse( c, &mt->mt_idassert ); + break; + + case LDAP_BACK_CFG_QUARANTINE: + /* quarantine */ + if ( META_BACK_CMN_QUARANTINE( mc ) ) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "quarantine already defined" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( mt ) { + mc->mc_quarantine.ri_interval = NULL; + mc->mc_quarantine.ri_num = NULL; + if ( !META_BACK_QUARANTINE( mi ) ) { + ldap_pvt_thread_mutex_init( &mt->mt_quarantine_mutex ); + } + } + + if ( mi->mi_ldap_extra->retry_info_parse( c->argv[ 1 ], &mc->mc_quarantine, c->cr_msg, sizeof( c->cr_msg ) ) ) { + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mc->mc_flags |= LDAP_BACK_F_QUARANTINE; + break; + +#ifdef SLAP_CONTROL_X_SESSION_TRACKING + case LDAP_BACK_CFG_ST_REQUEST: + /* session tracking request */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_ST_REQUEST; + } else { + mc->mc_flags &= ~LDAP_BACK_F_ST_REQUEST; + } + break; +#endif /* SLAP_CONTROL_X_SESSION_TRACKING */ + + case LDAP_BACK_CFG_SUFFIXM: /* FALLTHRU */ + case LDAP_BACK_CFG_REWRITE: { + /* rewrite stuff ... */ + ConfigArgs ca = { 0 }; + char *line, **argv; + struct rewrite_info *rwi; + int cnt = 0, argc, ix = c->valx; + + if ( mt->mt_rwmap.rwm_bva_rewrite ) { + for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_rewrite[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( ix >= cnt || ix < 0 ) { + ix = cnt; + } else { + rwi = mt->mt_rwmap.rwm_rw; + + mt->mt_rwmap.rwm_rw = NULL; + rc = meta_rwi_init( &mt->mt_rwmap.rwm_rw ); + + /* re-parse all rewrite rules, up to the one + * that needs to be added */ + ca.be = c->be; + ca.fname = c->fname; + ca.lineno = c->lineno; + for ( i = 0; i < ix; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, ca.argv ); + } + assert( rc == 0 ); + ch_free( ca.argv ); + ch_free( ca.tline ); + } + } + argc = c->argc; + argv = c->argv; + if ( c->op != SLAP_CONFIG_ADD ) { + argc--; + argv++; + } + /* add the new rule */ + if ( !strcasecmp( argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( c, argc, argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, argc, argv ); + } + if ( rc ) { + if ( ix < cnt ) { + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + mt->mt_rwmap.rwm_rw = rwi; + } + return 1; + } + if ( ix < cnt ) { + for ( ; i < cnt; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_rewrite[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + if ( !strcasecmp( ca.argv[0], "suffixmassage" )) { + rc = meta_suffixm_config( &ca, ca.argc, ca.argv, mt ); + } else { + rc = rewrite_parse( mt->mt_rwmap.rwm_rw, + c->fname, c->lineno, ca.argc, argv ); + } + assert( rc == 0 ); + ch_free( ca.argv ); + ch_free( ca.tline ); + } + } + + /* save the rule info */ + line = ldap_charray2str( argv, "\" \"" ); + if ( line != NULL ) { + struct berval bv; + int len = strlen( argv[ 0 ] ); + + ber_str2bv( line, 0, 0, &bv ); + AC_MEMCPY( &bv.bv_val[ len ], &bv.bv_val[ len + 1 ], + bv.bv_len - ( len + 1 )); + bv.bv_val[ bv.bv_len - 1] = '"'; + ber_bvarray_add( &mt->mt_rwmap.rwm_bva_rewrite, &bv ); + /* move it to the right slot */ + if ( ix < cnt ) { + for ( i=cnt; i>ix; i-- ) + mt->mt_rwmap.rwm_bva_rewrite[i+1] = mt->mt_rwmap.rwm_bva_rewrite[i]; + mt->mt_rwmap.rwm_bva_rewrite[i] = bv; + + /* destroy old rules */ + rewrite_info_delete( &rwi ); + } + } + } break; + + case LDAP_BACK_CFG_MAP: { + /* objectclass/attribute mapping */ + ConfigArgs ca = { 0 }; + char *argv[5]; + struct ldapmap rwm_oc; + struct ldapmap rwm_at; + int cnt = 0, ix = c->valx; + + if ( mt->mt_rwmap.rwm_bva_map ) { + for ( ; !BER_BVISNULL( &mt->mt_rwmap.rwm_bva_map[ cnt ] ); cnt++ ) + /* count */ ; + } + + if ( ix >= cnt || ix < 0 ) { + ix = cnt; + } else { + rwm_oc = mt->mt_rwmap.rwm_oc; + rwm_at = mt->mt_rwmap.rwm_at; + + memset( &mt->mt_rwmap.rwm_oc, 0, sizeof( mt->mt_rwmap.rwm_oc ) ); + memset( &mt->mt_rwmap.rwm_at, 0, sizeof( mt->mt_rwmap.rwm_at ) ); + + /* re-parse all mappings, up to the one + * that needs to be added */ + argv[0] = c->argv[0]; + ca.fname = c->fname; + ca.lineno = c->lineno; + for ( i = 0; i < ix; i++ ) { + ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + ch_free( ca.argv ); + ca.argv = argv; + ca.argc++; + rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + + ch_free( ca.tline ); + ca.tline = NULL; + ca.argv = NULL; + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto map_fail; + } + } + } + /* add the new mapping */ + rc = ldap_back_map_config( c, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + if ( rc ) { + goto map_fail; + } + + if ( ix < cnt ) { + for ( ; i<cnt ; cnt++ ) { + ca.line = mt->mt_rwmap.rwm_bva_map[ i ].bv_val; + ca.argc = 0; + config_fp_parse_line( &ca ); + + argv[1] = ca.argv[0]; + argv[2] = ca.argv[1]; + argv[3] = ca.argv[2]; + argv[4] = ca.argv[3]; + + ch_free( ca.argv ); + ca.argv = argv; + ca.argc++; + rc = ldap_back_map_config( &ca, &mt->mt_rwmap.rwm_oc, + &mt->mt_rwmap.rwm_at ); + + ch_free( ca.tline ); + ca.tline = NULL; + ca.argv = NULL; + + /* in case of failure, restore + * the existing mapping */ + if ( rc ) { + goto map_fail; + } + } + } + + /* save the map info */ + argv[0] = ldap_charray2str( &c->argv[ 1 ], " " ); + if ( argv[0] != NULL ) { + struct berval bv; + ber_str2bv( argv[0], 0, 0, &bv ); + ber_bvarray_add( &mt->mt_rwmap.rwm_bva_map, &bv ); + /* move it to the right slot */ + if ( ix < cnt ) { + for ( i=cnt; i>ix; i-- ) + mt->mt_rwmap.rwm_bva_map[i+1] = mt->mt_rwmap.rwm_bva_map[i]; + mt->mt_rwmap.rwm_bva_map[i] = bv; + + /* destroy old mapping */ + meta_back_map_free( &rwm_oc ); + meta_back_map_free( &rwm_at ); + } + } + break; + +map_fail:; + if ( ix < cnt ) { + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + mt->mt_rwmap.rwm_oc = rwm_oc; + mt->mt_rwmap.rwm_at = rwm_at; + } + } break; + + case LDAP_BACK_CFG_NRETRIES: { + int nretries = META_RETRY_UNDEFINED; + + if ( strcasecmp( c->argv[ 1 ], "forever" ) == 0 ) { + nretries = META_RETRY_FOREVER; + + } else if ( strcasecmp( c->argv[ 1 ], "never" ) == 0 ) { + nretries = META_RETRY_NEVER; + + } else { + if ( lutil_atoi( &nretries, c->argv[ 1 ] ) != 0 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse nretries {never|forever|<retries>}: \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + + mc->mc_nretries = nretries; + } break; + + case LDAP_BACK_CFG_VERSION: + if ( c->value_int != 0 && ( c->value_int < LDAP_VERSION_MIN || c->value_int > LDAP_VERSION_MAX ) ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unsupported protocol version \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + mc->mc_version = c->value_int; + break; + + case LDAP_BACK_CFG_NOREFS: + /* do not return search references */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_NOREFS; + } else { + mc->mc_flags &= ~LDAP_BACK_F_NOREFS; + } + break; + + case LDAP_BACK_CFG_NOUNDEFFILTER: + /* do not propagate undefined search filters */ + if ( c->value_int ) { + mc->mc_flags |= LDAP_BACK_F_NOUNDEFFILTER; + } else { + mc->mc_flags &= ~LDAP_BACK_F_NOUNDEFFILTER; + } + break; + +#ifdef SLAPD_META_CLIENT_PR + case LDAP_BACK_CFG_CLIENT_PR: + if ( strcasecmp( c->argv[ 1 ], "accept-unsolicited" ) == 0 ) { + mc->mc_ps = META_CLIENT_PR_ACCEPT_UNSOLICITED; + + } else if ( strcasecmp( c->argv[ 1 ], "disable" ) == 0 ) { + mc->mc_ps = META_CLIENT_PR_DISABLE; + + } else if ( lutil_atoi( &mc->mc_ps, c->argv[ 1 ] ) || mc->mc_ps < -1 ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "unable to parse client-pr {accept-unsolicited|disable|<size>}: \"%s\"", + c->argv[ 1 ] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return( 1 ); + } + break; +#endif /* SLAPD_META_CLIENT_PR */ + + case LDAP_BACK_CFG_KEEPALIVE: + slap_keepalive_parse( ber_bvstrdup(c->argv[1]), + &mt->mt_tls.sb_keepalive, 0, 0, 0); + break; + + /* anything else */ + default: + return SLAP_CONF_UNKNOWN; + } + + return rc; +} + +int +meta_back_init_cf( BackendInfo *bi ) +{ + int rc; + AttributeDescription *ad = NULL; + const char *text; + + /* Make sure we don't exceed the bits reserved for userland */ + config_check_userland( LDAP_BACK_CFG_LAST ); + + bi->bi_cf_ocs = metaocs; + + rc = config_register_schema( metacfg, metaocs ); + if ( rc ) { + return rc; + } + + /* setup olcDbAclPasswd and olcDbIDAssertPasswd + * to be base64-encoded when written in LDIF form; + * basically, we don't care if it fails */ + rc = slap_str2ad( "olcDbACLPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbACLPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + ad = NULL; + rc = slap_str2ad( "olcDbIDAssertPasswd", &ad, &text ); + if ( rc ) { + Debug( LDAP_DEBUG_ANY, "config_back_initialize: " + "warning, unable to get \"olcDbIDAssertPasswd\" " + "attribute description: %d: %s\n", + rc, text, 0 ); + } else { + (void)ldif_must_b64_encode_register( ad->ad_cname.bv_val, + ad->ad_type->sat_oid ); + } + + return 0; +} + +static int +ldap_back_map_config( + ConfigArgs *c, + struct ldapmap *oc_map, + struct ldapmap *at_map ) +{ + struct ldapmap *map; + struct ldapmapping *mapping; + char *src, *dst; + int is_oc = 0; + + if ( strcasecmp( c->argv[ 1 ], "objectclass" ) == 0 ) { + map = oc_map; + is_oc = 1; + + } else if ( strcasecmp( c->argv[ 1 ], "attribute" ) == 0 ) { + map = at_map; + + } else { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "%s unknown argument \"%s\"", + c->argv[0], c->argv[1] ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + if ( !is_oc && map->map == NULL ) { + /* only init if required */ + ldap_back_map_init( map, &mapping ); + } + + if ( strcmp( c->argv[ 2 ], "*" ) == 0 ) { + if ( c->argc < 4 || strcmp( c->argv[ 3 ], "*" ) == 0 ) { + map->drop_missing = ( c->argc < 4 ); + goto success_return; + } + src = dst = c->argv[ 3 ]; + + } else if ( c->argc < 4 ) { + src = ""; + dst = c->argv[ 2 ]; + + } else { + src = c->argv[ 2 ]; + dst = ( strcmp( c->argv[ 3 ], "*" ) == 0 ? src : c->argv[ 3 ] ); + } + + if ( ( map == at_map ) + && ( strcasecmp( src, "objectclass" ) == 0 + || strcasecmp( dst, "objectclass" ) == 0 ) ) + { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "objectclass attribute cannot be mapped" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof(struct ldapmapping) ); + if ( mapping == NULL ) { + snprintf( c->cr_msg, sizeof(c->cr_msg), + "out of memory" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + ber_str2bv( src, 0, 1, &mapping[ 0 ].src ); + ber_str2bv( dst, 0, 1, &mapping[ 0 ].dst ); + mapping[ 1 ].src = mapping[ 0 ].dst; + mapping[ 1 ].dst = mapping[ 0 ].src; + + /* + * schema check + */ + if ( is_oc ) { + if ( src[ 0 ] != '\0' ) { + if ( oc_bvfind( &mapping[ 0 ].src ) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "warning, source objectClass '%s' should be defined in schema\n", + c->log, src, 0 ); + + /* + * FIXME: this should become an err + */ + goto error_return; + } + } + + if ( oc_bvfind( &mapping[ 0 ].dst ) == NULL ) { + Debug( LDAP_DEBUG_ANY, + "warning, destination objectClass '%s' is not defined in schema\n", + c->log, dst, 0 ); + } + } else { + int rc; + const char *text = NULL; + AttributeDescription *ad = NULL; + + if ( src[ 0 ] != '\0' ) { + rc = slap_bv2ad( &mapping[ 0 ].src, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "warning, source attributeType '%s' should be defined in schema\n", + c->log, src, 0 ); + + /* + * FIXME: this should become an err + */ + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[ 0 ].src, + &ad, &text, SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "source attributeType \"%s\": %d (%s)", + src, rc, text ? text : "" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto error_return; + } + } + + ad = NULL; + } + + rc = slap_bv2ad( &mapping[ 0 ].dst, &ad, &text ); + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "warning, destination attributeType '%s' is not defined in schema\n", + c->log, dst, 0 ); + + /* + * we create a fake "proxied" ad + * and add it here. + */ + + rc = slap_bv2undef_ad( &mapping[ 0 ].dst, + &ad, &text, SLAP_AD_PROXIED ); + if ( rc != LDAP_SUCCESS ) { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "destination attributeType \"%s\": %d (%s)\n", + dst, rc, text ? text : "" ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + return 1; + } + } + } + + if ( (src[ 0 ] != '\0' && avl_find( map->map, (caddr_t)&mapping[ 0 ], mapping_cmp ) != NULL) + || avl_find( map->remap, (caddr_t)&mapping[ 1 ], mapping_cmp ) != NULL) + { + snprintf( c->cr_msg, sizeof( c->cr_msg ), + "duplicate mapping found." ); + Debug( LDAP_DEBUG_ANY, "%s: %s.\n", c->log, c->cr_msg, 0 ); + goto error_return; + } + + if ( src[ 0 ] != '\0' ) { + avl_insert( &map->map, (caddr_t)&mapping[ 0 ], + mapping_cmp, mapping_dup ); + } + avl_insert( &map->remap, (caddr_t)&mapping[ 1 ], + mapping_cmp, mapping_dup ); + +success_return:; + return 0; + +error_return:; + if ( mapping ) { + ch_free( mapping[ 0 ].src.bv_val ); + ch_free( mapping[ 0 ].dst.bv_val ); + ch_free( mapping ); + } + + return 1; +} + + +#ifdef ENABLE_REWRITE +static char * +suffix_massage_regexize( const char *s ) +{ + char *res, *ptr; + const char *p, *r; + int i; + + if ( s[ 0 ] == '\0' ) { + return ch_strdup( "^(.+)$" ); + } + + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1, i++ ) + ; + + res = ch_calloc( sizeof( char ), + strlen( s ) + + STRLENOF( "((.+),)?" ) + + STRLENOF( "[ ]?" ) * i + + STRLENOF( "$" ) + 1 ); + + ptr = lutil_strcopy( res, "((.+),)?" ); + for ( i = 0, p = s; + ( r = strchr( p, ',' ) ) != NULL; + p = r + 1 , i++ ) { + ptr = lutil_strncopy( ptr, p, r - p + 1 ); + ptr = lutil_strcopy( ptr, "[ ]?" ); + + if ( r[ 1 ] == ' ' ) { + r++; + } + } + ptr = lutil_strcopy( ptr, p ); + ptr[ 0 ] = '$'; + ptr++; + ptr[ 0 ] = '\0'; + + return res; +} + +static char * +suffix_massage_patternize( const char *s, const char *p ) +{ + ber_len_t len; + char *res, *ptr; + + len = strlen( p ); + + if ( s[ 0 ] == '\0' ) { + len++; + } + + res = ch_calloc( sizeof( char ), len + STRLENOF( "%1" ) + 1 ); + if ( res == NULL ) { + return NULL; + } + + ptr = lutil_strcopy( res, ( p[ 0 ] == '\0' ? "%2" : "%1" ) ); + if ( s[ 0 ] == '\0' ) { + ptr[ 0 ] = ','; + ptr++; + } + lutil_strcopy( ptr, p ); + + return res; +} + +int +suffix_massage_config( + struct rewrite_info *info, + struct berval *pvnc, + struct berval *nvnc, + struct berval *prnc, + struct berval *nrnc +) +{ + char *rargv[ 5 ]; + int line = 0; + + rargv[ 0 ] = "rewriteEngine"; + rargv[ 1 ] = "on"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "default"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = suffix_massage_regexize( pvnc->bv_val ); + rargv[ 2 ] = suffix_massage_patternize( pvnc->bv_val, prnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( pvnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = prnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchEntryDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = suffix_massage_regexize( prnc->bv_val ); + rargv[ 2 ] = suffix_massage_patternize( prnc->bv_val, pvnc->bv_val ); + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + ch_free( rargv[ 1 ] ); + ch_free( rargv[ 2 ] ); + + if ( BER_BVISEMPTY( prnc ) ) { + rargv[ 0 ] = "rewriteRule"; + rargv[ 1 ] = "^$"; + rargv[ 2 ] = pvnc->bv_val; + rargv[ 3 ] = ":"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + } + + /* backward compatibility */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchResult"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "matchedDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "searchAttrDN"; + rargv[ 2 ] = "alias"; + rargv[ 3 ] = "searchEntryDN"; + rargv[ 4 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 4, rargv ); + + /* NOTE: this corresponds to #undef'ining RWM_REFERRAL_REWRITE; + * see servers/slapd/overlays/rwm.h for details */ + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralAttrDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + rargv[ 0 ] = "rewriteContext"; + rargv[ 1 ] = "referralDN"; + rargv[ 2 ] = NULL; + rewrite_parse( info, "<suffix massage>", ++line, 2, rargv ); + + return 0; +} +#endif /* ENABLE_REWRITE */ + diff --git a/servers/slapd/back-meta/conn.c b/servers/slapd/back-meta/conn.c new file mode 100644 index 0000000..ba87d01 --- /dev/null +++ b/servers/slapd/back-meta/conn.c @@ -0,0 +1,1910 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + + +#define AVL_INTERNAL +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * meta_back_conndn_cmp + * + * compares two struct metaconn based on the value of the conn pointer + * and of the local DN; used by avl stuff + */ +int +meta_back_conndn_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn ); + } + + return rc; +} + +/* + * meta_back_conndnmc_cmp + * + * compares two struct metaconn based on the value of the conn pointer, + * the local DN and the struct pointer; used by avl stuff + */ +static int +meta_back_conndnmc_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + int rc; + + /* If local DNs don't match, it is definitely not a match */ + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + rc = SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); + if ( rc == 0 ) { + rc = ber_bvcmp( &mc1->mc_local_ndn, &mc2->mc_local_ndn ); + if ( rc == 0 ) { + rc = SLAP_PTRCMP( mc1, mc2 ); + } + } + + return rc; +} + +/* + * meta_back_conn_cmp + * + * compares two struct metaconn based on the value of the conn pointer; + * used by avl stuff + */ +int +meta_back_conn_cmp( + const void *c1, + const void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + + /* For shared sessions, conn is NULL. Only explicitly + * bound sessions will have non-NULL conn. + */ + return SLAP_PTRCMP( mc1->mc_conn, mc2->mc_conn ); +} + +/* + * meta_back_conndn_dup + * + * returns -1 in case a duplicate struct metaconn has been inserted; + * used by avl stuff + */ +int +meta_back_conndn_dup( + void *c1, + void *c2 ) +{ + metaconn_t *mc1 = ( metaconn_t * )c1; + metaconn_t *mc2 = ( metaconn_t * )c2; + + /* Cannot have more than one shared session with same DN */ + if ( mc1->mc_conn == mc2->mc_conn && + dn_match( &mc1->mc_local_ndn, &mc2->mc_local_ndn ) ) + { + return -1; + } + + return 0; +} + +/* + * Debug stuff (got it from libavl) + */ +#if META_BACK_PRINT_CONNTREE > 0 +static void +meta_back_print( metaconn_t *mc, char *avlstr ) +{ + int i; + + fputs( "targets=[", stderr ); + for ( i = 0; i < mc->mc_info->mi_ntargets; i++ ) { + fputc( mc->mc_conns[ i ].msc_ld ? '*' : 'o', stderr); + } + fputc( ']', stderr ); + + fprintf( stderr, " mc=%p local=\"%s\" conn=%p refcnt=%d%s %s\n", + (void *)mc, + mc->mc_local_ndn.bv_val ? mc->mc_local_ndn.bv_val : "", + (void *)mc->mc_conn, + mc->mc_refcnt, + LDAP_BACK_CONN_TAINTED( mc ) ? " tainted" : "", + avlstr ); +} + +static void +meta_back_ravl_print( Avlnode *root, int depth ) +{ + int i; + + if ( root == 0 ) { + return; + } + + meta_back_ravl_print( root->avl_right, depth + 1 ); + + for ( i = 0; i < depth; i++ ) { + fprintf( stderr, "-" ); + } + fputc( ' ', stderr ); + + meta_back_print( (metaconn_t *)root->avl_data, + avl_bf2str( root->avl_bf ) ); + + meta_back_ravl_print( root->avl_left, depth + 1 ); +} + +/* NOTE: duplicate from back-ldap/bind.c */ +static char* priv2str[] = { + "privileged", + "privileged/TLS", + "anonymous", + "anonymous/TLS", + "bind", + "bind/TLS", + NULL +}; + +void +meta_back_print_conntree( metainfo_t *mi, char *msg ) +{ + int c; + + fprintf( stderr, "========> %s\n", msg ); + + for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) { + int i = 0; + metaconn_t *mc; + + fprintf( stderr, " %s[%d]\n", priv2str[ c ], mi->mi_conn_priv[ c ].mic_num ); + + LDAP_TAILQ_FOREACH( mc, &mi->mi_conn_priv[ c ].mic_priv, mc_q ) + { + fprintf( stderr, " [%d] ", i ); + meta_back_print( mc, "" ); + i++; + } + } + + if ( mi->mi_conninfo.lai_tree == NULL ) { + fprintf( stderr, "\t(empty)\n" ); + + } else { + meta_back_ravl_print( mi->mi_conninfo.lai_tree, 0 ); + } + + fprintf( stderr, "<======== %s\n", msg ); +} +#endif /* META_BACK_PRINT_CONNTREE */ +/* + * End of debug stuff + */ + +/* + * metaconn_alloc + * + * Allocates a connection structure, making room for all the referenced targets + */ +static metaconn_t * +metaconn_alloc( + Operation *op ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc; + int ntargets = mi->mi_ntargets; + + assert( ntargets > 0 ); + + /* malloc all in one */ + mc = ( metaconn_t * )ch_calloc( 1, sizeof( metaconn_t ) + + sizeof( metasingleconn_t ) * ( ntargets - 1 ) ); + if ( mc == NULL ) { + return NULL; + } + + mc->mc_info = mi; + + mc->mc_authz_target = META_BOUND_NONE; + mc->mc_refcnt = 1; + + return mc; +} + +/* + * meta_back_init_one_conn + * + * Initializes one connection + */ +int +meta_back_init_one_conn( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int candidate, + int ispriv, + ldap_back_send_t sendok, + int dolock ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int version; + dncookie dc; + int isauthz = ( candidate == mc->mc_authz_target ); + int do_return = 0; +#ifdef HAVE_TLS + int is_ldaps = 0; + int do_start_tls = 0; +#endif /* HAVE_TLS */ + + /* if the server is quarantined, and + * - the current interval did not expire yet, or + * - no more retries should occur, + * don't return the connection */ + if ( mt->mt_isquarantined ) { + slap_retry_info_t *ri = &mt->mt_quarantine; + int dont_retry = 0; + + if ( mt->mt_quarantine.ri_interval ) { + ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex ); + dont_retry = ( mt->mt_isquarantined > LDAP_BACK_FQ_NO ); + if ( dont_retry ) { + dont_retry = ( ri->ri_num[ ri->ri_idx ] == SLAP_RETRYNUM_TAIL + || slap_get_time() < ri->ri_last + ri->ri_interval[ ri->ri_idx ] ); + if ( !dont_retry ) { + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_init_one_conn[%d]: quarantine " + "retry block #%d try #%d", + candidate, ri->ri_idx, ri->ri_count ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + mt->mt_isquarantined = LDAP_BACK_FQ_RETRYING; + } + + } + ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex ); + } + + if ( dont_retry ) { + rs->sr_err = LDAP_UNAVAILABLE; + if ( op->o_conn && ( sendok & LDAP_BACK_SENDERR ) ) { + rs->sr_text = "Target is quarantined"; + send_ldap_result( op, rs ); + } + return rs->sr_err; + } + } + +retry_lock:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + + /* + * Already init'ed + */ + if ( LDAP_BACK_CONN_ISBOUND( msc ) + || LDAP_BACK_CONN_ISANON( msc ) ) + { + assert( msc->msc_ld != NULL ); + rs->sr_err = LDAP_SUCCESS; + do_return = 1; + + } else if ( META_BACK_CONN_CREATING( msc ) + || LDAP_BACK_CONN_BINDING( msc ) ) + { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + ldap_pvt_thread_yield(); + goto retry_lock; + } + + /* sounds more appropriate */ + rs->sr_err = LDAP_BUSY; + rs->sr_text = "No connections to target are available"; + do_return = 1; + + } else if ( META_BACK_CONN_INITED( msc ) ) { + assert( msc->msc_ld != NULL ); + rs->sr_err = LDAP_SUCCESS; + do_return = 1; + + } else { + /* + * creating... + */ + META_BACK_CONN_CREATING_SET( msc ); + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + if ( do_return ) { + if ( rs->sr_err != LDAP_SUCCESS + && op->o_conn + && ( sendok & LDAP_BACK_SENDERR ) ) + { + send_ldap_result( op, rs ); + } + + return rs->sr_err; + } + + assert( msc->msc_ld == NULL ); + + /* + * Attempts to initialize the connection to the target ds + */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + rs->sr_err = ldap_initialize( &msc->msc_ld, mt->mt_uri ); +#ifdef HAVE_TLS + is_ldaps = ldap_is_ldaps_url( mt->mt_uri ); +#endif /* HAVE_TLS */ + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + if ( rs->sr_err != LDAP_SUCCESS ) { + goto error_return; + } + + /* + * Set LDAP version. This will always succeed: If the client + * bound with a particular version, then so can we. + */ + if ( mt->mt_version != 0 ) { + version = mt->mt_version; + + } else if ( op->o_conn->c_protocol != 0 ) { + version = op->o_conn->c_protocol; + + } else { + version = LDAP_VERSION3; + } + ldap_set_option( msc->msc_ld, LDAP_OPT_PROTOCOL_VERSION, &version ); + ldap_set_urllist_proc( msc->msc_ld, mt->mt_urllist_f, mt->mt_urllist_p ); + + /* automatically chase referrals ("chase-referrals [{yes|no}]" statement) */ + ldap_set_option( msc->msc_ld, LDAP_OPT_REFERRALS, + META_BACK_TGT_CHASE_REFERRALS( mt ) ? LDAP_OPT_ON : LDAP_OPT_OFF ); + + slap_client_keepalive(msc->msc_ld, &mt->mt_tls.sb_keepalive); + +#ifdef HAVE_TLS + { + slap_bindconf *sb = NULL; + + if ( ispriv ) { + sb = &mt->mt_idassert.si_bc; + } else { + sb = &mt->mt_tls; + } + + if ( sb->sb_tls_do_init ) { + bindconf_tls_set( sb, msc->msc_ld ); + } else if ( sb->sb_tls_ctx ) { + ldap_set_option( msc->msc_ld, LDAP_OPT_X_TLS_CTX, sb->sb_tls_ctx ); + } + + if ( !is_ldaps ) { + if ( sb == &mt->mt_idassert.si_bc && sb->sb_tls_ctx ) { + do_start_tls = 1; + + } else if ( META_BACK_TGT_USE_TLS( mt ) + || ( op->o_conn->c_is_tls && META_BACK_TGT_PROPAGATE_TLS( mt ) ) ) + { + do_start_tls = 1; + } + } + } + + /* start TLS ("tls [try-]{start|propagate}" statement) */ + if ( do_start_tls ) { +#ifdef SLAP_STARTTLS_ASYNCHRONOUS + /* + * use asynchronous StartTLS; in case, chase referral + * FIXME: OpenLDAP does not return referral on StartTLS yet + */ + int msgid; + + rs->sr_err = ldap_start_tls( msc->msc_ld, NULL, NULL, &msgid ); + if ( rs->sr_err == LDAP_SUCCESS ) { + LDAPMessage *res = NULL; + int rc, nretries = mt->mt_nretries; + struct timeval tv; + + LDAP_BACK_TV_SET( &tv ); + +retry:; + rc = ldap_result( msc->msc_ld, msgid, LDAP_MSG_ALL, &tv, &res ); + switch ( rc ) { + case -1: + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "Remote server down"; + break; + + case 0: + if ( nretries != 0 ) { + if ( nretries > 0 ) { + nretries--; + } + LDAP_BACK_TV_SET( &tv ); + goto retry; + } + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Timeout, no more retries"; + 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; + } + break; + } + + if ( rc == LDAP_RES_EXTENDED ) { + struct berval *data = NULL; + + /* NOTE: right now, data is unused, so don't get it */ + rs->sr_err = ldap_parse_extended_result( msc->msc_ld, + res, NULL, NULL /* &data */ , 0 ); + if ( rs->sr_err == LDAP_SUCCESS ) { + int err; + + /* FIXME: matched? referrals? response controls? */ + rs->sr_err = ldap_parse_result( msc->msc_ld, + res, &err, NULL, NULL, NULL, NULL, 1 ); + res = NULL; + + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = err; + } + rs->sr_err = slap_map_api2result( rs ); + + /* FIXME: in case a referral + * is returned, should we try + * using it instead of the + * configured URI? */ + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = ldap_install_tls( msc->msc_ld ); + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + /* FIXME: LDAP_OPERATIONS_ERROR? */ + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Unwilling to chase referral " + "returned by Start TLS exop"; + } + + if ( data ) { + ber_bvfree( data ); + } + } + + } else { + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Unknown response to StartTLS request :" + " an ExtendedResponse is expected"; + } + + if ( res != NULL ) { + ldap_msgfree( res ); + } + } +#else /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + /* + * use synchronous StartTLS + */ + rs->sr_err = ldap_start_tls_s( msc->msc_ld, NULL, NULL ); +#endif /* ! SLAP_STARTTLS_ASYNCHRONOUS */ + + /* if StartTLS is requested, only attempt it if the URL + * is not "ldaps://"; this may occur not only in case + * of misconfiguration, but also when used in the chain + * overlay, where the "uri" can be parsed out of a referral */ + if ( rs->sr_err == LDAP_SERVER_DOWN + || ( rs->sr_err != LDAP_SUCCESS + && META_BACK_TGT_TLS_CRITICAL( mt ) ) ) + { + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_init_one_conn(TLS) " + "ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, + (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + /* need to trash a failed Start TLS */ + meta_clear_one_candidate( op, mc, candidate ); + goto error_return; + } + } +#endif /* HAVE_TLS */ + + /* + * Set the network timeout if set + */ + if ( mt->mt_network_timeout != 0 ) { + struct timeval network_timeout; + + network_timeout.tv_usec = 0; + network_timeout.tv_sec = mt->mt_network_timeout; + + ldap_set_option( msc->msc_ld, LDAP_OPT_NETWORK_TIMEOUT, + (void *)&network_timeout ); + } + + /* + * If the connection DN is not null, an attempt to rewrite it is made + */ + + if ( ispriv ) { + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + ber_bvreplace( &msc->msc_bound_ndn, &mt->mt_idassert_authcDN ); + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, + msc->msc_cred.bv_len ); + } + ber_bvreplace( &msc->msc_cred, &mt->mt_idassert_passwd ); + } + LDAP_BACK_CONN_ISIDASSERT_SET( msc ); + + } else { + ber_bvreplace( &msc->msc_bound_ndn, &slap_empty_bv ); + } + + } else { + if ( !BER_BVISNULL( &msc->msc_cred ) ) { + memset( msc->msc_cred.bv_val, 0, msc->msc_cred.bv_len ); + ber_memfree_x( msc->msc_cred.bv_val, NULL ); + BER_BVZERO( &msc->msc_cred ); + } + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) ) { + ber_memfree_x( msc->msc_bound_ndn.bv_val, NULL ); + BER_BVZERO( &msc->msc_bound_ndn ); + } + if ( !BER_BVISEMPTY( &op->o_ndn ) + && SLAP_IS_AUTHZ_BACKEND( op ) + && isauthz ) + { + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "bindDN"; + + /* + * Rewrite the bind dn if needed + */ + if ( ldap_back_dn_massage( &dc, &op->o_conn->c_dn, + &msc->msc_bound_ndn ) ) + { + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, + "### %s meta_back_init_one_conn(rewrite) " + "ldap_unbind_ext[%d] ld=%p\n", + op->o_log_prefix, candidate, + (void *)msc->msc_ld ); +#endif /* DEBUG_205 */ + + /* need to trash a connection not fully established */ + meta_clear_one_candidate( op, mc, candidate ); + goto error_return; + } + + /* copy the DN if needed */ + if ( msc->msc_bound_ndn.bv_val == op->o_conn->c_dn.bv_val ) { + ber_dupbv( &msc->msc_bound_ndn, &op->o_conn->c_dn ); + } + + assert( !BER_BVISNULL( &msc->msc_bound_ndn ) ); + + } else { + ber_dupbv( &msc->msc_bound_ndn, (struct berval *)&slap_empty_bv ); + } + } + + assert( !BER_BVISNULL( &msc->msc_bound_ndn ) ); + +error_return:; + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + META_BACK_CONN_CREATING_CLEAR( msc ); + if ( rs->sr_err == LDAP_SUCCESS ) { + /* + * Sets a cookie for the rewrite session + */ + ( void )rewrite_session_init( mt->mt_rwmap.rwm_rw, op->o_conn ); + META_BACK_CONN_INITED_SET( msc ); + } + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + if ( rs->sr_err != LDAP_SUCCESS ) { + /* Get the error message and print it in TRACE mode */ + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + Log4( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_init_one_conn[%d] failed err=%d text=%s\n", + op->o_log_prefix, candidate, rs->sr_err, rs->sr_text ); + } + + rs->sr_err = slap_map_api2result( rs ); + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + } + + return rs->sr_err; +} + +/* + * meta_back_retry + * + * Retries one connection + */ +int +meta_back_retry( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + int rc = LDAP_UNAVAILABLE, + binding, + quarantine = 1; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + assert( !META_BACK_CONN_CREATING( msc ) ); + binding = LDAP_BACK_CONN_BINDING( msc ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + + assert( mc->mc_refcnt > 0 ); + if ( mc->mc_refcnt == 1 ) { + struct berval save_cred; + + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* this lock is required; however, + * it's invoked only when logging is on */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + snprintf( buf, sizeof( buf ), + "retrying URI=\"%s\" DN=\"%s\"", + mt->mt_uri, + BER_BVISNULL( &msc->msc_bound_ndn ) ? + "" : msc->msc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_retry[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + } + + /* save credentials, if any, for later use; + * meta_clear_one_candidate() would free them */ + save_cred = msc->msc_cred; + BER_BVZERO( &msc->msc_cred ); + + meta_clear_one_candidate( op, mc, candidate ); + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + + ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn ); + + /* mc here must be the regular mc, reset and ready for init */ + rc = meta_back_init_one_conn( op, rs, mc, candidate, + LDAP_BACK_CONN_ISPRIV( mc ), sendok, 0 ); + + /* restore credentials, if any and if needed; + * meta_back_init_one_conn() restores msc_bound_ndn, if any; + * if no msc_bound_ndn is restored, destroy credentials */ + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) + && BER_BVISNULL( &msc->msc_cred ) ) + { + msc->msc_cred = save_cred; + + } else if ( !BER_BVISNULL( &save_cred ) ) { + memset( save_cred.bv_val, 0, save_cred.bv_len ); + ber_memfree_x( save_cred.bv_val, NULL ); + } + + /* restore the "binding" flag, in case */ + if ( binding ) { + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + if ( rc == LDAP_SUCCESS ) { + quarantine = 0; + LDAP_BACK_CONN_BINDING_SET( msc ); binding = 1; + rc = meta_back_single_dobind( op, rs, mcp, candidate, + sendok, mt->mt_nretries, 0 ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_retry[%d]: " + "meta_back_single_dobind=%d\n", + op->o_log_prefix, candidate, rc ); + if ( rc == LDAP_SUCCESS ) { + if ( !BER_BVISNULL( &msc->msc_bound_ndn ) && + !BER_BVISEMPTY( &msc->msc_bound_ndn ) ) + { + LDAP_BACK_CONN_ISBOUND_SET( msc ); + + } else { + LDAP_BACK_CONN_ISANON_SET( msc ); + } + + /* when bound, dispose of the "binding" flag */ + if ( binding ) { + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + } + } + } + +#if 0 /* ITS#7591, following stmt drops needed result msgs */ + /* don't send twice */ + sendok &= ~LDAP_BACK_SENDERR; +#endif + } + + if ( rc != LDAP_SUCCESS ) { + SlapReply *candidates = meta_back_candidates_get( op ); + + candidates[ candidate ].sr_err = rc; + + if ( *mcp != NULL ) { + if ( mc->mc_refcnt == 1 ) { + if ( binding ) { + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + } + (void)meta_clear_one_candidate( op, mc, candidate ); + } + + LDAP_BACK_CONN_TAINTED_SET( mc ); + /* only release if mandatory; otherwise + * let the caller do what's best before + * releasing */ + if ( META_BACK_ONERR_STOP( mi ) ) { + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + + } else { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_retry" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* FIXME: could be done better, reworking meta_back_release_conn_lock() */ + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else { + /* FIXME: check if in tree, for consistency? */ + (void)avl_delete( &mi->mi_conninfo.lai_tree, + ( caddr_t )mc, meta_back_conndnmc_cmp ); + } + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_retry" ); +#endif /* META_BACK_PRINT_CONNTREE */ + } + } + + if ( sendok & LDAP_BACK_SENDERR ) { + rs->sr_err = rc; + rs->sr_text = "Unable to retry"; + send_ldap_result( op, rs ); + } + } + + if ( quarantine && META_BACK_TGT_QUARANTINE( mt ) ) { + meta_back_quarantine( op, rs, candidate ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + return rc == LDAP_SUCCESS ? 1 : 0; +} + +/* + * callback for unique candidate selection + */ +static int +meta_back_conn_cb( Operation *op, SlapReply *rs ) +{ + assert( op->o_tag == LDAP_REQ_SEARCH ); + + switch ( rs->sr_type ) { + case REP_SEARCH: + ((long *)op->o_callback->sc_private)[0] = (long)op->o_private; + break; + + case REP_SEARCHREF: + case REP_RESULT: + break; + + default: + return rs->sr_err; + } + + return 0; +} + + +static int +meta_back_get_candidate( + Operation *op, + SlapReply *rs, + struct berval *ndn ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + long candidate; + + /* + * tries to get a unique candidate + * (takes care of default target) + */ + candidate = meta_back_select_unique_candidate( mi, ndn ); + + /* + * if any is found, inits the connection + */ + if ( candidate == META_TARGET_NONE ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "No suitable candidate target found"; + + } else if ( candidate == META_TARGET_MULTIPLE ) { + Operation op2 = *op; + SlapReply rs2 = { REP_RESULT }; + slap_callback cb2 = { 0 }; + int rc; + + /* try to get a unique match for the request ndn + * among the multiple candidates available */ + op2.o_tag = LDAP_REQ_SEARCH; + op2.o_req_dn = *ndn; + op2.o_req_ndn = *ndn; + op2.ors_scope = LDAP_SCOPE_BASE; + op2.ors_deref = LDAP_DEREF_NEVER; + op2.ors_attrs = slap_anlist_no_attrs; + op2.ors_attrsonly = 0; + op2.ors_limit = NULL; + op2.ors_slimit = 1; + op2.ors_tlimit = SLAP_NO_LIMIT; + + op2.ors_filter = (Filter *)slap_filter_objectClass_pres; + op2.ors_filterstr = *slap_filterstr_objectClass_pres; + + op2.o_callback = &cb2; + cb2.sc_response = meta_back_conn_cb; + cb2.sc_private = (void *)&candidate; + + rc = op->o_bd->be_search( &op2, &rs2 ); + + switch ( rs2.sr_err ) { + case LDAP_SUCCESS: + default: + rs->sr_err = rs2.sr_err; + break; + + case LDAP_SIZELIMIT_EXCEEDED: + /* if multiple candidates can serve the operation, + * and a default target is defined, and it is + * a candidate, try using it (FIXME: YMMV) */ + if ( mi->mi_defaulttarget != META_DEFAULT_TARGET_NONE + && meta_back_is_candidate( mi->mi_targets[ mi->mi_defaulttarget ], + ndn, op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_BASE ) ) + { + candidate = mi->mi_defaulttarget; + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + + } else { + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Unable to select unique candidate target"; + } + break; + } + + } else { + rs->sr_err = LDAP_SUCCESS; + } + + return candidate; +} + +static void *meta_back_candidates_dummy; + +static void +meta_back_candidates_keyfree( + void *key, + void *data ) +{ + metacandidates_t *mc = (metacandidates_t *)data; + + ber_memfree_x( mc->mc_candidates, NULL ); + ber_memfree_x( data, NULL ); +} + +SlapReply * +meta_back_candidates_get( Operation *op ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metacandidates_t *mc; + + if ( op->o_threadctx ) { + void *data = NULL; + + ldap_pvt_thread_pool_getkey( op->o_threadctx, + &meta_back_candidates_dummy, &data, NULL ); + mc = (metacandidates_t *)data; + + } else { + mc = mi->mi_candidates; + } + + if ( mc == NULL ) { + mc = ch_calloc( sizeof( metacandidates_t ), 1 ); + mc->mc_ntargets = mi->mi_ntargets; + mc->mc_candidates = ch_calloc( sizeof( SlapReply ), mc->mc_ntargets ); + if ( op->o_threadctx ) { + void *data = NULL; + + data = (void *)mc; + ldap_pvt_thread_pool_setkey( op->o_threadctx, + &meta_back_candidates_dummy, data, + meta_back_candidates_keyfree, + NULL, NULL ); + + } else { + mi->mi_candidates = mc; + } + + } else if ( mc->mc_ntargets < mi->mi_ntargets ) { + /* NOTE: in the future, may want to allow back-config + * to add/remove targets from back-meta... */ + mc->mc_candidates = ch_realloc( mc->mc_candidates, + sizeof( SlapReply ) * mi->mi_ntargets ); + memset( &mc->mc_candidates[ mc->mc_ntargets ], 0, + sizeof( SlapReply ) * ( mi->mi_ntargets - mc->mc_ntargets ) ); + mc->mc_ntargets = mi->mi_ntargets; + } + + return mc->mc_candidates; +} + +/* + * meta_back_getconn + * + * Prepares the connection structure + * + * RATIONALE: + * + * - determine what DN is being requested: + * + * op requires candidate checks + * + * add unique parent of o_req_ndn + * bind unique^*[/all] o_req_ndn [no check] + * compare unique^+ o_req_ndn + * delete unique o_req_ndn + * modify unique o_req_ndn + * search any o_req_ndn + * modrdn unique[, unique] o_req_ndn[, orr_nnewSup] + * + * - for ops that require the candidate to be unique, in case of multiple + * occurrences an internal search with sizeLimit=1 is performed + * if a unique candidate can actually be determined. If none is found, + * the operation aborts; if multiple are found, the default target + * is used if defined and candidate; otherwise the operation aborts. + * + * *^note: actually, the bind operation is handled much like a search; + * i.e. the bind is broadcast to all candidate targets. + * + * +^note: actually, the compare operation is handled much like a search; + * i.e. the compare is broadcast to all candidate targets, while checking + * that exactly none (noSuchObject) or one (TRUE/FALSE/UNDEFINED) is + * returned. + */ +metaconn_t * +meta_back_getconn( + Operation *op, + SlapReply *rs, + int *candidate, + ldap_back_send_t sendok ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc = NULL, + mc_curr = {{ 0 }}; + int cached = META_TARGET_NONE, + i = META_TARGET_NONE, + err = LDAP_SUCCESS, + new_conn = 0, + ncandidates = 0; + + + meta_op_type op_type = META_OP_REQUIRE_SINGLE; + enum { + META_DNTYPE_ENTRY, + META_DNTYPE_PARENT, + META_DNTYPE_NEWPARENT + } dn_type = META_DNTYPE_ENTRY; + struct berval ndn = op->o_req_ndn, + pndn; + + SlapReply *candidates = meta_back_candidates_get( op ); + + /* Internal searches are privileged and shared. So is root. */ + if ( ( !BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ALWAYS( mi ) ) + || ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_ANON( mi ) ) + || op->o_do_not_cache || be_isroot( op ) ) + { + LDAP_BACK_CONN_ISPRIV_SET( &mc_curr ); + mc_curr.mc_local_ndn = op->o_bd->be_rootndn; + LDAP_BACK_PCONN_ROOTDN_SET( &mc_curr, op ); + + } else if ( BER_BVISEMPTY( &op->o_ndn ) && META_BACK_PROXYAUTHZ_NOANON( mi ) ) + { + LDAP_BACK_CONN_ISANON_SET( &mc_curr ); + BER_BVSTR( &mc_curr.mc_local_ndn, "" ); + LDAP_BACK_PCONN_ANON_SET( &mc_curr, op ); + + } else { + mc_curr.mc_local_ndn = op->o_ndn; + + /* Explicit binds must not be shared */ + if ( !BER_BVISEMPTY( &op->o_ndn ) + || op->o_tag == LDAP_REQ_BIND + || SLAP_IS_AUTHZ_BACKEND( op ) ) + { + mc_curr.mc_conn = op->o_conn; + + } else { + LDAP_BACK_CONN_ISANON_SET( &mc_curr ); + LDAP_BACK_PCONN_ANON_SET( &mc_curr, op ); + } + } + + /* Explicit Bind requests always get their own conn */ + if ( sendok & LDAP_BACK_BINDING ) { + mc_curr.mc_conn = op->o_conn; + + } else { + /* Searches for a metaconn in the avl tree */ +retry_lock:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_PCONN_ISPRIV( &mc_curr ) ) { + /* lookup a conn that's not binding */ + LDAP_TAILQ_FOREACH( mc, + &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv, + mc_q ) + { + if ( !LDAP_BACK_CONN_BINDING( mc ) && mc->mc_refcnt == 0 ) { + break; + } + } + + if ( mc != NULL ) { + /* move to tail of queue */ + if ( mc != LDAP_TAILQ_LAST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc_conn_priv_q ) ) + { + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + } + + } else if ( !LDAP_BACK_USE_TEMPORARIES( mi ) + && mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_num == mi->mi_conn_priv_max ) + { + mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( &mc_curr ) ].mic_priv ); + } + + + } else { + mc = (metaconn_t *)avl_find( mi->mi_conninfo.lai_tree, + (caddr_t)&mc_curr, meta_back_conndn_cmp ); + } + + if ( mc ) { + /* catch taint errors */ + assert( !LDAP_BACK_CONN_TAINTED( mc ) ); + + /* Don't reuse connections while they're still binding + * NOTE: only makes sense for binds */ + if ( LDAP_BACK_CONN_BINDING( mc ) ) { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + ldap_pvt_thread_yield(); + goto retry_lock; + } + + /* release conn, and create a temporary */ + mc = NULL; + + } else { + if ( mc->mc_refcnt == 0 && (( mi->mi_conn_ttl != 0 && op->o_time > mc->mc_create_time + mi->mi_conn_ttl ) + || ( mi->mi_idle_timeout != 0 && op->o_time > mc->mc_time + mi->mi_idle_timeout )) ) + { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, + ">>> meta_back_getconn(expired)" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + /* don't let anyone else use this expired connection */ + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, + mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else { + (void)avl_delete( &mi->mi_conninfo.lai_tree, + (caddr_t)mc, meta_back_conndnmc_cmp ); + } + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, + "<<< meta_back_getconn(expired)" ); +#endif /* META_BACK_PRINT_CONNTREE */ + LDAP_BACK_CONN_TAINTED_SET( mc ); + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: mc=%p conn=%s expired (tainted).\n", + op->o_log_prefix, (void *)mc, buf ); + } + } + + mc->mc_refcnt++; + } + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + switch ( op->o_tag ) { + case LDAP_REQ_ADD: + /* if we go to selection, the entry must not exist, + * and we must be able to resolve the parent */ + dn_type = META_DNTYPE_PARENT; + dnParent( &ndn, &pndn ); + break; + + case LDAP_REQ_MODRDN: + /* if nnewSuperior is not NULL, it must resolve + * to the same candidate as the req_ndn */ + if ( op->orr_nnewSup ) { + dn_type = META_DNTYPE_NEWPARENT; + } + break; + + case LDAP_REQ_BIND: + /* if bound as rootdn, the backend must bind to all targets + * with the administrative identity + * (unless pseoudoroot-bind-defer is TRUE) */ + if ( op->orb_method == LDAP_AUTH_SIMPLE && be_isroot_pw( op ) ) { + op_type = META_OP_REQUIRE_ALL; + } + break; + + case LDAP_REQ_COMPARE: + case LDAP_REQ_DELETE: + case LDAP_REQ_MODIFY: + /* just a unique candidate */ + break; + + case LDAP_REQ_SEARCH: + /* allow multiple candidates for the searchBase */ + op_type = META_OP_ALLOW_MULTIPLE; + break; + + default: + /* right now, just break (exop?) */ + break; + } + + /* + * require all connections ... + */ + if ( op_type == META_OP_REQUIRE_ALL ) { + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( mc ); + } + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + + } else if ( 0 ) { + /* TODO: if any of the connections is binding, + * release mc and create a new one */ + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + /* + * The target is activated; if needed, it is + * also init'd + */ + candidates[ i ].sr_err = meta_back_init_one_conn( op, + rs, mc, i, LDAP_BACK_CONN_ISPRIV( &mc_curr ), + LDAP_BACK_DONTSEND, !new_conn ); + if ( candidates[ i ].sr_err == LDAP_SUCCESS ) { + if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) { + LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] ); + } + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + } else { + + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + META_CANDIDATE_RESET( &candidates[ i ] ); + err = candidates[ i ].sr_err; + continue; + } + } + + if ( ncandidates == 0 ) { + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "Unable to select valid candidates"; + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + + goto done; + } + + /* + * looks in cache, if any + */ + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) { + cached = i = meta_dncache_get_target( &mi->mi_cache, &op->o_req_ndn ); + } + + if ( op_type == META_OP_REQUIRE_SINGLE ) { + metatarget_t *mt = NULL; + metasingleconn_t *msc = NULL; + + int j; + + for ( j = 0; j < mi->mi_ntargets; j++ ) { + META_CANDIDATE_RESET( &candidates[ j ] ); + } + + /* + * tries to get a unique candidate + * (takes care of default target) + */ + if ( i == META_TARGET_NONE ) { + i = meta_back_get_candidate( op, rs, &ndn ); + + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT && dn_type == META_DNTYPE_PARENT ) { + i = meta_back_get_candidate( op, rs, &pndn ); + } + + if ( i < 0 || rs->sr_err != LDAP_SUCCESS ) { + if ( mc != NULL ) { + meta_back_release_conn( mi, mc ); + } + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + } + + if ( dn_type == META_DNTYPE_NEWPARENT && meta_back_get_candidate( op, rs, op->orr_nnewSup ) != i ) + { + if ( mc != NULL ) { + meta_back_release_conn( mi, mc ); + } + + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Cross-target rename not supported"; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + + return NULL; + } + + Debug( LDAP_DEBUG_TRACE, + "==>meta_back_getconn: got target=%d for ndn=\"%s\" from cache\n", + i, op->o_req_ndn.bv_val, 0 ); + + if ( mc == NULL ) { + /* Retries searching for a metaconn in the avl tree + * the reason is that the connection might have been + * created by meta_back_get_candidate() */ + if ( !( sendok & LDAP_BACK_BINDING ) ) { +retry_lock2:; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + mc = (metaconn_t *)avl_find( mi->mi_conninfo.lai_tree, + (caddr_t)&mc_curr, meta_back_conndn_cmp ); + if ( mc != NULL ) { + /* catch taint errors */ + assert( !LDAP_BACK_CONN_TAINTED( mc ) ); + + /* Don't reuse connections while they're still binding */ + if ( META_BACK_CONN_CREATING( &mc->mc_conns[ i ] ) + || LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) ) + { + if ( !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_yield(); + goto retry_lock2; + } + + mc = NULL; + + } else { + mc->mc_refcnt++; + } + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( sendok & LDAP_BACK_BINDING ) { + LDAP_BACK_CONN_BINDING_SET( mc ); + } + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + } + } + + /* + * Clear all other candidates + */ + ( void )meta_clear_unused_candidates( op, i ); + + mt = mi->mi_targets[ i ]; + msc = &mc->mc_conns[ i ]; + + /* + * The target is activated; if needed, it is + * also init'd. In case of error, meta_back_init_one_conn + * sends the appropriate result. + */ + err = meta_back_init_one_conn( op, rs, mc, i, + LDAP_BACK_CONN_ISPRIV( &mc_curr ), sendok, !new_conn ); + if ( err != LDAP_SUCCESS ) { + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + META_CANDIDATE_RESET( &candidates[ i ] ); + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + return NULL; + } + + if ( new_conn && ( sendok & LDAP_BACK_BINDING ) ) { + LDAP_BACK_CONN_BINDING_SET( &mc->mc_conns[ i ] ); + } + + candidates[ i ].sr_err = LDAP_SUCCESS; + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + if ( candidate ) { + *candidate = i; + } + + /* + * if no unique candidate ... + */ + } else { + + /* Looks like we didn't get a bind. Open a new session... */ + if ( mc == NULL ) { + assert( new_conn == 0 ); + mc = metaconn_alloc( op ); + mc->mc_conn = mc_curr.mc_conn; + ber_dupbv( &mc->mc_local_ndn, &mc_curr.mc_local_ndn ); + new_conn = 1; + if ( LDAP_BACK_CONN_ISPRIV( &mc_curr ) ) { + LDAP_BACK_CONN_ISPRIV_SET( mc ); + + } else if ( LDAP_BACK_CONN_ISANON( &mc_curr ) ) { + LDAP_BACK_CONN_ISANON_SET( mc ); + } + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + META_CANDIDATE_RESET( &candidates[ i ] ); + + if ( i == cached + || meta_back_is_candidate( mt, &op->o_req_ndn, + op->o_tag == LDAP_REQ_SEARCH ? op->ors_scope : LDAP_SCOPE_SUBTREE ) ) + { + + /* + * The target is activated; if needed, it is + * also init'd + */ + int lerr = meta_back_init_one_conn( op, rs, mc, i, + LDAP_BACK_CONN_ISPRIV( &mc_curr ), + LDAP_BACK_DONTSEND, !new_conn ); + candidates[ i ].sr_err = lerr; + if ( lerr == LDAP_SUCCESS ) { + META_CANDIDATE_SET( &candidates[ i ] ); + ncandidates++; + + Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d]\n", + op->o_log_prefix, i, 0 ); + + } else if ( lerr == LDAP_UNAVAILABLE && !META_BACK_ONERR_STOP( mi ) ) { + META_CANDIDATE_SET( &candidates[ i ] ); + + Debug( LDAP_DEBUG_TRACE, "%s: meta_back_getconn[%d] %s\n", + op->o_log_prefix, i, + mt->mt_isquarantined != LDAP_BACK_FQ_NO ? "quarantined" : "unavailable" ); + + } else { + + /* + * FIXME: in case one target cannot + * be init'd, should the other ones + * be tried? + */ + if ( new_conn ) { + ( void )meta_clear_one_candidate( op, mc, i ); + } + /* leave the target candidate, but record the error for later use */ + err = lerr; + + if ( lerr == LDAP_UNAVAILABLE && mt->mt_isquarantined != LDAP_BACK_FQ_NO ) { + Log4( LDAP_DEBUG_TRACE, ldap_syslog_level, "%s: meta_back_getconn[%d] quarantined err=%d text=%s\n", + op->o_log_prefix, i, lerr, rs->sr_text ); + + } else { + Log4( LDAP_DEBUG_ANY, ldap_syslog, "%s: meta_back_getconn[%d] failed err=%d text=%s\n", + op->o_log_prefix, i, lerr, rs->sr_text ); + } + + if ( META_BACK_ONERR_STOP( mi ) ) { + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + return NULL; + } + + continue; + } + + } else { + if ( new_conn ) { + ( void )meta_clear_one_candidate( op, mc, i ); + } + } + } + + if ( ncandidates == 0 ) { + if ( new_conn ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + } else { + meta_back_release_conn( mi, mc ); + } + + if ( rs->sr_err == LDAP_SUCCESS ) { + rs->sr_err = LDAP_NO_SUCH_OBJECT; + rs->sr_text = "Unable to select valid candidates"; + } + + if ( sendok & LDAP_BACK_SENDERR ) { + if ( rs->sr_err == LDAP_NO_SUCH_OBJECT ) { + rs->sr_matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + send_ldap_result( op, rs ); + rs->sr_matched = NULL; + } + + return NULL; + } + } + +done:; + /* clear out meta_back_init_one_conn non-fatal errors */ + rs->sr_err = LDAP_SUCCESS; + rs->sr_text = NULL; + + /* touch the timestamp */ + if ( mi->mi_idle_timeout != 0 ) { + mc->mc_time = op->o_time; + } + + if ( new_conn ) { + if ( mi->mi_conn_ttl ) { + mc->mc_create_time = op->o_time; + } + + /* + * Inserts the newly created metaconn in the avl tree + */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_getconn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + err = 0; + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num < mi->mi_conn_priv_max ) { + LDAP_TAILQ_INSERT_TAIL( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num++; + LDAP_BACK_CONN_CACHED_SET( mc ); + + } else { + LDAP_BACK_CONN_TAINTED_SET( mc ); + } + rs->sr_err = 0; + + } else if ( !( sendok & LDAP_BACK_BINDING ) ) { + err = avl_insert( &mi->mi_conninfo.lai_tree, ( caddr_t )mc, + meta_back_conndn_cmp, meta_back_conndn_dup ); + LDAP_BACK_CONN_CACHED_SET( mc ); + } + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_getconn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( !LDAP_BACK_PCONN_ISPRIV( mc ) ) { + /* + * Err could be -1 in case a duplicate metaconn is inserted + */ + switch ( err ) { + case 0: + break; + + case -1: + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + /* duplicate: free and try to get the newly created one */ + if ( !( sendok & LDAP_BACK_BINDING ) && !LDAP_BACK_USE_TEMPORARIES( mi ) ) { + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + new_conn = 0; + goto retry_lock; + } + + LDAP_BACK_CONN_TAINTED_SET( mc ); + break; + + default: + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_getconn: candidates=%d conn=%s insert failed\n", + op->o_log_prefix, ncandidates, buf ); + } + + mc->mc_refcnt = 0; + meta_back_conn_free( mc ); + + rs->sr_err = LDAP_OTHER; + rs->sr_text = "Proxy bind collision"; + if ( sendok & LDAP_BACK_SENDERR ) { + send_ldap_result( op, rs ); + } + return NULL; + } + } + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: candidates=%d conn=%s inserted\n", + op->o_log_prefix, ncandidates, buf ); + } + + } else { + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char buf[STRLENOF("4294967295U") + 1] = { 0 }; + mi->mi_ldap_extra->connid2str( &mc->mc_base, buf, sizeof(buf) ); + + Debug( LDAP_DEBUG_TRACE, + "%s meta_back_getconn: candidates=%d conn=%s fetched\n", + op->o_log_prefix, ncandidates, buf ); + } + } + + return mc; +} + +void +meta_back_release_conn_lock( + metainfo_t *mi, + metaconn_t *mc, + int dolock ) +{ + assert( mc != NULL ); + + if ( dolock ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + } + assert( mc->mc_refcnt > 0 ); + mc->mc_refcnt--; + /* NOTE: the connection is removed if either it is tainted + * or if it is shared and no one else is using it. This needs + * to occur because for intrinsic reasons cached connections + * that are not privileged would live forever and pollute + * the connection space (and eat up resources). Maybe this + * should be configurable... */ + if ( LDAP_BACK_CONN_TAINTED( mc ) || !LDAP_BACK_CONN_CACHED( mc ) ) { +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_release_conn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + if ( LDAP_BACK_PCONN_ISPRIV( mc ) ) { + if ( mc->mc_q.tqe_prev != NULL ) { + assert( LDAP_BACK_CONN_CACHED( mc ) ); + assert( mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num > 0 ); + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_priv, mc, mc_q ); + mi->mi_conn_priv[ LDAP_BACK_CONN2PRIV( mc ) ].mic_num--; + LDAP_TAILQ_ENTRY_INIT( mc, mc_q ); + + } else { + assert( !LDAP_BACK_CONN_CACHED( mc ) ); + } + + } else if ( LDAP_BACK_CONN_CACHED( mc ) ) { + metaconn_t *tmpmc; + + tmpmc = avl_delete( &mi->mi_conninfo.lai_tree, + ( caddr_t )mc, meta_back_conndnmc_cmp ); + + /* Overparanoid, but useful... */ + assert( tmpmc == NULL || tmpmc == mc ); + } + + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_release_conn" ); +#endif /* META_BACK_PRINT_CONNTREE */ + + if ( mc->mc_refcnt == 0 ) { + meta_back_conn_free( mc ); + mc = NULL; + } + } + + if ( mc != NULL && LDAP_BACK_CONN_BINDING( mc ) ) { + LDAP_BACK_CONN_BINDING_CLEAR( mc ); + } + + if ( dolock ) { + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } +} + +void +meta_back_quarantine( + Operation *op, + SlapReply *rs, + int candidate ) +{ + metainfo_t *mi = (metainfo_t *)op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + + slap_retry_info_t *ri = &mt->mt_quarantine; + + ldap_pvt_thread_mutex_lock( &mt->mt_quarantine_mutex ); + + if ( rs->sr_err == LDAP_UNAVAILABLE ) { + time_t new_last = slap_get_time(); + + switch ( mt->mt_isquarantined ) { + case LDAP_BACK_FQ_NO: + if ( ri->ri_last == new_last ) { + goto done; + } + + Debug( LDAP_DEBUG_ANY, + "%s meta_back_quarantine[%d]: enter.\n", + op->o_log_prefix, candidate, 0 ); + + ri->ri_idx = 0; + ri->ri_count = 0; + break; + + case LDAP_BACK_FQ_RETRYING: + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "meta_back_quarantine[%d]: block #%d try #%d failed", + candidate, ri->ri_idx, ri->ri_count ); + Debug( LDAP_DEBUG_ANY, "%s %s.\n", + op->o_log_prefix, buf, 0 ); + } + + ++ri->ri_count; + if ( ri->ri_num[ ri->ri_idx ] != SLAP_RETRYNUM_FOREVER + && ri->ri_count == ri->ri_num[ ri->ri_idx ] ) + { + ri->ri_count = 0; + ++ri->ri_idx; + } + break; + + default: + break; + } + + mt->mt_isquarantined = LDAP_BACK_FQ_YES; + ri->ri_last = new_last; + + } else if ( mt->mt_isquarantined == LDAP_BACK_FQ_RETRYING ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_quarantine[%d]: exit.\n", + op->o_log_prefix, candidate, 0 ); + + if ( mi->mi_quarantine_f ) { + (void)mi->mi_quarantine_f( mi, candidate, + mi->mi_quarantine_p ); + } + + ri->ri_count = 0; + ri->ri_idx = 0; + mt->mt_isquarantined = LDAP_BACK_FQ_NO; + } + +done:; + ldap_pvt_thread_mutex_unlock( &mt->mt_quarantine_mutex ); +} diff --git a/servers/slapd/back-meta/delete.c b/servers/slapd/back-meta/delete.c new file mode 100644 index 0000000..1823a09 --- /dev/null +++ b/servers/slapd/back-meta/delete.c @@ -0,0 +1,103 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_delete( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc = NULL; + int candidate = -1; + struct berval mdn = BER_BVNULL; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the compare dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "deleteDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto cleanup; + } + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_delete_ext( mc->mc_conns[ candidate ].msc_ld, + mdn.bv_val, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_DELETE ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/dncache.c b/servers/slapd/back-meta/dncache.c new file mode 100644 index 0000000..d042654 --- /dev/null +++ b/servers/slapd/back-meta/dncache.c @@ -0,0 +1,235 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +/* + * The dncache, at present, maps an entry to the target that holds it. + */ + +typedef struct metadncacheentry_t { + struct berval dn; + int target; + + time_t lastupdated; +} metadncacheentry_t; + +/* + * meta_dncache_cmp + * + * compares two struct metadncacheentry; used by avl stuff + * FIXME: modify avl stuff to delete an entry based on cmp + * (e.g. when ttl expired?) + */ +int +meta_dncache_cmp( + const void *c1, + const void *c2 ) +{ + metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1; + metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ber_bvcmp( &cc1->dn, &cc2->dn); +} + +/* + * meta_dncache_dup + * + * returns -1 in case a duplicate struct metadncacheentry has been inserted; + * used by avl stuff + */ +int +meta_dncache_dup( + void *c1, + void *c2 ) +{ + metadncacheentry_t *cc1 = ( metadncacheentry_t * )c1; + metadncacheentry_t *cc2 = ( metadncacheentry_t * )c2; + + /* + * case sensitive, because the dn MUST be normalized + */ + return ( ber_bvcmp( &cc1->dn, &cc2->dn ) == 0 ) ? -1 : 0; +} + +/* + * meta_dncache_get_target + * + * returns the target a dn belongs to, or -1 in case the dn is not + * in the cache + */ +int +meta_dncache_get_target( + metadncache_t *cache, + struct berval *ndn ) +{ + metadncacheentry_t tmp_entry, + *entry; + int target = META_TARGET_NONE; + + assert( cache != NULL ); + assert( ndn != NULL ); + + tmp_entry.dn = *ndn; + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = ( metadncacheentry_t * )avl_find( cache->tree, + ( caddr_t )&tmp_entry, meta_dncache_cmp ); + + if ( entry != NULL ) { + + /* + * if cache->ttl < 0, cache never expires; + * if cache->ttl = 0 no cache is used; shouldn't get here + * else, cache is used with ttl + */ + if ( cache->ttl < 0 ) { + target = entry->target; + + } else { + if ( entry->lastupdated+cache->ttl > slap_get_time() ) { + target = entry->target; + } + } + } + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + return target; +} + +/* + * meta_dncache_update_entry + * + * updates target and lastupdated of a struct metadncacheentry if exists, + * otherwise it gets created; returns -1 in case of error + */ +int +meta_dncache_update_entry( + metadncache_t *cache, + struct berval *ndn, + int target ) +{ + metadncacheentry_t *entry, + tmp_entry; + time_t curr_time = 0L; + int err = 0; + + assert( cache != NULL ); + assert( ndn != NULL ); + + /* + * if cache->ttl < 0, cache never expires; + * if cache->ttl = 0 no cache is used; shouldn't get here + * else, cache is used with ttl + */ + if ( cache->ttl > 0 ) { + curr_time = slap_get_time(); + } + + tmp_entry.dn = *ndn; + + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = ( metadncacheentry_t * )avl_find( cache->tree, + ( caddr_t )&tmp_entry, meta_dncache_cmp ); + + if ( entry != NULL ) { + entry->target = target; + entry->lastupdated = curr_time; + + } else { + entry = ch_malloc( sizeof( metadncacheentry_t ) + ndn->bv_len + 1 ); + if ( entry == NULL ) { + err = -1; + goto error_return; + } + + entry->dn.bv_len = ndn->bv_len; + entry->dn.bv_val = (char *)&entry[ 1 ]; + AC_MEMCPY( entry->dn.bv_val, ndn->bv_val, ndn->bv_len ); + entry->dn.bv_val[ ndn->bv_len ] = '\0'; + + entry->target = target; + entry->lastupdated = curr_time; + + err = avl_insert( &cache->tree, ( caddr_t )entry, + meta_dncache_cmp, meta_dncache_dup ); + } + +error_return:; + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + return err; +} + +/* + * meta_dncache_update_entry + * + * updates target and lastupdated of a struct metadncacheentry if exists, + * otherwise it gets created; returns -1 in case of error + */ +int +meta_dncache_delete_entry( + metadncache_t *cache, + struct berval *ndn ) +{ + metadncacheentry_t *entry, + tmp_entry; + + assert( cache != NULL ); + assert( ndn != NULL ); + + tmp_entry.dn = *ndn; + + ldap_pvt_thread_mutex_lock( &cache->mutex ); + entry = avl_delete( &cache->tree, ( caddr_t )&tmp_entry, + meta_dncache_cmp ); + ldap_pvt_thread_mutex_unlock( &cache->mutex ); + + if ( entry != NULL ) { + meta_dncache_free( ( void * )entry ); + } + + return 0; +} + +/* + * meta_dncache_free + * + * frees an entry + * + */ +void +meta_dncache_free( + void *e ) +{ + free( e ); +} + diff --git a/servers/slapd/back-meta/init.c b/servers/slapd/back-meta/init.c new file mode 100644 index 0000000..3c405b3 --- /dev/null +++ b/servers/slapd/back-meta/init.c @@ -0,0 +1,475 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "config.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_open( + BackendInfo *bi ) +{ + /* FIXME: need to remove the pagedResults, and likely more... */ + bi->bi_controls = slap_known_controls; + + return 0; +} + +int +meta_back_initialize( + BackendInfo *bi ) +{ + bi->bi_flags = +#if 0 + /* this is not (yet) set essentially because back-meta does not + * directly support extended operations... */ +#ifdef LDAP_DYNAMIC_OBJECTS + /* this is set because all the support a proxy has to provide + * is the capability to forward the refresh exop, and to + * pass thru entries that contain the dynamicObject class + * and the entryTtl attribute */ + SLAP_BFLAG_DYNAMIC | +#endif /* LDAP_DYNAMIC_OBJECTS */ +#endif + + /* back-meta recognizes RFC4525 increment; + * let the remote server complain, if needed (ITS#5912) */ + SLAP_BFLAG_INCREMENT; + + bi->bi_open = meta_back_open; + bi->bi_config = 0; + bi->bi_close = 0; + bi->bi_destroy = 0; + + bi->bi_db_init = meta_back_db_init; + bi->bi_db_config = config_generic_wrapper; + bi->bi_db_open = meta_back_db_open; + bi->bi_db_close = 0; + bi->bi_db_destroy = meta_back_db_destroy; + + bi->bi_op_bind = meta_back_bind; + bi->bi_op_unbind = 0; + bi->bi_op_search = meta_back_search; + bi->bi_op_compare = meta_back_compare; + bi->bi_op_modify = meta_back_modify; + bi->bi_op_modrdn = meta_back_modrdn; + bi->bi_op_add = meta_back_add; + bi->bi_op_delete = meta_back_delete; + bi->bi_op_abandon = 0; + + bi->bi_extended = 0; + + bi->bi_chk_referrals = 0; + + bi->bi_connection_init = 0; + bi->bi_connection_destroy = meta_back_conn_destroy; + + return meta_back_init_cf( bi ); +} + +int +meta_back_db_init( + Backend *be, + ConfigReply *cr) +{ + metainfo_t *mi; + int i; + BackendInfo *bi; + + bi = backend_info( "ldap" ); + if ( !bi || !bi->bi_extra ) { + Debug( LDAP_DEBUG_ANY, + "meta_back_db_init: needs back-ldap\n", + 0, 0, 0 ); + return 1; + } + + mi = ch_calloc( 1, sizeof( metainfo_t ) ); + if ( mi == NULL ) { + return -1; + } + + /* set default flags */ + mi->mi_flags = + META_BACK_F_DEFER_ROOTDN_BIND + | META_BACK_F_PROXYAUTHZ_ALWAYS + | META_BACK_F_PROXYAUTHZ_ANON + | META_BACK_F_PROXYAUTHZ_NOANON; + + /* + * At present the default is no default target; + * this may change + */ + mi->mi_defaulttarget = META_DEFAULT_TARGET_NONE; + mi->mi_bind_timeout.tv_sec = 0; + mi->mi_bind_timeout.tv_usec = META_BIND_TIMEOUT; + + mi->mi_rebind_f = meta_back_default_rebind; + mi->mi_urllist_f = meta_back_default_urllist; + + ldap_pvt_thread_mutex_init( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_init( &mi->mi_cache.mutex ); + + /* safe default */ + mi->mi_nretries = META_RETRY_DEFAULT; + mi->mi_version = LDAP_VERSION3; + + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + mi->mi_conn_priv[ i ].mic_num = 0; + LDAP_TAILQ_INIT( &mi->mi_conn_priv[ i ].mic_priv ); + } + mi->mi_conn_priv_max = LDAP_BACK_CONN_PRIV_DEFAULT; + + mi->mi_ldap_extra = (ldap_extra_t *)bi->bi_extra; + + be->be_private = mi; + be->be_cf_ocs = be->bd_info->bi_cf_ocs; + + return 0; +} + +int +meta_target_finish( + metainfo_t *mi, + metatarget_t *mt, + const char *log, + char *msg, + size_t msize +) +{ + slap_bindconf sb = { BER_BVNULL }; + struct berval mapped; + int rc; + + ber_str2bv( mt->mt_uri, 0, 0, &sb.sb_uri ); + sb.sb_version = mt->mt_version; + sb.sb_method = LDAP_AUTH_SIMPLE; + BER_BVSTR( &sb.sb_binddn, "" ); + + if ( META_BACK_TGT_T_F_DISCOVER( mt ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedFeatures->ad_cname.bv_val, + LDAP_FEATURE_ABSOLUTE_FILTERS ); + if ( rc == LDAP_COMPARE_TRUE ) { + mt->mt_flags |= LDAP_BACK_F_T_F; + } + } + + if ( META_BACK_TGT_CANCEL_DISCOVER( mt ) ) { + rc = slap_discover_feature( &sb, + slap_schema.si_ad_supportedExtension->ad_cname.bv_val, + LDAP_EXOP_CANCEL ); + if ( rc == LDAP_COMPARE_TRUE ) { + mt->mt_flags |= LDAP_BACK_F_CANCEL_EXOP; + } + } + + if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_OVERRIDE ) + || mt->mt_idassert_authz != NULL ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ALWAYS; + } + + if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) + && !( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) ) + { + snprintf( msg, msize, + "%s: inconsistent idassert configuration " + "(likely authz=\"*\" used with \"non-prescriptive\" flag)", + log ); + Debug( LDAP_DEBUG_ANY, "%s (target %s)\n", + msg, mt->mt_uri, 0 ); + return 1; + } + + if ( !( mt->mt_idassert_flags & LDAP_BACK_AUTH_AUTHZ_ALL ) ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_ANON; + } + + if ( ( mt->mt_idassert_flags & LDAP_BACK_AUTH_PRESCRIPTIVE ) ) + { + mi->mi_flags &= ~META_BACK_F_PROXYAUTHZ_NOANON; + } + + BER_BVZERO( &mapped ); + ldap_back_map( &mt->mt_rwmap.rwm_at, + &slap_schema.si_ad_entryDN->ad_cname, &mapped, + BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + mt->mt_rep_flags |= REP_NO_ENTRYDN; + } + + BER_BVZERO( &mapped ); + ldap_back_map( &mt->mt_rwmap.rwm_at, + &slap_schema.si_ad_subschemaSubentry->ad_cname, &mapped, + BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + mt->mt_rep_flags |= REP_NO_SUBSCHEMA; + } + + return 0; +} + +int +meta_back_db_open( + Backend *be, + ConfigReply *cr ) +{ + metainfo_t *mi = (metainfo_t *)be->be_private; + char msg[SLAP_TEXT_BUFLEN]; + + int i, rc; + + if ( mi->mi_ntargets == 0 ) { + /* Dynamically added, nothing to check here until + * some targets get added + */ + if ( slapMode & SLAP_SERVER_RUNNING ) + return 0; + + Debug( LDAP_DEBUG_ANY, + "meta_back_db_open: no targets defined\n", + 0, 0, 0 ); + return 1; + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( meta_target_finish( mi, mt, + "meta_back_db_open", msg, sizeof( msg ))) + return 1; + } + + return 0; +} + +/* + * meta_back_conn_free() + * + * actually frees a connection; the reference count must be 0, + * and it must not (or no longer) be in the cache. + */ +void +meta_back_conn_free( + void *v_mc ) +{ + metaconn_t *mc = v_mc; + int ntargets; + + assert( mc != NULL ); + assert( mc->mc_refcnt == 0 ); + + /* at least one must be present... */ + ntargets = mc->mc_info->mi_ntargets; + assert( ntargets > 0 ); + + for ( ; ntargets--; ) { + (void)meta_clear_one_candidate( NULL, mc, ntargets ); + } + + if ( !BER_BVISNULL( &mc->mc_local_ndn ) ) { + free( mc->mc_local_ndn.bv_val ); + } + + free( mc ); +} + +static void +mapping_free( + void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + ch_free( mapping->src.bv_val ); + ch_free( mapping->dst.bv_val ); + ch_free( mapping ); +} + +static void +mapping_dst_free( + void *v_mapping ) +{ + struct ldapmapping *mapping = v_mapping; + + if ( BER_BVISEMPTY( &mapping->dst ) ) { + mapping_free( &mapping[ -1 ] ); + } +} + +void +meta_back_map_free( struct ldapmap *lm ) +{ + avl_free( lm->remap, mapping_dst_free ); + avl_free( lm->map, mapping_free ); + lm->remap = NULL; + lm->map = NULL; +} + +static void +target_free( + metatarget_t *mt ) +{ + if ( mt->mt_uri ) { + free( mt->mt_uri ); + ldap_pvt_thread_mutex_destroy( &mt->mt_uri_mutex ); + } + if ( mt->mt_subtree ) { + meta_subtree_destroy( mt->mt_subtree ); + mt->mt_subtree = NULL; + } + if ( mt->mt_filter ) { + meta_filter_destroy( mt->mt_filter ); + mt->mt_filter = NULL; + } + if ( !BER_BVISNULL( &mt->mt_psuffix ) ) { + free( mt->mt_psuffix.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_nsuffix ) ) { + free( mt->mt_nsuffix.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_binddn ) ) { + free( mt->mt_binddn.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_bindpw ) ) { + free( mt->mt_bindpw.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authcID ) ) { + ch_free( mt->mt_idassert_authcID.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authcDN ) ) { + ch_free( mt->mt_idassert_authcDN.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_passwd ) ) { + ch_free( mt->mt_idassert_passwd.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_authzID ) ) { + ch_free( mt->mt_idassert_authzID.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_sasl_mech ) ) { + ch_free( mt->mt_idassert_sasl_mech.bv_val ); + } + if ( !BER_BVISNULL( &mt->mt_idassert_sasl_realm ) ) { + ch_free( mt->mt_idassert_sasl_realm.bv_val ); + } + if ( mt->mt_idassert_authz != NULL ) { + ber_bvarray_free( mt->mt_idassert_authz ); + } + if ( mt->mt_rwmap.rwm_rw ) { + rewrite_info_delete( &mt->mt_rwmap.rwm_rw ); + if ( mt->mt_rwmap.rwm_bva_rewrite ) + ber_bvarray_free( mt->mt_rwmap.rwm_bva_rewrite ); + } + meta_back_map_free( &mt->mt_rwmap.rwm_oc ); + meta_back_map_free( &mt->mt_rwmap.rwm_at ); + ber_bvarray_free( mt->mt_rwmap.rwm_bva_map ); + + free( mt ); +} + +int +meta_back_db_destroy( + Backend *be, + ConfigReply *cr ) +{ + metainfo_t *mi; + + if ( be->be_private ) { + int i; + + mi = ( metainfo_t * )be->be_private; + + /* + * Destroy the connection tree + */ + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + if ( mi->mi_conninfo.lai_tree ) { + avl_free( mi->mi_conninfo.lai_tree, meta_back_conn_free ); + } + for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) { + while ( !LDAP_TAILQ_EMPTY( &mi->mi_conn_priv[ i ].mic_priv ) ) { + metaconn_t *mc = LDAP_TAILQ_FIRST( &mi->mi_conn_priv[ i ].mic_priv ); + + LDAP_TAILQ_REMOVE( &mi->mi_conn_priv[ i ].mic_priv, mc, mc_q ); + meta_back_conn_free( mc ); + } + } + + /* + * Destroy the per-target stuff (assuming there's at + * least one ...) + */ + if ( mi->mi_targets != NULL ) { + for ( i = 0; i < mi->mi_ntargets; i++ ) { + metatarget_t *mt = mi->mi_targets[ i ]; + + if ( META_BACK_TGT_QUARANTINE( mt ) ) { + if ( mt->mt_quarantine.ri_num != mi->mi_quarantine.ri_num ) + { + mi->mi_ldap_extra->retry_info_destroy( &mt->mt_quarantine ); + } + + ldap_pvt_thread_mutex_destroy( &mt->mt_quarantine_mutex ); + } + + target_free( mt ); + } + + free( mi->mi_targets ); + } + + ldap_pvt_thread_mutex_lock( &mi->mi_cache.mutex ); + if ( mi->mi_cache.tree ) { + avl_free( mi->mi_cache.tree, meta_dncache_free ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_cache.mutex ); + ldap_pvt_thread_mutex_destroy( &mi->mi_cache.mutex ); + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + ldap_pvt_thread_mutex_destroy( &mi->mi_conninfo.lai_mutex ); + + if ( mi->mi_candidates != NULL ) { + ber_memfree_x( mi->mi_candidates, NULL ); + } + + if ( META_BACK_QUARANTINE( mi ) ) { + mi->mi_ldap_extra->retry_info_destroy( &mi->mi_quarantine ); + } + } + + free( be->be_private ); + return 0; +} + +#if SLAPD_META == SLAPD_MOD_DYNAMIC + +/* conditionally define the init_module() function */ +SLAP_BACKEND_INIT_MODULE( meta ) + +#endif /* SLAPD_META == SLAPD_MOD_DYNAMIC */ + + diff --git a/servers/slapd/back-meta/map.c b/servers/slapd/back-meta/map.c new file mode 100644 index 0000000..f988776 --- /dev/null +++ b/servers/slapd/back-meta/map.c @@ -0,0 +1,877 @@ +/* map.c - ldap backend mapping routines */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1998-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ +/* This is an altered version */ +/* + * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com> + * + * Permission is granted to anyone to use this software for any purpose + * on any computer system, and to alter it and redistribute it, subject + * to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits should appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits should appear in the documentation. + * + * 4. This notice may not be removed or altered. + * + * + * + * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it> + * + * This software is being modified by Pierangelo Masarati. + * The previously reported conditions apply to the modified code as well. + * Changes in the original code are highlighted where required. + * Credits for the original code go to the author, Howard Chu. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "lutil.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +mapping_cmp ( const void *c1, const void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + int rc = map1->src.bv_len - map2->src.bv_len; + if (rc) return rc; + return ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) ); +} + +int +mapping_dup ( void *c1, void *c2 ) +{ + struct ldapmapping *map1 = (struct ldapmapping *)c1; + struct ldapmapping *map2 = (struct ldapmapping *)c2; + + return ( ( strcasecmp( map1->src.bv_val, map2->src.bv_val ) == 0 ) ? -1 : 0 ); +} + +void +ldap_back_map_init ( struct ldapmap *lm, struct ldapmapping **m ) +{ + struct ldapmapping *mapping; + + assert( m != NULL ); + + *m = NULL; + + mapping = (struct ldapmapping *)ch_calloc( 2, + sizeof( struct ldapmapping ) ); + if ( mapping == NULL ) { + return; + } + + ber_str2bv( "objectclass", STRLENOF("objectclass"), 1, &mapping[0].src); + ber_dupbv( &mapping[0].dst, &mapping[0].src ); + mapping[1].src = mapping[0].src; + mapping[1].dst = mapping[0].dst; + + avl_insert( &lm->map, (caddr_t)&mapping[0], + mapping_cmp, mapping_dup ); + avl_insert( &lm->remap, (caddr_t)&mapping[1], + mapping_cmp, mapping_dup ); + *m = mapping; +} + +int +ldap_back_mapping ( struct ldapmap *map, struct berval *s, struct ldapmapping **m, + int remap ) +{ + Avlnode *tree; + struct ldapmapping fmapping; + + assert( m != NULL ); + + /* let special attrnames slip through (ITS#5760) */ + if ( bvmatch( s, slap_bv_no_attrs ) + || bvmatch( s, slap_bv_all_user_attrs ) + || bvmatch( s, slap_bv_all_operational_attrs ) ) + { + *m = NULL; + return 0; + } + + if ( remap == BACKLDAP_REMAP ) { + tree = map->remap; + + } else { + tree = map->map; + } + + fmapping.src = *s; + *m = (struct ldapmapping *)avl_find( tree, (caddr_t)&fmapping, mapping_cmp ); + if ( *m == NULL ) { + return map->drop_missing; + } + + return 0; +} + +void +ldap_back_map ( struct ldapmap *map, struct berval *s, struct berval *bv, + int remap ) +{ + struct ldapmapping *mapping; + int drop_missing; + + /* map->map may be NULL when mapping is configured, + * but map->remap can't */ + if ( map->remap == NULL ) { + *bv = *s; + return; + } + + BER_BVZERO( bv ); + drop_missing = ldap_back_mapping( map, s, &mapping, remap ); + if ( mapping != NULL ) { + if ( !BER_BVISNULL( &mapping->dst ) ) { + *bv = mapping->dst; + } + return; + } + + if ( !drop_missing ) { + *bv = *s; + } +} + +int +ldap_back_map_attrs( + Operation *op, + struct ldapmap *at_map, + AttributeName *an, + int remap, + char ***mapped_attrs ) +{ + int i, x, j; + char **na; + struct berval mapped; + + if ( an == NULL && op->o_bd->be_extra_anlist == NULL ) { + *mapped_attrs = NULL; + return LDAP_SUCCESS; + } + + i = 0; + if ( an != NULL ) { + for ( ; !BER_BVISNULL( &an[i].an_name ); i++ ) + /* */ ; + } + + x = 0; + if ( op->o_bd->be_extra_anlist != NULL ) { + for ( ; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) + /* */ ; + } + + assert( i > 0 || x > 0 ); + + na = (char **)ber_memcalloc_x( i + x + 1, sizeof(char *), op->o_tmpmemctx ); + if ( na == NULL ) { + *mapped_attrs = NULL; + return LDAP_NO_MEMORY; + } + + j = 0; + if ( i > 0 ) { + for ( i = 0; !BER_BVISNULL( &an[i].an_name ); i++ ) { + ldap_back_map( at_map, &an[i].an_name, &mapped, remap ); + if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) { + na[j++] = mapped.bv_val; + } + } + } + + if ( x > 0 ) { + for ( x = 0; !BER_BVISNULL( &op->o_bd->be_extra_anlist[x].an_name ); x++ ) { + if ( op->o_bd->be_extra_anlist[x].an_desc && + ad_inlist( op->o_bd->be_extra_anlist[x].an_desc, an ) ) + { + continue; + } + + ldap_back_map( at_map, &op->o_bd->be_extra_anlist[x].an_name, &mapped, remap ); + if ( !BER_BVISNULL( &mapped ) && !BER_BVISEMPTY( &mapped ) ) { + na[j++] = mapped.bv_val; + } + } + } + + if ( j == 0 && ( i > 0 || x > 0 ) ) { + na[j++] = LDAP_NO_ATTRS; + } + na[j] = NULL; + + *mapped_attrs = na; + + return LDAP_SUCCESS; +} + +static int +map_attr_value( + dncookie *dc, + AttributeDescription *ad, + struct berval *mapped_attr, + struct berval *value, + struct berval *mapped_value, + int remap, + void *memctx ) +{ + struct berval vtmp; + int freeval = 0; + + ldap_back_map( &dc->target->mt_rwmap.rwm_at, &ad->ad_cname, mapped_attr, remap ); + if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) { +#if 0 + /* + * FIXME: are we sure we need to search oc_map if at_map fails? + */ + ldap_back_map( &dc->target->mt_rwmap.rwm_oc, &ad->ad_cname, mapped_attr, remap ); + if ( BER_BVISNULL( mapped_attr ) || BER_BVISEMPTY( mapped_attr ) ) { + *mapped_attr = ad->ad_cname; + } +#endif + if ( dc->target->mt_rwmap.rwm_at.drop_missing ) { + return -1; + } + + *mapped_attr = ad->ad_cname; + } + + if ( value == NULL ) { + return 0; + } + + if ( ad->ad_type->sat_syntax == slap_schema.si_syn_distinguishedName ) + { + dncookie fdc = *dc; + +#ifdef ENABLE_REWRITE + fdc.ctx = "searchFilterAttrDN"; +#endif + + switch ( ldap_back_dn_massage( &fdc, value, &vtmp ) ) { + case LDAP_SUCCESS: + if ( vtmp.bv_val != value->bv_val ) { + freeval = 1; + } + break; + + case LDAP_UNWILLING_TO_PERFORM: + return -1; + + case LDAP_OTHER: + return -1; + } + + } else if ( ad->ad_type->sat_equality && + ad->ad_type->sat_equality->smr_usage & SLAP_MR_MUTATION_NORMALIZER ) + { + if ( ad->ad_type->sat_equality->smr_normalize( + (SLAP_MR_DENORMALIZE|SLAP_MR_VALUE_OF_ASSERTION_SYNTAX), + NULL, NULL, value, &vtmp, memctx ) ) + { + return -1; + } + freeval = 2; + + } else if ( ad == slap_schema.si_ad_objectClass || ad == slap_schema.si_ad_structuralObjectClass ) { + ldap_back_map( &dc->target->mt_rwmap.rwm_oc, value, &vtmp, remap ); + if ( BER_BVISNULL( &vtmp ) || BER_BVISEMPTY( &vtmp ) ) { + vtmp = *value; + } + + } else { + vtmp = *value; + } + + filter_escape_value_x( &vtmp, mapped_value, memctx ); + + switch ( freeval ) { + case 1: + ber_memfree( vtmp.bv_val ); + break; + case 2: + ber_memfree_x( vtmp.bv_val, memctx ); + break; + } + + return 0; +} + +static int +ldap_back_int_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ) +{ + int i; + Filter *p; + struct berval atmp, + vtmp, + *tmp; + static struct berval + /* better than nothing... */ + ber_bvfalse = BER_BVC( "(!(objectClass=*))" ), + ber_bvtf_false = BER_BVC( "(|)" ), + /* better than nothing... */ + ber_bvtrue = BER_BVC( "(objectClass=*)" ), + ber_bvtf_true = BER_BVC( "(&)" ), +#if 0 + /* no longer needed; preserved for completeness */ + ber_bvundefined = BER_BVC( "(?=undefined)" ), +#endif + ber_bverror = BER_BVC( "(?=error)" ), + ber_bvunknown = BER_BVC( "(?=unknown)" ), + ber_bvnone = BER_BVC( "(?=none)" ); + ber_len_t len; + + assert( fstr != NULL ); + BER_BVZERO( fstr ); + + if ( f == NULL ) { + ber_dupbv_x( fstr, &ber_bvnone, memctx ); + return LDAP_OTHER; + } + + switch ( ( f->f_choice & SLAPD_FILTER_MASK ) ) { + case LDAP_FILTER_EQUALITY: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_GE: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(>=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s>=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_LE: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(<=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s<=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_APPROX: + if ( map_attr_value( dc, f->f_av_desc, &atmp, + &f->f_av_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + vtmp.bv_len + + ( sizeof("(~=)") - 1 ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s~=%s)", + atmp.bv_val, vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case LDAP_FILTER_SUBSTRINGS: + if ( map_attr_value( dc, f->f_sub_desc, &atmp, + NULL, NULL, remap, memctx ) ) + { + goto computed; + } + + /* cannot be a DN ... */ + + fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128 ? */ + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + + if ( !BER_BVISNULL( &f->f_sub_initial ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_initial, &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 2], vtmp.bv_len + 3, + /* "(attr=" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + if ( f->f_sub_any != NULL ) { + for ( i = 0; !BER_BVISNULL( &f->f_sub_any[i] ); i++ ) { + len = fstr->bv_len; + filter_escape_value_x( &f->f_sub_any[i], &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len + 1; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init]*[any*]" */ "%s*)", + vtmp.bv_len ? vtmp.bv_val : "" ); + ber_memfree_x( vtmp.bv_val, memctx ); + } + } + + if ( !BER_BVISNULL( &f->f_sub_final ) ) { + len = fstr->bv_len; + + filter_escape_value_x( &f->f_sub_final, &vtmp, memctx ); + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len - 1], vtmp.bv_len + 3, + /* "(attr=[init*][any*]" */ "%s)", + vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + break; + + case LDAP_FILTER_PRESENT: + if ( map_attr_value( dc, f->f_desc, &atmp, + NULL, NULL, remap, memctx ) ) + { + goto computed; + } + + fstr->bv_len = atmp.bv_len + ( STRLENOF( "(=*)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s=*)", + atmp.bv_val ); + break; + + case LDAP_FILTER_AND: + case LDAP_FILTER_OR: + case LDAP_FILTER_NOT: + fstr->bv_len = STRLENOF( "(%)" ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 128, memctx ); /* FIXME: why 128? */ + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%c)", + f->f_choice == LDAP_FILTER_AND ? '&' : + f->f_choice == LDAP_FILTER_OR ? '|' : '!' ); + + for ( p = f->f_list; p != NULL; p = p->f_next ) { + int rc; + + len = fstr->bv_len; + + rc = ldap_back_int_filter_map_rewrite( dc, p, &vtmp, remap, memctx ); + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fstr->bv_len += vtmp.bv_len; + fstr->bv_val = ber_memrealloc_x( fstr->bv_val, fstr->bv_len + 1, memctx ); + + snprintf( &fstr->bv_val[len-1], vtmp.bv_len + 2, + /*"("*/ "%s)", vtmp.bv_len ? vtmp.bv_val : "" ); + + ber_memfree_x( vtmp.bv_val, memctx ); + } + + break; + + case LDAP_FILTER_EXT: + if ( f->f_mr_desc ) { + if ( map_attr_value( dc, f->f_mr_desc, &atmp, + &f->f_mr_value, &vtmp, remap, memctx ) ) + { + goto computed; + } + + } else { + BER_BVSTR( &atmp, "" ); + filter_escape_value_x( &f->f_mr_value, &vtmp, memctx ); + } + + /* FIXME: cleanup (less ?: operators...) */ + fstr->bv_len = atmp.bv_len + + ( f->f_mr_dnattrs ? STRLENOF( ":dn" ) : 0 ) + + ( !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_len + 1 : 0 ) + + vtmp.bv_len + ( STRLENOF( "(:=)" ) ); + fstr->bv_val = ber_memalloc_x( fstr->bv_len + 1, memctx ); + + snprintf( fstr->bv_val, fstr->bv_len + 1, "(%s%s%s%s:=%s)", + atmp.bv_val, + f->f_mr_dnattrs ? ":dn" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? ":" : "", + !BER_BVISEMPTY( &f->f_mr_rule_text ) ? f->f_mr_rule_text.bv_val : "", + vtmp.bv_len ? vtmp.bv_val : "" ); + ber_memfree_x( vtmp.bv_val, memctx ); + break; + + case SLAPD_FILTER_COMPUTED: + switch ( f->f_result ) { + /* FIXME: treat UNDEFINED as FALSE */ + case SLAPD_COMPARE_UNDEFINED: +computed:; + if ( META_BACK_TGT_NOUNDEFFILTER( dc->target ) ) { + return LDAP_COMPARE_FALSE; + } + /* fallthru */ + + case LDAP_COMPARE_FALSE: + if ( META_BACK_TGT_T_F( dc->target ) ) { + tmp = &ber_bvtf_false; + break; + } + tmp = &ber_bvfalse; + break; + + case LDAP_COMPARE_TRUE: + if ( META_BACK_TGT_T_F( dc->target ) ) { + tmp = &ber_bvtf_true; + break; + } + + tmp = &ber_bvtrue; + break; + + default: + tmp = &ber_bverror; + break; + } + + ber_dupbv_x( fstr, tmp, memctx ); + break; + + default: + ber_dupbv_x( fstr, &ber_bvunknown, memctx ); + break; + } + + return 0; +} + +int +ldap_back_filter_map_rewrite( + dncookie *dc, + Filter *f, + struct berval *fstr, + int remap, + void *memctx ) +{ + int rc; + dncookie fdc; + struct berval ftmp; + static char *dmy = ""; + + rc = ldap_back_int_filter_map_rewrite( dc, f, fstr, remap, memctx ); + +#ifdef ENABLE_REWRITE + if ( rc != LDAP_SUCCESS ) { + return rc; + } + + fdc = *dc; + ftmp = *fstr; + + fdc.ctx = "searchFilter"; + + switch ( rewrite_session( fdc.target->mt_rwmap.rwm_rw, fdc.ctx, + ( !BER_BVISEMPTY( &ftmp ) ? ftmp.bv_val : dmy ), + fdc.conn, &fstr->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( !BER_BVISNULL( fstr ) ) { + fstr->bv_len = strlen( fstr->bv_val ); + + } else { + *fstr = ftmp; + } + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + fdc.ctx, BER_BVISNULL( &ftmp ) ? "" : ftmp.bv_val, + BER_BVISNULL( fstr ) ? "" : fstr->bv_val ); + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + fdc.rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( fdc.rs ) { + fdc.rs->sr_err = LDAP_OTHER; + fdc.rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( fstr->bv_val == dmy ) { + BER_BVZERO( fstr ); + + } else if ( fstr->bv_val != ftmp.bv_val ) { + /* NOTE: need to realloc mapped filter on slab + * and free the original one, until librewrite + * becomes slab-aware + */ + ber_dupbv_x( &ftmp, fstr, memctx ); + ch_free( fstr->bv_val ); + *fstr = ftmp; + } +#endif /* ENABLE_REWRITE */ + + return rc; +} + +int +ldap_back_referral_result_rewrite( + dncookie *dc, + BerVarray a_vals, + void *memctx +) +{ + int i, last; + + assert( dc != NULL ); + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[ last ] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[ i ] ); i++ ) { + struct berval dn, + olddn = BER_BVNULL; + int rc; + LDAPURLDesc *ludp; + + rc = ldap_url_parse( a_vals[ i ].bv_val, &ludp ); + if ( rc != LDAP_URL_SUCCESS ) { + /* leave attr untouched if massage failed */ + continue; + } + + /* FIXME: URLs like "ldap:///dc=suffix" if passed + * thru ldap_url_parse() and ldap_url_desc2str() + * get rewritten as "ldap:///dc=suffix??base"; + * we don't want this to occur... */ + if ( ludp->lud_scope == LDAP_SCOPE_BASE ) { + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + } + + ber_str2bv( ludp->lud_dn, 0, 0, &olddn ); + + rc = ldap_back_dn_massage( dc, &olddn, &dn ); + switch ( rc ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ber_memfree( a_vals[ i ].bv_val ); + if ( last > i ) { + a_vals[ i ] = a_vals[ last ]; + } + BER_BVZERO( &a_vals[ last ] ); + last--; + i--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &dn ) && olddn.bv_val != dn.bv_val ) + { + char *newurl; + + ludp->lud_dn = dn.bv_val; + newurl = ldap_url_desc2str( ludp ); + free( dn.bv_val ); + if ( newurl == NULL ) { + /* FIXME: leave attr untouched + * even if ldap_url_desc2str failed... + */ + break; + } + + ber_memfree_x( a_vals[ i ].bv_val, memctx ); + ber_str2bv_x( newurl, 0, 1, &a_vals[ i ], memctx ); + ber_memfree( newurl ); + ludp->lud_dn = olddn.bv_val; + } + break; + } + + ldap_free_urldesc( ludp ); + } + + return 0; +} + +/* + * I don't like this much, but we need two different + * functions because different heap managers may be + * in use in back-ldap/meta to reduce the amount of + * calls to malloc routines, and some of the free() + * routines may be macros with args + */ +int +ldap_dnattr_rewrite( + dncookie *dc, + BerVarray a_vals +) +{ + struct berval bv; + int i, last; + + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ch_free( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &bv ) && bv.bv_val != a_vals[i].bv_val ) { + ch_free( a_vals[i].bv_val ); + a_vals[i] = bv; + } + break; + } + } + + return 0; +} + +int +ldap_dnattr_result_rewrite( + dncookie *dc, + BerVarray a_vals +) +{ + struct berval bv; + int i, last; + + assert( a_vals != NULL ); + + for ( last = 0; !BER_BVISNULL( &a_vals[last] ); last++ ) + ; + last--; + + for ( i = 0; !BER_BVISNULL( &a_vals[i] ); i++ ) { + switch ( ldap_back_dn_massage( dc, &a_vals[i], &bv ) ) { + case LDAP_UNWILLING_TO_PERFORM: + /* + * FIXME: need to check if it may be considered + * legal to trim values when adding/modifying; + * it should be when searching (e.g. ACLs). + */ + ber_memfree( a_vals[i].bv_val ); + if ( last > i ) { + a_vals[i] = a_vals[last]; + } + BER_BVZERO( &a_vals[last] ); + last--; + break; + + default: + /* leave attr untouched if massage failed */ + if ( !BER_BVISNULL( &bv ) && a_vals[i].bv_val != bv.bv_val ) { + ber_memfree( a_vals[i].bv_val ); + a_vals[i] = bv; + } + break; + } + } + + return 0; +} + diff --git a/servers/slapd/back-meta/modify.c b/servers/slapd/back-meta/modify.c new file mode 100644 index 0000000..f06c076 --- /dev/null +++ b/servers/slapd/back-meta/modify.c @@ -0,0 +1,221 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_modify( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int rc = 0; + LDAPMod **modv = NULL; + LDAPMod *mods = NULL; + Modifications *ml; + int candidate = -1, i; + int isupdate; + struct berval mdn = BER_BVNULL; + struct berval mapped; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + /* + * Rewrite the modify dn, if needed + */ + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "modifyDN"; + + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + for ( i = 0, ml = op->orm_modlist; ml; i++ ,ml = ml->sml_next ) + ; + + mods = ch_malloc( sizeof( LDAPMod )*i ); + if ( mods == NULL ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + modv = ( LDAPMod ** )ch_malloc( ( i + 1 )*sizeof( LDAPMod * ) ); + if ( modv == NULL ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + + dc.ctx = "modifyAttrDN"; + isupdate = be_shadow_update( op ); + for ( i = 0, ml = op->orm_modlist; ml; ml = ml->sml_next ) { + int j, is_oc = 0; + + if ( !isupdate && !get_relax( op ) && ml->sml_desc->ad_type->sat_no_user_mod ) + { + continue; + } + + if ( ml->sml_desc == slap_schema.si_ad_objectClass + || ml->sml_desc == slap_schema.si_ad_structuralObjectClass ) + { + is_oc = 1; + mapped = ml->sml_desc->ad_cname; + + } else { + ldap_back_map( &mt->mt_rwmap.rwm_at, + &ml->sml_desc->ad_cname, &mapped, + BACKLDAP_MAP ); + if ( BER_BVISNULL( &mapped ) || BER_BVISEMPTY( &mapped ) ) { + continue; + } + } + + modv[ i ] = &mods[ i ]; + mods[ i ].mod_op = ml->sml_op | LDAP_MOD_BVALUES; + mods[ i ].mod_type = mapped.bv_val; + + /* + * FIXME: dn-valued attrs should be rewritten + * to allow their use in ACLs at the back-ldap + * level. + */ + if ( ml->sml_values != NULL ) { + if ( is_oc ) { + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); ) { + struct ldapmapping *mapping; + + ldap_back_mapping( &mt->mt_rwmap.rwm_oc, + &ml->sml_values[ j ], &mapping, BACKLDAP_MAP ); + + if ( mapping == NULL ) { + if ( mt->mt_rwmap.rwm_oc.drop_missing ) { + continue; + } + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + + } else { + mods[ i ].mod_bvalues[ j ] = &mapping->dst; + } + j++; + } + mods[ i ].mod_bvalues[ j ] = NULL; + + } else { + if ( ml->sml_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + ( void )ldap_dnattr_rewrite( &dc, ml->sml_values ); + if ( ml->sml_values == NULL ) { + continue; + } + } + + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) + ; + mods[ i ].mod_bvalues = + (struct berval **)ch_malloc( ( j + 1 ) * + sizeof( struct berval * ) ); + for ( j = 0; !BER_BVISNULL( &ml->sml_values[ j ] ); j++ ) { + mods[ i ].mod_bvalues[ j ] = &ml->sml_values[ j ]; + } + mods[ i ].mod_bvalues[ j ] = NULL; + } + + } else { + mods[ i ].mod_bvalues = NULL; + } + + i++; + } + modv[ i ] = 0; + +retry:; + ctrls = op->o_ctrls; + rc = meta_back_controls_add( op, rs, mc, candidate, &ctrls ); + if ( rc != LDAP_SUCCESS ) { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_modify_ext( mc->mc_conns[ candidate ].msc_ld, mdn.bv_val, + modv, ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_MODIFY ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + if ( modv != NULL ) { + for ( i = 0; modv[ i ]; i++ ) { + free( modv[ i ]->mod_bvalues ); + } + } + free( mods ); + free( modv ); + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/modrdn.c b/servers/slapd/back-meta/modrdn.c new file mode 100644 index 0000000..f14c23b --- /dev/null +++ b/servers/slapd/back-meta/modrdn.c @@ -0,0 +1,177 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_modrdn( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt; + metaconn_t *mc; + int candidate = -1; + struct berval mdn = BER_BVNULL, + mnewSuperior = BER_BVNULL; + dncookie dc; + int msgid; + ldap_back_send_t retrying = LDAP_BACK_RETRYING; + LDAPControl **ctrls = NULL; + struct berval newrdn = BER_BVNULL; + + mc = meta_back_getconn( op, rs, &candidate, LDAP_BACK_SENDERR ); + if ( !mc || !meta_back_dobind( op, rs, mc, LDAP_BACK_SENDERR ) ) { + return rs->sr_err; + } + + assert( mc->mc_conns[ candidate ].msc_ld != NULL ); + + mt = mi->mi_targets[ candidate ]; + dc.target = mt; + dc.conn = op->o_conn; + dc.rs = rs; + + if ( op->orr_newSup ) { + + /* + * NOTE: the newParent, if defined, must be on the + * same target as the entry to be renamed. This check + * has been anticipated in meta_back_getconn() + */ + /* + * FIXME: one possibility is to delete the entry + * from one target and add it to the other; + * unfortunately we'd need write access to both, + * which is nearly impossible; for administration + * needs, the rootdn of the metadirectory could + * be mapped to an administrative account on each + * target (the binddn?); we'll see. + */ + /* + * NOTE: we need to port the identity assertion + * feature from back-ldap + */ + + /* needs LDAPv3 */ + switch ( mt->mt_version ) { + case LDAP_VERSION3: + break; + + case 0: + if ( op->o_protocol == 0 || op->o_protocol == LDAP_VERSION3 ) { + break; + } + /* fall thru */ + + default: + /* op->o_protocol cannot be anything but LDAPv3, + * otherwise wouldn't be here */ + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + send_ldap_result( op, rs ); + goto cleanup; + } + + /* + * Rewrite the new superior, if defined and required + */ + dc.ctx = "newSuperiorDN"; + if ( ldap_back_dn_massage( &dc, op->orr_newSup, &mnewSuperior ) ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + } + + /* + * Rewrite the modrdn dn, if required + */ + dc.ctx = "modrDN"; + if ( ldap_back_dn_massage( &dc, &op->o_req_dn, &mdn ) ) { + rs->sr_err = LDAP_OTHER; + send_ldap_result( op, rs ); + goto cleanup; + } + + /* NOTE: we need to copy the newRDN in case it was formed + * from a DN by simply changing the length (ITS#5397) */ + newrdn = op->orr_newrdn; + if ( newrdn.bv_val[ newrdn.bv_len ] != '\0' ) { + ber_dupbv_x( &newrdn, &op->orr_newrdn, op->o_tmpmemctx ); + } + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, mc, candidate, &ctrls ) != LDAP_SUCCESS ) + { + send_ldap_result( op, rs ); + goto cleanup; + } + + rs->sr_err = ldap_rename( mc->mc_conns[ candidate ].msc_ld, + mdn.bv_val, newrdn.bv_val, + mnewSuperior.bv_val, op->orr_deleteoldrdn, + ctrls, NULL, &msgid ); + rs->sr_err = meta_back_op_result( mc, op, rs, candidate, msgid, + mt->mt_timeout[ SLAP_OP_MODRDN ], ( LDAP_BACK_SENDRESULT | retrying ) ); + if ( rs->sr_err == LDAP_UNAVAILABLE && retrying ) { + retrying &= ~LDAP_BACK_RETRYING; + if ( meta_back_retry( op, rs, &mc, candidate, LDAP_BACK_SENDERR ) ) { + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + } + +cleanup:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + + if ( mdn.bv_val != op->o_req_dn.bv_val ) { + free( mdn.bv_val ); + BER_BVZERO( &mdn ); + } + + if ( !BER_BVISNULL( &mnewSuperior ) + && mnewSuperior.bv_val != op->orr_newSup->bv_val ) + { + free( mnewSuperior.bv_val ); + BER_BVZERO( &mnewSuperior ); + } + + if ( newrdn.bv_val != op->orr_newrdn.bv_val ) { + op->o_tmpfree( newrdn.bv_val, op->o_tmpmemctx ); + } + + if ( mc ) { + meta_back_release_conn( mi, mc ); + } + + return rs->sr_err; +} + diff --git a/servers/slapd/back-meta/proto-meta.h b/servers/slapd/back-meta/proto-meta.h new file mode 100644 index 0000000..358bbd4 --- /dev/null +++ b/servers/slapd/back-meta/proto-meta.h @@ -0,0 +1,54 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#ifndef PROTO_META_H +#define PROTO_META_H + +LDAP_BEGIN_DECL + +extern BI_init meta_back_initialize; + +extern BI_open meta_back_open; +extern BI_close meta_back_close; +extern BI_destroy meta_back_destroy; + +extern BI_db_init meta_back_db_init; +extern BI_db_open meta_back_db_open; +extern BI_db_destroy meta_back_db_destroy; +extern BI_db_config meta_back_db_config; + +extern BI_op_bind meta_back_bind; +extern BI_op_search meta_back_search; +extern BI_op_compare meta_back_compare; +extern BI_op_modify meta_back_modify; +extern BI_op_modrdn meta_back_modrdn; +extern BI_op_add meta_back_add; +extern BI_op_delete meta_back_delete; +extern BI_op_abandon meta_back_abandon; + +extern BI_connection_destroy meta_back_conn_destroy; + +int meta_back_init_cf( BackendInfo *bi ); + +LDAP_END_DECL + +#endif /* PROTO_META_H */ diff --git a/servers/slapd/back-meta/search.c b/servers/slapd/back-meta/search.c new file mode 100644 index 0000000..7130bbc --- /dev/null +++ b/servers/slapd/back-meta/search.c @@ -0,0 +1,2465 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/socket.h> +#include <ac/string.h> +#include <ac/time.h> + +#include "lutil.h" +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" +#include "../../../libraries/liblber/lber-int.h" + +/* 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) + +static int +meta_send_entry( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int i, + LDAPMessage *e ); + +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; + +/* + * meta_search_dobind_init() + * + * initiates bind for a candidate target of a search. + */ +static meta_search_candidate_t +meta_search_dobind_init( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + SlapReply *candidates ) +{ + metaconn_t *mc = *mcp; + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + struct berval binddn = msc->msc_bound_ndn, + cred = msc->msc_cred; + int method; + + int rc; + + meta_search_candidate_t retcode; + + Debug( LDAP_DEBUG_TRACE, "%s >>> meta_search_dobind_init[%d]\n", + op->o_log_prefix, candidate, 0 ); + + /* + * all the targets are already bound as pseudoroot + */ + if ( mc->mc_authz_target == META_BOUND_ALL ) { + return META_SEARCH_CANDIDATE; + } + + retcode = META_SEARCH_BINDING; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + 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; + } + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p%s DN=\"%s\"", + (void *)mc, (void *)msc->msc_ld, + bound ? " bound" : " anonymous", + bound == 0 ? "" : msc->msc_bound_ndn.bv_val ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#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 + char buf[ SLAP_TEXT_BUFLEN ] = { '\0' }; + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p needbind", + (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#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 + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), " mc=%p ld=%p binding", + (void *)mc, (void *)msc->msc_ld ); + Debug( LDAP_DEBUG_ANY, "### %s meta_search_dobind_init[%d]%s\n", + op->o_log_prefix, candidate, buf ); +#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 meta_search_dobind_init[%d] mc=%p ld=NULL\n", + op->o_log_prefix, candidate, (void *)mc ); + + rc = meta_back_init_one_conn( op, rs, *mcp, candidate, + LDAP_BACK_CONN_ISPRIV( *mcp ), LDAP_BACK_DONTSEND, 0 ); + switch ( rc ) { + case LDAP_SUCCESS: + assert( msc->msc_ld != NULL ); + break; + + case LDAP_SERVER_DOWN: + case LDAP_UNAVAILABLE: + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + goto down; + + default: + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + goto other; + } + } + + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( retcode != META_SEARCH_BINDING ) { + return retcode; + } + + /* NOTE: this obsoletes pseudorootdn */ + 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 = meta_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_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + 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 ); + + /* connect must be async only the first time... */ + ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_ON ); + +retry:; + if ( !BER_BVISEMPTY( &binddn ) && BER_BVISEMPTY( &cred ) ) { + /* bind anonymously? */ + Debug( LDAP_DEBUG_ANY, "%s meta_search_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 meta_search_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; + } + + rc = ldap_sasl_bind( msc->msc_ld, binddn.bv_val, LDAP_SASL_SIMPLE, &cred, + NULL, NULL, &candidates[ candidate ].sr_msgid ); + +#ifdef DEBUG_205 + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), "meta_search_dobind_init[%d] mc=%p ld=%p rc=%d", + candidate, (void *)mc, (void *)mc->mc_conns[ candidate ].msc_ld, rc ); + Debug( LDAP_DEBUG_ANY, "### %s %s\n", + op->o_log_prefix, buf, 0 ); + } +#endif /* DEBUG_205 */ + + switch ( rc ) { + case LDAP_SUCCESS: + assert( candidates[ candidate ].sr_msgid >= 0 ); + META_BINDING_SET( &candidates[ candidate ] ); + return META_SEARCH_BINDING; + + case LDAP_X_CONNECTING: + /* must retry, same conn */ + candidates[ candidate ].sr_msgid = META_MSGID_CONNECTING; + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + return META_SEARCH_CONNECTING; + + case LDAP_SERVER_DOWN: +down:; + /* This is the worst thing that could happen: + * the search will wait until the retry is over. */ + if ( !META_IS_RETRYING( &candidates[ candidate ] ) ) { + META_RETRYING_SET( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + + assert( mc->mc_refcnt > 0 ); + if ( LogTest( LDAP_DEBUG_ANY ) ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + /* this lock is required; however, + * it's invoked only when logging is on */ + ldap_pvt_thread_mutex_lock( &mt->mt_uri_mutex ); + snprintf( buf, sizeof( buf ), + "retrying URI=\"%s\" DN=\"%s\"", + mt->mt_uri, + BER_BVISNULL( &msc->msc_bound_ndn ) ? + "" : msc->msc_bound_ndn.bv_val ); + ldap_pvt_thread_mutex_unlock( &mt->mt_uri_mutex ); + + Debug( LDAP_DEBUG_ANY, + "%s meta_search_dobind_init[%d]: %s.\n", + op->o_log_prefix, candidate, buf ); + } + + meta_clear_one_candidate( op, mc, candidate ); + LDAP_BACK_CONN_ISBOUND_CLEAR( msc ); + + ( void )rewrite_session_delete( mt->mt_rwmap.rwm_rw, op->o_conn ); + + /* mc here must be the regular mc, reset and ready for init */ + rc = meta_back_init_one_conn( op, rs, mc, candidate, + LDAP_BACK_CONN_ISPRIV( mc ), LDAP_BACK_DONTSEND, 0 ); + + if ( rc == LDAP_SUCCESS ) { + LDAP_BACK_CONN_BINDING_SET( msc ); + } + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + if ( rc == LDAP_SUCCESS ) { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + binddn = msc->msc_bound_ndn; + cred = msc->msc_cred; + goto retry; + } + } + + if ( *mcp == NULL ) { + retcode = META_SEARCH_ERR; + rc = LDAP_UNAVAILABLE; + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + break; + } + /* fall thru */ + + default: +other:; + /* convert rc to the correct LDAP error and send it back to the client: + assing the error to rs, so we can use it as argument to slap_map_api2result + and then assign the output back to rs->sr_err */ + rs->sr_err = rc; + rs->sr_err = slap_map_api2result( rs ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + meta_clear_one_candidate( op, mc, candidate ); + candidates[ candidate ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + + retcode = META_SEARCH_ERR; + + } else { + retcode = META_SEARCH_NOT_CANDIDATE; + } + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + break; + } + + return retcode; +} + +static meta_search_candidate_t +meta_search_dobind_result( + Operation *op, + SlapReply *rs, + metaconn_t **mcp, + int candidate, + SlapReply *candidates, + LDAPMessage *res ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metaconn_t *mc = *mcp; + metasingleconn_t *msc = &mc->mc_conns[ candidate ]; + + meta_search_candidate_t retcode = META_SEARCH_NOT_CANDIDATE; + int rc; + + assert( msc->msc_ld != NULL ); + + /* FIXME: matched? referrals? response controls? */ + rc = ldap_parse_result( msc->msc_ld, res, + &candidates[ candidate ].sr_err, + NULL, NULL, NULL, NULL, 0 ); + if ( rc != LDAP_SUCCESS ) { + candidates[ candidate ].sr_err = rc; + } + rc = slap_map_api2result( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + LDAP_BACK_CONN_BINDING_CLEAR( msc ); + if ( rc != LDAP_SUCCESS ) { + meta_clear_one_candidate( op, mc, candidate ); + candidates[ candidate ].sr_err = rc; + if ( META_BACK_ONERR_STOP( mi ) ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + meta_back_release_conn_lock( mi, mc, 0 ); + *mcp = NULL; + retcode = META_SEARCH_ERR; + rs->sr_err = rc; + } + + } else { + /* FIXME: check if bound as idassert authcDN! */ + if ( BER_BVISNULL( &msc->msc_bound_ndn ) + || BER_BVISEMPTY( &msc->msc_bound_ndn ) ) + { + LDAP_BACK_CONN_ISANON_SET( msc ); + + } else { + if ( META_BACK_TGT_SAVECRED( mt ) && + !BER_BVISNULL( &msc->msc_cred ) && + !BER_BVISEMPTY( &msc->msc_cred ) ) + { + ldap_set_rebind_proc( msc->msc_ld, mt->mt_rebind_f, msc ); + } + LDAP_BACK_CONN_ISBOUND_SET( msc ); + } + retcode = META_SEARCH_CANDIDATE; + + /* connect must be async */ + ldap_set_option( msc->msc_ld, LDAP_OPT_CONNECT_ASYNC, LDAP_OPT_OFF ); + } + + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + META_BINDING_CLEAR( &candidates[ candidate ] ); + + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + return retcode; +} + +static meta_search_candidate_t +meta_back_search_start( + Operation *op, + SlapReply *rs, + dncookie *dc, + metaconn_t **mcp, + int candidate, + SlapReply *candidates, + struct berval *prcookie, + ber_int_t prsize ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metatarget_t *mt = mi->mi_targets[ candidate ]; + metasingleconn_t *msc = &(*mcp)->mc_conns[ candidate ]; + struct berval realbase = op->o_req_dn; + int realscope = op->ors_scope; + struct berval mbase = BER_BVNULL; + struct berval mfilter = BER_BVNULL; + char **mapped_attrs = NULL; + int rc; + meta_search_candidate_t retcode; + struct timeval tv, *tvp = NULL; + int nretries = 1; + LDAPControl **ctrls = NULL; +#ifdef SLAPD_META_CLIENT_PR + LDAPControl **save_ctrls = NULL; +#endif /* SLAPD_META_CLIENT_PR */ + + /* this should not happen; just in case... */ + if ( msc->msc_ld == NULL ) { + Debug( LDAP_DEBUG_ANY, + "%s: meta_back_search_start candidate=%d ld=NULL%s.\n", + op->o_log_prefix, candidate, + META_BACK_ONERR_STOP( mi ) ? "" : " (ignored)" ); + candidates[ candidate ].sr_err = LDAP_OTHER; + if ( META_BACK_ONERR_STOP( mi ) ) { + return META_SEARCH_ERR; + } + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + return META_SEARCH_NOT_CANDIDATE; + } + + Debug( LDAP_DEBUG_TRACE, "%s >>> meta_back_search_start[%d]\n", op->o_log_prefix, candidate, 0 ); + + /* + * modifies the base according to the scope, if required + */ + if ( mt->mt_nsuffix.bv_len > op->o_req_ndn.bv_len ) { + switch ( op->ors_scope ) { + case LDAP_SCOPE_SUBTREE: + /* + * make the target suffix the new base + * FIXME: this is very forgiving, because + * "illegal" searchBases may be turned + * into the suffix of the target; however, + * the requested searchBase already passed + * thru the candidate analyzer... + */ + if ( dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) { + realbase = mt->mt_nsuffix; + if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) { + realscope = LDAP_SCOPE_SUBORDINATE; + } + + } else { + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + break; + + case LDAP_SCOPE_SUBORDINATE: + case LDAP_SCOPE_ONELEVEL: + { + struct berval rdn = mt->mt_nsuffix; + rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," ); + if ( dnIsOneLevelRDN( &rdn ) + && dnIsSuffix( &mt->mt_nsuffix, &op->o_req_ndn ) ) + { + /* + * if there is exactly one level, + * make the target suffix the new + * base, and make scope "base" + */ + realbase = mt->mt_nsuffix; + if ( op->ors_scope == LDAP_SCOPE_SUBORDINATE ) { + if ( mt->mt_scope == LDAP_SCOPE_SUBORDINATE ) { + realscope = LDAP_SCOPE_SUBORDINATE; + } else { + realscope = LDAP_SCOPE_SUBTREE; + } + } else { + realscope = LDAP_SCOPE_BASE; + } + break; + } /* else continue with the next case */ + } + + case LDAP_SCOPE_BASE: + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + } + + /* check filter expression */ + if ( mt->mt_filter ) { + metafilter_t *mf; + for ( mf = mt->mt_filter; mf; mf = mf->mf_next ) { + if ( regexec( &mf->mf_regex, op->ors_filterstr.bv_val, 0, NULL, 0 ) == 0 ) + break; + } + /* nothing matched, this target is no longer a candidate */ + if ( !mf ) { + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + } + + /* initiate dobind */ + retcode = meta_search_dobind_init( op, rs, mcp, candidate, candidates ); + + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%d]=%d\n", op->o_log_prefix, candidate, retcode ); + + if ( retcode != META_SEARCH_CANDIDATE ) { + goto doreturn; + } + + /* + * Rewrite the search base, if required + */ + dc->target = mt; + dc->ctx = "searchBase"; + switch ( ldap_back_dn_massage( dc, &realbase, &mbase ) ) { + case LDAP_SUCCESS: + break; + + case LDAP_UNWILLING_TO_PERFORM: + rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + rs->sr_text = "Operation not allowed"; + send_ldap_result( op, rs ); + retcode = META_SEARCH_ERR; + goto doreturn; + + default: + + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto doreturn; + } + + /* + * Maps filter + */ + rc = ldap_back_filter_map_rewrite( dc, op->ors_filter, + &mfilter, BACKLDAP_MAP, op->o_tmpmemctx ); + switch ( rc ) { + case LDAP_SUCCESS: + break; + + case LDAP_COMPARE_FALSE: + default: + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + /* + * Maps required attributes + */ + rc = ldap_back_map_attrs( op, &mt->mt_rwmap.rwm_at, + op->ors_attrs, BACKLDAP_MAP, &mapped_attrs ); + if ( rc != LDAP_SUCCESS ) { + /* + * this target is no longer candidate + */ + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + tv.tv_sec = op->ors_tlimit > 0 ? op->ors_tlimit : 1; + tv.tv_usec = 0; + tvp = &tv; + } + +#ifdef SLAPD_META_CLIENT_PR + save_ctrls = op->o_ctrls; + { + LDAPControl *pr_c = NULL; + int i = 0, nc = 0; + + if ( save_ctrls ) { + for ( ; save_ctrls[i] != NULL; i++ ); + nc = i; + pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, save_ctrls, NULL ); + } + + if ( pr_c != NULL ) nc--; + if ( mt->mt_ps > 0 || prcookie != NULL ) nc++; + + if ( mt->mt_ps > 0 || prcookie != NULL || pr_c != NULL ) { + int src = 0, dst = 0; + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + struct berval val = BER_BVNULL; + ber_len_t len; + + len = sizeof( LDAPControl * )*( nc + 1 ) + sizeof( LDAPControl ); + + if ( mt->mt_ps > 0 || prcookie != NULL ) { + struct berval nullcookie = BER_BVNULL; + ber_tag_t tag; + + if ( prsize == 0 && mt->mt_ps > 0 ) prsize = mt->mt_ps; + if ( prcookie == NULL ) prcookie = &nullcookie; + + ber_init2( ber, NULL, LBER_USE_DER ); + tag = ber_printf( ber, "{iO}", prsize, prcookie ); + if ( tag == LBER_ERROR ) { + /* error */ + (void) ber_free_buf( ber ); + goto done_pr; + } + + tag = ber_flatten2( ber, &val, 0 ); + if ( tag == LBER_ERROR ) { + /* error */ + (void) ber_free_buf( ber ); + goto done_pr; + } + + len += val.bv_len + 1; + } + + op->o_ctrls = op->o_tmpalloc( len, op->o_tmpmemctx ); + if ( save_ctrls ) { + for ( ; save_ctrls[ src ] != NULL; src++ ) { + if ( save_ctrls[ src ] != pr_c ) { + op->o_ctrls[ dst ] = save_ctrls[ src ]; + dst++; + } + } + } + + if ( mt->mt_ps > 0 || prcookie != NULL ) { + op->o_ctrls[ dst ] = (LDAPControl *)&op->o_ctrls[ nc + 1 ]; + + op->o_ctrls[ dst ]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS; + op->o_ctrls[ dst ]->ldctl_iscritical = 1; + + op->o_ctrls[ dst ]->ldctl_value.bv_val = (char *)&op->o_ctrls[ dst ][ 1 ]; + AC_MEMCPY( op->o_ctrls[ dst ]->ldctl_value.bv_val, val.bv_val, val.bv_len + 1 ); + op->o_ctrls[ dst ]->ldctl_value.bv_len = val.bv_len; + dst++; + + (void)ber_free_buf( ber ); + } + + op->o_ctrls[ dst ] = NULL; + } +done_pr:; + } +#endif /* SLAPD_META_CLIENT_PR */ + +retry:; + ctrls = op->o_ctrls; + if ( meta_back_controls_add( op, rs, *mcp, candidate, &ctrls ) + != LDAP_SUCCESS ) + { + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_NOT_CANDIDATE; + goto done; + } + + /* + * Starts the search + */ + assert( msc->msc_ld != NULL ); + rc = ldap_pvt_search( msc->msc_ld, + mbase.bv_val, realscope, mfilter.bv_val, + mapped_attrs, op->ors_attrsonly, + ctrls, NULL, tvp, op->ors_slimit, op->ors_deref, + &candidates[ candidate ].sr_msgid ); + switch ( rc ) { + case LDAP_SUCCESS: + retcode = META_SEARCH_CANDIDATE; + break; + + case LDAP_SERVER_DOWN: + if ( nretries && meta_back_retry( op, rs, mcp, candidate, LDAP_BACK_DONTSEND ) ) { + nretries = 0; + /* if the identity changed, there might be need to re-authz */ + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); + goto retry; + } + + if ( *mcp == NULL ) { + retcode = META_SEARCH_ERR; + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + break; + } + /* fall thru */ + + default: + candidates[ candidate ].sr_msgid = META_MSGID_IGNORE; + retcode = META_SEARCH_NOT_CANDIDATE; + } + +done:; + (void)mi->mi_ldap_extra->controls_free( op, rs, &ctrls ); +#ifdef SLAPD_META_CLIENT_PR + if ( save_ctrls != op->o_ctrls ) { + op->o_tmpfree( op->o_ctrls, op->o_tmpmemctx ); + op->o_ctrls = save_ctrls; + } +#endif /* SLAPD_META_CLIENT_PR */ + + if ( mapped_attrs ) { + ber_memfree_x( mapped_attrs, op->o_tmpmemctx ); + } + if ( mfilter.bv_val != op->ors_filterstr.bv_val ) { + ber_memfree_x( mfilter.bv_val, op->o_tmpmemctx ); + } + if ( mbase.bv_val != realbase.bv_val ) { + free( mbase.bv_val ); + } + +doreturn:; + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_back_search_start[%d]=%d\n", op->o_log_prefix, candidate, retcode ); + + return retcode; +} + +int +meta_back_search( Operation *op, SlapReply *rs ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + metaconn_t *mc; + struct timeval save_tv = { 0, 0 }, + tv; + time_t stoptime = (time_t)(-1), + lastres_time = slap_get_time(), + timeout = 0; + int rc = 0, sres = LDAP_SUCCESS; + char *matched = NULL; + int last = 0, ncandidates = 0, + initial_candidates = 0, candidate_match = 0, + needbind = 0; + ldap_back_send_t sendok = LDAP_BACK_SENDERR; + long i; + dncookie dc; + int is_ok = 0; + void *savepriv; + SlapReply *candidates = NULL; + int do_taint = 0; + + rs_assert_ready( rs ); + rs->sr_flags &= ~REP_ENTRY_MASK; /* paranoia, we can set rs = non-entry */ + + /* + * controls are set in ldap_back_dobind() + * + * FIXME: in case of values return filter, we might want + * to map attrs and maybe rewrite value + */ +getconn:; + mc = meta_back_getconn( op, rs, NULL, sendok ); + if ( !mc ) { + return rs->sr_err; + } + + dc.conn = op->o_conn; + dc.rs = rs; + + if ( candidates == NULL ) candidates = meta_back_candidates_get( op ); + /* + * Inits searches + */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + /* reset sr_msgid; it is used in most loops + * to check if that target is still to be considered */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + /* a target is marked as candidate by meta_back_getconn(); + * if for any reason (an error, it's over or so) it is + * no longer active, sr_msgid is set to META_MSGID_IGNORE + * but it remains candidate, which means it has been active + * at some point during the operation. This allows to + * use its response code and more to compute the final + * response */ + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + candidates[ i ].sr_matched = NULL; + candidates[ i ].sr_text = NULL; + candidates[ i ].sr_ref = NULL; + candidates[ i ].sr_ctrls = NULL; + candidates[ i ].sr_nentries = 0; + + /* get largest timeout among candidates */ + if ( mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] + && mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ] > timeout ) + { + timeout = mi->mi_targets[ i ]->mt_timeout[ SLAP_OP_SEARCH ]; + } + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) + || candidates[ i ].sr_err != LDAP_SUCCESS ) + { + continue; + } + + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + case META_SEARCH_NOT_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + break; + + case META_SEARCH_NEED_BIND: + ++needbind; + /* fallthru */ + + case META_SEARCH_CONNECTING: + case META_SEARCH_CANDIDATE: + case META_SEARCH_BINDING: + candidates[ i ].sr_type = REP_INTERMEDIATE; + ++ncandidates; + break; + + case META_SEARCH_ERR: + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + rc = -1; + goto finish; + + default: + assert( 0 ); + break; + } + } + + if ( ncandidates > 0 && needbind == ncandidates ) { + /* + * give up the second time... + * + * NOTE: this should not occur the second time, since a fresh + * connection has ben created; however, targets may also + * need bind because the bind timed out or so. + */ + if ( sendok & LDAP_BACK_BINDING ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search: unable to initialize conn\n", + op->o_log_prefix, 0, 0 ); + rs->sr_err = LDAP_UNAVAILABLE; + rs->sr_text = "unable to initialize connection to remote targets"; + send_ldap_result( op, rs ); + rc = -1; + goto finish; + } + + /* FIXME: better create a separate connection? */ + sendok |= LDAP_BACK_BINDING; + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "*** %s drop mc=%p create new connection\n", + op->o_log_prefix, (void *)mc, 0 ); +#endif /* DEBUG_205 */ + + meta_back_release_conn( mi, mc ); + mc = NULL; + + needbind = 0; + ncandidates = 0; + + goto getconn; + } + + initial_candidates = ncandidates; + + if ( LogTest( LDAP_DEBUG_TRACE ) ) { + char cnd[ SLAP_TEXT_BUFLEN ]; + int c; + + for ( c = 0; c < mi->mi_ntargets; c++ ) { + if ( META_IS_CANDIDATE( &candidates[ c ] ) ) { + cnd[ c ] = '*'; + } else { + cnd[ c ] = ' '; + } + } + cnd[ c ] = '\0'; + + Debug( LDAP_DEBUG_TRACE, "%s meta_back_search: ncandidates=%d " + "cnd=\"%s\"\n", op->o_log_prefix, ncandidates, cnd ); + } + + if ( initial_candidates == 0 ) { + /* NOTE: here we are not sending any matchedDN; + * this is intended, because if the back-meta + * is serving this search request, but no valid + * candidate could be looked up, it means that + * there is a hole in the mapping of the targets + * and thus no knowledge of any remote superior + * is available */ + Debug( LDAP_DEBUG_ANY, "%s meta_back_search: " + "base=\"%s\" scope=%d: " + "no candidate could be selected\n", + op->o_log_prefix, op->o_req_dn.bv_val, + op->ors_scope ); + + /* FIXME: we're sending the first error we encounter; + * maybe we should pick the worst... */ + rc = LDAP_NO_SUCH_OBJECT; + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( META_IS_CANDIDATE( &candidates[ i ] ) + && candidates[ i ].sr_err != LDAP_SUCCESS ) + { + rc = candidates[ i ].sr_err; + break; + } + } + + send_ldap_error( op, rs, rc, NULL ); + + goto finish; + } + + /* We pull apart the ber result, stuff it into a slapd entry, and + * let send_search_entry stuff it back into ber format. Slow & ugly, + * but this is necessary for version matching, and for ACL processing. + */ + + if ( op->ors_tlimit != SLAP_NO_LIMIT ) { + stoptime = op->o_time + op->ors_tlimit; + } + + /* + * In case there are no candidates, no cycle takes place... + * + * FIXME: we might use a queue, to better balance the load + * among the candidates + */ + for ( rc = 0; ncandidates > 0; ) { + int gotit = 0, + doabandon = 0, + alreadybound = ncandidates; + + /* check timeout */ + if ( timeout && lastres_time > 0 + && ( slap_get_time() - lastres_time ) > timeout ) + { + doabandon = 1; + rs->sr_text = "Operation timed out"; + rc = rs->sr_err = op->o_protocol >= LDAP_VERSION3 ? + LDAP_ADMINLIMIT_EXCEEDED : LDAP_OTHER; + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + + /* check time limit */ + if ( op->ors_tlimit != SLAP_NO_LIMIT + && slap_get_time() > stoptime ) + { + doabandon = 1; + rc = rs->sr_err = LDAP_TIMELIMIT_EXCEEDED; + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + meta_search_candidate_t retcode = META_SEARCH_UNDEFINED; + metasingleconn_t *msc = &mc->mc_conns[ i ]; + LDAPMessage *res = NULL, *msg; + + /* if msgid is invalid, don't ldap_result() */ + if ( candidates[ i ].sr_msgid == META_MSGID_IGNORE ) { + continue; + } + + /* if target still needs bind, retry */ + if ( candidates[ i ].sr_msgid == META_MSGID_NEED_BIND + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + /* initiate dobind */ + retcode = meta_search_dobind_init( op, rs, &mc, i, candidates ); + + Debug( LDAP_DEBUG_TRACE, "%s <<< meta_search_dobind_init[%ld]=%d\n", + op->o_log_prefix, i, retcode ); + + switch ( retcode ) { + case META_SEARCH_NEED_BIND: + alreadybound--; + /* fallthru */ + + case META_SEARCH_CONNECTING: + case META_SEARCH_BINDING: + break; + + case META_SEARCH_ERR: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* + * When no candidates are left, + * the outer cycle finishes + */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + case META_SEARCH_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + case META_SEARCH_CANDIDATE: + assert( candidates[ i ].sr_msgid >= 0 ); + break; + + case META_SEARCH_ERR: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* means that meta_back_search_start() + * failed but onerr == continue */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + continue; + } + + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) { + break; + } + +#ifdef DEBUG_205 + if ( msc->msc_ld == NULL ) { + char buf[ SLAP_TEXT_BUFLEN ]; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + snprintf( buf, sizeof( buf ), + "%s meta_back_search[%ld] mc=%p msgid=%d%s%s%s\n", + op->o_log_prefix, (long)i, (void *)mc, + candidates[ i ].sr_msgid, + META_IS_BINDING( &candidates[ i ] ) ? " binding" : "", + LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) ? " connbinding" : "", + META_BACK_CONN_CREATING( &mc->mc_conns[ i ] ) ? " conncreating" : "" ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + Debug( LDAP_DEBUG_ANY, "!!! %s\n", buf, 0, 0 ); + } +#endif /* DEBUG_205 */ + + /* + * FIXME: handle time limit as well? + * Note that target servers are likely + * to handle it, so at some time we'll + * get a LDAP_TIMELIMIT_EXCEEDED from + * one of them ... + */ + tv = save_tv; + rc = ldap_result( msc->msc_ld, candidates[ i ].sr_msgid, + LDAP_MSG_RECEIVED, &tv, &res ); + switch ( rc ) { + case 0: + /* FIXME: res should not need to be freed */ + assert( res == NULL ); + continue; + + case -1: +really_bad:; + /* something REALLY bad happened! */ + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + candidates[ i ].sr_type = REP_RESULT; + + if ( meta_back_retry( op, rs, &mc, i, LDAP_BACK_DONTSEND ) ) { + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ) ) + { + /* means that failed but onerr == continue */ + case META_SEARCH_NOT_CANDIDATE: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + assert( ncandidates > 0 ); + --ncandidates; + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + /* fall thru */ + + case META_SEARCH_CANDIDATE: + /* get back into business... */ + continue; + + case META_SEARCH_BINDING: + case META_SEARCH_CONNECTING: + case META_SEARCH_NEED_BIND: + case META_SEARCH_UNDEFINED: + assert( 0 ); + + default: + /* unrecoverable error */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + rc = rs->sr_err = LDAP_OTHER; + goto finish; + } + } + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + goto finish; + } + } + + /* + * When no candidates are left, + * the outer cycle finishes + */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + rs->sr_err = candidates[ i ].sr_err; + continue; + + default: + lastres_time = slap_get_time(); + + /* only touch when activity actually took place... */ + if ( mi->mi_idle_timeout != 0 && msc->msc_time < lastres_time ) { + msc->msc_time = lastres_time; + } + break; + } + + for ( msg = ldap_first_message( msc->msc_ld, res ); + msg != NULL; + msg = ldap_next_message( msc->msc_ld, msg ) ) + { + rc = ldap_msgtype( msg ); + if ( rc == LDAP_RES_SEARCH_ENTRY ) { + LDAPMessage *e; + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + /* count entries returned by target */ + candidates[ i ].sr_nentries++; + + is_ok++; + + e = ldap_first_entry( msc->msc_ld, msg ); + savepriv = op->o_private; + op->o_private = (void *)i; + rs->sr_err = meta_send_entry( op, rs, mc, i, e ); + + switch ( rs->sr_err ) { + case LDAP_SIZELIMIT_EXCEEDED: + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + rs->sr_err = LDAP_SUCCESS; + ldap_msgfree( res ); + res = NULL; + goto finish; + + case LDAP_UNAVAILABLE: + rs->sr_err = LDAP_OTHER; + ldap_msgfree( res ); + res = NULL; + goto finish; + } + op->o_private = savepriv; + + /* don't wait any longer... */ + gotit = 1; + save_tv.tv_sec = 0; + save_tv.tv_usec = 0; + + } else if ( rc == LDAP_RES_SEARCH_REFERENCE ) { + char **references = NULL; + int cnt; + + if ( META_BACK_TGT_NOREFS( mi->mi_targets[ i ] ) ) { + continue; + } + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + is_ok++; + + rc = ldap_parse_reference( msc->msc_ld, msg, + &references, &rs->sr_ctrls, 0 ); + + if ( rc != LDAP_SUCCESS ) { + continue; + } + + if ( references == NULL ) { + continue; + } + +#ifdef ENABLE_REWRITE + dc.ctx = "referralDN"; +#else /* ! ENABLE_REWRITE */ + dc.tofrom = 0; + dc.normalized = 0; +#endif /* ! ENABLE_REWRITE */ + + /* FIXME: merge all and return at the end */ + + for ( cnt = 0; references[ cnt ]; cnt++ ) + ; + + rs->sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv_x( references[ cnt ], 0, 1, &rs->sr_ref[ cnt ], + op->o_tmpmemctx ); + } + BER_BVZERO( &rs->sr_ref[ cnt ] ); + + ( void )ldap_back_referral_result_rewrite( &dc, rs->sr_ref, + op->o_tmpmemctx ); + + if ( rs->sr_ref != NULL && !BER_BVISNULL( &rs->sr_ref[ 0 ] ) ) { + /* ignore return value by now */ + savepriv = op->o_private; + op->o_private = (void *)i; + ( void )send_search_reference( op, rs ); + op->o_private = savepriv; + + ber_bvarray_free_x( rs->sr_ref, op->o_tmpmemctx ); + rs->sr_ref = NULL; + } + + /* cleanup */ + if ( references ) { + ber_memvfree( (void **)references ); + } + + if ( rs->sr_ctrls ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_INTERMEDIATE ) { + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + /* FIXME: response controls + * are passed without checks */ + rs->sr_err = ldap_parse_intermediate( msc->msc_ld, + msg, + (char **)&rs->sr_rspoid, + &rs->sr_rspdata, + &rs->sr_ctrls, + 0 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + candidates[ i ].sr_type = REP_RESULT; + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + + slap_send_ldap_intermediate( op, rs ); + + if ( rs->sr_rspoid != NULL ) { + ber_memfree( (char *)rs->sr_rspoid ); + rs->sr_rspoid = NULL; + } + + if ( rs->sr_rspdata != NULL ) { + ber_bvfree( rs->sr_rspdata ); + rs->sr_rspdata = NULL; + } + + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + + } else if ( rc == LDAP_RES_SEARCH_RESULT ) { + char buf[ SLAP_TEXT_BUFLEN ]; + char **references = NULL; + LDAPControl **ctrls = NULL; + + if ( candidates[ i ].sr_type == REP_INTERMEDIATE ) { + /* don't retry any more... */ + candidates[ i ].sr_type = REP_RESULT; + } + + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + + /* NOTE: ignores response controls + * (and intermediate response controls + * as well, except for those with search + * references); this may not be correct, + * but if they're not ignored then + * back-meta would need to merge them + * consistently (think of pagedResults...) + */ + /* FIXME: response controls? */ + rs->sr_err = ldap_parse_result( msc->msc_ld, + msg, + &candidates[ i ].sr_err, + (char **)&candidates[ i ].sr_matched, + (char **)&candidates[ i ].sr_text, + &references, + &ctrls /* &candidates[ i ].sr_ctrls (unused) */ , + 0 ); + if ( rs->sr_err != LDAP_SUCCESS ) { + candidates[ i ].sr_err = rs->sr_err; + sres = slap_map_api2result( &candidates[ i ] ); + candidates[ i ].sr_type = REP_RESULT; + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + + rs->sr_err = candidates[ i ].sr_err; + + /* massage matchedDN if need be */ + if ( candidates[ i ].sr_matched != NULL ) { + struct berval match, mmatch; + + ber_str2bv( candidates[ i ].sr_matched, + 0, 0, &match ); + candidates[ i ].sr_matched = NULL; + + dc.ctx = "matchedDN"; + dc.target = mi->mi_targets[ i ]; + if ( !ldap_back_dn_massage( &dc, &match, &mmatch ) ) { + if ( mmatch.bv_val == match.bv_val ) { + candidates[ i ].sr_matched + = ch_strdup( mmatch.bv_val ); + + } else { + candidates[ i ].sr_matched = mmatch.bv_val; + } + + candidate_match++; + } + ldap_memfree( match.bv_val ); + } + + /* add references to array */ + /* RFC 4511: referrals can only appear + * if result code is LDAP_REFERRAL */ + if ( references != NULL + && references[ 0 ] != NULL + && references[ 0 ][ 0 ] != '\0' ) + { + if ( rs->sr_err != LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "got referrals with err=%d\n", + op->o_log_prefix, + i, rs->sr_err ); + + } else { + BerVarray sr_ref; + int cnt; + + for ( cnt = 0; references[ cnt ]; cnt++ ) + ; + + sr_ref = ber_memalloc_x( sizeof( struct berval ) * ( cnt + 1 ), + op->o_tmpmemctx ); + + for ( cnt = 0; references[ cnt ]; cnt++ ) { + ber_str2bv_x( references[ cnt ], 0, 1, &sr_ref[ cnt ], + op->o_tmpmemctx ); + } + BER_BVZERO( &sr_ref[ cnt ] ); + + ( void )ldap_back_referral_result_rewrite( &dc, sr_ref, + op->o_tmpmemctx ); + + if ( rs->sr_v2ref == NULL ) { + rs->sr_v2ref = sr_ref; + + } else { + for ( cnt = 0; !BER_BVISNULL( &sr_ref[ cnt ] ); cnt++ ) { + ber_bvarray_add_x( &rs->sr_v2ref, &sr_ref[ cnt ], + op->o_tmpmemctx ); + } + ber_memfree_x( sr_ref, op->o_tmpmemctx ); + } + } + + } else if ( rs->sr_err == LDAP_REFERRAL ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "got err=%d with null " + "or empty referrals\n", + op->o_log_prefix, + i, rs->sr_err ); + + rs->sr_err = LDAP_NO_SUCH_OBJECT; + } + + /* cleanup */ + ber_memvfree( (void **)references ); + + sres = slap_map_api2result( rs ); + + if ( LogTest( LDAP_DEBUG_TRACE | LDAP_DEBUG_ANY ) ) { + snprintf( buf, sizeof( buf ), + "%s meta_back_search[%ld] " + "match=\"%s\" err=%ld", + op->o_log_prefix, i, + candidates[ i ].sr_matched ? candidates[ i ].sr_matched : "", + (long) candidates[ i ].sr_err ); + if ( candidates[ i ].sr_err == LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_TRACE, "%s.\n", buf, 0, 0 ); + + } else { + Debug( LDAP_DEBUG_ANY, "%s (%s) text=\"%s\".\n", + buf, ldap_err2string( candidates[ i ].sr_err ), + candidates[ i ].sr_text ? candidates[i].sr_text : "" ); + } + } + + switch ( sres ) { + case LDAP_NO_SUCH_OBJECT: + /* is_ok is touched any time a valid + * (even intermediate) result is + * returned; as a consequence, if + * a candidate returns noSuchObject + * it is ignored and the candidate + * is simply demoted. */ + if ( is_ok ) { + sres = LDAP_SUCCESS; + } + break; + + case LDAP_SUCCESS: + if ( ctrls != NULL && ctrls[0] != NULL ) { +#ifdef SLAPD_META_CLIENT_PR + LDAPControl *pr_c; + + pr_c = ldap_control_find( LDAP_CONTROL_PAGEDRESULTS, ctrls, NULL ); + if ( pr_c != NULL ) { + BerElementBuffer berbuf; + BerElement *ber = (BerElement *)&berbuf; + ber_tag_t tag; + ber_int_t prsize; + struct berval prcookie; + + /* unsolicited, do not accept */ + if ( mi->mi_targets[i]->mt_ps == 0 ) { + rs->sr_err = LDAP_OTHER; + goto err_pr; + } + + ber_init2( ber, &pr_c->ldctl_value, LBER_USE_DER ); + + tag = ber_scanf( ber, "{im}", &prsize, &prcookie ); + if ( tag == LBER_ERROR ) { + rs->sr_err = LDAP_OTHER; + goto err_pr; + } + + /* more pages? new search request */ + if ( !BER_BVISNULL( &prcookie ) && !BER_BVISEMPTY( &prcookie ) ) { + if ( mi->mi_targets[i]->mt_ps > 0 ) { + /* ignore size if specified */ + prsize = 0; + + } else if ( prsize == 0 ) { + /* guess the page size from the entries returned so far */ + prsize = candidates[ i ].sr_nentries; + } + + candidates[ i ].sr_nentries = 0; + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + candidates[ i ].sr_type = REP_INTERMEDIATE; + + assert( candidates[ i ].sr_matched == NULL ); + assert( candidates[ i ].sr_text == NULL ); + assert( candidates[ i ].sr_ref == NULL ); + + switch ( meta_back_search_start( op, rs, &dc, &mc, i, candidates, &prcookie, prsize ) ) + { + case META_SEARCH_CANDIDATE: + assert( candidates[ i ].sr_msgid >= 0 ); + ldap_controls_free( ctrls ); + goto free_message; + + case META_SEARCH_ERR: +err_pr:; + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + ldap_controls_free( ctrls ); + goto finish; + } + /* fallthru */ + + case META_SEARCH_NOT_CANDIDATE: + /* means that meta_back_search_start() + * failed but onerr == continue */ + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + break; + + default: + /* impossible */ + assert( 0 ); + break; + } + break; + } + } +#endif /* SLAPD_META_CLIENT_PR */ + } + /* fallthru */ + + case LDAP_REFERRAL: + is_ok++; + break; + + case LDAP_SIZELIMIT_EXCEEDED: + /* if a target returned sizelimitExceeded + * and the entry count is equal to the + * proxy's limit, the target would have + * returned more, and the error must be + * propagated to the client; otherwise, + * the target enforced a limit lower + * than what requested by the proxy; + * ignore it */ + candidates[ i ].sr_err = rs->sr_err; + if ( rs->sr_nentries == op->ors_slimit + || META_BACK_ONERR_STOP( mi ) ) + { + const char *save_text; +got_err: + save_text = rs->sr_text; + savepriv = op->o_private; + op->o_private = (void *)i; + rs->sr_text = candidates[ i ].sr_text; + send_ldap_result( op, rs ); + rs->sr_text = save_text; + op->o_private = savepriv; + ldap_msgfree( res ); + res = NULL; + ldap_controls_free( ctrls ); + goto finish; + } + break; + + default: + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) + goto got_err; + break; + } + + ldap_controls_free( ctrls ); + last = i; + rc = 0; + + /* + * When no candidates are left, + * the outer cycle finishes + */ + assert( ncandidates > 0 ); + --ncandidates; + + } else if ( rc == LDAP_RES_BIND ) { + meta_search_candidate_t retcode; + + retcode = meta_search_dobind_result( op, rs, &mc, i, candidates, msg ); + if ( retcode == META_SEARCH_CANDIDATE ) { + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + retcode = meta_back_search_start( op, rs, &dc, &mc, i, candidates, NULL, 0 ); + } + + switch ( retcode ) { + case META_SEARCH_CANDIDATE: + break; + + /* means that failed but onerr == continue */ + case META_SEARCH_NOT_CANDIDATE: + case META_SEARCH_ERR: + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + + candidates[ i ].sr_err = rs->sr_err; + if ( META_BACK_ONERR_STOP( mi ) ) { + savepriv = op->o_private; + op->o_private = (void *)i; + send_ldap_result( op, rs ); + op->o_private = savepriv; + ldap_msgfree( res ); + res = NULL; + goto finish; + } + goto free_message; + + default: + assert( 0 ); + break; + } + + } else { + Debug( LDAP_DEBUG_ANY, + "%s meta_back_search[%ld]: " + "unrecognized response message tag=%d\n", + op->o_log_prefix, + i, rc ); + + ldap_msgfree( res ); + res = NULL; + goto really_bad; + } + } + +free_message:; + ldap_msgfree( res ); + res = NULL; + } + + /* check for abandon */ + if ( op->o_abandon || LDAP_BACK_CONN_ABANDON( mc ) ) { + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( candidates[ i ].sr_msgid >= 0 + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + if ( META_IS_BINDING( &candidates[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + /* if still binding, destroy */ + +#ifdef DEBUG_205 + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf), "%s meta_back_search(abandon) " + "ldap_unbind_ext[%ld] mc=%p ld=%p", + op->o_log_prefix, i, (void *)mc, + (void *)mc->mc_conns[i].msc_ld ); + + Debug( LDAP_DEBUG_ANY, "### %s\n", buf, 0, 0 ); +#endif /* DEBUG_205 */ + + meta_clear_one_candidate( op, mc, i ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + META_BINDING_CLEAR( &candidates[ i ] ); + + } else { + (void)meta_back_cancel( mc, op, rs, + candidates[ i ].sr_msgid, i, + LDAP_BACK_DONTSEND ); + } + + candidates[ i ].sr_msgid = META_MSGID_IGNORE; + assert( ncandidates > 0 ); + --ncandidates; + } + } + + if ( op->o_abandon ) { + rc = SLAPD_ABANDON; + } + + /* let send_ldap_result play cleanup handlers (ITS#4645) */ + break; + } + + /* if no entry was found during this loop, + * set a minimal timeout */ + if ( ncandidates > 0 && gotit == 0 ) { + if ( save_tv.tv_sec == 0 && save_tv.tv_usec == 0 ) { + save_tv.tv_usec = LDAP_BACK_RESULT_UTIMEOUT/initial_candidates; + + /* arbitrarily limit to something between 1 and 2 minutes */ + } else if ( ( stoptime == -1 && save_tv.tv_sec < 60 ) + || save_tv.tv_sec < ( stoptime - slap_get_time() ) / ( 2 * ncandidates ) ) + { + /* double the timeout */ + lutil_timermul( &save_tv, 2, &save_tv ); + } + + if ( alreadybound == 0 ) { + tv = save_tv; + (void)select( 0, NULL, NULL, NULL, &tv ); + + } else { + ldap_pvt_thread_yield(); + } + } + } + + if ( rc == -1 ) { + /* + * FIXME: need a better strategy to handle errors + */ + if ( mc ) { + rc = meta_back_op_result( mc, op, rs, META_TARGET_NONE, + -1, stoptime != -1 ? (stoptime - slap_get_time()) : 0, + LDAP_BACK_SENDERR ); + } else { + rc = rs->sr_err; + } + goto finish; + } + + /* + * Rewrite the matched portion of the search base, if required + * + * FIXME: only the last one gets caught! + */ + savepriv = op->o_private; + op->o_private = (void *)(long)mi->mi_ntargets; + if ( candidate_match > 0 ) { + struct berval pmatched = BER_BVNULL; + + /* we use the first one */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( META_IS_CANDIDATE( &candidates[ i ] ) + && candidates[ i ].sr_matched != NULL ) + { + struct berval bv, pbv; + int rc; + + /* if we got success, and this target + * returned noSuchObject, and its suffix + * is a superior of the searchBase, + * ignore the matchedDN */ + if ( sres == LDAP_SUCCESS + && candidates[ i ].sr_err == LDAP_NO_SUCH_OBJECT + && op->o_req_ndn.bv_len > mi->mi_targets[ i ]->mt_nsuffix.bv_len ) + { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + continue; + } + + ber_str2bv( candidates[ i ].sr_matched, 0, 0, &bv ); + rc = dnPretty( NULL, &bv, &pbv, op->o_tmpmemctx ); + + if ( rc == LDAP_SUCCESS ) { + + /* NOTE: if they all are superiors + * of the baseDN, the shorter is also + * superior of the longer... */ + if ( pbv.bv_len > pmatched.bv_len ) { + if ( !BER_BVISNULL( &pmatched ) ) { + op->o_tmpfree( pmatched.bv_val, op->o_tmpmemctx ); + } + pmatched = pbv; + op->o_private = (void *)i; + + } else { + op->o_tmpfree( pbv.bv_val, op->o_tmpmemctx ); + } + } + + if ( candidates[ i ].sr_matched != NULL ) { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + } + } + } + + if ( !BER_BVISNULL( &pmatched ) ) { + matched = pmatched.bv_val; + } + + } else if ( sres == LDAP_NO_SUCH_OBJECT ) { + matched = op->o_bd->be_suffix[ 0 ].bv_val; + } + + /* + * In case we returned at least one entry, we return LDAP_SUCCESS + * otherwise, the latter error code we got + */ + + if ( sres == LDAP_SUCCESS ) { + if ( rs->sr_v2ref ) { + sres = LDAP_REFERRAL; + } + + if ( META_BACK_ONERR_REPORT( mi ) ) { + /* + * Report errors, if any + * + * FIXME: we should handle error codes and return the more + * important/reasonable + */ + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( candidates[ i ].sr_err != LDAP_SUCCESS + && candidates[ i ].sr_err != LDAP_NO_SUCH_OBJECT ) + { + sres = candidates[ i ].sr_err; + break; + } + } + } + } + + rs->sr_err = sres; + rs->sr_matched = ( sres == LDAP_SUCCESS ? NULL : matched ); + rs->sr_ref = ( sres == LDAP_REFERRAL ? rs->sr_v2ref : NULL ); + send_ldap_result( op, rs ); + op->o_private = savepriv; + rs->sr_matched = NULL; + rs->sr_ref = NULL; + +finish:; + if ( matched && matched != op->o_bd->be_suffix[ 0 ].bv_val ) { + op->o_tmpfree( matched, op->o_tmpmemctx ); + } + + if ( rs->sr_v2ref ) { + ber_bvarray_free_x( rs->sr_v2ref, op->o_tmpmemctx ); + } + + for ( i = 0; i < mi->mi_ntargets; i++ ) { + if ( !META_IS_CANDIDATE( &candidates[ i ] ) ) { + continue; + } + + if ( mc ) { + if ( META_IS_BINDING( &candidates[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( LDAP_BACK_CONN_BINDING( &mc->mc_conns[ i ] ) + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ) + { + assert( candidates[ i ].sr_msgid >= 0 + || candidates[ i ].sr_msgid == META_MSGID_CONNECTING ); + assert( mc->mc_conns[ i ].msc_ld != NULL ); + +#ifdef DEBUG_205 + Debug( LDAP_DEBUG_ANY, "### %s meta_back_search(cleanup) " + "ldap_unbind_ext[%ld] ld=%p\n", + op->o_log_prefix, i, (void *)mc->mc_conns[i].msc_ld ); +#endif /* DEBUG_205 */ + + /* if still binding, destroy */ + meta_clear_one_candidate( op, mc, i ); + } + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + META_BINDING_CLEAR( &candidates[ i ] ); + + } else if ( candidates[ i ].sr_msgid >= 0 ) { + (void)meta_back_cancel( mc, op, rs, + candidates[ i ].sr_msgid, i, + LDAP_BACK_DONTSEND ); + } + } + + if ( candidates[ i ].sr_matched ) { + free( (char *)candidates[ i ].sr_matched ); + candidates[ i ].sr_matched = NULL; + } + + if ( candidates[ i ].sr_text ) { + ldap_memfree( (char *)candidates[ i ].sr_text ); + candidates[ i ].sr_text = NULL; + } + + if ( candidates[ i ].sr_ref ) { + ber_bvarray_free( candidates[ i ].sr_ref ); + candidates[ i ].sr_ref = NULL; + } + + if ( candidates[ i ].sr_ctrls ) { + ldap_controls_free( candidates[ i ].sr_ctrls ); + candidates[ i ].sr_ctrls = NULL; + } + + if ( META_BACK_TGT_QUARANTINE( mi->mi_targets[ i ] ) ) { + meta_back_quarantine( op, &candidates[ i ], i ); + } + + /* only in case of timelimit exceeded, if the timelimit exceeded because + * one contacted target never responded, invalidate the connection + * NOTE: should we quarantine the target as well? right now, the connection + * is invalidated; the next time it will be recreated and the target + * will be quarantined if it cannot be contacted */ + if ( mi->mi_idle_timeout != 0 + && rs->sr_err == LDAP_TIMELIMIT_EXCEEDED + && op->o_time > mc->mc_conns[ i ].msc_time ) + { + /* don't let anyone else use this expired connection */ + do_taint++; + } + } + + if ( mc ) { + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); + if ( do_taint ) { + LDAP_BACK_CONN_TAINTED_SET( mc ); + } + meta_back_release_conn_lock( mi, mc, 0 ); + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + } + + return rs->sr_err; +} + +static int +meta_send_entry( + Operation *op, + SlapReply *rs, + metaconn_t *mc, + int target, + LDAPMessage *e ) +{ + metainfo_t *mi = ( metainfo_t * )op->o_bd->be_private; + struct berval a, mapped; + int check_duplicate_attrs = 0; + int check_sorted_attrs = 0; + Entry ent = { 0 }; + BerElement ber = *ldap_get_message_ber( e ); + Attribute *attr, **attrp; + struct berval bdn, + dn = BER_BVNULL; + const char *text; + dncookie dc; + ber_len_t len; + int rc; + + if ( ber_scanf( &ber, "l{", &len ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + if ( ber_set_option( &ber, LBER_OPT_REMAINING_BYTES, &len ) != LBER_OPT_SUCCESS ) { + return LDAP_OTHER; + } + + if ( ber_scanf( &ber, "m{", &bdn ) == LBER_ERROR ) { + return LDAP_DECODING_ERROR; + } + + /* + * Rewrite the dn of the result, if needed + */ + dc.target = mi->mi_targets[ target ]; + dc.conn = op->o_conn; + dc.rs = rs; + dc.ctx = "searchResult"; + + rs->sr_err = ldap_back_dn_massage( &dc, &bdn, &dn ); + if ( rs->sr_err != LDAP_SUCCESS) { + return rs->sr_err; + } + + /* + * Note: this may fail if the target host(s) schema differs + * from the one known to the meta, and a DN with unknown + * attributes is returned. + * + * FIXME: should we log anything, or delegate to dnNormalize? + */ + rc = dnPrettyNormal( NULL, &dn, &ent.e_name, &ent.e_nname, + op->o_tmpmemctx ); + if ( dn.bv_val != bdn.bv_val ) { + free( dn.bv_val ); + } + BER_BVZERO( &dn ); + + if ( rc != LDAP_SUCCESS ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_send_entry(\"%s\"): " + "invalid DN syntax\n", + op->o_log_prefix, ent.e_name.bv_val, 0 ); + rc = LDAP_INVALID_DN_SYNTAX; + goto done; + } + + /* + * cache dn + */ + if ( mi->mi_cache.ttl != META_DNCACHE_DISABLED ) { + ( void )meta_dncache_update_entry( &mi->mi_cache, + &ent.e_nname, target ); + } + + attrp = &ent.e_attrs; + + dc.ctx = "searchAttrDN"; + while ( ber_scanf( &ber, "{m", &a ) != LBER_ERROR ) { + int last = 0; + slap_syntax_validate_func *validate; + slap_syntax_transform_func *pretty; + + if ( ber_pvt_ber_remaining( &ber ) < 0 ) { + Debug( LDAP_DEBUG_ANY, + "%s meta_send_entry(\"%s\"): " + "unable to parse attr \"%s\".\n", + op->o_log_prefix, ent.e_name.bv_val, a.bv_val ); + + rc = LDAP_OTHER; + goto done; + } + + if ( ber_pvt_ber_remaining( &ber ) == 0 ) { + break; + } + + ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_at, + &a, &mapped, BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0' ) { + ( void )ber_scanf( &ber, "x" /* [W] */ ); + continue; + } + if ( mapped.bv_val != a.bv_val ) { + /* will need to check for duplicate attrs */ + check_duplicate_attrs++; + } + attr = attr_alloc( NULL ); + if ( attr == NULL ) { + rc = LDAP_OTHER; + goto done; + } + if ( slap_bv2ad( &mapped, &attr->a_desc, &text ) + != LDAP_SUCCESS) { + if ( slap_bv2undef_ad( &mapped, &attr->a_desc, &text, + SLAP_AD_PROXIED ) != LDAP_SUCCESS ) + { + char buf[ SLAP_TEXT_BUFLEN ]; + + snprintf( buf, sizeof( buf ), + "%s meta_send_entry(\"%s\"): " + "slap_bv2undef_ad(%s): %s\n", + op->o_log_prefix, ent.e_name.bv_val, + mapped.bv_val, text ); + + Debug( LDAP_DEBUG_ANY, "%s", buf, 0, 0 ); + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free( attr ); + continue; + } + } + + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) + check_sorted_attrs = 1; + + /* no subschemaSubentry */ + if ( attr->a_desc == slap_schema.si_ad_subschemaSubentry + || attr->a_desc == slap_schema.si_ad_entryDN ) + { + + /* + * We eat target's subschemaSubentry because + * a search for this value is likely not + * to resolve to the appropriate backend; + * later, the local subschemaSubentry is + * added. + * + * We also eat entryDN because the frontend + * will reattach it without checking if already + * present... + */ + ( void )ber_scanf( &ber, "x" /* [W] */ ); + attr_free(attr); + continue; + } + + if ( ber_scanf( &ber, "[W]", &attr->a_vals ) == LBER_ERROR + || attr->a_vals == NULL ) + { + attr->a_vals = (struct berval *)&slap_dummy_bv; + + } else { + for ( last = 0; !BER_BVISNULL( &attr->a_vals[ last ] ); ++last ) + ; + } + attr->a_numvals = last; + + validate = attr->a_desc->ad_type->sat_syntax->ssyn_validate; + pretty = attr->a_desc->ad_type->sat_syntax->ssyn_pretty; + + if ( !validate && !pretty ) { + attr_free( attr ); + goto next_attr; + } + + if ( attr->a_desc == slap_schema.si_ad_objectClass + || attr->a_desc == slap_schema.si_ad_structuralObjectClass ) + { + struct berval *bv; + + for ( bv = attr->a_vals; !BER_BVISNULL( bv ); bv++ ) { + ObjectClass *oc; + + ldap_back_map( &mi->mi_targets[ target ]->mt_rwmap.rwm_oc, + bv, &mapped, BACKLDAP_REMAP ); + if ( BER_BVISNULL( &mapped ) || mapped.bv_val[0] == '\0') { +remove_oc:; + free( bv->bv_val ); + BER_BVZERO( bv ); + if ( --last < 0 ) { + break; + } + *bv = attr->a_vals[ last ]; + BER_BVZERO( &attr->a_vals[ last ] ); + bv--; + + } else if ( mapped.bv_val != bv->bv_val ) { + int i; + + for ( i = 0; !BER_BVISNULL( &attr->a_vals[ i ] ); i++ ) { + if ( &attr->a_vals[ i ] == bv ) { + continue; + } + + if ( ber_bvstrcasecmp( &mapped, &attr->a_vals[ i ] ) == 0 ) { + break; + } + } + + if ( !BER_BVISNULL( &attr->a_vals[ i ] ) ) { + goto remove_oc; + } + + ber_bvreplace( bv, &mapped ); + + } else if ( ( oc = oc_bvfind_undef( bv ) ) == NULL ) { + goto remove_oc; + + } else { + ber_bvreplace( bv, &oc->soc_cname ); + } + } + /* + * It is necessary to try to rewrite attributes with + * dn syntax because they might be used in ACLs as + * members of groups; since ACLs are applied to the + * rewritten stuff, no dn-based subecj clause could + * be used at the ldap backend side (see + * http://www.OpenLDAP.org/faq/data/cache/452.html) + * The problem can be overcome by moving the dn-based + * ACLs to the target directory server, and letting + * everything pass thru the ldap backend. + */ + } else { + int i; + + if ( attr->a_desc->ad_type->sat_syntax == + slap_schema.si_syn_distinguishedName ) + { + ldap_dnattr_result_rewrite( &dc, attr->a_vals ); + + } else if ( attr->a_desc == slap_schema.si_ad_ref ) { + ldap_back_referral_result_rewrite( &dc, attr->a_vals, NULL ); + + } + + for ( i = 0; i < last; i++ ) { + struct berval pval; + int rc; + + if ( pretty ) { + rc = ordered_value_pretty( attr->a_desc, + &attr->a_vals[i], &pval, NULL ); + + } else { + rc = ordered_value_validate( attr->a_desc, + &attr->a_vals[i], 0 ); + } + + if ( rc ) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[ i ] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + continue; + } + + if ( pretty ) { + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_vals[i] = pval; + } + } + + if ( last == 0 && attr->a_vals != &slap_dummy_bv ) { + attr_free( attr ); + goto next_attr; + } + } + + if ( last && attr->a_desc->ad_type->sat_equality && + attr->a_desc->ad_type->sat_equality->smr_normalize ) + { + int i; + + attr->a_nvals = ch_malloc( ( last + 1 ) * sizeof( struct berval ) ); + for ( i = 0; i<last; i++ ) { + /* if normalizer fails, drop this value */ + if ( ordered_value_normalize( + SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX, + attr->a_desc, + attr->a_desc->ad_type->sat_equality, + &attr->a_vals[i], &attr->a_nvals[i], + NULL )) { + ber_memfree( attr->a_vals[i].bv_val ); + if ( --last == i ) { + BER_BVZERO( &attr->a_vals[ i ] ); + break; + } + attr->a_vals[i] = attr->a_vals[last]; + BER_BVZERO( &attr->a_vals[last] ); + i--; + } + } + BER_BVZERO( &attr->a_nvals[i] ); + if ( last == 0 ) { + attr_free( attr ); + goto next_attr; + } + + } else { + attr->a_nvals = attr->a_vals; + } + + attr->a_numvals = last; + *attrp = attr; + attrp = &attr->a_next; +next_attr:; + } + + /* only check if some mapping occurred */ + if ( check_duplicate_attrs ) { + Attribute **ap; + + for ( ap = &ent.e_attrs; *ap != NULL; ap = &(*ap)->a_next ) { + Attribute **tap; + + for ( tap = &(*ap)->a_next; *tap != NULL; ) { + if ( (*tap)->a_desc == (*ap)->a_desc ) { + Entry e = { 0 }; + Modification mod = { 0 }; + const char *text = NULL; + char textbuf[ SLAP_TEXT_BUFLEN ]; + Attribute *next = (*tap)->a_next; + + BER_BVSTR( &e.e_name, "" ); + BER_BVSTR( &e.e_nname, "" ); + e.e_attrs = *ap; + mod.sm_op = LDAP_MOD_ADD; + mod.sm_desc = (*ap)->a_desc; + mod.sm_type = mod.sm_desc->ad_cname; + mod.sm_numvals = (*ap)->a_numvals; + mod.sm_values = (*tap)->a_vals; + if ( (*tap)->a_nvals != (*tap)->a_vals ) { + mod.sm_nvalues = (*tap)->a_nvals; + } + + (void)modify_add_values( &e, &mod, + /* permissive */ 1, + &text, textbuf, sizeof( textbuf ) ); + + /* should not insert new attrs! */ + assert( e.e_attrs == *ap ); + + attr_free( *tap ); + *tap = next; + + } else { + tap = &(*tap)->a_next; + } + } + } + } + + /* Check for sorted attributes */ + if ( check_sorted_attrs ) { + for ( attr = ent.e_attrs; attr; attr = attr->a_next ) { + if ( attr->a_desc->ad_type->sat_flags & SLAP_AT_SORTED_VAL ) { + while ( attr->a_numvals > 1 ) { + int i; + int rc = slap_sort_vals( (Modifications *)attr, &text, &i, op->o_tmpmemctx ); + if ( rc != LDAP_TYPE_OR_VALUE_EXISTS ) + break; + + /* Strip duplicate values */ + if ( attr->a_nvals != attr->a_vals ) + ber_memfree( attr->a_nvals[i].bv_val ); + ber_memfree( attr->a_vals[i].bv_val ); + attr->a_numvals--; + if ( (unsigned)i < attr->a_numvals ) { + attr->a_vals[i] = attr->a_vals[attr->a_numvals]; + if ( attr->a_nvals != attr->a_vals ) + attr->a_nvals[i] = attr->a_nvals[attr->a_numvals]; + } + BER_BVZERO(&attr->a_vals[attr->a_numvals]); + if ( attr->a_nvals != attr->a_vals ) + BER_BVZERO(&attr->a_nvals[attr->a_numvals]); + } + attr->a_flags |= SLAP_ATTR_SORTED_VALS; + } + } + } + + ldap_get_entry_controls( mc->mc_conns[target].msc_ld, + e, &rs->sr_ctrls ); + rs->sr_entry = &ent; + rs->sr_attrs = op->ors_attrs; + rs->sr_operational_attrs = NULL; + rs->sr_flags = mi->mi_targets[ target ]->mt_rep_flags; + rs->sr_err = LDAP_SUCCESS; + rc = send_search_entry( op, rs ); + switch ( rc ) { + case LDAP_UNAVAILABLE: + rc = LDAP_OTHER; + break; + } + +done:; + rs->sr_entry = NULL; + rs->sr_attrs = NULL; + if ( rs->sr_ctrls != NULL ) { + ldap_controls_free( rs->sr_ctrls ); + rs->sr_ctrls = NULL; + } + if ( !BER_BVISNULL( &ent.e_name ) ) { + free( ent.e_name.bv_val ); + BER_BVZERO( &ent.e_name ); + } + if ( !BER_BVISNULL( &ent.e_nname ) ) { + free( ent.e_nname.bv_val ); + BER_BVZERO( &ent.e_nname ); + } + entry_clean( &ent ); + + return rc; +} + diff --git a/servers/slapd/back-meta/suffixmassage.c b/servers/slapd/back-meta/suffixmassage.c new file mode 100644 index 0000000..b7bb4ab --- /dev/null +++ b/servers/slapd/back-meta/suffixmassage.c @@ -0,0 +1,193 @@ +/* suffixmassage.c - massages ldap backend dns */ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 2003-2021 The OpenLDAP Foundation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ +/* This is an altered version */ + +/* + * Copyright 1999, Howard Chu, All rights reserved. <hyc@highlandsun.com> + * Copyright 2000, Pierangelo Masarati, All rights reserved. <ando@sys-net.it> + * + * Module back-ldap, originally developed by Howard Chu + * + * has been modified by Pierangelo Masarati. The original copyright + * notice has been maintained. + * + * Permission is granted to anyone to use this software for any purpose + * on any computer system, and to alter it and redistribute it, subject + * to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of this + * software, no matter how awful, even if they arise from flaws in it. + * + * 2. The origin of this software must not be misrepresented, either by + * explicit claim or by omission. Since few users ever read sources, + * credits should appear in the documentation. + * + * 3. Altered versions must be plainly marked as such, and must not be + * misrepresented as being the original software. Since few users + * ever read sources, credits should appear in the documentation. + * + * 4. This notice may not be removed or altered. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/string.h> +#include <ac/socket.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +#ifdef ENABLE_REWRITE +int +ldap_back_dn_massage( + dncookie *dc, + struct berval *dn, + struct berval *res ) +{ + int rc = 0; + static char *dmy = ""; + + switch ( rewrite_session( dc->target->mt_rwmap.rwm_rw, dc->ctx, + ( dn->bv_val ? dn->bv_val : dmy ), + dc->conn, &res->bv_val ) ) + { + case REWRITE_REGEXEC_OK: + if ( res->bv_val != NULL ) { + res->bv_len = strlen( res->bv_val ); + } else { + *res = *dn; + } + Debug( LDAP_DEBUG_ARGS, + "[rw] %s: \"%s\" -> \"%s\"\n", + dc->ctx, + BER_BVISNULL( dn ) ? "" : dn->bv_val, + BER_BVISNULL( res ) ? "" : res->bv_val ); + rc = LDAP_SUCCESS; + break; + + case REWRITE_REGEXEC_UNWILLING: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_UNWILLING_TO_PERFORM; + dc->rs->sr_text = "Operation not allowed"; + } + rc = LDAP_UNWILLING_TO_PERFORM; + break; + + case REWRITE_REGEXEC_ERR: + if ( dc->rs ) { + dc->rs->sr_err = LDAP_OTHER; + dc->rs->sr_text = "Rewrite error"; + } + rc = LDAP_OTHER; + break; + } + + if ( res->bv_val == dmy ) { + BER_BVZERO( res ); + } + + return rc; +} + +#else +/* + * ldap_back_dn_massage + * + * Aliases the suffix; based on suffix_alias (servers/slapd/suffixalias.c). + */ +int +ldap_back_dn_massage( + dncookie *dc, + struct berval *odn, + struct berval *res +) +{ + int i, src, dst; + struct berval pretty = {0,NULL}, *dn = odn; + + assert( res != NULL ); + + if ( dn == NULL ) { + res->bv_val = NULL; + res->bv_len = 0; + return 0; + } + if ( dc->target->mt_rwmap.rwm_suffix_massage == NULL ) { + *res = *dn; + return 0; + } + + if ( dc->tofrom ) { + src = 0 + dc->normalized; + dst = 2 + dc->normalized; + } else { + src = 2 + dc->normalized; + dst = 0 + dc->normalized; + /* DN from remote server may be in arbitrary form. + * Pretty it so we can parse reliably. + */ + dnPretty( NULL, dn, &pretty, NULL ); + if (pretty.bv_val) dn = &pretty; + } + + for ( i = 0; + dc->target->mt_rwmap.rwm_suffix_massage[i].bv_val != NULL; + i += 4 ) { + int aliasLength = dc->target->mt_rwmap.rwm_suffix_massage[i+src].bv_len; + int diff = dn->bv_len - aliasLength; + + if ( diff < 0 ) { + /* alias is longer than dn */ + continue; + } else if ( diff > 0 && ( !DN_SEPARATOR(dn->bv_val[diff-1]))) { + /* boundary is not at a DN separator */ + continue; + /* At a DN Separator */ + } + + if ( !strcmp( dc->target->mt_rwmap.rwm_suffix_massage[i+src].bv_val, &dn->bv_val[diff] ) ) { + res->bv_len = diff + dc->target->mt_rwmap.rwm_suffix_massage[i+dst].bv_len; + res->bv_val = ch_malloc( res->bv_len + 1 ); + strncpy( res->bv_val, dn->bv_val, diff ); + strcpy( &res->bv_val[diff], dc->target->mt_rwmap.rwm_suffix_massage[i+dst].bv_val ); + Debug( LDAP_DEBUG_ARGS, + "ldap_back_dn_massage:" + " converted \"%s\" to \"%s\"\n", + BER_BVISNULL( dn ) ? "" : dn->bv_val, + BER_BVISNULL( res ) ? "" : res->bv_val, 0 ); + break; + } + } + if (pretty.bv_val) { + ch_free(pretty.bv_val); + dn = odn; + } + /* Nothing matched, just return the original DN */ + if (res->bv_val == NULL) { + *res = *dn; + } + + return 0; +} +#endif /* !ENABLE_REWRITE */ diff --git a/servers/slapd/back-meta/unbind.c b/servers/slapd/back-meta/unbind.c new file mode 100644 index 0000000..4e363de --- /dev/null +++ b/servers/slapd/back-meta/unbind.c @@ -0,0 +1,89 @@ +/* $OpenLDAP$ */ +/* This work is part of OpenLDAP Software <http://www.openldap.org/>. + * + * Copyright 1999-2021 The OpenLDAP Foundation. + * Portions Copyright 2001-2003 Pierangelo Masarati. + * Portions Copyright 1999-2003 Howard Chu. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * <http://www.OpenLDAP.org/license.html>. + */ +/* ACKNOWLEDGEMENTS: + * This work was initially developed by the Howard Chu for inclusion + * in OpenLDAP Software and subsequently enhanced by Pierangelo + * Masarati. + */ + +#include "portable.h" + +#include <stdio.h> + +#include <ac/errno.h> +#include <ac/socket.h> +#include <ac/string.h> + +#include "slap.h" +#include "../back-ldap/back-ldap.h" +#include "back-meta.h" + +int +meta_back_conn_destroy( + Backend *be, + Connection *conn ) +{ + metainfo_t *mi = ( metainfo_t * )be->be_private; + metaconn_t *mc, + mc_curr = {{ 0 }}; + int i; + + + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_conn_destroy: fetching conn=%ld DN=\"%s\"\n", + conn->c_connid, + BER_BVISNULL( &conn->c_ndn ) ? "" : conn->c_ndn.bv_val, 0 ); + + mc_curr.mc_conn = conn; + + ldap_pvt_thread_mutex_lock( &mi->mi_conninfo.lai_mutex ); +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, ">>> meta_back_conn_destroy" ); +#endif /* META_BACK_PRINT_CONNTREE */ + while ( ( mc = avl_delete( &mi->mi_conninfo.lai_tree, ( caddr_t )&mc_curr, meta_back_conn_cmp ) ) != NULL ) + { + assert( !LDAP_BACK_PCONN_ISPRIV( mc ) ); + Debug( LDAP_DEBUG_TRACE, + "=>meta_back_conn_destroy: destroying conn %lu " + "refcnt=%d flags=0x%08x\n", + mc->mc_conn->c_connid, mc->mc_refcnt, mc->msc_mscflags ); + + if ( mc->mc_refcnt > 0 ) { + /* someone else might be accessing the connection; + * mark for deletion */ + LDAP_BACK_CONN_CACHED_CLEAR( mc ); + LDAP_BACK_CONN_TAINTED_SET( mc ); + + } else { + meta_back_conn_free( mc ); + } + } +#if META_BACK_PRINT_CONNTREE > 0 + meta_back_print_conntree( mi, "<<< meta_back_conn_destroy" ); +#endif /* META_BACK_PRINT_CONNTREE */ + ldap_pvt_thread_mutex_unlock( &mi->mi_conninfo.lai_mutex ); + + /* + * Cleanup rewrite session + */ + for ( i = 0; i < mi->mi_ntargets; ++i ) { + rewrite_session_delete( mi->mi_targets[ i ]->mt_rwmap.rwm_rw, conn ); + } + + return 0; +} + |