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