xref: /illumos-gate/usr/src/lib/libldap5/sources/ldap/common/search.c (revision 9b9d39d2a32ff806d2431dbcc50968ef1e6d46b2)
1 /*
2  * Copyright 2001-2002 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  *
5  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
6  */
7 
8 
9 /*
10  * The contents of this file are subject to the Netscape Public
11  * License Version 1.1 (the "License"); you may not use this file
12  * except in compliance with the License. You may obtain a copy of
13  * the License at http://www.mozilla.org/NPL/
14  *
15  * Software distributed under the License is distributed on an "AS
16  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
17  * implied. See the License for the specific language governing
18  * rights and limitations under the License.
19  *
20  * The Original Code is Mozilla Communicator client code, released
21  * March 31, 1998.
22  *
23  * The Initial Developer of the Original Code is Netscape
24  * Communications Corporation. Portions created by Netscape are
25  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
26  * Rights Reserved.
27  *
28  * Contributor(s):
29  */
30 /*
31  *  Copyright (c) 1990 Regents of the University of Michigan.
32  *  All rights reserved.
33  */
34 /*
35  *  search.c
36  */
37 
38 #if 0
39 #ifndef lint
40 static char copyright[] = "@(#) Copyright (c) 1990 Regents of the University of Michigan.\nAll rights reserved.\n";
41 #endif
42 #endif
43 
44 #include "ldap-int.h"
45 
46 static int nsldapi_timeval2ldaplimit( struct timeval *timeoutp,
47 	int defaultvalue );
48 static int nsldapi_search( LDAP *ld, const char *base, int scope,
49 	const char *filter, char **attrs, int attrsonly,
50 	LDAPControl **serverctrls, LDAPControl **clientctrls,
51 	int timelimit, int sizelimit, int *msgidp );
52 static char *find_right_paren( char *s );
53 static char *put_complex_filter( BerElement *ber, char *str,
54 	ber_tag_t tag, int not );
55 static int unescape_filterval( char *str );
56 static int hexchar2int( char c );
57 static int is_valid_attr( char *a );
58 static int put_simple_filter( BerElement *ber, char *str );
59 static int put_substring_filter( BerElement *ber, char *type,
60 	char *str );
61 static int put_filter_list( BerElement *ber, char *str );
62 static int nsldapi_search_s( LDAP *ld, const char *base, int scope,
63 	const char *filter, char **attrs, int attrsonly,
64 	LDAPControl **serverctrls, LDAPControl **clientctrls,
65 	struct timeval *localtimeoutp, int timelimit, int sizelimit,
66 	LDAPMessage **res );
67 
68 /*
69  * ldap_search - initiate an ldap search operation.  Parameters:
70  *
71  *	ld		LDAP descriptor
72  *	base		DN of the base object
73  *	scope		the search scope - one of LDAP_SCOPE_BASE,
74  *			    LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE
75  *	filter		a string containing the search filter
76  *			(e.g., "(|(cn=bob)(sn=bob))")
77  *	attrs		list of attribute types to return for matches
78  *	attrsonly	1 => attributes only 0 => attributes and values
79  *
80  * Example:
81  *	char	*attrs[] = { "mail", "title", 0 };
82  *	msgid = ldap_search( ld, "c=us@o=UM", LDAP_SCOPE_SUBTREE, "cn~=bob",
83  *	    attrs, attrsonly );
84  */
85 int
86 LDAP_CALL
87 ldap_search(
88     LDAP 	*ld,
89     const char 	*base,
90     int 	scope,
91     const char 	*filter,
92     char 	**attrs,
93     int 	attrsonly
94 )
95 {
96 	int		msgid;
97 
98 	LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search\n", 0, 0, 0 );
99 
100 	if ( ldap_search_ext( ld, base, scope, filter, attrs, attrsonly, NULL,
101 	    NULL, NULL, -1, &msgid ) == LDAP_SUCCESS ) {
102 		return( msgid );
103 	} else {
104 		return( -1 );	/* error is in ld handle */
105 	}
106 }
107 
108 
109 /*
110  * LDAPv3 extended search.
111  * Returns an LDAP error code.
112  */
113 int
114 LDAP_CALL
115 ldap_search_ext(
116     LDAP 		*ld,
117     const char 		*base,
118     int 		scope,
119     const char 		*filter,
120     char 		**attrs,
121     int 		attrsonly,
122     LDAPControl		**serverctrls,
123     LDAPControl		**clientctrls,
124     struct timeval	*timeoutp,	/* NULL means use ld->ld_timelimit */
125     int			sizelimit,
126     int			*msgidp
127 )
128 {
129 	/*
130 	 * It is an error to pass in a zero'd timeval.
131 	 */
132 	if ( timeoutp != NULL && timeoutp->tv_sec == 0 &&
133 	    timeoutp->tv_usec == 0 ) {
134 		if ( ld != NULL ) {
135 			LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
136 		}
137                 return( LDAP_PARAM_ERROR );
138         }
139 
140 	return( nsldapi_search( ld, base, scope, filter, attrs, attrsonly,
141 	    serverctrls, clientctrls,
142 	    nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, msgidp ));
143 }
144 
145 
146 /*
147  * Like ldap_search_ext() except an integer timelimit is passed instead of
148  * using the overloaded struct timeval *timeoutp.
149  */
150 static int
151 nsldapi_search(
152     LDAP 		*ld,
153     const char 		*base,
154     int 		scope,
155     const char 		*filter,
156     char 		**attrs,
157     int 		attrsonly,
158     LDAPControl		**serverctrls,
159     LDAPControl		**clientctrls,
160     int			timelimit,	/* -1 means use ld->ld_timelimit */
161     int			sizelimit,	/* -1 means use ld->ld_sizelimit */
162     int			*msgidp
163 )
164 {
165 	BerElement	*ber;
166 	int		rc, rc_key;
167 	unsigned long	key;	/* XXXmcs: memcache */
168 
169 	LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search_ext\n", 0, 0, 0 );
170 
171 	if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) {
172 		return( LDAP_PARAM_ERROR );
173 	}
174 
175 	if ( base == NULL ) {
176 	    base = "";
177 	}
178 
179 	if ( filter == NULL ) {
180 	    filter = "(objectclass=*)";
181 	}
182 
183 	if ( msgidp == NULL || ( scope != LDAP_SCOPE_BASE
184 	    && scope != LDAP_SCOPE_ONELEVEL && scope != LDAP_SCOPE_SUBTREE )
185 		|| ( sizelimit < -1 )) {
186 		LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
187                 return( LDAP_PARAM_ERROR );
188         }
189 	LDAP_MUTEX_LOCK( ld, LDAP_MSGID_LOCK );
190 	*msgidp = ++ld->ld_msgid;
191 	LDAP_MUTEX_UNLOCK( ld, LDAP_MSGID_LOCK );
192 
193 	/*
194 	 * XXXmcs: should use cache function pointers to hook in memcache
195 	 */
196 	if ( ld->ld_memcache == NULL ) {
197 		rc_key = LDAP_NOT_SUPPORTED;
198 	} else if (( rc_key = ldap_memcache_createkey( ld, base, scope, filter,
199 	    attrs, attrsonly, serverctrls, clientctrls, &key)) == LDAP_SUCCESS
200 	    && ldap_memcache_result( ld, *msgidp, key ) == LDAP_SUCCESS ) {
201 		return LDAP_SUCCESS;
202 	}
203 
204 	/* check the cache */
205 	if ( ld->ld_cache_on && ld->ld_cache_search != NULL ) {
206 		LDAP_MUTEX_LOCK( ld, LDAP_CACHE_LOCK );
207 		if ( (rc = (ld->ld_cache_search)( ld, *msgidp, LDAP_REQ_SEARCH,
208 		    base, scope, filter, attrs, attrsonly )) != 0 ) {
209 			*msgidp = rc;
210 			LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK );
211 			return( LDAP_SUCCESS );
212 		}
213 		LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK );
214 	}
215 
216 	/* caching off or did not find it in the cache - check the net */
217 	if (( rc = nsldapi_build_search_req( ld, base, scope, filter, attrs,
218 	    attrsonly, serverctrls, clientctrls, timelimit, sizelimit,
219 	    *msgidp, &ber )) != LDAP_SUCCESS ) {
220 		return( rc );
221 	}
222 
223 	/* send the message */
224 	rc = nsldapi_send_initial_request( ld, *msgidp, LDAP_REQ_SEARCH,
225 		(char *) base, ber );
226 
227 	/*
228 	 * XXXmcs: should use cache function pointers to hook in memcache
229 	 */
230 	if ( (rc_key == LDAP_SUCCESS) && (rc >= 0) ) {
231 		ldap_memcache_new( ld, rc, key, base );
232 	}
233 
234 	*msgidp = rc;
235 	return( rc < 0 ? LDAP_GET_LDERRNO( ld, NULL, NULL ) : LDAP_SUCCESS );
236 }
237 
238 
239 /*
240  * Convert a non-NULL timeoutp to a value in seconds that is appropriate to
241  * send in an LDAP search request.  If timeoutp is NULL, return defaultvalue.
242  */
243 static int
244 nsldapi_timeval2ldaplimit( struct timeval *timeoutp, int defaultvalue )
245 {
246 	int		timelimit;
247 
248 	if ( NULL == timeoutp ) {
249 		timelimit = defaultvalue;
250 	} else if ( timeoutp->tv_sec > 0 ) {
251 		timelimit = timeoutp->tv_sec;
252 	} else if ( timeoutp->tv_usec > 0 ) {
253 		timelimit = 1;	/* minimum we can express in LDAP */
254 	} else {
255 		/*
256 		 * both tv_sec and tv_usec are less than one (zero?) so
257 		 * to maintain compatiblity with our "zero means no limit"
258 		 * convention we pass no limit to the server.
259 		 */
260 		timelimit = 0;	/* no limit */
261 	}
262 
263 	return( timelimit );
264 }
265 
266 
267 /* returns an LDAP error code and also sets it in ld */
268 int
269 nsldapi_build_search_req(
270     LDAP		*ld,
271     const char		*base,
272     int			scope,
273     const char		*filter,
274     char		**attrs,
275     int			attrsonly,
276     LDAPControl		**serverctrls,
277     LDAPControl		**clientctrls,	/* not used for anything yet */
278     int			timelimit,	/* if -1, ld->ld_timelimit is used */
279     int			sizelimit,	/* if -1, ld->ld_sizelimit is used */
280     int			msgid,
281     BerElement		**berp
282 )
283 {
284 	BerElement	*ber;
285 	int		err;
286 	char		*fdup;
287 
288 	/*
289 	 * Create the search request.  It looks like this:
290 	 *	SearchRequest := [APPLICATION 3] SEQUENCE {
291 	 *		baseObject	DistinguishedName,
292 	 *		scope		ENUMERATED {
293 	 *			baseObject	(0),
294 	 *			singleLevel	(1),
295 	 *			wholeSubtree	(2)
296 	 *		},
297 	 *		derefAliases	ENUMERATED {
298 	 *			neverDerefaliases	(0),
299 	 *			derefInSearching	(1),
300 	 *			derefFindingBaseObj	(2),
301 	 *			alwaysDerefAliases	(3)
302 	 *		},
303 	 *		sizelimit	INTEGER (0 .. 65535),
304 	 *		timelimit	INTEGER (0 .. 65535),
305 	 *		attrsOnly	BOOLEAN,
306 	 *		filter		Filter,
307 	 *		attributes	SEQUENCE OF AttributeType
308 	 *	}
309 	 * wrapped in an ldap message.
310 	 */
311 
312 	/* create a message to send */
313 	if (( err = nsldapi_alloc_ber_with_options( ld, &ber ))
314 	    != LDAP_SUCCESS ) {
315 		return( err );
316 	}
317 
318 	if ( base == NULL ) {
319 	    base = "";
320 	}
321 
322 	if ( sizelimit == -1 ) {
323 	    sizelimit = ld->ld_sizelimit;
324 	}
325 
326 	if ( timelimit == -1 ) {
327 	    timelimit = ld->ld_timelimit;
328 	}
329 
330 #ifdef CLDAP
331 	if ( ld->ld_sbp->sb_naddr > 0 ) {
332 	    err = ber_printf( ber, "{ist{seeiib", msgid,
333 		ld->ld_cldapdn, LDAP_REQ_SEARCH, base, scope, ld->ld_deref,
334 		sizelimit, timelimit, attrsonly );
335 	} else {
336 #endif /* CLDAP */
337 		err = ber_printf( ber, "{it{seeiib", msgid,
338 		    LDAP_REQ_SEARCH, base, scope, ld->ld_deref,
339 		    sizelimit, timelimit, attrsonly );
340 #ifdef CLDAP
341 	}
342 #endif /* CLDAP */
343 
344 	if ( err == -1 ) {
345 		LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL );
346 		ber_free( ber, 1 );
347 		return( LDAP_ENCODING_ERROR );
348 	}
349 
350 	fdup = nsldapi_strdup( filter );
351 	if (fdup == NULL) {
352 		LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY, NULL, NULL );
353 		ber_free( ber, 1 );
354 		return( LDAP_NO_MEMORY );
355 	}
356         err = ldap_put_filter( ber, fdup );
357 	NSLDAPI_FREE( fdup );
358 
359 	if ( err == -1 ) {
360 		LDAP_SET_LDERRNO( ld, LDAP_FILTER_ERROR, NULL, NULL );
361 		ber_free( ber, 1 );
362 		return( LDAP_FILTER_ERROR );
363 	}
364 
365 	if ( ber_printf( ber, "{v}}", attrs ) == -1 ) {
366 		LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL );
367 		ber_free( ber, 1 );
368 		return( LDAP_ENCODING_ERROR );
369 	}
370 
371 	if ( (err = nsldapi_put_controls( ld, serverctrls, 1, ber ))
372 	    != LDAP_SUCCESS ) {
373 		ber_free( ber, 1 );
374 		return( err );
375 	}
376 
377 	*berp = ber;
378 	return( LDAP_SUCCESS );
379 }
380 
381 static char *
382 find_right_paren( char *s )
383 {
384 	int	balance, escape;
385 
386 	balance = 1;
387 	escape = 0;
388 	while ( *s && balance ) {
389 		if ( escape == 0 ) {
390 			if ( *s == '(' )
391 				balance++;
392 			else if ( *s == ')' )
393 				balance--;
394 		}
395 		if ( *s == '\\' && ! escape )
396 			escape = 1;
397 		else
398 			escape = 0;
399 		if ( balance )
400 			s++;
401 	}
402 
403 	return( *s ? s : NULL );
404 }
405 
406 static char *
407 put_complex_filter(
408     BerElement		*ber,
409     char		*str,
410     ber_tag_t		tag,
411     int			not
412 )
413 {
414 	char	*next;
415 
416 	/*
417 	 * We have (x(filter)...) with str sitting on
418 	 * the x.  We have to find the paren matching
419 	 * the one before the x and put the intervening
420 	 * filters by calling put_filter_list().
421 	 */
422 
423 	/* put explicit tag */
424 	if ( ber_printf( ber, "t{", tag ) == -1 )
425 		return( NULL );
426 
427 	str++;
428 	if ( (next = find_right_paren( str )) == NULL )
429 		return( NULL );
430 
431 	*next = '\0';
432 	if ( put_filter_list( ber, str ) == -1 )
433 		return( NULL );
434 	*next++ = ')';
435 
436 	/* flush explicit tagged thang */
437 	if ( ber_printf( ber, "}" ) == -1 )
438 		return( NULL );
439 
440 	return( next );
441 }
442 
443 int
444 ldap_put_filter( BerElement *ber, char *str )
445 {
446 	char	*next;
447 	int	parens, balance, escape;
448 
449 	/*
450 	 * A Filter looks like this:
451 	 *      Filter ::= CHOICE {
452 	 *              and             [0]     SET OF Filter,
453 	 *              or              [1]     SET OF Filter,
454 	 *              not             [2]     Filter,
455 	 *              equalityMatch   [3]     AttributeValueAssertion,
456 	 *              substrings      [4]     SubstringFilter,
457 	 *              greaterOrEqual  [5]     AttributeValueAssertion,
458 	 *              lessOrEqual     [6]     AttributeValueAssertion,
459 	 *              present         [7]     AttributeType,,
460 	 *              approxMatch     [8]     AttributeValueAssertion
461 	 *      }
462 	 *
463 	 *      SubstringFilter ::= SEQUENCE {
464 	 *              type               AttributeType,
465 	 *              SEQUENCE OF CHOICE {
466 	 *                      initial          [0] IA5String,
467 	 *                      any              [1] IA5String,
468 	 *                      final            [2] IA5String
469 	 *              }
470 	 *      }
471 	 * Note: tags in a choice are always explicit
472 	 */
473 
474 	LDAPDebug( LDAP_DEBUG_TRACE, "put_filter \"%s\"\n", str, 0, 0 );
475 
476 	parens = 0;
477 	while ( *str ) {
478 		switch ( *str ) {
479 		case '(':
480 			str++;
481 			parens++;
482 			switch ( *str ) {
483 			case '&':
484 				LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: AND\n",
485 				    0, 0, 0 );
486 
487 				if ( (str = put_complex_filter( ber, str,
488 				    LDAP_FILTER_AND, 0 )) == NULL )
489 					return( -1 );
490 
491 				parens--;
492 				break;
493 
494 			case '|':
495 				LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: OR\n",
496 				    0, 0, 0 );
497 
498 				if ( (str = put_complex_filter( ber, str,
499 				    LDAP_FILTER_OR, 0 )) == NULL )
500 					return( -1 );
501 
502 				parens--;
503 				break;
504 
505 			case '!':
506 				LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: NOT\n",
507 				    0, 0, 0 );
508 
509 				if ( (str = put_complex_filter( ber, str,
510 				    LDAP_FILTER_NOT, 1 )) == NULL )
511 					return( -1 );
512 
513 				parens--;
514 				break;
515 
516 			default:
517 				LDAPDebug( LDAP_DEBUG_TRACE,
518 				    "put_filter: simple\n", 0, 0, 0 );
519 
520 				balance = 1;
521 				escape = 0;
522 				next = str;
523 				while ( *next && balance ) {
524 					if ( escape == 0 ) {
525 						if ( *next == '(' )
526 							balance++;
527 						else if ( *next == ')' )
528 							balance--;
529 					}
530 					if ( *next == '\\' && ! escape )
531 						escape = 1;
532 					else
533 						escape = 0;
534 					if ( balance )
535 						next++;
536 				}
537 				if ( balance != 0 )
538 					return( -1 );
539 
540 				*next = '\0';
541 				if ( put_simple_filter( ber, str ) == -1 ) {
542 					return( -1 );
543 				}
544 				*next++ = ')';
545 				str = next;
546 				parens--;
547 				break;
548 			}
549 			break;
550 
551 		case ')':
552 			LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: end\n", 0, 0,
553 			    0 );
554 			if ( ber_printf( ber, "]" ) == -1 )
555 				return( -1 );
556 			str++;
557 			parens--;
558 			break;
559 
560 		case ' ':
561 			str++;
562 			break;
563 
564 		default:	/* assume it's a simple type=value filter */
565 			LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: default\n", 0, 0,
566 			    0 );
567 			next = strchr( str, '\0' );
568 			if ( put_simple_filter( ber, str ) == -1 ) {
569 				return( -1 );
570 			}
571 			str = next;
572 			break;
573 		}
574 	}
575 
576 	return( parens ? -1 : 0 );
577 }
578 
579 
580 /*
581  * Put a list of filters like this "(filter1)(filter2)..."
582  */
583 
584 static int
585 put_filter_list( BerElement *ber, char *str )
586 {
587 	char	*next;
588 	char	save;
589 
590 	LDAPDebug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n", str, 0, 0 );
591 
592 	while ( *str ) {
593 		while ( *str && isspace( *str ) )
594 			str++;
595 		if ( *str == '\0' )
596 			break;
597 
598 		if ( (next = find_right_paren( str + 1 )) == NULL )
599 			return( -1 );
600 		save = *++next;
601 
602 		/* now we have "(filter)" with str pointing to it */
603 		*next = '\0';
604                 if ( ldap_put_filter( ber, str ) == -1 )
605 			return( -1 );
606 		*next = save;
607 
608 		str = next;
609 	}
610 
611 	return( 0 );
612 }
613 
614 
615 /*
616  * is_valid_attr - returns 1 if a is a syntactically valid left-hand side
617  * of a filter expression, 0 otherwise.  A valid string may contain only
618  * letters, numbers, hyphens, semi-colons, colons and periods. examples:
619  *	cn
620  *	cn;lang-fr
621  *	1.2.3.4;binary;dynamic
622  *	mail;dynamic
623  *	cn:dn:1.2.3.4
624  *
625  * For compatibility with older servers, we also allow underscores in
626  * attribute types, even through they are not allowed by the LDAPv3 RFCs.
627  */
628 static int
629 is_valid_attr( char *a )
630 {
631 	for ( ; *a; a++ ) {
632 	    if ( !isascii( *a ) ) {
633 		return( 0 );
634 	    } else if ( !isalnum( *a ) ) {
635 		switch ( *a ) {
636 		  case '-':
637 		  case '.':
638 		  case ';':
639 		  case ':':
640 		  case '_':
641 		    break; /* valid */
642 		  default:
643 		    return( 0 );
644 		}
645 	    }
646 	}
647 
648 	return( 1 );
649 }
650 
651 static char *
652 find_star( char *s )
653 {
654     for ( ; *s; ++s ) {
655 	switch ( *s ) {
656 	  case '*': return s;
657 	  case '\\':
658 	    ++s;
659 	    if ( hexchar2int(s[0]) >= 0 && hexchar2int(s[1]) >= 0 ) ++s;
660 	  default: break;
661 	}
662     }
663     return NULL;
664 }
665 
666 static int
667 put_simple_filter( BerElement *ber, char *str )
668 {
669 	char		*s, *s2, *s3, filterop;
670 	char		*value;
671 	ber_uint_t	ftype;
672 	int		rc, len;
673 	char		*oid;	/* for v3 extended filter */
674 	int		dnattr;	/* for v3 extended filter */
675 
676 	LDAPDebug( LDAP_DEBUG_TRACE, "put_simple_filter \"%s\"\n", str, 0, 0 );
677 
678 	rc = -1;	/* pessimistic */
679 
680 	if (( str = nsldapi_strdup( str )) == NULL ) {
681 		return( rc );
682 	}
683 
684 	if ( (s = strchr( str, '=' )) == NULL ) {
685 		goto free_and_return;
686 	}
687 	value = s + 1;
688 	*s-- = '\0';
689 	filterop = *s;
690 	if ( filterop == '<' || filterop == '>' || filterop == '~' ||
691 	    filterop == ':' ) {
692 		*s = '\0';
693 	}
694 
695 	if ( ! is_valid_attr( str ) ) {
696 		goto free_and_return;
697 	}
698 
699 	switch ( filterop ) {
700 	case '<':
701 		ftype = LDAP_FILTER_LE;
702 		break;
703 	case '>':
704 		ftype = LDAP_FILTER_GE;
705 		break;
706 	case '~':
707 		ftype = LDAP_FILTER_APPROX;
708 		break;
709 	case ':':	/* extended filter - v3 only */
710 		/*
711 		 * extended filter looks like this:
712 		 *
713 		 *	[type][':dn'][':'oid]':='value
714 		 *
715 		 * where one of type or :oid is required.
716 		 *
717 		 */
718 		ftype = LDAP_FILTER_EXTENDED;
719 		s2 = s3 = NULL;
720 		if ( (s2 = strrchr( str, ':' )) == NULL ) {
721 			goto free_and_return;
722 		}
723 		if ( strcasecmp( s2, ":dn" ) == 0 ) {
724 			oid = NULL;
725 			dnattr = 1;
726 			*s2 = '\0';
727 		} else {
728 			oid = s2 + 1;
729 			dnattr = 0;
730 			*s2 = '\0';
731 			if ( (s3 = strrchr( str, ':' )) != NULL ) {
732 				if ( strcasecmp( s3, ":dn" ) == 0 ) {
733 					dnattr = 1;
734 				} else {
735 					goto free_and_return;
736 				}
737 				*s3 = '\0';
738 			}
739 		}
740 		if ( (rc = ber_printf( ber, "t{", ftype )) == -1 ) {
741 			goto free_and_return;
742 		}
743 		if ( oid != NULL ) {
744 			if ( (rc = ber_printf( ber, "ts", LDAP_TAG_MRA_OID,
745 			    oid )) == -1 ) {
746 				goto free_and_return;
747 			}
748 		}
749 		if ( *str != '\0' ) {
750 			if ( (rc = ber_printf( ber, "ts",
751 			    LDAP_TAG_MRA_TYPE, str )) == -1 ) {
752 				goto free_and_return;
753 			}
754 		}
755 		if (( len = unescape_filterval( value )) < 0 ||
756 		    ( rc = ber_printf( ber, "totb}", LDAP_TAG_MRA_VALUE,
757 		    value, len, LDAP_TAG_MRA_DNATTRS, dnattr )) == -1 ) {
758 			goto free_and_return;
759 		}
760 		rc = 0;
761 		goto free_and_return;
762 		/* break; */
763 	default:
764 		if ( find_star( value ) == NULL ) {
765 			ftype = LDAP_FILTER_EQUALITY;
766 		} else if ( strcmp( value, "*" ) == 0 ) {
767 			ftype = LDAP_FILTER_PRESENT;
768 		} else {
769 			rc = put_substring_filter( ber, str, value );
770 			goto free_and_return;
771 		}
772 		break;
773 	}
774 
775 	if ( ftype == LDAP_FILTER_PRESENT ) {
776 		rc = ber_printf( ber, "ts", ftype, str );
777 	} else if (( len = unescape_filterval( value )) >= 0 ) {
778 		rc = ber_printf( ber, "t{so}", ftype, str, value, len );
779 	}
780 	if ( rc != -1 ) {
781 		rc = 0;
782 	}
783 
784 free_and_return:
785 	NSLDAPI_FREE( str );
786 	return( rc );
787 }
788 
789 
790 /*
791  * Undo in place both LDAPv2 (RFC-1960) and LDAPv3 (hexadecimal) escape
792  * sequences within the null-terminated string 'val'.  The resulting value
793  * may contain null characters.
794  *
795  * If 'val' contains invalid escape sequences we return -1.
796  * Otherwise the length of the unescaped value is returned.
797  */
798 static int
799 unescape_filterval( char *val )
800 {
801 	int	escape, firstdigit, ival;
802 	char	*s, *d;
803 
804 	escape = 0;
805 	for ( s = d = val; *s; s++ ) {
806 		if ( escape ) {
807 			/*
808 			 * need to leave escaped comma as-is, i.e.
809 			 * val="CN=Last\, First,OU=..."
810 			 */
811 			if (*s == ',') {
812 				*d++ = '\\';
813 				*d++ = *s;
814 				escape = 0;
815 				continue;
816 			}
817 			/*
818 			 * first try LDAPv3 escape (hexadecimal) sequence
819 			 */
820 			if (( ival = hexchar2int( *s )) < 0 ) {
821 				if ( firstdigit ) {
822 					/*
823 					 * LDAPv2 (RFC1960) escape sequence
824 					 */
825 					*d++ = *s;
826 					escape = 0;
827 				} else {
828 					return(-1);
829 				}
830 			}
831 			if ( firstdigit ) {
832 			    *d = ( ival<<4 );
833 			    firstdigit = 0;
834 			} else {
835 			    *d++ |= ival;
836 			    escape = 0;
837 			}
838 
839 		} else if ( *s != '\\' ) {
840 			*d++ = *s;
841 			escape = 0;
842 
843 		} else {
844 			escape = 1;
845 			firstdigit = 1;
846 		}
847 	}
848 
849 	return( d - val );
850 }
851 
852 
853 /*
854  * convert character 'c' that represents a hexadecimal digit to an integer.
855  * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned.
856  * otherwise the converted value is returned.
857  */
858 static int
859 hexchar2int( char c )
860 {
861     if ( c >= '0' && c <= '9' ) {
862 	return( c - '0' );
863     }
864     if ( c >= 'A' && c <= 'F' ) {
865 	return( c - 'A' + 10 );
866     }
867     if ( c >= 'a' && c <= 'f' ) {
868 	return( c - 'a' + 10 );
869     }
870     return( -1 );
871 }
872 
873 static int
874 put_substring_filter( BerElement *ber, char *type, char *val )
875 {
876 	char		*nextstar, gotstar = 0;
877 	ber_uint_t	ftype;
878 	int		len;
879 
880 	LDAPDebug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n", type,
881 	    val, 0 );
882 
883 	if ( ber_printf( ber, "t{s{", LDAP_FILTER_SUBSTRINGS, type ) == -1 ) {
884 		return( -1 );
885 	}
886 
887 	for ( ; val != NULL; val = nextstar ) {
888 		if ( (nextstar = find_star( val )) != NULL ) {
889 			*nextstar++ = '\0';
890 		}
891 
892 		if ( gotstar == 0 ) {
893 			ftype = LDAP_SUBSTRING_INITIAL;
894 		} else if ( nextstar == NULL ) {
895 			ftype = LDAP_SUBSTRING_FINAL;
896 		} else {
897 			ftype = LDAP_SUBSTRING_ANY;
898 		}
899 		if ( *val != '\0' ) {
900 			if (( len = unescape_filterval( val )) < 0 ||
901 			    ber_printf( ber, "to", ftype, val, len ) == -1 ) {
902 				return( -1 );
903 			}
904 		}
905 
906 		gotstar = 1;
907 	}
908 
909 	if ( ber_printf( ber, "}}" ) == -1 ) {
910 		return( -1 );
911 	}
912 
913 	return( 0 );
914 }
915 
916 int
917 LDAP_CALL
918 ldap_search_st(
919     LDAP		*ld,
920     const char 		*base,
921     int 		scope,
922     const char 		*filter,
923     char 		**attrs,
924     int 		attrsonly,
925     struct timeval	*timeout,
926     LDAPMessage 	**res
927 )
928 {
929 	return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
930 	    NULL, NULL, timeout, -1, -1, res ));
931 }
932 
933 int
934 LDAP_CALL
935 ldap_search_s(
936     LDAP	*ld,
937     const char 	*base,
938     int 	scope,
939     const char 	*filter,
940     char 	**attrs,
941     int		attrsonly,
942     LDAPMessage	**res
943 )
944 {
945 	return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
946 	    NULL, NULL, NULL, -1, -1, res ));
947 }
948 
949 int LDAP_CALL
950 ldap_search_ext_s(
951     LDAP		*ld,
952     const char 		*base,
953     int 		scope,
954     const char 		*filter,
955     char 		**attrs,
956     int			attrsonly,
957     LDAPControl		**serverctrls,
958     LDAPControl		**clientctrls,
959     struct timeval	*timeoutp,
960     int			sizelimit,
961     LDAPMessage		**res
962 )
963 {
964 	return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly,
965 	    serverctrls, clientctrls, timeoutp,
966 	    nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, res ));
967 }
968 
969 
970 static int
971 nsldapi_search_s(
972     LDAP		*ld,
973     const char 		*base,
974     int 		scope,
975     const char 		*filter,
976     char 		**attrs,
977     int			attrsonly,
978     LDAPControl		**serverctrls,
979     LDAPControl		**clientctrls,
980     struct timeval	*localtimeoutp,
981     int			timelimit,	/* -1 means use ld->ld_timelimit */
982     int			sizelimit,	/* -1 means use ld->ld_sizelimit */
983     LDAPMessage		**res
984 )
985 {
986 	int	err, msgid;
987 
988 	/*
989 	 * It is an error to pass in a zero'd timeval.
990 	 */
991 	if ( localtimeoutp != NULL && localtimeoutp->tv_sec == 0 &&
992 	    localtimeoutp->tv_usec == 0 ) {
993 		if ( ld != NULL ) {
994 			LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL );
995 		}
996 		if ( res != NULL ) {
997 			*res = NULL;
998 		}
999                 return( LDAP_PARAM_ERROR );
1000         }
1001 
1002 	if (( err = nsldapi_search( ld, base, scope, filter, attrs, attrsonly,
1003 	    serverctrls, clientctrls, timelimit, sizelimit, &msgid ))
1004 	    != LDAP_SUCCESS ) {
1005 		if ( res != NULL ) {
1006 			*res = NULL;
1007 		}
1008 		return( err );
1009 	}
1010 
1011 	if ( ldap_result( ld, msgid, 1, localtimeoutp, res ) == -1 ) {
1012 		/*
1013 		 * Error.  ldap_result() sets *res to NULL for us.
1014 		 */
1015 		return( LDAP_GET_LDERRNO( ld, NULL, NULL ) );
1016 	}
1017 
1018 	if ( LDAP_GET_LDERRNO( ld, NULL, NULL ) == LDAP_TIMEOUT ) {
1019 		(void) ldap_abandon( ld, msgid );
1020 		err = LDAP_TIMEOUT;
1021 		LDAP_SET_LDERRNO( ld, err, NULL, NULL );
1022 		if ( res != NULL ) {
1023 			*res = NULL;
1024 		}
1025 		return( err );
1026 	}
1027 
1028 	return( ldap_result2error( ld, *res, 0 ) );
1029 }
1030