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