Home | History | Annotate | Line # | Download | only in back-wt
search.c revision 1.3
      1 /*	$NetBSD: search.c,v 1.3 2025/09/05 21:16:32 christos Exp $	*/
      2 
      3 /* OpenLDAP WiredTiger backend */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright 2002-2024 The OpenLDAP Foundation.
      8  * All rights reserved.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted only as authorized by the OpenLDAP
     12  * Public License.
     13  *
     14  * A copy of this license is available in the file LICENSE in the
     15  * top-level directory of the distribution or, alternatively, at
     16  * <http://www.OpenLDAP.org/license.html>.
     17  */
     18 /* ACKNOWLEDGEMENTS:
     19  * This work was developed by HAMANO Tsukasa <hamano (at) osstech.co.jp>
     20  * based on back-bdb for inclusion in OpenLDAP Software.
     21  * WiredTiger is a product of MongoDB Inc.
     22  */
     23 
     24 #include <sys/cdefs.h>
     25 __RCSID("$NetBSD: search.c,v 1.3 2025/09/05 21:16:32 christos Exp $");
     26 
     27 #include "portable.h"
     28 
     29 #include <stdio.h>
     30 #include <ac/string.h>
     31 
     32 #include "back-wt.h"
     33 #include "idl.h"
     34 
     35 static int search_aliases(
     36 	Operation *op,
     37 	SlapReply *rs,
     38 	Entry *e,
     39 	WT_SESSION *session,
     40 	ID *ids,
     41 	ID *scopes,
     42 	ID *stack )
     43 {
     44 	/* TODO: search_aliases does not implement yet. */
     45 	WT_IDL_ZERO( ids );
     46 	return 0;
     47 }
     48 
     49 static int base_candidate(
     50 	BackendDB *be,
     51 	Entry *e,
     52 	ID *ids )
     53 {
     54 	Debug(LDAP_DEBUG_ARGS,
     55 		  "base_candidate: base: \"%s\" (0x%08lx)\n",
     56 		  e->e_nname.bv_val, (long) e->e_id );
     57 
     58 	ids[0] = 1;
     59 	ids[1] = e->e_id;
     60 	return 0;
     61 }
     62 
     63 /* Look for "objectClass Present" in this filter.
     64  * Also count depth of filter tree while we're at it.
     65  */
     66 static int oc_filter(
     67 	Filter *f,
     68 	int cur,
     69 	int *max )
     70 {
     71 	int rc = 0;
     72 
     73 	assert( f != NULL );
     74 
     75 	if( cur > *max ) *max = cur;
     76 
     77 	switch( f->f_choice ) {
     78 	case LDAP_FILTER_PRESENT:
     79 		if (f->f_desc == slap_schema.si_ad_objectClass) {
     80 			rc = 1;
     81 		}
     82 		break;
     83 
     84 	case LDAP_FILTER_AND:
     85 	case LDAP_FILTER_OR:
     86 		cur++;
     87 		for ( f=f->f_and; f; f=f->f_next ) {
     88 			(void) oc_filter(f, cur, max);
     89 		}
     90 		break;
     91 
     92 	default:
     93 		break;
     94 	}
     95 	return rc;
     96 }
     97 
     98 static void search_stack_free( void *key, void *data )
     99 {
    100 	ber_memfree_x(data, NULL);
    101 }
    102 
    103 static void *search_stack( Operation *op )
    104 {
    105 	struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
    106 	void *ret = NULL;
    107 
    108 	if ( op->o_threadctx ) {
    109 		ldap_pvt_thread_pool_getkey( op->o_threadctx, (void *)search_stack,
    110 									 &ret, NULL );
    111 	} else {
    112 		ret = wi->wi_search_stack;
    113 	}
    114 
    115 	if ( !ret ) {
    116 		ret = ch_malloc( wi->wi_search_stack_depth * WT_IDL_UM_SIZE
    117 						 * sizeof( ID ) );
    118 		if ( op->o_threadctx ) {
    119 			ldap_pvt_thread_pool_setkey( op->o_threadctx, (void *)search_stack,
    120 										 ret, search_stack_free, NULL, NULL );
    121 		} else {
    122 			wi->wi_search_stack = ret;
    123 		}
    124 	}
    125 	return ret;
    126 }
    127 
    128 static int search_candidates(
    129 	Operation *op,
    130 	SlapReply *rs,
    131 	Entry *e,
    132 	wt_ctx *wc,
    133 	ID  *ids,
    134 	ID  *scopes )
    135 {
    136     struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
    137 	int rc, depth = 1;
    138 	Filter f, rf, xf, nf;
    139 	ID *stack;
    140 	AttributeAssertion aa_ref = ATTRIBUTEASSERTION_INIT;
    141 	Filter sf;
    142 	AttributeAssertion aa_subentry = ATTRIBUTEASSERTION_INIT;
    143 
    144 	Debug(LDAP_DEBUG_TRACE,
    145 		  "wt_search_candidates: base=\"%s\" (0x%08lx) scope=%d\n",
    146 		  e->e_nname.bv_val, (long) e->e_id, op->oq_search.rs_scope );
    147 
    148 	xf.f_or = op->oq_search.rs_filter;
    149 	xf.f_choice = LDAP_FILTER_OR;
    150 	xf.f_next = NULL;
    151 
    152 	/* If the user's filter uses objectClass=*,
    153      * these clauses are redundant.
    154      */
    155 	if (!oc_filter(op->oq_search.rs_filter, 1, &depth)
    156 		&& !get_subentries_visibility(op)) {
    157 		if( !get_manageDSAit(op) && !get_domainScope(op) ) {
    158 			/* match referral objects */
    159 			struct berval bv_ref = BER_BVC( "referral" );
    160 			rf.f_choice = LDAP_FILTER_EQUALITY;
    161 			rf.f_ava = &aa_ref;
    162 			rf.f_av_desc = slap_schema.si_ad_objectClass;
    163 			rf.f_av_value = bv_ref;
    164 			rf.f_next = xf.f_or;
    165 			xf.f_or = &rf;
    166 			depth++;
    167 		}
    168 	}
    169 
    170 	f.f_next = NULL;
    171 	f.f_choice = LDAP_FILTER_AND;
    172 	f.f_and = &nf;
    173 	/* Dummy; we compute scope separately now */
    174 	nf.f_choice = SLAPD_FILTER_COMPUTED;
    175 	nf.f_result = LDAP_SUCCESS;
    176 	nf.f_next = ( xf.f_or == op->oq_search.rs_filter )
    177 		? op->oq_search.rs_filter : &xf ;
    178 	/* Filter depth increased again, adding dummy clause */
    179 	depth++;
    180 
    181 	if( get_subentries_visibility( op ) ) {
    182 		struct berval bv_subentry = BER_BVC( "subentry" );
    183 		sf.f_choice = LDAP_FILTER_EQUALITY;
    184 		sf.f_ava = &aa_subentry;
    185 		sf.f_av_desc = slap_schema.si_ad_objectClass;
    186 		sf.f_av_value = bv_subentry;
    187 		sf.f_next = nf.f_next;
    188 		nf.f_next = &sf;
    189 	}
    190 
    191 	/* Allocate IDL stack, plus 1 more for former tmp */
    192 	if ( depth+1 > wi->wi_search_stack_depth ) {
    193 		stack = ch_malloc( (depth + 1) * WT_IDL_UM_SIZE * sizeof( ID ) );
    194 	} else {
    195 		stack = search_stack( op );
    196 	}
    197 
    198     if( op->ors_deref & LDAP_DEREF_SEARCHING ) {
    199 		rc = search_aliases( op, rs, e, wc->session, ids, scopes, stack );
    200 		if ( WT_IDL_IS_ZERO( ids ) && rc == LDAP_SUCCESS )
    201 			rc = wt_dn2idl( op, wc, &e->e_nname, e, ids, stack );
    202 	} else {
    203 		rc = wt_dn2idl(op, wc, &e->e_nname, e, ids, stack );
    204 	}
    205 
    206 	if ( rc == LDAP_SUCCESS ) {
    207 		rc = wt_filter_candidates( op, wc, &f, ids,
    208 								   stack, stack+WT_IDL_UM_SIZE );
    209 	}
    210 
    211 	if ( depth+1 > wi->wi_search_stack_depth ) {
    212 		ch_free( stack );
    213 	}
    214 
    215     if( rc ) {
    216 		Debug(LDAP_DEBUG_TRACE,
    217 			  "wt_search_candidates: failed (rc=%d)\n", rc );
    218 
    219 	} else {
    220 		Debug(LDAP_DEBUG_TRACE,
    221 			  "wt_search_candidates: id=%ld first=%ld last=%ld\n",
    222 			  (long) ids[0],
    223 			  (long) WT_IDL_FIRST(ids),
    224 			  (long) WT_IDL_LAST(ids));
    225 	}
    226 	return 0;
    227 }
    228 
    229 static int
    230 parse_paged_cookie( Operation *op, SlapReply *rs )
    231 {
    232 	int     rc = LDAP_SUCCESS;
    233 	PagedResultsState *ps = op->o_pagedresults_state;
    234 
    235 	/* this function must be invoked only if the pagedResults
    236      * control has been detected, parsed and partially checked
    237      * by the frontend */
    238 	assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED );
    239 
    240 	/* cookie decoding/checks deferred to backend... */
    241 	if ( ps->ps_cookieval.bv_len ) {
    242 		PagedResultsCookie reqcookie;
    243 		if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) {
    244 			/* bad cookie */
    245 			rs->sr_text = "paged results cookie is invalid";
    246 			rc = LDAP_PROTOCOL_ERROR;
    247 			goto done;
    248 		}
    249 
    250 		memcpy( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie ));
    251 
    252 		if ( reqcookie > ps->ps_cookie ) {
    253 			/* bad cookie */
    254 			rs->sr_text = "paged results cookie is invalid";
    255 			rc = LDAP_PROTOCOL_ERROR;
    256 			goto done;
    257 
    258 		} else if ( reqcookie < ps->ps_cookie ) {
    259 			rs->sr_text = "paged results cookie is invalid or old";
    260 			rc = LDAP_UNWILLING_TO_PERFORM;
    261 			goto done;
    262 		}
    263 
    264 	} else {
    265 		/* we're going to use ps_cookie */
    266 		op->o_conn->c_pagedresults_state.ps_cookie = 0;
    267 	}
    268 
    269 done:;
    270 
    271 	return rc;
    272 }
    273 
    274 static void
    275 send_paged_response(
    276 	Operation   *op,
    277 	SlapReply   *rs,
    278 	ID      *lastid,
    279 	int     tentries )
    280 {
    281 	LDAPControl *ctrls[2];
    282 	BerElementBuffer berbuf;
    283 	BerElement  *ber = (BerElement *)&berbuf;
    284 	PagedResultsCookie respcookie;
    285 	struct berval cookie;
    286 
    287 	Debug(LDAP_DEBUG_ARGS,
    288 		  "send_paged_response: lastid=0x%08lx nentries=%d\n",
    289 		  lastid ? *lastid : 0, rs->sr_nentries );
    290 
    291 	ctrls[1] = NULL;
    292 
    293 	ber_init2( ber, NULL, LBER_USE_DER );
    294 
    295 	if ( lastid ) {
    296 		respcookie = ( PagedResultsCookie )(*lastid);
    297 		cookie.bv_len = sizeof( respcookie );
    298 		cookie.bv_val = (char *)&respcookie;
    299 
    300 	} else {
    301 		respcookie = ( PagedResultsCookie )0;
    302 		BER_BVSTR( &cookie, "" );
    303 	}
    304 
    305 	op->o_conn->c_pagedresults_state.ps_cookie = respcookie;
    306 	op->o_conn->c_pagedresults_state.ps_count =
    307 		((PagedResultsState *)op->o_pagedresults_state)->ps_count +
    308 		rs->sr_nentries;
    309 
    310 	/* return size of 0 -- no estimate */
    311 	ber_printf( ber, "{iO}", 0, &cookie );
    312 
    313 	ctrls[0] = op->o_tmpalloc( sizeof(LDAPControl), op->o_tmpmemctx );
    314 	if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) {
    315 		goto done;
    316 	}
    317 
    318 	ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
    319 	ctrls[0]->ldctl_iscritical = 0;
    320 
    321 	slap_add_ctrls( op, rs, ctrls );
    322 	rs->sr_err = LDAP_SUCCESS;
    323 	send_ldap_result( op, rs );
    324 
    325 done:
    326 	(void) ber_free_buf( ber );
    327 }
    328 
    329 int
    330 wt_search( Operation *op, SlapReply *rs )
    331 {
    332     struct wt_info *wi = (struct wt_info *) op->o_bd->be_private;
    333 	ID id, cursor;
    334 	ID lastid = NOID;
    335 	int manageDSAit;
    336 	wt_ctx *wc;
    337 	int rc = LDAP_OTHER;
    338 	Entry *e = NULL;
    339 	Entry *ae = NULL;
    340 	Entry *base = NULL;
    341 	slap_mask_t mask;
    342 	time_t stoptime;
    343 
    344 	ID candidates[WT_IDL_UM_SIZE];
    345 	ID scopes[WT_IDL_DB_SIZE];
    346 	int tentries = 0;
    347 	unsigned nentries = 0;
    348 
    349 	Debug( LDAP_DEBUG_ARGS, "==> wt_search: %s\n", op->o_req_dn.bv_val );
    350 
    351 	manageDSAit = get_manageDSAit( op );
    352 
    353 	wc = wt_ctx_get(op, wi);
    354 	if( !wc ){
    355         Debug( LDAP_DEBUG_ANY,
    356 			   "wt_search: wt_ctx_get failed: %d\n", rc );
    357 		send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
    358         return rc;
    359 	}
    360 
    361 	/* get entry */
    362 	rc = wt_dn2entry(op->o_bd, wc, &op->o_req_ndn, &e);
    363 	switch( rc ) {
    364 	case 0:
    365 		break;
    366 	case WT_NOTFOUND:
    367 		rc = wt_dn2aentry(op->o_bd, wc, &op->o_req_ndn, &ae);
    368 		break;
    369 	default:
    370 		/* TODO: error handling */
    371 		Debug( LDAP_DEBUG_ANY,
    372 			   "<== wt_search: error at wt_dn2entry() rc=%d\n", rc );
    373 		send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
    374 		goto done;
    375 	}
    376 
    377 	if ( op->ors_deref & LDAP_DEREF_FINDING ) {
    378 		/* not implement yet */
    379 	}
    380 
    381 	if ( e == NULL ) {
    382 		if ( ae ) {
    383 			struct berval matched_dn = BER_BVNULL;
    384 			/* found ancestor entry */
    385 			if ( access_allowed( op, ae,
    386 								 slap_schema.si_ad_entry,
    387 								 NULL, ACL_DISCLOSE, NULL ) ) {
    388 				BerVarray erefs = NULL;
    389 				ber_dupbv( &matched_dn, &ae->e_name );
    390 				erefs = is_entry_referral( ae )
    391 					? get_entry_referrals( op, ae )
    392 					: NULL;
    393 				rs->sr_err = LDAP_REFERRAL;
    394 				rs->sr_matched = matched_dn.bv_val;
    395 				if ( erefs ) {
    396 					rs->sr_ref = referral_rewrite( erefs, &matched_dn,
    397 												   &op->o_req_dn, op->oq_search.rs_scope );
    398 					ber_bvarray_free( erefs );
    399 				}
    400 				Debug( LDAP_DEBUG_ARGS,
    401 					   "wt_search: ancestor is referral\n");
    402 				rs->sr_flags = REP_MATCHED_MUSTBEFREED | REP_REF_MUSTBEFREED;
    403 				send_ldap_result( op, rs );
    404 				goto done;
    405 			}
    406 		}
    407 		Debug( LDAP_DEBUG_ARGS,
    408 			   "wt_search: no such object %s\n",
    409 			   op->o_req_dn.bv_val);
    410 		rs->sr_err = LDAP_NO_SUCH_OBJECT;
    411 		send_ldap_result( op, rs );
    412 		goto done;
    413 	}
    414 
    415 	/* NOTE: __NEW__ "search" access is required
    416      * on searchBase object */
    417 	if ( ! access_allowed_mask( op, e, slap_schema.si_ad_entry,
    418 								NULL, ACL_SEARCH, NULL, &mask ) )
    419 	{
    420 		if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
    421 			rs->sr_err = LDAP_NO_SUCH_OBJECT;
    422 		} else {
    423 			rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
    424 		}
    425 
    426 		send_ldap_result( op, rs );
    427 		goto done;
    428 	}
    429 
    430 	if ( !manageDSAit && is_entry_referral( e ) ) {
    431 		struct berval matched_dn = BER_BVNULL;
    432 		BerVarray erefs = NULL;
    433 		ber_dupbv( &matched_dn, &e->e_name );
    434 		erefs = get_entry_referrals( op, e );
    435 		rs->sr_err = LDAP_REFERRAL;
    436 		if ( erefs ) {
    437 			rs->sr_ref = referral_rewrite( erefs, &matched_dn,
    438 										   &op->o_req_dn, op->oq_search.rs_scope );
    439 			ber_bvarray_free( erefs );
    440 			if ( !rs->sr_ref ) {
    441 				rs->sr_text = "bad_referral object";
    442 			}
    443 		}
    444 		Debug( LDAP_DEBUG_ARGS, "wt_search: entry is referral\n");
    445 		rs->sr_matched = matched_dn.bv_val;
    446 		send_ldap_result( op, rs );
    447 		ber_bvarray_free( rs->sr_ref );
    448 		rs->sr_ref = NULL;
    449 		ber_memfree( matched_dn.bv_val );
    450 		rs->sr_matched = NULL;
    451 		goto done;
    452 	}
    453 
    454 	if ( get_assert( op ) &&
    455 		 ( test_filter( op, e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
    456 	{
    457 		rs->sr_err = LDAP_ASSERTION_FAILED;
    458 		send_ldap_result( op, rs );
    459 		goto done;
    460 	}
    461 
    462 	/* compute it anyway; root does not use it */
    463 	stoptime = op->o_time + op->ors_tlimit;
    464 
    465 	base = e;
    466 
    467 	e = NULL;
    468 
    469 	/* select candidates */
    470 	if ( op->oq_search.rs_scope == LDAP_SCOPE_BASE ) {
    471 		rs->sr_err = base_candidate( op->o_bd, base, candidates );
    472 	}else{
    473 		WT_IDL_ZERO( candidates );
    474 		WT_IDL_ZERO( scopes );
    475 		rc = search_candidates( op, rs, base,
    476 								wc, candidates, scopes );
    477 		switch(rc){
    478 		case 0:
    479 		case WT_NOTFOUND:
    480 			break;
    481 		default:
    482 			Debug( LDAP_DEBUG_ANY, "wt_search: error search_candidates\n" );
    483 			send_ldap_error( op, rs, LDAP_OTHER, "internal error" );
    484 			goto done;
    485 		}
    486 	}
    487 
    488 	/* start cursor at beginning of candidates.
    489      */
    490 	cursor = 0;
    491 
    492 	if ( candidates[0] == 0 ) {
    493 		Debug( LDAP_DEBUG_TRACE, "wt_search: no candidates\n" );
    494 		goto nochange;
    495 	}
    496 
    497 	if ( op->ors_limit &&
    498 		 op->ors_limit->lms_s_unchecked != -1 &&
    499 		 WT_IDL_N(candidates) > (unsigned) op->ors_limit->lms_s_unchecked )
    500 	{
    501 		rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
    502 		send_ldap_result( op, rs );
    503 		rs->sr_err = LDAP_SUCCESS;
    504 		goto done;
    505 	}
    506 
    507 	if ( op->ors_limit == NULL  /* isroot == TRUE */ ||
    508 		 !op->ors_limit->lms_s_pr_hide )
    509 	{
    510 		tentries = WT_IDL_N(candidates);
    511 	}
    512 
    513 	if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
    514 		/* TODO: pageresult */
    515 		PagedResultsState *ps = op->o_pagedresults_state;
    516 		/* deferred cookie parsing */
    517 		rs->sr_err = parse_paged_cookie( op, rs );
    518 		if ( rs->sr_err != LDAP_SUCCESS ) {
    519 			send_ldap_result( op, rs );
    520 			goto done;
    521 		}
    522 
    523 		cursor = (ID) ps->ps_cookie;
    524 		if ( cursor && ps->ps_size == 0 ) {
    525 			rs->sr_err = LDAP_SUCCESS;
    526 			rs->sr_text = "search abandoned by pagedResult size=0";
    527 			send_ldap_result( op, rs );
    528 			goto done;
    529 		}
    530 		id = wt_idl_first( candidates, &cursor );
    531 		if ( id == NOID ) {
    532 			Debug( LDAP_DEBUG_TRACE, "wt_search: no paged results candidates\n" );
    533 			send_paged_response( op, rs, &lastid, 0 );
    534 
    535 			rs->sr_err = LDAP_OTHER;
    536 			goto done;
    537 		}
    538 		nentries = ps->ps_count;
    539 		if ( id == (ID)ps->ps_cookie )
    540 			id = wt_idl_next( candidates, &cursor );
    541 		goto loop_begin;
    542 	}
    543 
    544 	for ( id = wt_idl_first( candidates, &cursor );
    545 		  id != NOID ; id = wt_idl_next( candidates, &cursor ) )
    546 	{
    547 		int scopeok;
    548 
    549 loop_begin:
    550 
    551 		/* check for abandon */
    552 		if ( op->o_abandon ) {
    553 			rs->sr_err = SLAPD_ABANDON;
    554 			send_ldap_result( op, rs );
    555 			goto done;
    556 		}
    557 
    558 		/* mostly needed by internal searches,
    559          * e.g. related to syncrepl, for whom
    560          * abandon does not get set... */
    561 		if ( slapd_shutdown ) {
    562 			rs->sr_err = LDAP_UNAVAILABLE;
    563 			send_ldap_disconnect( op, rs );
    564 			goto done;
    565 		}
    566 
    567 		/* check time limit */
    568 		if ( op->ors_tlimit != SLAP_NO_LIMIT
    569 			 && slap_get_time() > stoptime )
    570 		{
    571 			rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
    572 			rs->sr_ref = rs->sr_v2ref;
    573 			send_ldap_result( op, rs );
    574 			rs->sr_err = LDAP_SUCCESS;
    575 			goto done;
    576 		}
    577 
    578 		nentries++;
    579 
    580 	fetch_entry_retry:
    581 
    582 		rc = wt_id2entry(op->o_bd, wc, id, &e);
    583 		/* TODO: error handling */
    584 		if ( e == NULL ) {
    585 			/* TODO: */
    586 			goto loop_continue;
    587 		}
    588 		if ( is_entry_subentry( e ) ) {
    589             if( op->oq_search.rs_scope != LDAP_SCOPE_BASE ) {
    590 				if(!get_subentries_visibility( op )) {
    591 					/* only subentries are visible */
    592 					goto loop_continue;
    593 				}
    594 
    595 			} else if ( get_subentries( op ) &&
    596 						!get_subentries_visibility( op ))
    597 			{
    598 				/* only subentries are visible */
    599 				goto loop_continue;
    600 			}
    601 
    602 		} else if ( get_subentries_visibility( op )) {
    603 			/* only subentries are visible */
    604 			goto loop_continue;
    605 		}
    606 
    607 		scopeok = 0;
    608 		switch( op->ors_scope ) {
    609 		case LDAP_SCOPE_BASE:
    610 			/* This is always true, yes? */
    611 			if ( id == base->e_id ) scopeok = 1;
    612 			break;
    613 		case LDAP_SCOPE_ONELEVEL:
    614 			scopeok = dnIsSuffixScope(&e->e_nname, &base->e_nname, LDAP_SCOPE_ONELEVEL);
    615 			break;
    616 		case LDAP_SCOPE_CHILDREN:
    617 			if ( id == base->e_id ) break;
    618 			/* Fall-thru */
    619 		case LDAP_SCOPE_SUBTREE:
    620  			scopeok = dnIsSuffix(&e->e_nname, &base->e_nname);
    621 			break;
    622 		}
    623 
    624 		/* aliases were already dereferenced in candidate list */
    625 		if ( op->ors_deref & LDAP_DEREF_SEARCHING ) {
    626 			/* but if the search base is an alias, and we didn't
    627 			 * deref it when finding, return it.
    628 			 */
    629 			if ( is_entry_alias(e) &&
    630 				 ((op->ors_deref & LDAP_DEREF_FINDING) ||
    631 				  !bvmatch(&e->e_nname, &op->o_req_ndn)))
    632 			{
    633 				goto loop_continue;
    634 			}
    635 			/* TODO: alias handling */
    636 		}
    637 
    638 		/* Not in scope, ignore it */
    639 		if ( !scopeok )
    640 		{
    641 			Debug( LDAP_DEBUG_TRACE, "wt_search: %ld scope not okay\n",
    642 				   (long) id );
    643 			goto loop_continue;
    644 		}
    645 
    646 		/*
    647          * if it's a referral, add it to the list of referrals. only do
    648          * this for non-base searches, and don't check the filter
    649          * explicitly here since it's only a candidate anyway.
    650          */
    651 		if ( !manageDSAit && op->oq_search.rs_scope != LDAP_SCOPE_BASE
    652 			 && is_entry_referral( e ) )
    653 		{
    654 			BerVarray erefs = get_entry_referrals( op, e );
    655 			rs->sr_ref = referral_rewrite( erefs, &e->e_name, NULL,
    656 										   op->oq_search.rs_scope == LDAP_SCOPE_ONELEVEL
    657 										   ? LDAP_SCOPE_BASE : LDAP_SCOPE_SUBTREE );
    658 			rs->sr_entry = e;
    659 			send_search_reference( op, rs );
    660 			rs->sr_entry = NULL;
    661 			ber_bvarray_free( rs->sr_ref );
    662 			ber_bvarray_free( erefs );
    663 			goto loop_continue;
    664 		}
    665 
    666 		if ( !manageDSAit && is_entry_glue( e )) {
    667 			goto loop_continue;
    668 		}
    669 
    670 		/* if it matches the filter and scope, send it */
    671 		rs->sr_err = test_filter( op, e, op->oq_search.rs_filter );
    672 		if ( rs->sr_err == LDAP_COMPARE_TRUE ) {
    673 			/* check size limit */
    674 			if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
    675 				if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size ) {
    676 					wt_entry_return( e );
    677 					e = NULL;
    678 					send_paged_response( op, rs, &lastid, tentries );
    679 					goto done;
    680 				}
    681 				lastid = id;
    682 			}
    683 
    684 			if (e) {
    685 				/* safe default */
    686 				rs->sr_attrs = op->oq_search.rs_attrs;
    687 				rs->sr_operational_attrs = NULL;
    688 				rs->sr_ctrls = NULL;
    689 				rs->sr_entry = e;
    690 				RS_ASSERT( e->e_private != NULL );
    691 				rs->sr_flags = REP_ENTRY_MUSTRELEASE;
    692 				rs->sr_err = LDAP_SUCCESS;
    693 				rs->sr_err = send_search_entry( op, rs );
    694 				rs->sr_attrs = NULL;
    695 				rs->sr_entry = NULL;
    696 				e = NULL;
    697 
    698 				switch ( rs->sr_err ) {
    699 				case LDAP_SUCCESS:  /* entry sent ok */
    700 					break;
    701 				default: /* entry not sent */
    702 					break;
    703 				case LDAP_BUSY:
    704 					send_ldap_result( op, rs );
    705 					goto done;
    706 				case LDAP_UNAVAILABLE:
    707 					rs->sr_err = LDAP_OTHER;
    708 					goto done;
    709 				case LDAP_SIZELIMIT_EXCEEDED:
    710 					rs->sr_ref = rs->sr_v2ref;
    711 					send_ldap_result( op, rs );
    712 					rs->sr_err = LDAP_SUCCESS;
    713 					goto done;
    714 				}
    715 			}
    716 		} else {
    717 			Debug( LDAP_DEBUG_TRACE,
    718 				   "wt_search: %ld does not match filter\n", (long) id );
    719 		}
    720 
    721 	loop_continue:
    722 		if( e ) {
    723 			wt_entry_return( e );
    724 			e = NULL;
    725 		}
    726 	}
    727 
    728 nochange:
    729     rs->sr_ctrls = NULL;
    730 	rs->sr_ref = rs->sr_v2ref;
    731 	rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS : LDAP_REFERRAL;
    732 	rs->sr_rspoid = NULL;
    733 	if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
    734 		send_paged_response( op, rs, NULL, 0 );
    735 	} else {
    736 		send_ldap_result( op, rs );
    737 	}
    738 
    739 	rs->sr_err = LDAP_SUCCESS;
    740 
    741 done:
    742 
    743 	if( base ) {
    744 		wt_entry_return( base );
    745 	}
    746 
    747 	if( e ) {
    748 		wt_entry_return( e );
    749 	}
    750 
    751 	if( ae ) {
    752 		wt_entry_return( ae );
    753 	}
    754 
    755     return rs->sr_err;
    756 }
    757 
    758 /*
    759  * Local variables:
    760  * indent-tabs-mode: t
    761  * tab-width: 4
    762  * c-basic-offset: 4
    763  * End:
    764  */
    765