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