]> git.sur5r.net Git - openldap/blob - libraries/libldap/search.c
56b07a7df273a2c87581a42c4ad90d4a9d47b0fe
[openldap] / libraries / libldap / search.c
1 /*
2  * Copyright 1998-1999 The OpenLDAP Foundation, All Rights Reserved.
3  * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
4  */
5 /*  Portions
6  *  Copyright (c) 1990 Regents of the University of Michigan.
7  *  All rights reserved.
8  *
9  *  search.c
10  */
11
12 #include "portable.h"
13
14 #include <stdio.h>
15
16 #include <ac/stdlib.h>
17
18 #include <ac/ctype.h>
19 #include <ac/socket.h>
20 #include <ac/string.h>
21 #include <ac/time.h>
22
23 #include "ldap-int.h"
24
25 static int ldap_is_attr_oid LDAP_P((
26         const char *attr ));
27
28 static int ldap_is_attr_desc LDAP_P((
29         const char *attr ));
30
31 static int hex2value LDAP_P((
32         int c ));
33
34 static ber_slen_t filter_value_unescape LDAP_P((
35         char *filter ));
36
37 static char *find_right_paren LDAP_P((
38         char *s ));
39
40 static char *find_wildcard LDAP_P((
41         char *s ));
42
43 static char *put_complex_filter LDAP_P((
44         BerElement *ber,
45         char *str,
46         ber_tag_t tag,
47         int not ));
48
49 static int put_filter LDAP_P((
50         BerElement *ber,
51         char *str ));
52
53 static int put_simple_filter LDAP_P((
54         BerElement *ber,
55         char *str ));
56
57 static int put_substring_filter LDAP_P((
58         BerElement *ber,
59         char *type,
60         char *str ));
61
62 static int put_filter_list LDAP_P((
63         BerElement *ber,
64         char *str ));
65
66 /*
67  * ldap_search_ext - initiate an ldap search operation.
68  *
69  * Parameters:
70  *
71  *      ld              LDAP descriptor
72  *      base            DN of the base object
73  *      scope           the search scope - one of LDAP_SCOPE_BASE,
74  *                          LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
75  *      filter          a string containing the search filter
76  *                      (e.g., "(|(cn=bob)(sn=bob))")
77  *      attrs           list of attribute types to return for matches
78  *      attrsonly       1 => attributes only 0 => attributes and values
79  *
80  * Example:
81  *      char    *attrs[] = { "mail", "title", 0 };
82  *      ldap_search_ext( ld, "c=us,o=UM", LDAP_SCOPE_SUBTREE, "cn~=bob",
83  *          attrs, attrsonly, sctrls, ctrls, timeout, sizelimit,
84  *              &msgid );
85  */
86 int
87 ldap_search_ext(
88         LDAP *ld,
89         LDAP_CONST char *base,
90         int scope,
91         LDAP_CONST char *filter,
92         char **attrs,
93         int attrsonly,
94         LDAPControl **sctrls,
95         LDAPControl **cctrls,
96         struct timeval *timeout,
97         int sizelimit,
98         int *msgidp )
99 {
100         BerElement      *ber;
101         int timelimit;
102
103         Debug( LDAP_DEBUG_TRACE, "ldap_search_ext\n", 0, 0, 0 );
104
105         /*
106          * if timeout is provided, use only tv_sec as timelimit.
107          * otherwise, use default.
108          */
109         timelimit = (timeout != NULL)
110                         ?  timeout->tv_sec
111                         : -1;
112
113         ber = ldap_build_search_req( ld, base, scope, filter, attrs,
114             attrsonly, sctrls, cctrls, timelimit, sizelimit ); 
115
116         if ( ber == NULL ) {
117                 return ld->ld_errno;
118         }
119
120 #ifndef LDAP_NOCACHE
121         if ( ld->ld_cache != NULL ) {
122                 if ( ldap_check_cache( ld, LDAP_REQ_SEARCH, ber ) == 0 ) {
123                         ber_free( ber, 1 );
124                         ld->ld_errno = LDAP_SUCCESS;
125                         *msgidp = ld->ld_msgid;
126                         return ld->ld_errno;
127                 }
128                 ldap_add_request_to_cache( ld, LDAP_REQ_SEARCH, ber );
129         }
130 #endif /* LDAP_NOCACHE */
131
132         /* send the message */
133         *msgidp = ldap_send_initial_request( ld, LDAP_REQ_SEARCH, base, ber );
134
135         if( *msgidp < 0 )
136                 return ld->ld_errno;
137
138         return LDAP_SUCCESS;
139 }
140
141 int
142 ldap_search_ext_s(
143         LDAP *ld,
144         LDAP_CONST char *base,
145         int scope,
146         LDAP_CONST char *filter,
147         char **attrs,
148         int attrsonly,
149         LDAPControl **sctrls,
150         LDAPControl **cctrls,
151         struct timeval *timeout,
152         int sizelimit,
153         LDAPMessage **res )
154 {
155         int rc;
156         int     msgid;
157
158         rc = ldap_search_ext( ld, base, scope, filter, attrs, attrsonly,
159                 sctrls, cctrls, timeout, sizelimit, &msgid );
160
161         if ( rc != LDAP_SUCCESS ) {
162                 return( rc );
163         }
164
165         if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 )
166                 return( ld->ld_errno );
167
168         return( ldap_result2error( ld, *res, 0 ) );
169 }
170
171 /*
172  * ldap_search - initiate an ldap search operation.
173  *
174  * Parameters:
175  *
176  *      ld              LDAP descriptor
177  *      base            DN of the base object
178  *      scope           the search scope - one of LDAP_SCOPE_BASE,
179  *                          LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
180  *      filter          a string containing the search filter
181  *                      (e.g., "(|(cn=bob)(sn=bob))")
182  *      attrs           list of attribute types to return for matches
183  *      attrsonly       1 => attributes only 0 => attributes and values
184  *
185  * Example:
186  *      char    *attrs[] = { "mail", "title", 0 };
187  *      msgid = ldap_search( ld, "c=us@o=UM", LDAP_SCOPE_SUBTREE, "cn~=bob",
188  *          attrs, attrsonly );
189  */
190 int
191 ldap_search(
192         LDAP *ld, LDAP_CONST char *base, int scope, LDAP_CONST char *filter,
193         char **attrs, int attrsonly )
194 {
195         BerElement      *ber;
196
197         Debug( LDAP_DEBUG_TRACE, "ldap_search\n", 0, 0, 0 );
198
199         ber = ldap_build_search_req( ld, base, scope, filter, attrs,
200             attrsonly, NULL, NULL, -1, -1 ); 
201
202         if ( ber == NULL ) {
203                 return( -1 );
204         }
205
206 #ifndef LDAP_NOCACHE
207         if ( ld->ld_cache != NULL ) {
208                 if ( ldap_check_cache( ld, LDAP_REQ_SEARCH, ber ) == 0 ) {
209                         ber_free( ber, 1 );
210                         ld->ld_errno = LDAP_SUCCESS;
211                         return( ld->ld_msgid );
212                 }
213                 ldap_add_request_to_cache( ld, LDAP_REQ_SEARCH, ber );
214         }
215 #endif /* LDAP_NOCACHE */
216
217         /* send the message */
218         return ( ldap_send_initial_request( ld, LDAP_REQ_SEARCH, base, ber ));
219 }
220
221
222 BerElement *
223 ldap_build_search_req(
224         LDAP *ld,
225         LDAP_CONST char *base_in,
226         ber_int_t scope,
227         LDAP_CONST char *filter_in,
228         char **attrs,
229         ber_int_t attrsonly,
230         LDAPControl **sctrls,
231         LDAPControl **cctrls,
232         ber_int_t timelimit,
233         ber_int_t sizelimit )
234 {
235         BerElement      *ber;
236         int             err;
237         char    *base;
238         char    *filter;
239
240         /*
241          * Create the search request.  It looks like this:
242          *      SearchRequest := [APPLICATION 3] SEQUENCE {
243          *              baseObject      DistinguishedName,
244          *              scope           ENUMERATED {
245          *                      baseObject      (0),
246          *                      singleLevel     (1),
247          *                      wholeSubtree    (2)
248          *              },
249          *              derefAliases    ENUMERATED {
250          *                      neverDerefaliases       (0),
251          *                      derefInSearching        (1),
252          *                      derefFindingBaseObj     (2),
253          *                      alwaysDerefAliases      (3)
254          *              },
255          *              sizelimit       INTEGER (0 .. 65535),
256          *              timelimit       INTEGER (0 .. 65535),
257          *              attrsOnly       BOOLEAN,
258          *              filter          Filter,
259          *              attributes      SEQUENCE OF AttributeType
260          *      }
261          * wrapped in an ldap message.
262          */
263
264         /* create a message to send */
265         if ( (ber = ldap_alloc_ber_with_options( ld )) == NULL ) {
266                 return( NULL );
267         }
268
269         if ( base_in == NULL ) {
270                 /* no base provided, use session default base */
271                 base = ld->ld_options.ldo_defbase;
272         } else {
273                 base = (char *) base_in;
274         }
275
276         if ( base == NULL ) {
277                 /* no session default base, use top */
278             base = "";
279         }
280
281 #ifdef LDAP_CONNECTIONLESS
282         if ( ld->ld_cldapnaddr > 0 ) {
283             err = ber_printf( ber, "{ist{seeiib", ++ld->ld_msgid,
284                         ld->ld_cldapdn, LDAP_REQ_SEARCH, base, scope, ld->ld_deref,
285                         (sizelimit < 0) ? ld->ld_sizelimit : sizelimit,
286                         (timelimit < 0) ? ld->ld_timelimit : timelimit,
287                         attrsonly );
288         } else {
289 #endif /* LDAP_CONNECTIONLESS */
290                 err = ber_printf( ber, "{it{seeiib", ++ld->ld_msgid,
291                     LDAP_REQ_SEARCH, base, (ber_int_t) scope, ld->ld_deref,
292                         (sizelimit < 0) ? ld->ld_sizelimit : sizelimit,
293                         (timelimit < 0) ? ld->ld_timelimit : timelimit,
294                     attrsonly );
295 #ifdef LDAP_CONNECTIONLESS
296         }
297 #endif /* LDAP_CONNECTIONLESS */
298
299         if ( err == -1 ) {
300                 ld->ld_errno = LDAP_ENCODING_ERROR;
301                 ber_free( ber, 1 );
302                 return( NULL );
303         }
304
305         filter = LDAP_STRDUP( filter_in );
306         err = put_filter( ber, filter );
307         LDAP_FREE( filter );
308
309         if ( err  == -1 ) {
310                 ld->ld_errno = LDAP_FILTER_ERROR;
311                 ber_free( ber, 1 );
312                 return( NULL );
313         }
314
315         if ( ber_printf( ber, /*{*/ "{v}}", attrs ) == -1 ) {
316                 ld->ld_errno = LDAP_ENCODING_ERROR;
317                 ber_free( ber, 1 );
318                 return( NULL );
319         }
320
321         /* Put Server Controls */
322         if( ldap_int_put_controls( ld, sctrls, ber ) != LDAP_SUCCESS ) {
323                 ber_free( ber, 1 );
324                 return( NULL );
325         }
326
327         if ( ber_printf( ber, /*{*/ "}", attrs ) == -1 ) {
328                 ld->ld_errno = LDAP_ENCODING_ERROR;
329                 ber_free( ber, 1 );
330                 return( NULL );
331         }
332
333         return( ber );
334 }
335
336 static int ldap_is_attr_oid ( const char *attr )
337 {
338         int i, c, digit=0;
339
340         for( i=0 ; c = attr[i] ; i++ ) {
341                 if( c >= '0' && c <= '9' ) {
342                         digit=1;
343
344                 } else if ( c != '.' ) {
345                         /* not digit nor '.' */
346                         return 0;
347
348                 } else if ( !digit ) {
349                         /* '.' but prev not digit */
350                         return 0;
351
352                 } else {
353                         /* '.' */
354                         digit = 0;
355                 }
356         }
357
358         return digit;
359
360 }
361
362 static int ldap_is_attr_desc ( const char *attr )
363 {
364         /* cheap attribute description check */
365         int i, c;
366
367         for( i=0; c = attr[i]; i++ ) {
368                 if (( c >= '0' && c <= '9' )
369                         || ( c >= 'A' && c <= 'Z' )
370                         || ( c >= 'a' && c <= 'z' )
371                         || ( c == '.' || c == '-' )
372                         || ( c == ';' )) continue;
373
374                 return 0;
375         }
376
377         return i > 0;
378 }
379
380 static char *
381 find_right_paren( char *s )
382 {
383         int     balance, escape;
384
385         balance = 1;
386         escape = 0;
387         while ( *s && balance ) {
388                 if ( escape == 0 ) {
389                         if ( *s == '(' )
390                                 balance++;
391                         else if ( *s == ')' )
392                                 balance--;
393                 }
394                 if ( *s == '\\' && ! escape )
395                         escape = 1;
396                 else
397                         escape = 0;
398                 if ( balance )
399                         s++;
400         }
401
402         return( *s ? s : NULL );
403 }
404
405 static int hex2value( int c )
406 {
407         if( c >= '0' && c <= '9' ) {
408                 return c - '0';
409         }
410
411         if( c >= 'A' && c <= 'F' ) {
412                 return c + (10 - (int) 'A');
413         }
414
415         if( c >= 'a' && c <= 'f' ) {
416                 return c + (10 - (int) 'a');
417         }
418
419         return -1;
420 }
421
422 static char *
423 find_wildcard( char *s )
424 {
425         for( ; *s != '\0' ; s++ ) {
426                 switch( *s ) {
427                 case '*':       /* found wildcard */
428                         return s;
429
430                 case '\\':
431                         s++; /* skip over escape */
432                         if( hex2value( s[0] ) >= 0 && hex2value( s[1] ) >= 0 ) {
433                                 /* skip over lead digit of two hex digit code */
434                                 s++;
435                         }
436                 }
437         }
438
439         return NULL;
440 }
441
442 /* unescape filter value */
443 /* support both LDAP v2 and v3 escapes */
444 /* output can include nul characters */
445 static ber_slen_t
446 filter_value_unescape( char *fval )
447 {
448         ber_slen_t r, v;
449         int v1, v2;
450
451         for( r=v=0; fval[v] != '\0'; v++ ) {
452                 switch( fval[v] ) {
453                 case '\\':
454                         /* escape */
455                         v++;
456
457                         if ( fval[v] == '\0' ) {
458                                 /* escape at end of string */
459                                 return -1;
460
461                         }
462
463                         if (( v1 = hex2value( fval[v] )) >= 0 ) {
464                                 /* LDAPv3 escape */
465
466                                 if (( v2 = hex2value( fval[v+1] )) < 0 ) {
467                                         /* must be two digit code */
468                                         return -1;
469                                 }
470
471                                 fval[r++] = v1 * 16 + v2;
472                                 v++;
473
474                         } else {
475                                 /* LDAPv2 escape */
476                                 fval[r++] = fval[v];
477                         }
478
479                         break;
480
481                 default:
482                         fval[r++] = fval[v];
483                 }
484         }
485
486         fval[r] = '\0';
487         return r;
488 }
489
490 static char *
491 put_complex_filter( BerElement *ber, char *str, ber_tag_t tag, int not )
492 {
493         char    *next;
494
495         /*
496          * We have (x(filter)...) with str sitting on
497          * the x.  We have to find the paren matching
498          * the one before the x and put the intervening
499          * filters by calling put_filter_list().
500          */
501
502         /* put explicit tag */
503         if ( ber_printf( ber, "t{" /*}*/, tag ) == -1 )
504                 return( NULL );
505
506         str++;
507         if ( (next = find_right_paren( str )) == NULL )
508                 return( NULL );
509
510         *next = '\0';
511         if ( put_filter_list( ber, str ) == -1 )
512                 return( NULL );
513         *next++ = ')';
514
515         /* flush explicit tagged thang */
516         if ( ber_printf( ber, /*{*/ "}" ) == -1 )
517                 return( NULL );
518
519         return( next );
520 }
521
522 static int
523 put_filter( BerElement *ber, char *str )
524 {
525         char    *next;
526         int     parens, balance, escape;
527
528         /*
529          * A Filter looks like this:
530          *      Filter ::= CHOICE {
531          *              and             [0]     SET OF Filter,
532          *              or              [1]     SET OF Filter,
533          *              not             [2]     Filter,
534          *              equalityMatch   [3]     AttributeValueAssertion,
535          *              substrings      [4]     SubstringFilter,
536          *              greaterOrEqual  [5]     AttributeValueAssertion,
537          *              lessOrEqual     [6]     AttributeValueAssertion,
538          *              present         [7]     AttributeType,
539          *              approxMatch     [8]     AttributeValueAssertion,
540          *                              extensibleMatch [9]             MatchingRuleAssertion -- LDAPv3
541          *      }
542          *
543          *      SubstringFilter ::= SEQUENCE {
544          *              type               AttributeType,
545          *              SEQUENCE OF CHOICE {
546          *                      initial          [0] IA5String,
547          *                      any              [1] IA5String,
548          *                      final            [2] IA5String
549          *              }
550          *      }
551          *
552          *              MatchingRuleAssertion ::= SEQUENCE {    -- LDAPv3
553          *                      matchingRule    [1] MatchingRuleId OPTIONAL,
554          *                      type            [2] AttributeDescription OPTIONAL,
555          *                      matchValue      [3] AssertionValue,
556          *                      dnAttributes    [4] BOOLEAN DEFAULT FALSE }
557          *
558          * Note: tags in a choice are always explicit
559          */
560
561         Debug( LDAP_DEBUG_TRACE, "put_filter \"%s\"\n", str, 0, 0 );
562
563         parens = 0;
564         while ( *str ) {
565                 switch ( *str ) {
566                 case '(':
567                         str++;
568                         parens++;
569                         switch ( *str ) {
570                         case '&':
571                                 Debug( LDAP_DEBUG_TRACE, "put_filter: AND\n",
572                                     0, 0, 0 );
573
574                                 if ( (str = put_complex_filter( ber, str,
575                                     LDAP_FILTER_AND, 0 )) == NULL )
576                                         return( -1 );
577
578                                 parens--;
579                                 break;
580
581                         case '|':
582                                 Debug( LDAP_DEBUG_TRACE, "put_filter: OR\n",
583                                     0, 0, 0 );
584
585                                 if ( (str = put_complex_filter( ber, str,
586                                     LDAP_FILTER_OR, 0 )) == NULL )
587                                         return( -1 );
588
589                                 parens--;
590                                 break;
591
592                         case '!':
593                                 Debug( LDAP_DEBUG_TRACE, "put_filter: NOT\n",
594                                     0, 0, 0 );
595
596                                 if ( (str = put_complex_filter( ber, str,
597                                     LDAP_FILTER_NOT, 1 )) == NULL )
598                                         return( -1 );
599
600                                 parens--;
601                                 break;
602
603                         default:
604                                 Debug( LDAP_DEBUG_TRACE, "put_filter: simple\n",
605                                     0, 0, 0 );
606
607                                 balance = 1;
608                                 escape = 0;
609                                 next = str;
610                                 while ( *next && balance ) {
611                                         if ( escape == 0 ) {
612                                                 if ( *next == '(' )
613                                                         balance++;
614                                                 else if ( *next == ')' )
615                                                         balance--;
616                                         }
617                                         if ( *next == '\\' && ! escape )
618                                                 escape = 1;
619                                         else
620                                                 escape = 0;
621                                         if ( balance )
622                                                 next++;
623                                 }
624                                 if ( balance != 0 )
625                                         return( -1 );
626
627                                 *next = '\0';
628                                 if ( put_simple_filter( ber, str ) == -1 ) {
629                                         return( -1 );
630                                 }
631                                 *next++ = ')';
632                                 str = next;
633                                 parens--;
634                                 break;
635                         }
636                         break;
637
638                 case ')':
639                         Debug( LDAP_DEBUG_TRACE, "put_filter: end\n", 0, 0,
640                             0 );
641                         if ( ber_printf( ber, /*[*/ "]" ) == -1 )
642                                 return( -1 );
643                         str++;
644                         parens--;
645                         break;
646
647                 case ' ':
648                         str++;
649                         break;
650
651                 default:        /* assume it's a simple type=value filter */
652                         Debug( LDAP_DEBUG_TRACE, "put_filter: default\n", 0, 0,
653                             0 );
654                         next = strchr( str, '\0' );
655                         if ( put_simple_filter( ber, str ) == -1 ) {
656                                 return( -1 );
657                         }
658                         str = next;
659                         break;
660                 }
661         }
662
663         return( parens ? -1 : 0 );
664 }
665
666 /*
667  * Put a list of filters like this "(filter1)(filter2)..."
668  */
669
670 static int
671 put_filter_list( BerElement *ber, char *str )
672 {
673         char    *next;
674         char    save;
675
676         Debug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n", str, 0, 0 );
677
678         while ( *str ) {
679                 while ( *str && isspace( (unsigned char) *str ) )
680                         str++;
681                 if ( *str == '\0' )
682                         break;
683
684                 if ( (next = find_right_paren( str + 1 )) == NULL )
685                         return( -1 );
686                 save = *++next;
687
688                 /* now we have "(filter)" with str pointing to it */
689                 *next = '\0';
690                 if ( put_filter( ber, str ) == -1 )
691                         return( -1 );
692                 *next = save;
693
694                 str = next;
695         }
696
697         return( 0 );
698 }
699
700 static int
701 put_simple_filter(
702         BerElement *ber,
703         char *str )
704 {
705         char            *s;
706         char            *value;
707         ber_tag_t       ftype;
708         int             rc = -1;
709
710         Debug( LDAP_DEBUG_TRACE, "put_simple_filter \"%s\"\n", str, 0, 0 );
711
712         str = LDAP_STRDUP( str );
713         if( str == NULL ) return -1;
714
715         if ( (s = strchr( str, '=' )) == NULL ) {
716                 goto done;
717         }
718
719         value = s + 1;
720         *s-- = '\0';
721
722         switch ( *s ) {
723         case '<':
724                 ftype = LDAP_FILTER_LE;
725                 *s = '\0';
726                 if(! ldap_is_attr_desc( str ) ) goto done;
727                 break;
728
729         case '>':
730                 ftype = LDAP_FILTER_GE;
731                 *s = '\0';
732                 if(! ldap_is_attr_desc( str ) ) goto done;
733                 break;
734
735         case '~':
736                 ftype = LDAP_FILTER_APPROX;
737                 *s = '\0';
738                 if(! ldap_is_attr_desc( str ) ) goto done;
739                 break;
740
741         case ':':
742                 /* RFC2254 extensible filters are off the form:
743                  *              type [:dn] [:rule] := value
744                  * or   [:dn]:rule := value             
745                  */
746                 ftype = LDAP_FILTER_EXT;
747                 *s = '\0';
748
749                 {
750                         char *dn = strchr( str, ':' );
751                         char *rule = NULL;
752
753                         if( dn == NULL ) {
754                                 if(! ldap_is_attr_desc( str ) ) goto done;
755                                 break;
756                         }
757
758                         *dn++ = '\0';
759                         rule = strchr( dn, ':' );
760
761                         if( rule == NULL ) {
762                                 /* one colon */
763                                 if ( strcmp(dn, "dn") == 0 ) {
764                                         /* must have attribute */
765                                         if( !ldap_is_attr_desc( str ) ) {
766                                                 goto done;
767                                         }
768
769                                         rule = "";
770
771                                 } else {
772                                         rule = dn;
773                                         dn = NULL;
774                                 }
775                                 
776                         } else {
777                                 /* two colons */
778                                 *rule++ = '\0';
779
780                                 if ( strcmp(dn, "dn") != 0 ) {
781                                         /* must have "dn" */
782                                         goto done;
783                                 }
784                         }
785
786                         if ( *str == '\0' && *rule == '\0' ) {
787                                 /* must have either type or rule */
788                                 goto done;
789                         }
790
791                         if ( *str != '\0' && !ldap_is_attr_desc( str ) ) {
792                                 goto done;
793                         }
794
795                         if ( *rule != '\0' && !ldap_is_attr_oid( rule ) ) {
796                                 goto done;
797                         }
798
799                         rc = ber_printf( ber, "t{" /*}*/, ftype );
800
801                         if( rc != -1 && *rule != '\0' ) {
802                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule );
803                         }
804                         if( rc != -1 && *str != '\0' ) {
805                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str );
806                         }
807
808                         if( rc != -1 ) {
809                                 ber_slen_t len = filter_value_unescape( value );
810
811                                 if( len >= 0 ) {
812                                         rc = ber_printf( ber, "totb}",
813                                                 LDAP_FILTER_EXT_VALUE, value, len,
814                                                 LDAP_FILTER_EXT_DNATTRS, dn != NULL);
815                                 } else {
816                                         rc = -1;
817                                 }
818                         }
819                 }
820                 break;
821
822         default:
823                 if ( find_wildcard( value ) == NULL ) {
824                         ftype = LDAP_FILTER_EQUALITY;
825                 } else if ( strcmp( value, "*" ) == 0 ) {
826                         ftype = LDAP_FILTER_PRESENT;
827                 } else {
828                         rc = put_substring_filter( ber, str, value );
829                         goto done;
830                 }
831                 break;
832         }
833
834         if ( ftype == LDAP_FILTER_PRESENT ) {
835                 rc = ber_printf( ber, "ts", ftype, str );
836
837         } else {
838                 ber_slen_t len = filter_value_unescape( value );
839
840                 if( len >= 0 ) {
841                         rc = ber_printf( ber, "t{so}",
842                                 ftype, str, value, len );
843                 }
844         }
845
846         if( rc != -1 ) rc = 0;
847
848 done:
849         LDAP_FREE( str );
850         return rc;
851 }
852
853 static int
854 put_substring_filter( BerElement *ber, char *type, char *val )
855 {
856         char            *nextstar, gotstar = 0;
857         ber_tag_t       ftype = LDAP_FILTER_SUBSTRINGS;
858
859         Debug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n", type,
860             val, 0 );
861
862         if ( ber_printf( ber, "t{s{", ftype, type ) == -1 )
863                 return( -1 );
864
865         for( ; val != NULL; val=nextstar ) {
866                 if ( (nextstar = find_wildcard( val )) != NULL )
867                         *nextstar++ = '\0';
868
869                 if ( gotstar == 0 ) {
870                         ftype = LDAP_SUBSTRING_INITIAL;
871                 } else if ( nextstar == NULL ) {
872                         ftype = LDAP_SUBSTRING_FINAL;
873                 } else {
874                         ftype = LDAP_SUBSTRING_ANY;
875                 }
876
877                 if ( *val != '\0' ) {
878                         ber_slen_t len = filter_value_unescape( val );
879
880                         if ( len < 0  ) {
881                                 return -1;
882                         }
883
884                         if ( ber_printf( ber, "to", ftype, val, len ) == -1 ) {
885                                 return( -1 );
886                         }
887                 }
888
889                 gotstar = 1;
890         }
891
892         if ( ber_printf( ber, /* {{ */ "}}" ) == -1 )
893                 return( -1 );
894
895         return( 0 );
896 }
897
898 int
899 ldap_search_st(
900         LDAP *ld, LDAP_CONST char *base, int scope,
901         LDAP_CONST char *filter, char **attrs,
902         int attrsonly, struct timeval *timeout, LDAPMessage **res )
903 {
904         int     msgid;
905
906         if ( (msgid = ldap_search( ld, base, scope, filter, attrs, attrsonly ))
907             == -1 )
908                 return( ld->ld_errno );
909
910         if ( ldap_result( ld, msgid, 1, timeout, res ) == -1 )
911                 return( ld->ld_errno );
912
913         if ( ld->ld_errno == LDAP_TIMEOUT ) {
914                 (void) ldap_abandon( ld, msgid );
915                 ld->ld_errno = LDAP_TIMEOUT;
916                 return( ld->ld_errno );
917         }
918
919         return( ldap_result2error( ld, *res, 0 ) );
920 }
921
922 int
923 ldap_search_s(
924         LDAP *ld,
925         LDAP_CONST char *base,
926         int scope,
927         LDAP_CONST char *filter,
928         char **attrs,
929         int attrsonly,
930         LDAPMessage **res )
931 {
932         int     msgid;
933
934         if ( (msgid = ldap_search( ld, base, scope, filter, attrs, attrsonly ))
935             == -1 )
936                 return( ld->ld_errno );
937
938         if ( ldap_result( ld, msgid, 1, (struct timeval *) NULL, res ) == -1 )
939                 return( ld->ld_errno );
940
941         return( ldap_result2error( ld, *res, 0 ) );
942 }
943