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