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