]> git.sur5r.net Git - openldap/blob - libraries/libldap/filter.c
90dd25425aceb2ac4795a423dc002a43df69bebe
[openldap] / libraries / libldap / filter.c
1 /* search.c */
2 /* $OpenLDAP$ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 1998-2005 The OpenLDAP Foundation.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16 /* Portions Copyright (c) 1990 Regents of the University of Michigan.
17  * All rights reserved.
18  */
19 /* Portions Copyright (C) The Internet Society (1997).
20  * ASN.1 fragments are from RFC 2251; see RFC for full legal notices.
21  */
22
23 #include "portable.h"
24
25 #include <stdio.h>
26
27 #include <ac/stdlib.h>
28
29 #include <ac/socket.h>
30 #include <ac/string.h>
31 #include <ac/time.h>
32
33 #include "ldap-int.h"
34
35 static int put_simple_vrFilter LDAP_P((
36         BerElement *ber,
37         char *str ));
38
39 static int put_vrFilter_list LDAP_P((
40         BerElement *ber,
41         char *str ));
42
43 static char *put_complex_filter LDAP_P((
44         BerElement *ber,
45         char *str,
46         ber_tag_t tag,
47         int not ));
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 static int ldap_is_oid ( const char *str )
64 {
65         int i;
66
67         if( LDAP_ALPHA( str[0] )) {
68                 for( i=1; str[i]; i++ ) {
69                         if( !LDAP_LDH( str[i] )) {
70                                 return 0;
71                         }
72                 }
73                 return 1;
74
75         } else if LDAP_DIGIT( str[0] ) {
76                 int dot=0;
77                 for( i=1; str[i]; i++ ) {
78                         if( LDAP_DIGIT( str[i] )) {
79                                 dot=0;
80
81                         } else if ( str[i] == '.' ) {
82                                 if( dot ) return 0;
83                                 if( ++dot > 1 ) return 0;
84
85                         } else {
86                                 return 0;
87                         }
88                 }
89                 return !dot;
90         }
91
92         return 0;
93 }
94
95 static int ldap_is_desc ( const char *str )
96 {
97         int i;
98
99         if( LDAP_ALPHA( str[0] )) {
100                 for( i=1; str[i]; i++ ) {
101                         if( str[i] == ';' ) {
102                                 str = &str[i+1];
103                                 goto options;
104                         }
105
106                         if( !LDAP_LDH( str[i] )) {
107                                 return 0;
108                         }
109                 }
110                 return 1;
111
112         } else if LDAP_DIGIT( str[0] ) {
113                 int dot=0;
114                 for( i=1; str[i]; i++ ) {
115                         if( str[i] == ';' ) {
116                                 if( dot ) return 0;
117                                 str = &str[i+1];
118                                 goto options;
119                         }
120
121                         if( LDAP_DIGIT( str[i] )) {
122                                 dot=0;
123
124                         } else if ( str[i] == '.' ) {
125                                 if( dot ) return 0;
126                                 if( ++dot > 1 ) return 0;
127
128                         } else {
129                                 return 0;
130                         }
131                 }
132                 return !dot;
133         }
134
135         return 0;
136
137 options:
138         if( !LDAP_LDH( str[0] )) {
139                 return 0;
140         }
141         for( i=1; str[i]; i++ ) {
142                 if( str[i] == ';' ) {
143                         str = &str[i+1];
144                         goto options;
145                 }
146                 if( !LDAP_LDH( str[i] )) {
147                         return 0;
148                 }
149         }
150         return 1;
151 }
152
153 static char *
154 find_right_paren( char *s )
155 {
156         int     balance, escape;
157
158         balance = 1;
159         escape = 0;
160         while ( *s && balance ) {
161                 if ( !escape ) {
162                         if ( *s == '(' ) {
163                                 balance++;
164                         } else if ( *s == ')' ) {
165                                 balance--;
166                         }
167                 }
168
169                 escape = ( *s == '\\' && !escape );
170
171                 if ( balance ) s++;
172         }
173
174         return *s ? s : NULL;
175 }
176
177 static int hex2value( int c )
178 {
179         if( c >= '0' && c <= '9' ) {
180                 return c - '0';
181         }
182
183         if( c >= 'A' && c <= 'F' ) {
184                 return c + (10 - (int) 'A');
185         }
186
187         if( c >= 'a' && c <= 'f' ) {
188                 return c + (10 - (int) 'a');
189         }
190
191         return -1;
192 }
193
194 char *
195 ldap_pvt_find_wildcard( const char *s )
196 {
197         for( ; *s; s++ ) {
198                 switch( *s ) {
199                 case '*':       /* found wildcard */
200                         return (char *) s;
201
202                 case '(':
203                 case ')':
204                         return NULL;
205
206                 case '\\':
207                         if( s[1] == '\0' ) return NULL;
208
209                         if( LDAP_HEX( s[1] ) && LDAP_HEX( s[2] ) ) {
210                                 s+=2;
211
212                         } else switch( s[1] ) {
213                         default:
214                                 return NULL;
215
216                         /* allow RFC 1960 escapes */
217                         case '*':
218                         case '(':
219                         case ')':
220                         case '\\':
221                                 s++;
222                         }
223                 }
224         }
225
226         return (char *) s;
227 }
228
229 /* unescape filter value */
230 /* support both LDAP v2 and v3 escapes */
231 /* output can include nul characters! */
232 ber_slen_t
233 ldap_pvt_filter_value_unescape( char *fval )
234 {
235         ber_slen_t r, v;
236         int v1, v2;
237
238         for( r=v=0; fval[v] != '\0'; v++ ) {
239                 switch( fval[v] ) {
240                 case '(':
241                 case ')':
242                 case '*':
243                         return -1;
244
245                 case '\\':
246                         /* escape */
247                         v++;
248
249                         if ( fval[v] == '\0' ) {
250                                 /* escape at end of string */
251                                 return -1;
252                         }
253
254                         if (( v1 = hex2value( fval[v] )) >= 0 ) {
255                                 /* LDAPv3 escape */
256                                 if (( v2 = hex2value( fval[v+1] )) < 0 ) {
257                                         /* must be two digit code */
258                                         return -1;
259                                 }
260
261                                 fval[r++] = v1 * 16 + v2;
262                                 v++;
263
264                         } else {
265                                 /* LDAPv2 escape */
266                                 switch( fval[v] ) {
267                                 case '(':
268                                 case ')':
269                                 case '*':
270                                 case '\\':
271                                         fval[r++] = fval[v];
272                                         break;
273                                 default:
274                                         /* illegal escape */
275                                         return -1;
276                                 }
277                         }
278                         break;
279
280                 default:
281                         fval[r++] = fval[v];
282                 }
283         }
284
285         fval[r] = '\0';
286         return r;
287 }
288
289 static char *
290 put_complex_filter( BerElement *ber, char *str, ber_tag_t tag, int not )
291 {
292         char    *next;
293
294         /*
295          * We have (x(filter)...) with str sitting on
296          * the x.  We have to find the paren matching
297          * the one before the x and put the intervening
298          * filters by calling put_filter_list().
299          */
300
301         /* put explicit tag */
302         if ( ber_printf( ber, "t{" /*"}"*/, tag ) == -1 ) {
303                 return NULL;
304         }
305
306         str++;
307         if ( (next = find_right_paren( str )) == NULL ) {
308                 return NULL;
309         }
310
311         *next = '\0';
312         if ( put_filter_list( ber, str, tag ) == -1 ) {
313                 return NULL;
314         }
315
316         /* close the '(' */
317         *next++ = ')';
318
319         /* flush explicit tagged thang */
320         if ( ber_printf( ber, /*"{"*/ "N}" ) == -1 ) {
321                 return NULL;
322         }
323
324         return next;
325 }
326
327 int
328 ldap_pvt_put_filter( BerElement *ber, const char *str_in )
329 {
330         int rc;
331         char    *freeme;
332         char    *str;
333         char    *next;
334         int     parens, balance, escape;
335
336         /*
337          * A Filter looks like this:
338          *      Filter ::= CHOICE {
339          *              and             [0]     SET OF Filter,
340          *              or              [1]     SET OF Filter,
341          *              not             [2]     Filter,
342          *              equalityMatch   [3]     AttributeValueAssertion,
343          *              substrings      [4]     SubstringFilter,
344          *              greaterOrEqual  [5]     AttributeValueAssertion,
345          *              lessOrEqual     [6]     AttributeValueAssertion,
346          *              present         [7]     AttributeType,
347          *              approxMatch     [8]     AttributeValueAssertion,
348          *              extensibleMatch [9]     MatchingRuleAssertion -- LDAPv3
349          *      }
350          *
351          *      SubstringFilter ::= SEQUENCE {
352          *              type               AttributeType,
353          *              SEQUENCE OF CHOICE {
354          *                      initial          [0] IA5String,
355          *                      any              [1] IA5String,
356          *                      final            [2] IA5String
357          *              }
358          *      }
359          *
360          *      MatchingRuleAssertion ::= SEQUENCE {    -- LDAPv3
361          *              matchingRule    [1] MatchingRuleId OPTIONAL,
362          *              type            [2] AttributeDescription OPTIONAL,
363          *              matchValue      [3] AssertionValue,
364          *              dnAttributes    [4] BOOLEAN DEFAULT FALSE }
365          *
366          * Note: tags in a choice are always explicit
367          */
368
369         Debug( LDAP_DEBUG_TRACE, "put_filter: \"%s\"\n", str_in, 0, 0 );
370
371         freeme = LDAP_STRDUP( str_in );
372         if( freeme == NULL ) return LDAP_NO_MEMORY;
373         str = freeme;
374
375         parens = 0;
376         while ( *str ) {
377                 switch ( *str ) {
378                 case '(': /*')'*/
379                         str++;
380                         parens++;
381
382                         /* skip spaces */
383                         while( LDAP_SPACE( *str ) ) str++;
384
385                         switch ( *str ) {
386                         case '&':
387                                 Debug( LDAP_DEBUG_TRACE, "put_filter: AND\n",
388                                     0, 0, 0 );
389
390                                 str = put_complex_filter( ber, str,
391                                     LDAP_FILTER_AND, 0 );
392                                 if( str == NULL ) {
393                                         rc = -1;
394                                         goto done;
395                                 }
396
397                                 parens--;
398                                 break;
399
400                         case '|':
401                                 Debug( LDAP_DEBUG_TRACE, "put_filter: OR\n",
402                                     0, 0, 0 );
403
404                                 str = put_complex_filter( ber, str,
405                                     LDAP_FILTER_OR, 0 );
406                                 if( str == NULL ) {
407                                         rc = -1;
408                                         goto done;
409                                 }
410
411                                 parens--;
412                                 break;
413
414                         case '!':
415                                 Debug( LDAP_DEBUG_TRACE, "put_filter: NOT\n",
416                                     0, 0, 0 );
417
418                                 str = put_complex_filter( ber, str,
419                                     LDAP_FILTER_NOT, 0 );
420                                 if( str == NULL ) {
421                                         rc = -1;
422                                         goto done;
423                                 }
424
425                                 parens--;
426                                 break;
427
428                         default:
429                                 Debug( LDAP_DEBUG_TRACE, "put_filter: simple\n",
430                                     0, 0, 0 );
431
432                                 balance = 1;
433                                 escape = 0;
434                                 next = str;
435
436                                 while ( *next && balance ) {
437                                         if ( escape == 0 ) {
438                                                 if ( *next == '(' ) {
439                                                         balance++;
440                                                 } else if ( *next == ')' ) {
441                                                         balance--;
442                                                 }
443                                         }
444
445                                         if ( *next == '\\' && ! escape ) {
446                                                 escape = 1;
447                                         } else {
448                                                 escape = 0;
449                                         }
450
451                                         if ( balance ) next++;
452                                 }
453
454                                 if ( balance != 0 ) {
455                                         rc = -1;
456                                         goto done;
457                                 }
458
459                                 *next = '\0';
460
461                                 if ( put_simple_filter( ber, str ) == -1 ) {
462                                         rc = -1;
463                                         goto done;
464                                 }
465
466                                 *next++ = /*'('*/ ')';
467
468                                 str = next;
469                                 parens--;
470                                 break;
471                         }
472                         break;
473
474                 case /*'('*/ ')':
475                         Debug( LDAP_DEBUG_TRACE, "put_filter: end\n",
476                                 0, 0, 0 );
477                         if ( ber_printf( ber, /*"["*/ "]" ) == -1 ) {
478                                 rc = -1;
479                                 goto done;
480                         }
481                         str++;
482                         parens--;
483                         break;
484
485                 case ' ':
486                         str++;
487                         break;
488
489                 default:        /* assume it's a simple type=value filter */
490                         Debug( LDAP_DEBUG_TRACE, "put_filter: default\n",
491                                 0, 0, 0 );
492                         next = strchr( str, '\0' );
493                         if ( put_simple_filter( ber, str ) == -1 ) {
494                                 rc = -1;
495                                 goto done;
496                         }
497                         str = next;
498                         break;
499                 }
500         }
501
502         rc = parens ? -1 : 0;
503
504 done:
505         LDAP_FREE( freeme );
506         return rc;
507 }
508
509 /*
510  * Put a list of filters like this "(filter1)(filter2)..."
511  */
512
513 static int
514 put_filter_list( BerElement *ber, char *str, ber_tag_t tag )
515 {
516         char    *next = NULL;
517         char    save;
518
519         Debug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n",
520                 str, 0, 0 );
521
522         while ( *str ) {
523                 while ( *str && LDAP_SPACE( (unsigned char) *str ) ) {
524                         str++;
525                 }
526                 if ( *str == '\0' ) break;
527
528                 if ( (next = find_right_paren( str + 1 )) == NULL ) {
529                         return -1;
530                 }
531                 save = *++next;
532
533                 /* now we have "(filter)" with str pointing to it */
534                 *next = '\0';
535                 if ( ldap_pvt_put_filter( ber, str ) == -1 ) return -1;
536                 *next = save;
537                 str = next;
538
539                 if( tag == LDAP_FILTER_NOT ) break;
540         }
541
542         if( tag == LDAP_FILTER_NOT && ( next == NULL || *str )) {
543                 return -1;
544         }
545
546         return 0;
547 }
548
549 static int
550 put_simple_filter(
551         BerElement *ber,
552         char *str )
553 {
554         char            *s;
555         char            *value;
556         ber_tag_t       ftype;
557         int             rc = -1;
558
559         Debug( LDAP_DEBUG_TRACE, "put_simple_filter: \"%s\"\n",
560                 str, 0, 0 );
561
562         str = LDAP_STRDUP( str );
563         if( str == NULL ) return -1;
564
565         if ( (s = strchr( str, '=' )) == NULL ) {
566                 goto done;
567         }
568
569         value = s + 1;
570         *s-- = '\0';
571
572         switch ( *s ) {
573         case '<':
574                 ftype = LDAP_FILTER_LE;
575                 *s = '\0';
576                 break;
577
578         case '>':
579                 ftype = LDAP_FILTER_GE;
580                 *s = '\0';
581                 break;
582
583         case '~':
584                 ftype = LDAP_FILTER_APPROX;
585                 *s = '\0';
586                 break;
587
588         case ':':
589                 /* RFC2254 extensible filters are off the form:
590                  *              type [:dn] [:rule] := value
591                  * or   [:dn]:rule := value             
592                  */
593                 ftype = LDAP_FILTER_EXT;
594                 *s = '\0';
595
596                 {
597                         char *dn = strchr( str, ':' );
598                         char *rule = NULL;
599
600                         if( dn != NULL ) {
601                                 *dn++ = '\0';
602                                 rule = strchr( dn, ':' );
603
604                                 if( rule == NULL ) {
605                                         /* one colon */
606                                         if ( strcasecmp(dn, "dn") == 0 ) {
607                                                 /* must have attribute */
608                                                 if( !ldap_is_desc( str ) ) {
609                                                         goto done;
610                                                 }
611
612                                                 rule = "";
613
614                                         } else {
615                                           rule = dn;
616                                           dn = NULL;
617                                         }
618                                 
619                                 } else {
620                                         /* two colons */
621                                         *rule++ = '\0';
622
623                                         if ( strcasecmp(dn, "dn") != 0 ) {
624                                                 /* must have "dn" */
625                                                 goto done;
626                                         }
627                                 }
628
629                         }
630
631                         if ( *str == '\0' && ( !rule || *rule == '\0' ) ) {
632                                 /* must have either type or rule */
633                                 goto done;
634                         }
635
636                         if ( *str != '\0' && !ldap_is_desc( str ) ) {
637                                 goto done;
638                         }
639
640                         if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) {
641                                 goto done;
642                         }
643
644                         rc = ber_printf( ber, "t{" /*"}"*/, ftype );
645
646                         if( rc != -1 && rule && *rule != '\0' ) {
647                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule );
648                         }
649
650                         if( rc != -1 && *str != '\0' ) {
651                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str );
652                         }
653
654                         if( rc != -1 ) {
655                                 ber_slen_t len = ldap_pvt_filter_value_unescape( value );
656
657                                 if( len >= 0 ) {
658                                         rc = ber_printf( ber, "to",
659                                                 LDAP_FILTER_EXT_VALUE, value, len );
660                                 } else {
661                                         rc = -1;
662                                 }
663                         }
664
665                         if( rc != -1 && dn ) {
666                                 rc = ber_printf( ber, "tb",
667                                         LDAP_FILTER_EXT_DNATTRS, (ber_int_t) 1 );
668                         }
669
670                         if( rc != -1 ) { 
671                                 rc = ber_printf( ber, /*"{"*/ "N}" );
672                         }
673                 }
674                 goto done;
675
676         default:
677                 if( !ldap_is_desc( str ) ) {
678                         goto done;
679
680                 } else {
681                         char *nextstar = ldap_pvt_find_wildcard( value );
682
683                         if ( nextstar == NULL ) {
684                                 goto done;
685
686                         } else if ( *nextstar == '\0' ) {
687                                 ftype = LDAP_FILTER_EQUALITY;
688
689                         } else if ( strcmp( value, "*" ) == 0 ) {
690                                 ftype = LDAP_FILTER_PRESENT;
691
692                         } else {
693                                 rc = put_substring_filter( ber, str, value );
694                                 goto done;
695                         }
696                 } break;
697         }
698
699         if( !ldap_is_desc( str ) ) goto done;
700
701         if ( ftype == LDAP_FILTER_PRESENT ) {
702                 rc = ber_printf( ber, "ts", ftype, str );
703
704         } else {
705                 ber_slen_t len = ldap_pvt_filter_value_unescape( value );
706
707                 if( len >= 0 ) {
708                         rc = ber_printf( ber, "t{soN}",
709                                 ftype, str, value, len );
710                 }
711         }
712
713 done:
714         if( rc != -1 ) rc = 0;
715         LDAP_FREE( str );
716         return rc;
717 }
718
719 static int
720 put_substring_filter( BerElement *ber, char *type, char *val )
721 {
722         char *nextstar;
723         int gotstar = 0;
724         ber_tag_t       ftype = LDAP_FILTER_SUBSTRINGS;
725
726         Debug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n",
727                 type, val, 0 );
728
729         if ( ber_printf( ber, "t{s{" /*"}}"*/, ftype, type ) == -1 ) {
730                 return -1;
731         }
732
733         for( ; *val; val=nextstar ) {
734                 nextstar = ldap_pvt_find_wildcard( val );
735
736                 if ( nextstar == NULL ) {
737                         return -1;
738                 }
739                 
740                 if ( *nextstar == '\0' ) {
741                         ftype = LDAP_SUBSTRING_FINAL;
742                 } else {
743                         *nextstar++ = '\0';
744                         if ( gotstar++ == 0 ) {
745                                 ftype = LDAP_SUBSTRING_INITIAL;
746                         } else {
747                                 ftype = LDAP_SUBSTRING_ANY;
748                         }
749                 }
750
751                 if ( *val != '\0' || ftype == LDAP_SUBSTRING_ANY ) {
752                         ber_slen_t len = ldap_pvt_filter_value_unescape( val );
753
754                         if ( len < 0  ) {
755                                 return -1;
756                         }
757
758                         if ( ber_printf( ber, "to", ftype, val, len ) == -1 ) {
759                                 return -1;
760                         }
761                 }
762         }
763
764         if ( ber_printf( ber, /*"{{"*/ "N}N}" ) == -1 ) {
765                 return -1;
766         }
767
768         return 0;
769 }
770
771 static int
772 put_vrFilter( BerElement *ber, const char *str_in )
773 {
774         int rc;
775         char    *freeme;
776         char    *str;
777         char    *next;
778         int     parens, balance, escape;
779
780         /*
781          * A ValuesReturnFilter looks like this:
782          *
783          *      ValuesReturnFilter ::= SEQUENCE OF SimpleFilterItem
784          *      SimpleFilterItem ::= CHOICE {
785          *              equalityMatch   [3]     AttributeValueAssertion,
786          *              substrings      [4]     SubstringFilter,
787          *              greaterOrEqual  [5]     AttributeValueAssertion,
788          *              lessOrEqual     [6]     AttributeValueAssertion,
789          *              present         [7]     AttributeType,
790          *              approxMatch     [8]     AttributeValueAssertion,
791          *              extensibleMatch [9]     SimpleMatchingAssertion -- LDAPv3
792          *      }
793          *
794          *      SubstringFilter ::= SEQUENCE {
795          *              type               AttributeType,
796          *              SEQUENCE OF CHOICE {
797          *                      initial          [0] IA5String,
798          *                      any              [1] IA5String,
799          *                      final            [2] IA5String
800          *              }
801          *      }
802          *
803          *      SimpleMatchingAssertion ::= SEQUENCE {  -- LDAPv3
804          *              matchingRule    [1] MatchingRuleId OPTIONAL,
805          *              type            [2] AttributeDescription OPTIONAL,
806          *              matchValue      [3] AssertionValue }
807          */
808
809         Debug( LDAP_DEBUG_TRACE, "put_vrFilter: \"%s\"\n", str_in, 0, 0 );
810
811         freeme = LDAP_STRDUP( str_in );
812         if( freeme == NULL ) return LDAP_NO_MEMORY;
813         str = freeme;
814
815         parens = 0;
816         while ( *str ) {
817                 switch ( *str ) {
818                 case '(': /*')'*/
819                         str++;
820                         parens++;
821
822                         /* skip spaces */
823                         while( LDAP_SPACE( *str ) ) str++;
824
825                         switch ( *str ) {
826                         case '(':
827                                 if ( (next = find_right_paren( str )) == NULL ) {
828                                         rc = -1;
829                                         goto done;
830                                 }
831
832                                 *next = '\0';
833
834                                 if ( put_vrFilter_list( ber, str ) == -1 ) {
835                                         rc = -1;
836                                         goto done;
837                                 }
838
839                                 /* close the '(' */
840                                 *next++ = ')';
841
842                                 str = next;
843
844                                 parens--;
845                                 break;
846
847
848                         default:
849                                 Debug( LDAP_DEBUG_TRACE, "put_vrFilter: simple\n",
850                                     0, 0, 0 );
851
852                                 balance = 1;
853                                 escape = 0;
854                                 next = str;
855
856                                 while ( *next && balance ) {
857                                         if ( escape == 0 ) {
858                                                 if ( *next == '(' ) {
859                                                         balance++;
860                                                 } else if ( *next == ')' ) {
861                                                         balance--;
862                                                 }
863                                         }
864
865                                         if ( *next == '\\' && ! escape ) {
866                                                 escape = 1;
867                                         } else {
868                                                 escape = 0;
869                                         }
870
871                                         if ( balance ) next++;
872                                 }
873
874                                 if ( balance != 0 ) {
875                                         rc = -1;
876                                         goto done;
877                                 }
878
879                                 *next = '\0';
880
881                                 if ( put_simple_vrFilter( ber, str ) == -1 ) {
882                                         rc = -1;
883                                         goto done;
884                                 }
885
886                                 *next++ = /*'('*/ ')';
887
888                                 str = next;
889                                 parens--;
890                                 break;
891                         }
892                         break;
893
894                 case /*'('*/ ')':
895                         Debug( LDAP_DEBUG_TRACE, "put_vrFilter: end\n",
896                                 0, 0, 0 );
897                         if ( ber_printf( ber, /*"["*/ "]" ) == -1 ) {
898                                 rc = -1;
899                                 goto done;
900                         }
901                         str++;
902                         parens--;
903                         break;
904
905                 case ' ':
906                         str++;
907                         break;
908
909                 default:        /* assume it's a simple type=value filter */
910                         Debug( LDAP_DEBUG_TRACE, "put_vrFilter: default\n",
911                                 0, 0, 0 );
912                         next = strchr( str, '\0' );
913                         if ( put_simple_vrFilter( ber, str ) == -1 ) {
914                                 rc = -1;
915                                 goto done;
916                         }
917                         str = next;
918                         break;
919                 }
920         }
921
922         rc = parens ? -1 : 0;
923
924 done:
925         LDAP_FREE( freeme );
926         return rc;
927 }
928
929 int
930 ldap_put_vrFilter( BerElement *ber, const char *str_in )
931 {
932         int rc =0;
933         
934         if ( ber_printf( ber, "{" /*"}"*/ ) == -1 ) {
935                 rc = -1;
936         }
937         
938         rc = put_vrFilter( ber, str_in );
939
940         if ( ber_printf( ber, /*"{"*/ "N}" ) == -1 ) {
941                 rc = -1;
942         }
943         
944         return rc;
945 }
946
947 static int
948 put_vrFilter_list( BerElement *ber, char *str )
949 {
950         char    *next = NULL;
951         char    save;
952
953         Debug( LDAP_DEBUG_TRACE, "put_vrFilter_list \"%s\"\n",
954                 str, 0, 0 );
955
956         while ( *str ) {
957                 while ( *str && LDAP_SPACE( (unsigned char) *str ) ) {
958                         str++;
959                 }
960                 if ( *str == '\0' ) break;
961
962                 if ( (next = find_right_paren( str + 1 )) == NULL ) {
963                         return -1;
964                 }
965                 save = *++next;
966
967                 /* now we have "(filter)" with str pointing to it */
968                 *next = '\0';
969                 if ( put_vrFilter( ber, str ) == -1 ) return -1;
970                 *next = save;
971                 str = next;
972         }
973
974         return 0;
975 }
976
977 static int
978 put_simple_vrFilter(
979         BerElement *ber,
980         char *str )
981 {
982         char            *s;
983         char            *value;
984         ber_tag_t       ftype;
985         int             rc = -1;
986
987         Debug( LDAP_DEBUG_TRACE, "put_simple_vrFilter: \"%s\"\n",
988                 str, 0, 0 );
989
990         str = LDAP_STRDUP( str );
991         if( str == NULL ) return -1;
992
993         if ( (s = strchr( str, '=' )) == NULL ) {
994                 goto done;
995         }
996
997         value = s + 1;
998         *s-- = '\0';
999
1000         switch ( *s ) {
1001         case '<':
1002                 ftype = LDAP_FILTER_LE;
1003                 *s = '\0';
1004                 break;
1005
1006         case '>':
1007                 ftype = LDAP_FILTER_GE;
1008                 *s = '\0';
1009                 break;
1010
1011         case '~':
1012                 ftype = LDAP_FILTER_APPROX;
1013                 *s = '\0';
1014                 break;
1015
1016         case ':':
1017                 /* According to ValuesReturnFilter control definition
1018                  * extensible filters are off the form:
1019                  *              type [:rule] := value
1020                  * or   :rule := value          
1021                  */
1022                 ftype = LDAP_FILTER_EXT;
1023                 *s = '\0';
1024
1025                 {
1026                         char *rule = strchr( str, ':' );
1027
1028                         if( rule == NULL ) {
1029                                 /* must have attribute */
1030                                 if( !ldap_is_desc( str ) ) {
1031                                         goto done;
1032                                 }
1033                                 rule = "";
1034                         } else {
1035                                 *rule++ = '\0';
1036                         }
1037
1038                         if ( *str == '\0' && ( !rule || *rule == '\0' ) ) {
1039                                 /* must have either type or rule */
1040                                 goto done;
1041                         }
1042
1043                         if ( *str != '\0' && !ldap_is_desc( str ) ) {
1044                                 goto done;
1045                         }
1046
1047                         if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) {
1048                                 goto done;
1049                         }
1050
1051                         rc = ber_printf( ber, "t{" /*"}"*/, ftype );
1052
1053                         if( rc != -1 && rule && *rule != '\0' ) {
1054                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule );
1055                         }
1056
1057                         if( rc != -1 && *str != '\0' ) {
1058                                 rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str );
1059                         }
1060
1061                         if( rc != -1 ) {
1062                                 ber_slen_t len = ldap_pvt_filter_value_unescape( value );
1063
1064                                 if( len >= 0 ) {
1065                                         rc = ber_printf( ber, "to",
1066                                                 LDAP_FILTER_EXT_VALUE, value, len );
1067                                 } else {
1068                                         rc = -1;
1069                                 }
1070                         }
1071
1072                         if( rc != -1 ) { 
1073                                 rc = ber_printf( ber, /*"{"*/ "N}" );
1074                         }
1075                 }
1076                 goto done;
1077
1078         default:
1079                 if( !ldap_is_desc( str ) ) {
1080                         goto done;
1081
1082                 } else {
1083                         char *nextstar = ldap_pvt_find_wildcard( value );
1084
1085                         if ( nextstar == NULL ) {
1086                                 goto done;
1087
1088                         } else if ( *nextstar == '\0' ) {
1089                                 ftype = LDAP_FILTER_EQUALITY;
1090
1091                         } else if ( strcmp( value, "*" ) == 0 ) {
1092                                 ftype = LDAP_FILTER_PRESENT;
1093
1094                         } else {
1095                                 rc = put_substring_filter( ber, str, value );
1096                                 goto done;
1097                         }
1098                 } break;
1099         }
1100
1101         if( !ldap_is_desc( str ) ) goto done;
1102
1103         if ( ftype == LDAP_FILTER_PRESENT ) {
1104                 rc = ber_printf( ber, "ts", ftype, str );
1105
1106         } else {
1107                 ber_slen_t len = ldap_pvt_filter_value_unescape( value );
1108
1109                 if( len >= 0 ) {
1110                         rc = ber_printf( ber, "t{soN}",
1111                                 ftype, str, value, len );
1112                 }
1113         }
1114
1115 done:
1116         if( rc != -1 ) rc = 0;
1117         LDAP_FREE( str );
1118         return rc;
1119 }
1120