Home | History | Annotate | Line # | Download | only in libldap
      1 /*	$NetBSD: filter.c,v 1.4 2025/09/05 21:16:21 christos Exp $	*/
      2 
      3 /* search.c */
      4 /* $OpenLDAP$ */
      5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
      6  *
      7  * Copyright 1998-2024 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.4 2025/09/05 21:16:21 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 
     37 static int put_simple_vrFilter LDAP_P((
     38 	BerElement *ber,
     39 	char *str ));
     40 
     41 static int put_vrFilter_list LDAP_P((
     42 	BerElement *ber,
     43 	char *str ));
     44 
     45 static char *put_complex_filter LDAP_P((
     46 	BerElement *ber,
     47 	char *str,
     48 	ber_tag_t tag,
     49 	int not ));
     50 
     51 static int put_simple_filter LDAP_P((
     52 	BerElement *ber,
     53 	char *str ));
     54 
     55 static int put_substring_filter LDAP_P((
     56 	BerElement *ber,
     57 	char *type,
     58 	char *str,
     59 	char *nextstar ));
     60 
     61 static int put_filter_list LDAP_P((
     62 	BerElement *ber,
     63 	char *str,
     64 	ber_tag_t tag ));
     65 
     66 static 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 
     97 static 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 
    138 options:
    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 
    154 static char *
    155 find_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 
    178 static 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 
    195 char *
    196 ldap_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! */
    233 ber_slen_t
    234 ldap_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 
    290 static char *
    291 put_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 
    328 int
    329 ldap_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 
    505 done:
    506 	LDAP_FREE( freeme );
    507 	return rc;
    508 }
    509 
    510 /*
    511  * Put a list of filters like this "(filter1)(filter2)..."
    512  */
    513 
    514 static int
    515 put_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 
    550 static int
    551 put_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 	if ( str[0] == '=' ) return -1;
    564 
    565 	str = LDAP_STRDUP( str );
    566 	if( str == NULL ) return -1;
    567 
    568 	if ( (s = strchr( str, '=' )) == NULL ) {
    569 		goto done;
    570 	}
    571 
    572 	value = s + 1;
    573 	*s-- = '\0';
    574 
    575 	switch ( *s ) {
    576 	case '<':
    577 		ftype = LDAP_FILTER_LE;
    578 		*s = '\0';
    579 		break;
    580 
    581 	case '>':
    582 		ftype = LDAP_FILTER_GE;
    583 		*s = '\0';
    584 		break;
    585 
    586 	case '~':
    587 		ftype = LDAP_FILTER_APPROX;
    588 		*s = '\0';
    589 		break;
    590 
    591 	case ':':
    592 		/* RFC 4515 extensible filters are off the form:
    593 		 *		type [:dn] [:rule] := value
    594 		 * or	[:dn]:rule := value
    595 		 */
    596 		ftype = LDAP_FILTER_EXT;
    597 		*s = '\0';
    598 
    599 		{
    600 			char *dn = strchr( str, ':' );
    601 			char *rule = NULL;
    602 
    603 			if( dn != NULL ) {
    604 				*dn++ = '\0';
    605 				rule = strchr( dn, ':' );
    606 
    607 				if( rule == NULL ) {
    608 					/* one colon */
    609 					if ( strcasecmp(dn, "dn") == 0 ) {
    610 						/* must have attribute */
    611 						if( !ldap_is_desc( str ) ) {
    612 							goto done;
    613 						}
    614 
    615 						rule = "";
    616 
    617 					} else {
    618 					  rule = dn;
    619 					  dn = NULL;
    620 					}
    621 
    622 				} else {
    623 					/* two colons */
    624 					*rule++ = '\0';
    625 
    626 					if ( strcasecmp(dn, "dn") != 0 ) {
    627 						/* must have "dn" */
    628 						goto done;
    629 					}
    630 				}
    631 
    632 			}
    633 
    634 			if ( *str == '\0' && ( !rule || *rule == '\0' ) ) {
    635 				/* must have either type or rule */
    636 				goto done;
    637 			}
    638 
    639 			if ( *str != '\0' && !ldap_is_desc( str ) ) {
    640 				goto done;
    641 			}
    642 
    643 			if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) {
    644 				goto done;
    645 			}
    646 
    647 			rc = ber_printf( ber, "t{" /*"}"*/, ftype );
    648 
    649 			if( rc != -1 && rule && *rule != '\0' ) {
    650 				rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule );
    651 			}
    652 
    653 			if( rc != -1 && *str != '\0' ) {
    654 				rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str );
    655 			}
    656 
    657 			if( rc != -1 ) {
    658 				ber_slen_t len = ldap_pvt_filter_value_unescape( value );
    659 
    660 				if( len >= 0 ) {
    661 					rc = ber_printf( ber, "to",
    662 						LDAP_FILTER_EXT_VALUE, value, len );
    663 				} else {
    664 					rc = -1;
    665 				}
    666 			}
    667 
    668 			if( rc != -1 && dn ) {
    669 				rc = ber_printf( ber, "tb",
    670 					LDAP_FILTER_EXT_DNATTRS, (ber_int_t) 1 );
    671 			}
    672 
    673 			if( rc != -1 ) {
    674 				rc = ber_printf( ber, /*"{"*/ "N}" );
    675 			}
    676 		}
    677 		goto done;
    678 
    679 	default:
    680 		if( !ldap_is_desc( str ) ) {
    681 			goto done;
    682 
    683 		} else {
    684 			char *nextstar = ldap_pvt_find_wildcard( value );
    685 
    686 			if ( nextstar == NULL ) {
    687 				goto done;
    688 
    689 			} else if ( *nextstar == '\0' ) {
    690 				ftype = LDAP_FILTER_EQUALITY;
    691 
    692 			} else if ( strcmp( value, "*" ) == 0 ) {
    693 				ftype = LDAP_FILTER_PRESENT;
    694 
    695 			} else {
    696 				rc = put_substring_filter( ber, str, value, nextstar );
    697 				goto done;
    698 			}
    699 		} break;
    700 	}
    701 
    702 	if( !ldap_is_desc( str ) ) goto done;
    703 
    704 	if ( ftype == LDAP_FILTER_PRESENT ) {
    705 		rc = ber_printf( ber, "ts", ftype, str );
    706 
    707 	} else {
    708 		ber_slen_t len = ldap_pvt_filter_value_unescape( value );
    709 
    710 		if( len >= 0 ) {
    711 			rc = ber_printf( ber, "t{soN}",
    712 				ftype, str, value, len );
    713 		}
    714 	}
    715 
    716 done:
    717 	if( rc != -1 ) rc = 0;
    718 	LDAP_FREE( str );
    719 	return rc;
    720 }
    721 
    722 static int
    723 put_substring_filter( BerElement *ber, char *type, char *val, char *nextstar )
    724 {
    725 	int gotstar = 0;
    726 	ber_tag_t	ftype = LDAP_FILTER_SUBSTRINGS;
    727 
    728 	Debug2( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n",
    729 		type, val );
    730 
    731 	if ( ber_printf( ber, "t{s{" /*"}}"*/, ftype, type ) == -1 ) {
    732 		return -1;
    733 	}
    734 
    735 	for( ; *val; val=nextstar ) {
    736 		if ( gotstar )
    737 			nextstar = ldap_pvt_find_wildcard( val );
    738 
    739 		if ( nextstar == NULL ) {
    740 			return -1;
    741 		}
    742 
    743 		if ( *nextstar == '\0' ) {
    744 			ftype = LDAP_SUBSTRING_FINAL;
    745 		} else {
    746 			*nextstar++ = '\0';
    747 			if ( gotstar++ == 0 ) {
    748 				ftype = LDAP_SUBSTRING_INITIAL;
    749 			} else {
    750 				ftype = LDAP_SUBSTRING_ANY;
    751 			}
    752 		}
    753 
    754 		if ( *val != '\0' || ftype == LDAP_SUBSTRING_ANY ) {
    755 			ber_slen_t len = ldap_pvt_filter_value_unescape( val );
    756 
    757 			if ( len <= 0  ) {
    758 				return -1;
    759 			}
    760 
    761 			if ( ber_printf( ber, "to", ftype, val, len ) == -1 ) {
    762 				return -1;
    763 			}
    764 		}
    765 	}
    766 
    767 	if ( ber_printf( ber, /*"{{"*/ "N}N}" ) == -1 ) {
    768 		return -1;
    769 	}
    770 
    771 	return 0;
    772 }
    773 
    774 static int
    775 put_vrFilter( BerElement *ber, const char *str_in )
    776 {
    777 	int rc;
    778 	char	*freeme;
    779 	char	*str;
    780 	char	*next;
    781 	int	parens, balance, escape;
    782 
    783 	/*
    784 	 * A ValuesReturnFilter looks like this:
    785 	 *
    786 	 *	ValuesReturnFilter ::= SEQUENCE OF SimpleFilterItem
    787 	 *      SimpleFilterItem ::= CHOICE {
    788 	 *              equalityMatch   [3]     AttributeValueAssertion,
    789 	 *              substrings      [4]     SubstringFilter,
    790 	 *              greaterOrEqual  [5]     AttributeValueAssertion,
    791 	 *              lessOrEqual     [6]     AttributeValueAssertion,
    792 	 *              present         [7]     AttributeType,
    793 	 *              approxMatch     [8]     AttributeValueAssertion,
    794 	 *		extensibleMatch [9]	SimpleMatchingAssertion -- LDAPv3
    795 	 *      }
    796 	 *
    797 	 *      SubstringFilter ::= SEQUENCE {
    798 	 *              type               AttributeType,
    799 	 *              SEQUENCE OF CHOICE {
    800 	 *                      initial          [0] IA5String,
    801 	 *                      any              [1] IA5String,
    802 	 *                      final            [2] IA5String
    803 	 *              }
    804 	 *      }
    805 	 *
    806 	 *	SimpleMatchingAssertion ::= SEQUENCE {	-- LDAPv3
    807 	 *		matchingRule    [1] MatchingRuleId OPTIONAL,
    808 	 *		type            [2] AttributeDescription OPTIONAL,
    809 	 *		matchValue      [3] AssertionValue }
    810 	 *
    811 	 * (Source: RFC 3876)
    812 	 */
    813 
    814 	Debug1( LDAP_DEBUG_TRACE, "put_vrFilter: \"%s\"\n", str_in );
    815 
    816 	freeme = LDAP_STRDUP( str_in );
    817 	if( freeme == NULL ) return LDAP_NO_MEMORY;
    818 	str = freeme;
    819 
    820 	parens = 0;
    821 	while ( *str ) {
    822 		switch ( *str ) {
    823 		case '(': /*')'*/
    824 			str++;
    825 			parens++;
    826 
    827 			/* skip spaces */
    828 			while( LDAP_SPACE( *str ) ) str++;
    829 
    830 			switch ( *str ) {
    831 			case '(':
    832 				if ( (next = find_right_paren( str )) == NULL ) {
    833 					rc = -1;
    834 					goto done;
    835 				}
    836 
    837 				*next = '\0';
    838 
    839 				if ( put_vrFilter_list( ber, str ) == -1 ) {
    840 					rc = -1;
    841 					goto done;
    842 				}
    843 
    844 				/* close the '(' */
    845 				*next++ = ')';
    846 
    847 				str = next;
    848 
    849 				parens--;
    850 				break;
    851 
    852 
    853 			default:
    854 				Debug0( LDAP_DEBUG_TRACE, "put_vrFilter: simple\n" );
    855 
    856 				balance = 1;
    857 				escape = 0;
    858 				next = str;
    859 
    860 				while ( *next && balance ) {
    861 					if ( escape == 0 ) {
    862 						if ( *next == '(' ) {
    863 							balance++;
    864 						} else if ( *next == ')' ) {
    865 							balance--;
    866 						}
    867 					}
    868 
    869 					if ( *next == '\\' && ! escape ) {
    870 						escape = 1;
    871 					} else {
    872 						escape = 0;
    873 					}
    874 
    875 					if ( balance ) next++;
    876 				}
    877 
    878 				if ( balance != 0 ) {
    879 					rc = -1;
    880 					goto done;
    881 				}
    882 
    883 				*next = '\0';
    884 
    885 				if ( put_simple_vrFilter( ber, str ) == -1 ) {
    886 					rc = -1;
    887 					goto done;
    888 				}
    889 
    890 				*next++ = /*'('*/ ')';
    891 
    892 				str = next;
    893 				parens--;
    894 				break;
    895 			}
    896 			break;
    897 
    898 		case /*'('*/ ')':
    899 			Debug0( LDAP_DEBUG_TRACE, "put_vrFilter: end\n" );
    900 			if ( ber_printf( ber, /*"["*/ "]" ) == -1 ) {
    901 				rc = -1;
    902 				goto done;
    903 			}
    904 			str++;
    905 			parens--;
    906 			break;
    907 
    908 		case ' ':
    909 			str++;
    910 			break;
    911 
    912 		default:	/* assume it's a simple type=value filter */
    913 			Debug0( LDAP_DEBUG_TRACE, "put_vrFilter: default\n" );
    914 			next = strchr( str, '\0' );
    915 			if ( put_simple_vrFilter( ber, str ) == -1 ) {
    916 				rc = -1;
    917 				goto done;
    918 			}
    919 			str = next;
    920 			break;
    921 		}
    922 	}
    923 
    924 	rc = parens ? -1 : 0;
    925 
    926 done:
    927 	LDAP_FREE( freeme );
    928 	return rc;
    929 }
    930 
    931 int
    932 ldap_put_vrFilter( BerElement *ber, const char *str_in )
    933 {
    934 	int rc =0;
    935 
    936 	if ( ber_printf( ber, "{" /*"}"*/ ) == -1 ) {
    937 		return -1;
    938 	}
    939 
    940 	rc = put_vrFilter( ber, str_in );
    941 
    942 	if ( ber_printf( ber, /*"{"*/ "N}" ) == -1 ) {
    943 		rc = -1;
    944 	}
    945 
    946 	return rc;
    947 }
    948 
    949 static int
    950 put_vrFilter_list( BerElement *ber, char *str )
    951 {
    952 	char	*next = NULL;
    953 	char	save;
    954 
    955 	Debug1( LDAP_DEBUG_TRACE, "put_vrFilter_list \"%s\"\n",
    956 		str );
    957 
    958 	while ( *str ) {
    959 		while ( *str && LDAP_SPACE( (unsigned char) *str ) ) {
    960 			str++;
    961 		}
    962 		if ( *str == '\0' ) break;
    963 
    964 		if ( (next = find_right_paren( str + 1 )) == NULL ) {
    965 			return -1;
    966 		}
    967 		save = *++next;
    968 
    969 		/* now we have "(filter)" with str pointing to it */
    970 		*next = '\0';
    971 		if ( put_vrFilter( ber, str ) == -1 ) return -1;
    972 		*next = save;
    973 		str = next;
    974 	}
    975 
    976 	return 0;
    977 }
    978 
    979 static int
    980 put_simple_vrFilter(
    981 	BerElement *ber,
    982 	char *str )
    983 {
    984 	char		*s;
    985 	char		*value;
    986 	ber_tag_t	ftype;
    987 	int		rc = -1;
    988 
    989 	Debug1( LDAP_DEBUG_TRACE, "put_simple_vrFilter: \"%s\"\n",
    990 		str );
    991 
    992 	str = LDAP_STRDUP( str );
    993 	if( str == NULL ) return -1;
    994 
    995 	if ( (s = strchr( str, '=' )) == NULL ) {
    996 		goto done;
    997 	}
    998 
    999 	value = s + 1;
   1000 	*s-- = '\0';
   1001 
   1002 	switch ( *s ) {
   1003 	case '<':
   1004 		ftype = LDAP_FILTER_LE;
   1005 		*s = '\0';
   1006 		break;
   1007 
   1008 	case '>':
   1009 		ftype = LDAP_FILTER_GE;
   1010 		*s = '\0';
   1011 		break;
   1012 
   1013 	case '~':
   1014 		ftype = LDAP_FILTER_APPROX;
   1015 		*s = '\0';
   1016 		break;
   1017 
   1018 	case ':':
   1019 		/* According to ValuesReturnFilter control definition
   1020 		 * extensible filters are off the form:
   1021 		 *		type [:rule] := value
   1022 		 * or	:rule := value
   1023 		 */
   1024 		ftype = LDAP_FILTER_EXT;
   1025 		*s = '\0';
   1026 
   1027 		{
   1028 			char *rule = strchr( str, ':' );
   1029 
   1030 			if( rule == NULL ) {
   1031 				/* must have attribute */
   1032 				if( !ldap_is_desc( str ) ) {
   1033 					goto done;
   1034 				}
   1035 				rule = "";
   1036 			} else {
   1037 				*rule++ = '\0';
   1038 			}
   1039 
   1040 			if ( *str == '\0' && ( !rule || *rule == '\0' ) ) {
   1041 				/* must have either type or rule */
   1042 				goto done;
   1043 			}
   1044 
   1045 			if ( *str != '\0' && !ldap_is_desc( str ) ) {
   1046 				goto done;
   1047 			}
   1048 
   1049 			if ( rule && *rule != '\0' && !ldap_is_oid( rule ) ) {
   1050 				goto done;
   1051 			}
   1052 
   1053 			rc = ber_printf( ber, "t{" /*"}"*/, ftype );
   1054 
   1055 			if( rc != -1 && rule && *rule != '\0' ) {
   1056 				rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_OID, rule );
   1057 			}
   1058 
   1059 			if( rc != -1 && *str != '\0' ) {
   1060 				rc = ber_printf( ber, "ts", LDAP_FILTER_EXT_TYPE, str );
   1061 			}
   1062 
   1063 			if( rc != -1 ) {
   1064 				ber_slen_t len = ldap_pvt_filter_value_unescape( value );
   1065 
   1066 				if( len >= 0 ) {
   1067 					rc = ber_printf( ber, "to",
   1068 						LDAP_FILTER_EXT_VALUE, value, len );
   1069 				} else {
   1070 					rc = -1;
   1071 				}
   1072 			}
   1073 
   1074 			if( rc != -1 ) {
   1075 				rc = ber_printf( ber, /*"{"*/ "N}" );
   1076 			}
   1077 		}
   1078 		goto done;
   1079 
   1080 	default:
   1081 		if( !ldap_is_desc( str ) ) {
   1082 			goto done;
   1083 
   1084 		} else {
   1085 			char *nextstar = ldap_pvt_find_wildcard( value );
   1086 
   1087 			if ( nextstar == NULL ) {
   1088 				goto done;
   1089 
   1090 			} else if ( *nextstar == '\0' ) {
   1091 				ftype = LDAP_FILTER_EQUALITY;
   1092 
   1093 			} else if ( strcmp( value, "*" ) == 0 ) {
   1094 				ftype = LDAP_FILTER_PRESENT;
   1095 
   1096 			} else {
   1097 				rc = put_substring_filter( ber, str, value, nextstar );
   1098 				goto done;
   1099 			}
   1100 		} break;
   1101 	}
   1102 
   1103 	if( !ldap_is_desc( str ) ) goto done;
   1104 
   1105 	if ( ftype == LDAP_FILTER_PRESENT ) {
   1106 		rc = ber_printf( ber, "ts", ftype, str );
   1107 
   1108 	} else {
   1109 		ber_slen_t len = ldap_pvt_filter_value_unescape( value );
   1110 
   1111 		if( len >= 0 ) {
   1112 			rc = ber_printf( ber, "t{soN}",
   1113 				ftype, str, value, len );
   1114 		}
   1115 	}
   1116 
   1117 done:
   1118 	if( rc != -1 ) rc = 0;
   1119 	LDAP_FREE( str );
   1120 	return rc;
   1121 }
   1122 
   1123