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
40static 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
46static int nsldapi_timeval2ldaplimit( struct timeval *timeoutp,
47	int defaultvalue );
48static 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 );
52static char *find_right_paren( char *s );
53static char *put_complex_filter( BerElement *ber, char *str,
54	ber_tag_t tag, int not );
55static int unescape_filterval( char *str );
56static int hexchar2int( char c );
57static int is_valid_attr( char *a );
58static int put_simple_filter( BerElement *ber, char *str );
59static int put_substring_filter( BerElement *ber, char *type,
60	char *str );
61static int put_filter_list( BerElement *ber, char *str );
62static 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 */
85int
86LDAP_CALL
87ldap_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 */
113int
114LDAP_CALL
115ldap_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 */
150static int
151nsldapi_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 */
243static int
244nsldapi_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 */
268int
269nsldapi_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
381static char *
382find_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
406static char *
407put_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
443int
444ldap_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
584static int
585put_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 */
628static int
629is_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
651static char *
652find_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
666static int
667put_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
784free_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 */
798static int
799unescape_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 */
858static int
859hexchar2int( 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
873static int
874put_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
916int
917LDAP_CALL
918ldap_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
933int
934LDAP_CALL
935ldap_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
949int LDAP_CALL
950ldap_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
970static int
971nsldapi_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