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