File 0226-ITS-9197-back-ldap-added-task-that-prunes-expired-co.patch of Package openldap2.24986

From 8ee47ce0fdb1e6796fdf46c3eb7bd453933c3a6b Mon Sep 17 00:00:00 2001
From: Tero Saarni <tero.saarni@gmail.com>
Date: Wed, 24 Feb 2021 22:07:48 +0000
Subject: [PATCH 226/230] ITS#9197 back-ldap: added task that prunes expired
 connections

---
 servers/slapd/back-ldap/back-ldap.h |   3 +-
 servers/slapd/back-ldap/bind.c      | 206 +++++++++++++++---
 servers/slapd/back-ldap/chain.c     | 141 +++++-------
 servers/slapd/back-ldap/config.c    |   2 +-
 servers/slapd/back-ldap/distproc.c  | 114 +++++-----
 servers/slapd/back-ldap/init.c      |  16 +-
 servers/slapd/back-ldap/monitor.c   |  10 +-
 servers/slapd/back-ldap/unbind.c    |   2 +-
 servers/slapd/back-meta/bind.c      |   4 +-
 servers/slapd/back-meta/conn.c      |  14 +-
 servers/slapd/back-meta/init.c      |   2 +-
 servers/slapd/back-meta/unbind.c    |   2 +-
 tests/data/slapd-proxytimeout.conf  |  71 ++++++
 tests/scripts/conf.sh               |   1 +
 tests/scripts/defines.sh            |   1 +
 tests/scripts/test079-proxy-timeout | 324 ++++++++++++++++++++++++++++
 16 files changed, 716 insertions(+), 197 deletions(-)
 create mode 100644 tests/data/slapd-proxytimeout.conf
 create mode 100644 tests/scripts/test079-proxy-timeout

diff --git a/servers/slapd/back-ldap/back-ldap.h b/servers/slapd/back-ldap/back-ldap.h
index c4639e4f4..1f5054cf3 100644
--- a/servers/slapd/back-ldap/back-ldap.h
+++ b/servers/slapd/back-ldap/back-ldap.h
@@ -180,7 +180,7 @@ typedef struct ldapconn_t {
 
 typedef struct ldap_avl_info_t {
 	ldap_pvt_thread_mutex_t		lai_mutex;
-	Avlnode				*lai_tree;
+	TAvlnode			*lai_tree;
 } ldap_avl_info_t;
 
 typedef struct slap_retry_info_t {
@@ -414,6 +414,7 @@ typedef struct ldapinfo_t {
 
 	ldap_pvt_thread_mutex_t li_counter_mutex;
 	ldap_pvt_mp_t		li_ops_completed[SLAP_OP_LAST];
+	struct re_s*		li_conn_expire_task;
 } ldapinfo_t;
 
 #define	LDAP_ERR_OK(err) ((err) == LDAP_SUCCESS || (err) == LDAP_COMPARE_FALSE || (err) == LDAP_COMPARE_TRUE)
diff --git a/servers/slapd/back-ldap/bind.c b/servers/slapd/back-ldap/bind.c
index 998b2c796..1f9cbf185 100644
--- a/servers/slapd/back-ldap/bind.c
+++ b/servers/slapd/back-ldap/bind.c
@@ -34,6 +34,7 @@
 #include "back-ldap.h"
 #include "lutil.h"
 #include "lutil_ldap.h"
+#include "ldap_rq.h"
 
 #define LDAP_CONTROL_OBSOLETE_PROXY_AUTHZ	"2.16.840.1.113730.3.4.12"
 
@@ -60,7 +61,7 @@ static const struct {
 };
 
 static void
-ldap_back_conn_print( ldapconn_t *lc, const char *avlstr )
+ldap_back_conn_print( ldapconn_t *lc )
 {
 	char buf[ SLAP_TEXT_BUFLEN ];
 	char fbuf[ sizeof("BAPTIENSC") ];
@@ -77,31 +78,10 @@ ldap_back_conn_print( ldapconn_t *lc, const char *avlstr )
 	}
 	fbuf[i] = '\0';
 	
-	fprintf( stderr, "lc=%p %s %s flags=0x%08x (%s)\n",
-		(void *)lc, buf, avlstr, lc->lc_lcflags, fbuf );
+	fprintf( stderr, "lc=%p %s flags=0x%08x (%s)\n",
+		(void *)lc, buf, lc->lc_lcflags, fbuf );
 }
 
-static void
-ldap_back_ravl_print( Avlnode *root, int depth )
-{
-	int		i;
-	ldapconn_t	*lc;
-	
-	if ( root == 0 ) {
-		return;
-	}
-	
-	ldap_back_ravl_print( root->avl_right, depth+1 );
-	
-	for ( i = 0; i < depth; i++ ) {
-		fprintf( stderr, "-" );
-	}
-
-	lc = root->avl_data;
-	ldap_back_conn_print( lc, avl_bf2str( root->avl_bf ) );
-
-	ldap_back_ravl_print( root->avl_left, depth + 1 );
-}
 
 static char* priv2str[] = {
 	"privileged",
@@ -129,7 +109,7 @@ ldap_back_print_conntree( ldapinfo_t *li, char *msg )
 		LDAP_TAILQ_FOREACH( lc, &li->li_conn_priv[ c ].lic_priv, lc_q )
 		{
 			fprintf( stderr, "    [%d] ", i );
-			ldap_back_conn_print( lc, "" );
+			ldap_back_conn_print( lc );
 			i++;
 		}
 	}
@@ -138,7 +118,11 @@ ldap_back_print_conntree( ldapinfo_t *li, char *msg )
 		fprintf( stderr, "\t(empty)\n" );
 
 	} else {
-		ldap_back_ravl_print( li->li_conninfo.lai_tree, 0 );
+		TAvlnode *edge = tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+		while ( edge ) {
+			ldap_back_conn_print( (ldapconn_t *)edge->avl_data );
+			edge = tavl_next( edge, TAVL_DIR_RIGHT );
+		}
 	}
 	
 	fprintf( stderr, "<======== %s\n", msg );
@@ -167,6 +151,12 @@ ldap_back_prepare_conn( ldapconn_t *lc, Operation *op, SlapReply *rs,
 static int
 ldap_back_conndnlc_cmp( const void *c1, const void *c2 );
 
+static void
+ldap_back_conn_prune( ldapinfo_t *li );
+
+static void
+ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc );
+
 ldapconn_t *
 ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc )
 {
@@ -189,7 +179,7 @@ ldap_back_conn_delete( ldapinfo_t *li, ldapconn_t *lc )
 
 		if ( LDAP_BACK_CONN_CACHED( lc ) ) {
 			assert( !LDAP_BACK_CONN_TAINTED( lc ) );
-			tmplc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc,
+			tmplc = tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc,
 				ldap_back_conndnlc_cmp );
 			assert( tmplc == lc );
 			LDAP_BACK_CONN_CACHED_CLEAR( lc );
@@ -343,7 +333,7 @@ retry_lock:;
 
 		/* delete all cached connections with the current connection */
 		if ( LDAP_BACK_SINGLECONN( li ) ) {
-			while ( ( tmplc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, ldap_back_conn_cmp ) ) != NULL )
+			while ( ( tmplc = tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)lc, ldap_back_conn_cmp ) ) != NULL )
 			{
 				assert( !LDAP_BACK_PCONN_ISPRIV( lc ) );
 				Debug( LDAP_DEBUG_TRACE,
@@ -371,7 +361,7 @@ retry_lock:;
 			if ( be_isroot_dn( op->o_bd, &op->o_req_ndn ) ) {
 				LDAP_BACK_PCONN_ROOTDN_SET( lc, op );
 			}
-			lerr = avl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
+			lerr = tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
 				ldap_back_conndn_cmp, ldap_back_conndn_dup );
 		}
 
@@ -938,7 +928,7 @@ retry_lock:
 		} else {
 
 			/* Searches for a ldapconn in the avl tree */
-			lc = (ldapconn_t *)avl_find( li->li_conninfo.lai_tree, 
+			lc = (ldapconn_t *)tavl_find( li->li_conninfo.lai_tree, 
 					(caddr_t)&lc_curr, ldap_back_conndn_cmp );
 		}
 
@@ -1084,7 +1074,7 @@ retry_lock:
 			rs->sr_err = 0;
 
 		} else {
-			rs->sr_err = avl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
+			rs->sr_err = tavl_insert( &li->li_conninfo.lai_tree, (caddr_t)lc,
 				ldap_back_conndn_cmp, ldap_back_conndn_dup );
 			LDAP_BACK_CONN_CACHED_SET( lc );
 		}
@@ -1137,6 +1127,7 @@ retry_lock:
 				return NULL;
 			}
 		}
+		ldap_back_schedule_conn_expiry( li, lc );
 
 	} else {
 		int	expiring = 0;
@@ -3066,3 +3057,156 @@ ldap_back_connid2str( const ldapconn_base_t *lc, char *buf, ber_len_t buflen )
 
 	return len;
 }
+
+void *
+ldap_back_conn_expire_fn( void *ctx, void *arg )
+{
+	struct re_s *rtask = arg;
+	ldapinfo_t *li = (ldapinfo_t *)rtask->arg;
+	ldap_back_conn_prune( li );
+
+	return NULL;
+}
+
+/* Pick which expires first: connection TTL or idle timeout */
+static time_t
+ldap_back_conn_expire_time( ldapinfo_t *li, ldapconn_t *lc) {
+	if ( li->li_conn_ttl != 0 && li->li_idle_timeout != 0 ) {
+		return ( lc->lc_create_time + li->li_conn_ttl ) < ( lc->lc_time + li->li_idle_timeout ) ?
+			( lc->lc_create_time + li->li_conn_ttl ) : ( lc->lc_time + li->li_idle_timeout );
+	} else if ( li->li_conn_ttl != 0 ) {
+		return lc->lc_create_time + li->li_conn_ttl;
+	} else if ( li->li_idle_timeout != 0 ) {
+		return lc->lc_time + li->li_idle_timeout;
+	}
+	return -1;
+}
+
+static void
+ldap_back_conn_prune( ldapinfo_t *li )
+{
+	time_t		now = slap_get_time();
+	time_t 		next_timeout = -1; /* -1 means uninitialized */
+	TAvlnode	*edge;
+	int		c;
+
+	/*
+	 * Iterate though connections and close those that are pass the expiry time.
+	 * Also calculate the time for next connection to to expire.
+	 */
+	ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
+
+	for ( c = LDAP_BACK_PCONN_FIRST; c < LDAP_BACK_PCONN_LAST; c++ ) {
+		ldapconn_t *lc = LDAP_TAILQ_FIRST( &li->li_conn_priv[ c ].lic_priv );
+
+		while ( lc ) {
+			ldapconn_t *next = LDAP_TAILQ_NEXT( lc, lc_q );
+			time_t conn_expires = ldap_back_conn_expire_time( li, lc );
+
+			if ( now >= conn_expires ) {
+				if ( lc->lc_refcnt == 0 ) {
+					Debug( LDAP_DEBUG_TRACE,
+						"ldap_back_conn_prune: closing expired connection lc=%p\n",
+						lc, 0, 0 );
+					ldap_back_freeconn( li, lc, 0 );
+				} else {
+					Debug( LDAP_DEBUG_TRACE,
+						"ldap_back_conn_prune: tainting expired connection lc=%p\n",
+						lc, 0, 0 );
+					LDAP_BACK_CONN_TAINTED_SET( lc );
+				}
+			} else if ( next_timeout == -1 || conn_expires < next_timeout ) {
+				/* next_timeout was not yet initialized or current connection expires sooner */
+				next_timeout = conn_expires;
+			}
+
+			lc = next;
+		}
+	}
+
+	edge = tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+	while ( edge ) {
+		TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+		ldapconn_t *lc = (ldapconn_t *)edge->avl_data;
+		time_t conn_expires = ldap_back_conn_expire_time( li, lc );
+
+		if ( now >= conn_expires ) {
+			if ( lc->lc_refcnt == 0 ) {
+				Debug( LDAP_DEBUG_TRACE,
+					"ldap_back_conn_prune: closing expired connection lc=%p\n",
+					lc, 0, 0 );
+				ldap_back_freeconn( li, lc, 0 );
+			} else {
+				Debug( LDAP_DEBUG_TRACE,
+					"ldap_back_conn_prune: tainting expired connection lc=%p\n",
+					lc, 0, 0 );
+				LDAP_BACK_CONN_TAINTED_SET( lc );
+			}
+		} else if ( next_timeout == -1 || conn_expires < next_timeout ) {
+			next_timeout = conn_expires;
+		}
+
+		edge = next;
+	}
+
+	ldap_pvt_thread_mutex_unlock( &li->li_conninfo.lai_mutex );
+
+	/* Reschedule for next timeout or cancel the task */
+	ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+	if ( next_timeout > 0 ) {
+		if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+			ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+		}
+		li->li_conn_expire_task->interval.tv_sec = next_timeout - now;
+		ldap_pvt_runqueue_resched( &slapd_rq, li->li_conn_expire_task, 0 );
+
+		/*
+		 * The thread that handles runqueue might have already processed all tasks
+		 * before we insertered new task or rescheduled the existing task with new
+		 * timeout period. Wake it up to ensure that the task will be picked up.
+		 */
+		slap_wake_listener();
+		Debug( LDAP_DEBUG_TRACE,
+			"ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n",
+			li->li_conn_expire_task->interval.tv_sec, 0, 0 );
+	} else if ( next_timeout == -1 && li->li_conn_expire_task != NULL ) {
+		if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+			ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+		}
+		ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task );
+		li->li_conn_expire_task = NULL;
+	}
+	ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+	return;
+}
+
+static void
+ldap_back_schedule_conn_expiry( ldapinfo_t *li, ldapconn_t *lc ) {
+	/* Do nothing if timeouts are not set. */
+	if ( li->li_conn_ttl == 0 && li->li_idle_timeout == 0 ) {
+		return;
+	}
+
+	/*
+	 * If connection expire task is not running, create it and schedule for
+	 * timeout of this connection.
+	 *
+	 * If the task is already running, this connection cannot be next one
+	 * to expire and therefore timeout does not need to be re-calculated.
+	 */
+	ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+	if ( li->li_conn_expire_task == NULL ) {
+		li->li_conn_expire_task = ldap_pvt_runqueue_insert( &slapd_rq,
+			ldap_back_conn_expire_time( li, lc ) - slap_get_time(),
+			ldap_back_conn_expire_fn, li, "ldap_back_conn_expire_fn",
+			"ldap_back_conn_expire_timer" );
+		slap_wake_listener();
+		Debug( LDAP_DEBUG_TRACE,
+			"ldap_back_conn_prune: scheduled connection expiry timer to %ld sec\n",
+			li->li_conn_expire_task->interval.tv_sec, 0, 0 );
+	}
+	ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+
+	return;
+}
\ No newline at end of file
diff --git a/servers/slapd/back-ldap/chain.c b/servers/slapd/back-ldap/chain.c
index eeceb4c66..643fce9cf 100644
--- a/servers/slapd/back-ldap/chain.c
+++ b/servers/slapd/back-ldap/chain.c
@@ -556,7 +556,7 @@ Document: RFC 4511
 
 		/* Searches for a ldapinfo in the avl tree */
 		ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-		lip = (ldapinfo_t *)avl_find( lc->lc_lai.lai_tree, 
+		lip = (ldapinfo_t *)tavl_find( lc->lc_lai.lai_tree,
 			(caddr_t)&li, ldap_chain_uri_cmp );
 		ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
 
@@ -588,7 +588,7 @@ Document: RFC 4511
 
 			if ( LDAP_CHAIN_CACHE_URI( lc ) ) {
 				ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-				if ( avl_insert( &lc->lc_lai.lai_tree,
+				if ( tavl_insert( &lc->lc_lai.lai_tree,
 					(caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
 				{
 					/* someone just inserted another;
@@ -828,7 +828,7 @@ ldap_chain_search(
 
 		/* Searches for a ldapinfo in the avl tree */
 		ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-		lip = (ldapinfo_t *)avl_find( lc->lc_lai.lai_tree, 
+		lip = (ldapinfo_t *)tavl_find( lc->lc_lai.lai_tree,
 			(caddr_t)&li, ldap_chain_uri_cmp );
 		ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
 
@@ -861,7 +861,7 @@ ldap_chain_search(
 
 			if ( LDAP_CHAIN_CACHE_URI( lc ) ) {
 				ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-				if ( avl_insert( &lc->lc_lai.lai_tree,
+				if ( tavl_insert( &lc->lc_lai.lai_tree,
 					(caddr_t)lip, ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
 				{
 					/* someone just inserted another;
@@ -1366,7 +1366,7 @@ fail:
 	if ( at ) {
 		li->li_uri = ch_strdup( at->a_vals[ 0 ].bv_val );
 		value_add_one( &li->li_bvuri, &at->a_vals[ 0 ] );
-		if ( avl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
+		if ( tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
 			ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
 		{
 			Debug( LDAP_DEBUG_ANY, "slapd-chain: "
@@ -1389,34 +1389,27 @@ done:;
 	return rc;
 }
 
-typedef struct ldap_chain_cfadd_apply_t {
-	Operation	*op;
-	SlapReply	*rs;
-	Entry		*p;
-	ConfigArgs	*ca;
-	int		count;
-} ldap_chain_cfadd_apply_t;
-
-static int
-ldap_chain_cfadd_apply( void *datum, void *arg )
+static void
+ldap_chain_cfadd_apply(
+	ldapinfo_t *li,
+	Operation *op,
+	SlapReply *rs,
+	Entry *p,
+	ConfigArgs *ca,
+	int count )
 {
-	ldapinfo_t			*li = (ldapinfo_t *)datum;
-	ldap_chain_cfadd_apply_t	*lca = (ldap_chain_cfadd_apply_t *)arg;
-
 	struct berval			bv;
 
 	/* FIXME: should not hardcode "olcDatabase" here */
-	bv.bv_len = snprintf( lca->ca->cr_msg, sizeof( lca->ca->cr_msg ),
-		"olcDatabase={%d}%s", lca->count, lback->bi_type );
-	bv.bv_val = lca->ca->cr_msg;
+	bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+		"olcDatabase={%d}%s", count, lback->bi_type );
+	bv.bv_val = ca->cr_msg;
 
-	lca->ca->be->be_private = (void *)li;
-	config_build_entry( lca->op, lca->rs, lca->p->e_private, lca->ca,
+	ca->be->be_private = (void *)li;
+	config_build_entry( op, rs, p->e_private, ca,
 		&bv, lback->bi_cf_ocs, &chainocs[1] );
 
-	lca->count++;
-
-	return 0;
+	return;
 }
 
 static int
@@ -1426,20 +1419,20 @@ chain_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
 	slap_overinst	*on = (slap_overinst *)pe->ce_bi;
 	ldap_chain_t	*lc = (ldap_chain_t *)on->on_bi.bi_private;
 	void		*priv = (void *)ca->be->be_private;
+	TAvlnode	*edge;
+	int		count = 0;
 
 	if ( lback->bi_cf_ocs ) {
-		ldap_chain_cfadd_apply_t	lca = { 0 };
-
-		lca.op = op;
-		lca.rs = rs;
-		lca.p = p;
-		lca.ca = ca;
-		lca.count = 0;
 
-		(void)ldap_chain_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca );
+		ldap_chain_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ );
 
-		(void)avl_apply( lc->lc_lai.lai_tree, ldap_chain_cfadd_apply,
-			&lca, 1, AVL_INORDER );
+		edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+		while ( edge ) {
+			TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+			ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+			ldap_chain_cfadd_apply( li, op, rs, p, ca, count++ );
+			edge = next;
+		}
 
 		ca->be->be_private = priv;
 	}
@@ -1457,7 +1450,7 @@ chain_lddel( CfEntryInfo *ce, Operation *op )
 	ldapinfo_t	*li = (ldapinfo_t *) ce->ce_be->be_private;
 
 	if ( li != lc->lc_common_li ) {
-		if (! avl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) {
+		if (! tavl_delete( &lc->lc_lai.lai_tree, li, ldap_chain_uri_cmp ) ) {
 			Debug( LDAP_DEBUG_ANY, "slapd-chain: avl_delete failed. "
 				"\"%s\" not found.\n", li->li_uri, 0, 0 );
 			return -1;
@@ -1866,7 +1859,7 @@ private_destroy:;
 					goto private_destroy;
 				}
 
-				if ( avl_insert( &lc->lc_lai.lai_tree,
+				if ( tavl_insert( &lc->lc_lai.lai_tree,
 					(caddr_t)lc->lc_cfg_li,
 					ldap_chain_uri_cmp, ldap_chain_uri_dup ) )
 				{
@@ -1891,22 +1884,6 @@ enum db_which {
 	db_last
 };
 
-typedef struct ldap_chain_db_apply_t {
-	BackendDB	*be;
-	BI_db_func	*func;
-} ldap_chain_db_apply_t;
-
-static int
-ldap_chain_db_apply( void *datum, void *arg )
-{
-	ldapinfo_t		*li = (ldapinfo_t *)datum;
-	ldap_chain_db_apply_t	*lca = (ldap_chain_db_apply_t *)arg;
-
-	lca->be->be_private = (void *)li;
-
-	return lca->func( lca->be, NULL );
-}
-
 static int
 ldap_chain_db_func(
 	BackendDB *be,
@@ -1934,14 +1911,17 @@ ldap_chain_db_func(
 			}
 
 			if ( lc->lc_lai.lai_tree != NULL ) {
-				ldap_chain_db_apply_t	lca;
-
-				lca.be = &db;
-				lca.func = func;
-
-				rc = avl_apply( lc->lc_lai.lai_tree,
-					ldap_chain_db_apply, (void *)&lca,
-					1, AVL_INORDER ) != AVL_NOMORE;
+				TAvlnode *edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+				while ( edge ) {
+					TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+					ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+					db.be_private = (void *)li;
+					rc = func( &db, NULL );
+					if ( rc == 1 ) {
+						break;
+					}
+					edge = next;
+				}
 			}
 		}
 	}
@@ -2008,7 +1988,7 @@ ldap_chain_db_destroy(
 	rc = ldap_chain_db_func( be, db_destroy );
 
 	if ( lc ) {
-		avl_free( lc->lc_lai.lai_tree, NULL );
+		tavl_free( lc->lc_lai.lai_tree, NULL );
 		ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex );
 		ch_free( lc );
 	}
@@ -2122,22 +2102,6 @@ ldap_chain_db_open_one(
 	return lback->bi_db_open( be, NULL );
 }
 
-typedef struct ldap_chain_conn_apply_t {
-	BackendDB	*be;
-	Connection	*conn;
-} ldap_chain_conn_apply_t;
-
-static int
-ldap_chain_conn_apply( void *datum, void *arg )
-{
-	ldapinfo_t		*li = (ldapinfo_t *)datum;
-	ldap_chain_conn_apply_t	*lca = (ldap_chain_conn_apply_t *)arg;
-
-	lca->be->be_private = (void *)li;
-
-	return lback->bi_connection_destroy( lca->be, lca->conn );
-}
-
 static int
 ldap_chain_connection_destroy(
 	BackendDB *be,
@@ -2147,15 +2111,24 @@ ldap_chain_connection_destroy(
 	slap_overinst		*on = (slap_overinst *) be->bd_info;
 	ldap_chain_t		*lc = (ldap_chain_t *)on->on_bi.bi_private;
 	void			*private = be->be_private;
-	ldap_chain_conn_apply_t	lca;
+	TAvlnode		*edge;
 	int			rc;
 
 	be->be_private = NULL;
-	lca.be = be;
-	lca.conn = conn;
 	ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-	rc = avl_apply( lc->lc_lai.lai_tree, ldap_chain_conn_apply,
-		(void *)&lca, 1, AVL_INORDER ) != AVL_NOMORE;
+	edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+	while ( edge ) {
+		TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+		ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+		be->be_private = (void *)li;
+		rc = lback->bi_connection_destroy( be, conn );
+		if ( rc == 1 ) {
+			break;
+		}
+		edge = next;
+	}
+
+
 	ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
 	be->be_private = private;
 
diff --git a/servers/slapd/back-ldap/config.c b/servers/slapd/back-ldap/config.c
index 38da178d1..cff5cd289 100644
--- a/servers/slapd/back-ldap/config.c
+++ b/servers/slapd/back-ldap/config.c
@@ -1424,7 +1424,7 @@ ldap_back_cf_gen( ConfigArgs *c )
 			/* NOTE: don't worry about locking: if we got here,
 			 * other threads are suspended. */
 			if ( li->li_conninfo.lai_tree != NULL ) {
-				avl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
+				tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
 				li->li_conninfo.lai_tree = NULL;
 			}
 			
diff --git a/servers/slapd/back-ldap/distproc.c b/servers/slapd/back-ldap/distproc.c
index ed978728a..a8ea803b0 100644
--- a/servers/slapd/back-ldap/distproc.c
+++ b/servers/slapd/back-ldap/distproc.c
@@ -435,7 +435,7 @@ distproc_ldadd( CfEntryInfo *p, Entry *e, ConfigArgs *ca )
 	if ( lc->lc_common_li == NULL ) {
 		lc->lc_common_li = li;
 
-	} else if ( avl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
+	} else if ( tavl_insert( &lc->lc_lai.lai_tree, (caddr_t)li,
 		ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) )
 	{
 		Debug( LDAP_DEBUG_ANY, "slapd-distproc: "
@@ -463,26 +463,27 @@ typedef struct ldap_distproc_cfadd_apply_t {
 	int		count;
 } ldap_distproc_cfadd_apply_t;
 
-static int
-ldap_distproc_cfadd_apply( void *datum, void *arg )
+static void
+ldap_distproc_cfadd_apply(
+	ldapinfo_t *li,
+	Operation *op,
+	SlapReply *rs,
+	Entry *p,
+	ConfigArgs *ca,
+	int count )
 {
-	ldapinfo_t			*li = (ldapinfo_t *)datum;
-	ldap_distproc_cfadd_apply_t	*lca = (ldap_distproc_cfadd_apply_t *)arg;
-
 	struct berval			bv;
 
 	/* FIXME: should not hardcode "olcDatabase" here */
-	bv.bv_len = snprintf( lca->ca->cr_msg, sizeof( lca->ca->cr_msg ),
-		"olcDatabase={%d}%s", lca->count, lback->bi_type );
-	bv.bv_val = lca->ca->cr_msg;
+	bv.bv_len = snprintf( ca->cr_msg, sizeof( ca->cr_msg ),
+		"olcDatabase={%d}%s", count, lback->bi_type );
+	bv.bv_val = ca->cr_msg;
 
-	lca->ca->be->be_private = (void *)li;
-	config_build_entry( lca->op, lca->rs, lca->p->e_private, lca->ca,
+	ca->be->be_private = (void *)li;
+	config_build_entry( op, rs, p->e_private, ca,
 		&bv, lback->bi_cf_ocs, &distproc_ocs[ 1 ] );
 
-	lca->count++;
-
-	return 0;
+	return;
 }
 
 static int
@@ -492,6 +493,8 @@ distproc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
 	slap_overinst	*on = (slap_overinst *)pe->ce_bi;
 	ldap_distproc_t	*lc = (ldap_distproc_t *)on->on_bi.bi_private;
 	void		*priv = (void *)ca->be->be_private;
+	TAvlnode	*edge;
+	int		count = 0;
 
 	if ( lback->bi_cf_ocs ) {
 		ldap_distproc_cfadd_apply_t	lca = { 0 };
@@ -502,10 +505,15 @@ distproc_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
 		lca.ca = ca;
 		lca.count = 0;
 
-		(void)ldap_distproc_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca );
+		ldap_distproc_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ );
 
-		(void)avl_apply( lc->lc_lai.lai_tree, ldap_distproc_cfadd_apply,
-			&lca, 1, AVL_INORDER );
+		edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+		while ( edge ) {
+			TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+			ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+			ldap_distproc_cfadd_apply( li, op, rs, p, ca, count++ );
+			edge = next;
+		}
 
 		ca->be->be_private = priv;
 	}
@@ -675,7 +683,7 @@ private_destroy:;
 					goto private_destroy;
 				}
 
-				if ( avl_insert( &lc->lc_lai.lai_tree,
+				if ( tavl_insert( &lc->lc_lai.lai_tree,
 					(caddr_t)lc->lc_cfg_li,
 					ldap_distproc_uri_cmp, ldap_distproc_uri_dup ) )
 				{
@@ -700,22 +708,6 @@ enum db_which {
 	db_last
 };
 
-typedef struct ldap_distproc_db_apply_t {
-	BackendDB	*be;
-	BI_db_func	*func;
-} ldap_distproc_db_apply_t;
-
-static int
-ldap_distproc_db_apply( void *datum, void *arg )
-{
-	ldapinfo_t		*li = (ldapinfo_t *)datum;
-	ldap_distproc_db_apply_t	*lca = (ldap_distproc_db_apply_t *)arg;
-
-	lca->be->be_private = (void *)li;
-
-	return lca->func( lca->be, NULL );
-}
-
 static int
 ldap_distproc_db_func(
 	BackendDB *be,
@@ -743,14 +735,17 @@ ldap_distproc_db_func(
 			}
 
 			if ( lc->lc_lai.lai_tree != NULL ) {
-				ldap_distproc_db_apply_t	lca;
-
-				lca.be = &db;
-				lca.func = func;
-
-				rc = avl_apply( lc->lc_lai.lai_tree,
-					ldap_distproc_db_apply, (void *)&lca,
-					1, AVL_INORDER ) != AVL_NOMORE;
+				TAvlnode *edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+				while ( edge ) {
+					TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+					ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+					be->be_private = (void *)li;
+					rc = func( &db, NULL );
+					if ( rc == 1 ) {
+						break;
+					}
+					edge = next;
+				}
 			}
 		}
 	}
@@ -787,7 +782,7 @@ ldap_distproc_db_destroy(
 	rc = ldap_distproc_db_func( be, db_destroy );
 
 	if ( lc ) {
-		avl_free( lc->lc_lai.lai_tree, NULL );
+		tavl_free( lc->lc_lai.lai_tree, NULL );
 		ldap_pvt_thread_mutex_destroy( &lc->lc_lai.lai_mutex );
 		ch_free( lc );
 	}
@@ -857,22 +852,6 @@ ldap_distproc_db_init_one(
 	return 0;
 }
 
-typedef struct ldap_distproc_conn_apply_t {
-	BackendDB	*be;
-	Connection	*conn;
-} ldap_distproc_conn_apply_t;
-
-static int
-ldap_distproc_conn_apply( void *datum, void *arg )
-{
-	ldapinfo_t		*li = (ldapinfo_t *)datum;
-	ldap_distproc_conn_apply_t	*lca = (ldap_distproc_conn_apply_t *)arg;
-
-	lca->be->be_private = (void *)li;
-
-	return lback->bi_connection_destroy( lca->be, lca->conn );
-}
-
 static int
 ldap_distproc_connection_destroy(
 	BackendDB *be,
@@ -882,15 +861,22 @@ ldap_distproc_connection_destroy(
 	slap_overinst		*on = (slap_overinst *) be->bd_info;
 	ldap_distproc_t		*lc = (ldap_distproc_t *)on->on_bi.bi_private;
 	void			*private = be->be_private;
-	ldap_distproc_conn_apply_t	lca;
 	int			rc;
+	TAvlnode		*edge;
 
 	be->be_private = NULL;
-	lca.be = be;
-	lca.conn = conn;
 	ldap_pvt_thread_mutex_lock( &lc->lc_lai.lai_mutex );
-	rc = avl_apply( lc->lc_lai.lai_tree, ldap_distproc_conn_apply,
-		(void *)&lca, 1, AVL_INORDER ) != AVL_NOMORE;
+	edge = tavl_end( lc->lc_lai.lai_tree, TAVL_DIR_LEFT );
+	while ( edge ) {
+		TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+		ldapinfo_t *li = (ldapinfo_t *)edge->avl_data;
+		be->be_private = (void *)li;
+		rc = lback->bi_connection_destroy( be, conn );
+		if ( rc == 1 ) {
+			break;
+		}
+		edge = next;
+	}
 	ldap_pvt_thread_mutex_unlock( &lc->lc_lai.lai_mutex );
 	be->be_private = private;
 
diff --git a/servers/slapd/back-ldap/init.c b/servers/slapd/back-ldap/init.c
index f73344c99..8b801ff67 100644
--- a/servers/slapd/back-ldap/init.c
+++ b/servers/slapd/back-ldap/init.c
@@ -31,6 +31,7 @@
 #include "slap.h"
 #include "config.h"
 #include "back-ldap.h"
+#include "ldap_rq.h"
 
 static const ldap_extra_t ldap_extra = {
 	ldap_back_proxy_authz_ctrl,
@@ -185,6 +186,8 @@ ldap_back_db_init( Backend *be, ConfigReply *cr )
 		ldap_pvt_mp_init( li->li_ops_completed[ i ] );
 	}
 
+	li->li_conn_expire_task = NULL;
+
 	be->be_private = li;
 	SLAP_DBFLAGS( be ) |= SLAP_DBFLAG_NOLASTMOD;
 
@@ -303,6 +306,16 @@ ldap_back_db_destroy( Backend *be, ConfigReply *cr )
 
 		(void)ldap_back_monitor_db_destroy( be );
 
+		/* Stop and remove the task that prunes expired connections */
+		if ( li->li_conn_expire_task != NULL ) {
+			ldap_pvt_thread_mutex_lock( &slapd_rq.rq_mutex );
+			if ( ldap_pvt_runqueue_isrunning( &slapd_rq, li->li_conn_expire_task ) ) {
+					ldap_pvt_runqueue_stoptask( &slapd_rq, li->li_conn_expire_task );
+			}
+			ldap_pvt_runqueue_remove( &slapd_rq, li->li_conn_expire_task );
+			ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
+		}
+
 		ldap_pvt_thread_mutex_lock( &li->li_conninfo.lai_mutex );
 
 		if ( li->li_uri != NULL ) {
@@ -323,7 +336,7 @@ ldap_back_db_destroy( Backend *be, ConfigReply *cr )
 			li->li_idassert_authz = NULL;
 		}
                	if ( li->li_conninfo.lai_tree ) {
-			avl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
+			tavl_free( li->li_conninfo.lai_tree, ldap_back_conn_free );
 		}
 		for ( i = LDAP_BACK_PCONN_FIRST; i < LDAP_BACK_PCONN_LAST; i++ ) {
 			while ( !LDAP_TAILQ_EMPTY( &li->li_conn_priv[ i ].lic_priv ) ) {
@@ -359,4 +372,3 @@ ldap_back_db_destroy( Backend *be, ConfigReply *cr )
 SLAP_BACKEND_INIT_MODULE( ldap )
 
 #endif /* SLAPD_LDAP == SLAPD_MOD_DYNAMIC */
-
diff --git a/servers/slapd/back-ldap/monitor.c b/servers/slapd/back-ldap/monitor.c
index e12de8e4e..77f11ccb5 100644
--- a/servers/slapd/back-ldap/monitor.c
+++ b/servers/slapd/back-ldap/monitor.c
@@ -540,6 +540,7 @@ ldap_back_monitor_conn_create(
 
 	struct ldap_back_monitor_conn_arg *arg;
 	int conn_type;
+	TAvlnode *edge;
 
 	assert( e_parent->e_private != NULL );
 
@@ -564,8 +565,13 @@ ldap_back_monitor_conn_create(
 		}
 	}
 
-	avl_apply( li->li_conninfo.lai_tree, (AVL_APPLY)ldap_back_monitor_conn_entry,
-		arg, -1, AVL_INORDER );
+	edge = tavl_end( li->li_conninfo.lai_tree, TAVL_DIR_LEFT );
+	while ( edge ) {
+		TAvlnode *next = tavl_next( edge, TAVL_DIR_RIGHT );
+		ldapconn_t *lc = (ldapconn_t *)edge->avl_data;
+		ldap_back_monitor_conn_entry( lc, arg );
+		edge = next;
+	}
 
 	ch_free( arg );
 
diff --git a/servers/slapd/back-ldap/unbind.c b/servers/slapd/back-ldap/unbind.c
index d8121f72d..6768baf16 100644
--- a/servers/slapd/back-ldap/unbind.c
+++ b/servers/slapd/back-ldap/unbind.c
@@ -51,7 +51,7 @@ ldap_back_conn_destroy(
 #if LDAP_BACK_PRINT_CONNTREE > 0
 	ldap_back_print_conntree( li, ">>> ldap_back_conn_destroy" );
 #endif /* LDAP_BACK_PRINT_CONNTREE */
-	while ( ( lc = avl_delete( &li->li_conninfo.lai_tree, (caddr_t)&lc_curr, ldap_back_conn_cmp ) ) != NULL )
+	while ( ( lc = tavl_delete( &li->li_conninfo.lai_tree, (caddr_t)&lc_curr, ldap_back_conn_cmp ) ) != NULL )
 	{
 		assert( !LDAP_BACK_PCONN_ISPRIV( lc ) );
 		Debug( LDAP_DEBUG_TRACE,
diff --git a/servers/slapd/back-meta/bind.c b/servers/slapd/back-meta/bind.c
index f5d4df4e8..0e5e8853b 100644
--- a/servers/slapd/back-meta/bind.c
+++ b/servers/slapd/back-meta/bind.c
@@ -220,7 +220,7 @@ meta_back_bind( Operation *op, SlapReply *rs )
 			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 )
+				while ( ( tmpmc = tavl_delete( &mi->mi_conninfo.lai_tree, (caddr_t)mc, meta_back_conn_cmp ) ) != NULL )
 				{
 					assert( !LDAP_BACK_PCONN_ISPRIV( mc ) );
 					Debug( LDAP_DEBUG_TRACE,
@@ -243,7 +243,7 @@ meta_back_bind( Operation *op, SlapReply *rs )
 			}
 
 			ber_bvreplace( &mc->mc_local_ndn, &op->o_req_ndn );
-			lerr = avl_insert( &mi->mi_conninfo.lai_tree, (caddr_t)mc,
+			lerr = tavl_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" );
diff --git a/servers/slapd/back-meta/conn.c b/servers/slapd/back-meta/conn.c
index 8a781e573..239c86e5e 100644
--- a/servers/slapd/back-meta/conn.c
+++ b/servers/slapd/back-meta/conn.c
@@ -160,7 +160,7 @@ meta_back_print( metaconn_t *mc, char *avlstr )
 }
 
 static void
-meta_back_ravl_print( Avlnode *root, int depth )
+meta_back_ravl_print( TAvlnode *root, int depth )
 {
 	int     	i;
 
@@ -849,7 +849,7 @@ meta_back_retry(
 
 				} else {
 					/* FIXME: check if in tree, for consistency? */
-					(void)avl_delete( &mi->mi_conninfo.lai_tree,
+					(void)tavl_delete( &mi->mi_conninfo.lai_tree,
 						( caddr_t )mc, meta_back_conndnmc_cmp );
 				}
 				LDAP_BACK_CONN_CACHED_CLEAR( mc );
@@ -1174,7 +1174,7 @@ retry_lock:;
 			
 
 		} else {
-			mc = (metaconn_t *)avl_find( mi->mi_conninfo.lai_tree, 
+			mc = (metaconn_t *)tavl_find( mi->mi_conninfo.lai_tree,
 				(caddr_t)&mc_curr, meta_back_conndn_cmp );
 		}
 
@@ -1219,7 +1219,7 @@ retry_lock:;
 						}
 
 					} else {
-						(void)avl_delete( &mi->mi_conninfo.lai_tree,
+						(void)tavl_delete( &mi->mi_conninfo.lai_tree,
 							(caddr_t)mc, meta_back_conndnmc_cmp );
 					}
 
@@ -1439,7 +1439,7 @@ retry_lock:;
 			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, 
+				mc = (metaconn_t *)tavl_find( mi->mi_conninfo.lai_tree,
 					(caddr_t)&mc_curr, meta_back_conndn_cmp );
 				if ( mc != NULL ) {
 					/* catch taint errors */
@@ -1688,7 +1688,7 @@ done:;
 			rs->sr_err = 0;
 
 		} else if ( !( sendok & LDAP_BACK_BINDING ) ) {
-			err = avl_insert( &mi->mi_conninfo.lai_tree, ( caddr_t )mc,
+			err = tavl_insert( &mi->mi_conninfo.lai_tree, ( caddr_t )mc,
 			       	meta_back_conndn_cmp, meta_back_conndn_dup );
 			LDAP_BACK_CONN_CACHED_SET( mc );
 		}
@@ -1805,7 +1805,7 @@ meta_back_release_conn_lock(
 		} else if ( LDAP_BACK_CONN_CACHED( mc ) ) {
 			metaconn_t	*tmpmc;
 
-			tmpmc = avl_delete( &mi->mi_conninfo.lai_tree,
+			tmpmc = tavl_delete( &mi->mi_conninfo.lai_tree,
 				( caddr_t )mc, meta_back_conndnmc_cmp );
 
 			/* Overparanoid, but useful... */
diff --git a/servers/slapd/back-meta/init.c b/servers/slapd/back-meta/init.c
index fefdba812..d98fe3625 100644
--- a/servers/slapd/back-meta/init.c
+++ b/servers/slapd/back-meta/init.c
@@ -407,7 +407,7 @@ meta_back_db_destroy(
 		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 );
+			tavl_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 ) ) {
diff --git a/servers/slapd/back-meta/unbind.c b/servers/slapd/back-meta/unbind.c
index 2a866a6c7..8589262e6 100644
--- a/servers/slapd/back-meta/unbind.c
+++ b/servers/slapd/back-meta/unbind.c
@@ -54,7 +54,7 @@ meta_back_conn_destroy(
 #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 )
+	while ( ( mc = tavl_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,
diff --git a/tests/data/slapd-proxytimeout.conf b/tests/data/slapd-proxytimeout.conf
new file mode 100644
index 000000000..2fb7c6dfa
--- /dev/null
+++ b/tests/data/slapd-proxytimeout.conf
@@ -0,0 +1,71 @@
+# provider slapd config -- for testing
+# $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>.
+
+include		@SCHEMADIR@/core.schema
+include		@SCHEMADIR@/cosine.schema
+include		@SCHEMADIR@/inetorgperson.schema
+include		@SCHEMADIR@/openldap.schema
+include		@SCHEMADIR@/nis.schema
+pidfile		@TESTDIR@/slapd.m.pid
+argsfile	@TESTDIR@/slapd.m.args
+
+#######################################################################
+# database definitions
+#######################################################################
+
+#mod#modulepath ../servers/slapd/back-@BACKEND@/:../servers/slapd/overlays
+#mod#moduleload back_@BACKEND@.la
+#ldapmod#modulepath ../servers/slapd/back-ldap/
+#ldapmod#moduleload back_ldap.la
+#rwmmod#modulepath ../servers/slapd/overlays/
+#rwmmod#moduleload rwm.la
+#monitormod#modulepath ../servers/slapd/back-monitor/
+#monitormod#moduleload back_monitor.la
+
+# here the proxy is not only acting as a proxy, but it also has a local database dc=local,dc=com"
+database	@BACKEND@
+suffix		"dc=local,dc=com"
+rootdn		"cn=Manager,dc=local,dc=com"
+rootpw		"secret"
+#~null~#directory	@TESTDIR@/db.2.a
+
+
+# Configure proxy
+# - normal user binds to "*,dc=example,dc=com" are proxied through to the remote slapd
+# - admin bind to local "cn=Manager,dc=local,dc=com" is overwritten by using idassert-bind
+database	ldap
+uri			"@URI1@"
+suffix		"dc=idle-timeout,dc=example,dc=com"
+idassert-bind bindmethod=simple binddn="cn=Manager,dc=example,dc=com" credentials="secret"
+idassert-authzFrom "dn.exact:cn=Manager,dc=local,dc=com"
+rebind-as-user	yes
+monitoring 		on
+idle-timeout 	@TIMEOUT@
+overlay 		rwm
+rwm-suffixmassage   "dc=idle-timeout,dc=example,dc=com" "ou=People,dc=example,dc=com"
+
+database	ldap
+uri			"@URI1@"
+suffix		"dc=conn-ttl,dc=example,dc=com"
+idassert-bind bindmethod=simple binddn="cn=Manager,dc=example,dc=com" credentials="secret"
+idassert-authzFrom "dn.exact:cn=Manager,dc=local,dc=com"
+rebind-as-user	yes
+monitoring		on
+conn-ttl 		@TIMEOUT@
+overlay 		rwm
+rwm-suffixmassage   "dc=conn-ttl,dc=example,dc=com" "ou=People,dc=example,dc=com"
+
+database	monitor
+
diff --git a/tests/scripts/conf.sh b/tests/scripts/conf.sh
index 98bfb5194..6200880c4 100755
--- a/tests/scripts/conf.sh
+++ b/tests/scripts/conf.sh
@@ -79,4 +79,5 @@ sed -e "s/@BACKEND@/${BACKEND}/"			\
 	-e "s;@TESTWD@;${TESTWD};"			\
 	-e "s;@DATADIR@;${DATADIR};"			\
 	-e "s;@SCHEMADIR@;${SCHEMADIR};"		\
+	-e "s;@TIMEOUT@;${TIMEOUT};"			\
 	-e "/^#/d"
diff --git a/tests/scripts/defines.sh b/tests/scripts/defines.sh
index 97cf08fd7..273f039cf 100755
--- a/tests/scripts/defines.sh
+++ b/tests/scripts/defines.sh
@@ -50,6 +50,7 @@ THREADS=${AC_THREADS-threadsno}
 SLEEP0=${SLEEP0-1}
 SLEEP1=${SLEEP1-7}
 SLEEP2=${SLEEP2-15}
+TIMEOUT=${TIMEOUT-4}
 
 # dirs
 PROGDIR=./progs
diff --git a/tests/scripts/test079-proxy-timeout b/tests/scripts/test079-proxy-timeout
new file mode 100644
index 000000000..e097c0739
--- /dev/null
+++ b/tests/scripts/test079-proxy-timeout
@@ -0,0 +1,324 @@
+#! /bin/sh
+# $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>.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test $BACKLDAP = "ldapno" ; then
+	echo "LDAP backend not available, test skipped"
+	exit 0
+fi
+if test $RWM = "rwmno" ; then
+        echo "rwm (rewrite/remap) overlay not available, test skipped"
+        exit 0
+fi
+
+mkdir -p $TESTDIR $DBDIR1 $DBDIR2
+$SLAPPASSWD -g -n >$CONFIGPWF
+
+#
+# Start slapd that acts as a remote LDAP server that will be proxied
+#
+echo "Running slapadd to build database for the remote slapd server..."
+. $CONFFILTER $BACKEND < $CONF > $CONF1
+$SLAPADD -f $CONF1 -l $LDIFORDERED
+RC=$?
+if test $RC != 0 ; then
+        echo "slapadd failed ($RC)!"
+        exit $RC
+fi
+
+echo "Starting remote slapd server on TCP/IP port $PORT1..."
+$SLAPD -f $CONF1 -h $URI1 -d $LVL > $LOG1 2>&1 &
+SERVERPID=$!
+if test $WAIT != 0 ; then
+    echo SERVERPID $SERVERPID
+    read foo
+fi
+
+#
+# Start ldapd that will proxy for the remote server
+#
+# Proxy is configured with two slapd-ldap backends:
+# - one with idle timeout set: dc=idle-timeout,$BASED
+# - one with connection TTL set: dc=conn-ttl,$BASEDN
+#
+echo "Starting slapd proxy on TCP/IP port $PORT2..."
+. $CONFFILTER $BACKEND < $DATADIR/slapd-proxytimeout.conf > $CONF2
+$SLAPD -f $CONF2 -h $URI2 -d $LVL > $LOG2 2>&1 &
+PROXYPID=$!
+if test $WAIT != 0 ; then
+    echo PROXYPID $PROXYPID
+    read foo
+fi
+
+KILLPIDS="$SERVERPID $PROXYPID"
+
+sleep $SLEEP0
+
+##############################################################################
+#
+# Test 1: Test that shared connections are timed out
+#
+
+NOW=`date +%s`
+echo "Create shared connection towards remote LDAP (time_t now=$NOW timeout=`expr $NOW + $TIMEOUT`)"
+
+$LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD \
+        'objectclass=*' > $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+        echo "ldapsearch failed for base: dc=idle-timeout,$BASEDN ($RC)!"
+        test $KILLSERVERS != no && kill -HUP $KILLPIDS
+        exit $RC
+fi
+
+$LDAPSEARCH -b "dc=conn-ttl,$BASEDN" \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD \
+        'objectclass=*' >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+        echo "ldapsearch failed for base: dc=conn-ttl,$BASEDN ($RC)!"
+        test $KILLSERVERS != no && kill -HUP $KILLPIDS
+        exit $RC
+fi
+
+# Check that connections are established by searching for olmDbConnURI from Monitor
+
+echo "Checking that proxy has created connections towards backend"
+
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 0 ; then
+    echo "Error: LDAP connection to remote LDAP server is not found ($RC)"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+$LDAPSEARCH -b "cn=Connections,cn=database 3,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 0 ; then
+    echo "Error: LDAP connection to remote LDAP server is not found ($RC)"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+# Wait for connections to be closed, either due to
+# - idle-timeout and
+# - conn-ttl
+
+echo "Sleeping until idle-timeout and conn-ttl have passed"
+sleep `expr $TIMEOUT + 1`
+
+echo "Checking that proxy has closed expired connections towards the remote LDAP server (time_t now=`date +%s`)"
+
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 1 ; then
+    echo "Error: LDAP connection to remote LDAP server was not closed"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+$LDAPSEARCH -b "cn=Connections,cn=database 3,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 1 ; then
+    echo "Error: LDAP connection to remote LDAP server was not closed"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+
+##############################################################################
+#
+# Test 2: Test that private connections are timed out
+#
+
+NOW=`date +%s`
+echo "Create private connection towards remote LDAP (time_t now=$NOW timeout=`expr $NOW + $TIMEOUT`)"
+
+# Create fifos that are used to pass searches from the test case to ldapsearch
+rm -f $TESTDIR/ldapsearch1.fifo $TESTDIR/ldapsearch2.fifo
+mkfifo $TESTDIR/ldapsearch1.fifo $TESTDIR/ldapsearch2.fifo
+
+# Execute ldapsearch on background and have it read searches from the fifo
+$LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \
+        -D "cn=Barbara Jensen,ou=Information Technology Division,dc=idle-timeout,$BASEDN" \
+        -H $URI2 \
+        -w "bjensen" \
+        -f $TESTDIR/ldapsearch1.fifo >> $TESTOUT 2>&1 &
+LDAPSEARCHPIDS=$!
+
+$LDAPSEARCH -b "dc=conn-ttl,$BASEDN" \
+        -D "cn=Barbara Jensen,ou=Information Technology Division,dc=conn-ttl,$BASEDN" \
+        -H $URI2 \
+        -w "bjensen" \
+        -f $TESTDIR/ldapsearch2.fifo >> $TESTOUT 2>&1 &
+LDAPSEARCHPIDS="$LDAPSEARCHPIDS $!"
+
+# Open fifos as file descriptor
+exec 3>$TESTDIR/ldapsearch1.fifo
+exec 4>$TESTDIR/ldapsearch2.fifo
+
+# Trigger LDAP connections towards the proxy by executing a search
+echo 'objectclass=*' >&3
+echo 'objectclass=*' >&4
+sleep 1
+
+echo "Checking that proxy has created connections towards backend"
+
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 0 ; then
+    echo "Error: LDAP connection to remote LDAP server is not found ($RC)"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS $LDAPSEARCHPIDS
+    exit $RC
+fi
+
+$LDAPSEARCH -b "cn=Connections,cn=database 3,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 0 ; then
+    echo "Error: LDAP connection to remote LDAP server is not found ($RC)"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS $LDAPSEARCHPIDS
+    exit $RC
+fi
+
+
+echo "Sleeping until idle-timeout and conn-ttl have passed"
+sleep `expr $TIMEOUT + 1`
+
+echo "Checking that proxy has closed expired connections towards the remote LDAP server (time_t now=`date +%s`)"
+
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 1 ; then
+    echo "Error: LDAP connection to remote LDAP server was not closed"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS $LDAPSEARCHPIDS
+    exit $RC
+fi
+
+$LDAPSEARCH -b "cn=Connections,cn=database 3,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 1 ; then
+    echo "Error: LDAP connection to remote LDAP server was not closed"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS $LDAPSEARCHPIDS
+    exit $RC
+fi
+
+# Close the file descriptors associated with the fifos.
+# This will trigger EOF to ldapsearch which will cause it to exit.
+exec 3>&-
+exec 4>&-
+
+
+##############################################################################
+#
+# Test 3: Check that idle-timeout is reset on activity
+#
+
+echo "Checking that idle-timeout is reset on activity"
+NOW=`date +%s`
+echo "Create cached connection: idle-timeout timeout starts (time_t now=$NOW, original_timeout=`expr $NOW + $TIMEOUT`)"
+$LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD \
+        'objectclass=*' >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+        echo "ldapsearch failed for base: dc=idle-timeout,$BASEDN ($RC)!"
+        test $KILLSERVERS != no && kill -HUP $KILLPIDS
+        exit $RC
+fi
+
+# sleep second less than idle-timeout to extend the timeout
+sleep `expr $TIMEOUT - 1`
+NOW=`date +%s`
+echo "Do another search to reset the timeout (time_t now=$NOW, new_timeout=`expr $NOW + $TIMEOUT`)"
+$LDAPSEARCH -b "dc=idle-timeout,$BASEDN" \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD \
+        'objectclass=*' >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+        echo "ldapsearch failed for base: dc=idle-timeout,$BASEDN ($RC)!"
+        test $KILLSERVERS != no && kill -HUP $KILLPIDS
+        exit $RC
+fi
+
+sleep `expr $TIMEOUT - 1`
+echo "Check that connection is still alive due to idle-timeout reset (time_t now=`date +%s`)"
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD 2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 0 ; then
+    echo "Error: LDAP connection to remote LDAP server is not found ($RC)"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+sleep 2
+echo "Check that connection is closed after extended idle-timeout has passed (time_t now=`date +%s`)"
+$LDAPSEARCH -b "cn=Connections,cn=database 2,cn=databases,cn=monitor" -s one -LLL olmDbConnURI \
+        -D "cn=Manager,dc=local,dc=com" \
+        -H $URI2 \
+        -w $PASSWD  2>&1 | tee -a $TESTOUT | grep ldap://${LOCALHOST}:$PORT1 >/dev/null
+RC=$?
+if test $RC != 1 ; then
+    echo "Error: LDAP connection to remote LDAP server was not closed"
+    test $KILLSERVERS != no && kill -HUP $KILLPIDS
+    exit $RC
+fi
+
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
-- 
2.30.1

openSUSE Build Service is sponsored by