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