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