mirror of
https://git.openldap.org/openldap/openldap.git
synced 2026-01-23 15:23:00 -05:00
ITS#9197 back-ldap: added task that prunes expired connections
This commit is contained in:
parent
b57d317fd9
commit
0eacc4a793
16 changed files with 716 additions and 197 deletions
|
|
@ -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 {
|
||||
|
|
@ -417,6 +417,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)
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +924,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 );
|
||||
}
|
||||
|
||||
|
|
@ -1080,7 +1070,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 );
|
||||
}
|
||||
|
|
@ -1126,6 +1116,7 @@ retry_lock:
|
|||
return NULL;
|
||||
}
|
||||
}
|
||||
ldap_back_schedule_conn_expiry( li, lc );
|
||||
|
||||
} else {
|
||||
int expiring = 0;
|
||||
|
|
@ -3054,3 +3045,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 );
|
||||
ldap_back_freeconn( li, lc, 0 );
|
||||
} else {
|
||||
Debug( LDAP_DEBUG_TRACE,
|
||||
"ldap_back_conn_prune: tainting expired connection lc=%p\n",
|
||||
lc );
|
||||
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 );
|
||||
ldap_back_freeconn( li, lc, 0 );
|
||||
} else {
|
||||
Debug( LDAP_DEBUG_TRACE,
|
||||
"ldap_back_conn_prune: tainting expired connection lc=%p\n",
|
||||
lc );
|
||||
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 );
|
||||
} 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 );
|
||||
}
|
||||
ldap_pvt_thread_mutex_unlock( &slapd_rq.rq_mutex );
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -558,7 +558,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 );
|
||||
|
||||
|
|
@ -590,7 +590,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;
|
||||
|
|
@ -830,7 +830,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 );
|
||||
|
||||
|
|
@ -863,7 +863,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;
|
||||
|
|
@ -1387,7 +1387,7 @@ fail:
|
|||
|
||||
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: "
|
||||
|
|
@ -1410,34 +1410,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
|
||||
|
|
@ -1447,20 +1440,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;
|
||||
ldap_chain_cfadd_apply( lc->lc_common_li, op, rs, p, ca, count++ );
|
||||
|
||||
(void)ldap_chain_cfadd_apply( (void *)lc->lc_common_li, (void *)&lca );
|
||||
|
||||
(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;
|
||||
}
|
||||
|
|
@ -1480,7 +1473,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 );
|
||||
return -1;
|
||||
|
|
@ -1889,7 +1882,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 ) )
|
||||
{
|
||||
|
|
@ -1914,22 +1907,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,
|
||||
|
|
@ -1957,14 +1934,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2031,7 +2011,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 );
|
||||
}
|
||||
|
|
@ -2145,22 +2125,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,
|
||||
|
|
@ -2170,15 +2134,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1384,7 +1384,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -432,7 +432,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: "
|
||||
|
|
@ -460,26 +460,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
|
||||
|
|
@ -489,6 +490,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 };
|
||||
|
|
@ -499,10 +502,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;
|
||||
}
|
||||
|
|
@ -672,7 +680,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 ) )
|
||||
{
|
||||
|
|
@ -697,22 +705,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,
|
||||
|
|
@ -740,14 +732,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -784,7 +779,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 );
|
||||
}
|
||||
|
|
@ -854,22 +849,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,
|
||||
|
|
@ -879,15 +858,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "slap.h"
|
||||
#include "slap-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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -212,7 +212,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,
|
||||
|
|
@ -235,7 +235,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" );
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -830,7 +830,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 );
|
||||
|
|
@ -1155,7 +1155,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 );
|
||||
}
|
||||
|
||||
|
|
@ -1200,7 +1200,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 );
|
||||
}
|
||||
|
||||
|
|
@ -1420,7 +1420,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 */
|
||||
|
|
@ -1669,7 +1669,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 );
|
||||
}
|
||||
|
|
@ -1786,7 +1786,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... */
|
||||
|
|
|
|||
|
|
@ -405,7 +405,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 ) ) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
71
tests/data/slapd-proxytimeout.conf
Normal file
71
tests/data/slapd-proxytimeout.conf
Normal file
|
|
@ -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
|
||||
|
||||
|
|
@ -89,4 +89,5 @@ sed -e "s/@BACKEND@/${BACKEND}/" \
|
|||
-e "s;@KRB5REALM@;${KRB5REALM};" \
|
||||
-e "s;@KDCHOST@;${KDCHOST};" \
|
||||
-e "s;@KDCPORT@;${KDCPORT};" \
|
||||
-e "s;@TIMEOUT@;${TIMEOUT};" \
|
||||
-e "/^#/d"
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ ACI=${AC_ACI_ENABLED-acino}
|
|||
SLEEP0=${SLEEP0-1}
|
||||
SLEEP1=${SLEEP1-7}
|
||||
SLEEP2=${SLEEP2-15}
|
||||
TIMEOUT=${TIMEOUT-4}
|
||||
|
||||
# dirs
|
||||
PROGDIR=./progs
|
||||
|
|
|
|||
324
tests/scripts/test079-proxy-timeout
Executable file
324
tests/scripts/test079-proxy-timeout
Executable file
|
|
@ -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
|
||||
Loading…
Reference in a new issue