/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998-1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * Copyright (c) 1990 Regents of the University of Michigan. * All rights reserved. */ /* * search.c */ #if 0 #ifndef lint static char copyright[] = "@(#) Copyright (c) 1990 Regents of the University of Michigan.\nAll rights reserved.\n"; #endif #endif #include "ldap-int.h" static int nsldapi_timeval2ldaplimit( struct timeval *timeoutp, int defaultvalue ); static int nsldapi_search( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, int timelimit, int sizelimit, int *msgidp ); static char *find_right_paren( char *s ); static char *put_complex_filter( BerElement *ber, char *str, unsigned long tag, int not ); static int put_filter( BerElement *ber, char *str ); static int unescape_filterval( char *str ); static int hexchar2int( char c ); static int is_valid_attr( char *a ); static int put_simple_filter( BerElement *ber, char *str ); static int put_substring_filter( BerElement *ber, char *type, char *str ); static int put_filter_list( BerElement *ber, char *str ); static int nsldapi_search_s( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, struct timeval *localtimeoutp, int timelimit, int sizelimit, LDAPMessage **res ); /* * ldap_search - initiate an ldap search operation. Parameters: * * ld LDAP descriptor * base DN of the base object * scope the search scope - one of LDAP_SCOPE_BASE, * LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE * filter a string containing the search filter * (e.g., "(|(cn=bob)(sn=bob))") * attrs list of attribute types to return for matches * attrsonly 1 => attributes only 0 => attributes and values * * Example: * char *attrs[] = { "mail", "title", 0 }; * msgid = ldap_search( ld, "c=us@o=UM", LDAP_SCOPE_SUBTREE, "cn~=bob", * attrs, attrsonly ); */ int LDAP_CALL ldap_search( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly ) { int msgid; LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search\n", 0, 0, 0 ); if ( ldap_search_ext( ld, base, scope, filter, attrs, attrsonly, NULL, NULL, NULL, -1, &msgid ) == LDAP_SUCCESS ) { return( msgid ); } else { return( -1 ); /* error is in ld handle */ } } /* * LDAPv3 extended search. * Returns an LDAP error code. */ int LDAP_CALL ldap_search_ext( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, struct timeval *timeoutp, /* NULL means use ld->ld_timelimit */ int sizelimit, int *msgidp ) { /* * It is an error to pass in a zero'd timeval. */ if ( timeoutp != NULL && timeoutp->tv_sec == 0 && timeoutp->tv_usec == 0 ) { if ( ld != NULL ) { LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL ); } return( LDAP_PARAM_ERROR ); } return( nsldapi_search( ld, base, scope, filter, attrs, attrsonly, serverctrls, clientctrls, nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, msgidp )); } /* * Like ldap_search_ext() except an integer timelimit is passed instead of * using the overloaded struct timeval *timeoutp. */ static int nsldapi_search( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, int timelimit, /* -1 means use ld->ld_timelimit */ int sizelimit, /* -1 means use ld->ld_sizelimit */ int *msgidp ) { BerElement *ber; int rc, rc_key; unsigned long key; /* XXXmcs: memcache */ LDAPDebug( LDAP_DEBUG_TRACE, "ldap_search_ext\n", 0, 0, 0 ); if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) { return( LDAP_PARAM_ERROR ); } if ( base == NULL ) { base = ""; } if ( filter == NULL ) { filter = "(objectclass=*)"; } if ( msgidp == NULL || ( scope != LDAP_SCOPE_BASE && scope != LDAP_SCOPE_ONELEVEL && scope != LDAP_SCOPE_SUBTREE ) || ( sizelimit < -1 )) { LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL ); return( LDAP_PARAM_ERROR ); } LDAP_MUTEX_LOCK( ld, LDAP_MSGID_LOCK ); *msgidp = ++ld->ld_msgid; LDAP_MUTEX_UNLOCK( ld, LDAP_MSGID_LOCK ); /* * XXXmcs: should use cache function pointers to hook in memcache */ if ( ld->ld_memcache == NULL ) { rc_key = LDAP_NOT_SUPPORTED; } else if (( rc_key = ldap_memcache_createkey( ld, base, scope, filter, attrs, attrsonly, serverctrls, clientctrls, &key)) == LDAP_SUCCESS && ldap_memcache_result( ld, *msgidp, key ) == LDAP_SUCCESS ) { return LDAP_SUCCESS; } /* check the cache */ if ( ld->ld_cache_on && ld->ld_cache_search != NULL ) { LDAP_MUTEX_LOCK( ld, LDAP_CACHE_LOCK ); if ( (rc = (ld->ld_cache_search)( ld, *msgidp, LDAP_REQ_SEARCH, base, scope, filter, attrs, attrsonly )) != 0 ) { *msgidp = rc; LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK ); return( LDAP_SUCCESS ); } LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK ); } /* caching off or did not find it in the cache - check the net */ if (( rc = nsldapi_build_search_req( ld, base, scope, filter, attrs, attrsonly, serverctrls, clientctrls, timelimit, sizelimit, *msgidp, &ber )) != LDAP_SUCCESS ) { return( rc ); } /* send the message */ rc = nsldapi_send_initial_request( ld, *msgidp, LDAP_REQ_SEARCH, (char *) base, ber ); /* * XXXmcs: should use cache function pointers to hook in memcache */ if ( (rc_key == LDAP_SUCCESS) && (rc >= 0) ) { ldap_memcache_new( ld, rc, key, base ); } *msgidp = rc; return( rc < 0 ? LDAP_GET_LDERRNO( ld, NULL, NULL ) : LDAP_SUCCESS ); } /* * Convert a non-NULL timeoutp to a value in seconds that is appropriate to * send in an LDAP search request. If timeoutp is NULL, return defaultvalue. */ static int nsldapi_timeval2ldaplimit( struct timeval *timeoutp, int defaultvalue ) { int timelimit; if ( NULL == timeoutp ) { timelimit = defaultvalue; } else if ( timeoutp->tv_sec > 0 ) { timelimit = timeoutp->tv_sec; } else if ( timeoutp->tv_usec > 0 ) { timelimit = 1; /* minimum we can express in LDAP */ } else { /* * both tv_sec and tv_usec are less than one (zero?) so * to maintain compatiblity with our "zero means no limit" * convention we pass no limit to the server. */ timelimit = 0; /* no limit */ } return( timelimit ); } /* returns an LDAP error code and also sets it in ld */ int nsldapi_build_search_req( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, /* not used for anything yet */ int timelimit, /* if -1, ld->ld_timelimit is used */ int sizelimit, /* if -1, ld->ld_sizelimit is used */ int msgid, BerElement **berp ) { BerElement *ber; int err; char *fdup; /* * Create the search request. It looks like this: * SearchRequest := [APPLICATION 3] SEQUENCE { * baseObject DistinguishedName, * scope ENUMERATED { * baseObject (0), * singleLevel (1), * wholeSubtree (2) * }, * derefAliases ENUMERATED { * neverDerefaliases (0), * derefInSearching (1), * derefFindingBaseObj (2), * alwaysDerefAliases (3) * }, * sizelimit INTEGER (0 .. 65535), * timelimit INTEGER (0 .. 65535), * attrsOnly BOOLEAN, * filter Filter, * attributes SEQUENCE OF AttributeType * } * wrapped in an ldap message. */ /* create a message to send */ if (( err = nsldapi_alloc_ber_with_options( ld, &ber )) != LDAP_SUCCESS ) { return( err ); } if ( base == NULL ) { base = ""; } if ( sizelimit == -1 ) { sizelimit = ld->ld_sizelimit; } if ( timelimit == -1 ) { timelimit = ld->ld_timelimit; } #ifdef CLDAP if ( ld->ld_sbp->sb_naddr > 0 ) { err = ber_printf( ber, "{ist{seeiib", msgid, ld->ld_cldapdn, LDAP_REQ_SEARCH, base, scope, ld->ld_deref, sizelimit, timelimit, attrsonly ); } else { #endif /* CLDAP */ err = ber_printf( ber, "{it{seeiib", msgid, LDAP_REQ_SEARCH, base, scope, ld->ld_deref, sizelimit, timelimit, attrsonly ); #ifdef CLDAP } #endif /* CLDAP */ if ( err == -1 ) { LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL ); ber_free( ber, 1 ); return( LDAP_ENCODING_ERROR ); } fdup = nsldapi_strdup( filter ); err = put_filter( ber, fdup ); NSLDAPI_FREE( fdup ); if ( err == -1 ) { LDAP_SET_LDERRNO( ld, LDAP_FILTER_ERROR, NULL, NULL ); ber_free( ber, 1 ); return( LDAP_FILTER_ERROR ); } if ( ber_printf( ber, "{v}}", attrs ) == -1 ) { LDAP_SET_LDERRNO( ld, LDAP_ENCODING_ERROR, NULL, NULL ); ber_free( ber, 1 ); return( LDAP_ENCODING_ERROR ); } if ( (err = nsldapi_put_controls( ld, serverctrls, 1, ber )) != LDAP_SUCCESS ) { ber_free( ber, 1 ); return( err ); } *berp = ber; return( LDAP_SUCCESS ); } static char * find_right_paren( char *s ) { int balance, escape; balance = 1; escape = 0; while ( *s && balance ) { if ( escape == 0 ) { if ( *s == '(' ) balance++; else if ( *s == ')' ) balance--; } if ( *s == '\\' && ! escape ) escape = 1; else escape = 0; if ( balance ) s++; } return( *s ? s : NULL ); } static char * put_complex_filter( BerElement *ber, char *str, unsigned long tag, int not ) { char *next; /* * We have (x(filter)...) with str sitting on * the x. We have to find the paren matching * the one before the x and put the intervening * filters by calling put_filter_list(). */ /* put explicit tag */ if ( ber_printf( ber, "t{", tag ) == -1 ) return( NULL ); str++; if ( (next = find_right_paren( str )) == NULL ) return( NULL ); *next = '\0'; if ( put_filter_list( ber, str ) == -1 ) return( NULL ); *next++ = ')'; /* flush explicit tagged thang */ if ( ber_printf( ber, "}" ) == -1 ) return( NULL ); return( next ); } static int put_filter( BerElement *ber, char *str ) { char *next; int parens, balance, escape; /* * A Filter looks like this: * Filter ::= CHOICE { * and [0] SET OF Filter, * or [1] SET OF Filter, * not [2] Filter, * equalityMatch [3] AttributeValueAssertion, * substrings [4] SubstringFilter, * greaterOrEqual [5] AttributeValueAssertion, * lessOrEqual [6] AttributeValueAssertion, * present [7] AttributeType,, * approxMatch [8] AttributeValueAssertion * } * * SubstringFilter ::= SEQUENCE { * type AttributeType, * SEQUENCE OF CHOICE { * initial [0] IA5String, * any [1] IA5String, * final [2] IA5String * } * } * Note: tags in a choice are always explicit */ LDAPDebug( LDAP_DEBUG_TRACE, "put_filter \"%s\"\n", str, 0, 0 ); parens = 0; while ( *str ) { switch ( *str ) { case '(': str++; parens++; switch ( *str ) { case '&': LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: AND\n", 0, 0, 0 ); if ( (str = put_complex_filter( ber, str, LDAP_FILTER_AND, 0 )) == NULL ) return( -1 ); parens--; break; case '|': LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: OR\n", 0, 0, 0 ); if ( (str = put_complex_filter( ber, str, LDAP_FILTER_OR, 0 )) == NULL ) return( -1 ); parens--; break; case '!': LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: NOT\n", 0, 0, 0 ); if ( (str = put_complex_filter( ber, str, LDAP_FILTER_NOT, 1 )) == NULL ) return( -1 ); parens--; break; default: LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: simple\n", 0, 0, 0 ); balance = 1; escape = 0; next = str; while ( *next && balance ) { if ( escape == 0 ) { if ( *next == '(' ) balance++; else if ( *next == ')' ) balance--; } if ( *next == '\\' && ! escape ) escape = 1; else escape = 0; if ( balance ) next++; } if ( balance != 0 ) return( -1 ); *next = '\0'; if ( put_simple_filter( ber, str ) == -1 ) { return( -1 ); } *next++ = ')'; str = next; parens--; break; } break; case ')': LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: end\n", 0, 0, 0 ); if ( ber_printf( ber, "]" ) == -1 ) return( -1 ); str++; parens--; break; case ' ': str++; break; default: /* assume it's a simple type=value filter */ LDAPDebug( LDAP_DEBUG_TRACE, "put_filter: default\n", 0, 0, 0 ); next = strchr( str, '\0' ); if ( put_simple_filter( ber, str ) == -1 ) { return( -1 ); } str = next; break; } } return( parens ? -1 : 0 ); } /* * Put a list of filters like this "(filter1)(filter2)..." */ static int put_filter_list( BerElement *ber, char *str ) { char *next; char save; LDAPDebug( LDAP_DEBUG_TRACE, "put_filter_list \"%s\"\n", str, 0, 0 ); while ( *str ) { while ( *str && isspace( *str ) ) str++; if ( *str == '\0' ) break; if ( (next = find_right_paren( str + 1 )) == NULL ) return( -1 ); save = *++next; /* now we have "(filter)" with str pointing to it */ *next = '\0'; if ( put_filter( ber, str ) == -1 ) return( -1 ); *next = save; str = next; } return( 0 ); } /* * is_valid_attr - returns 1 if a is a syntactically valid left-hand side * of a filter expression, 0 otherwise. A valid string may contain only * letters, numbers, hyphens, semi-colons, colons and periods. examples: * cn * cn;lang-fr * 1.2.3.4;binary;dynamic * mail;dynamic * cn:dn:1.2.3.4 * * For compatibility with older servers, we also allow underscores in * attribute types, even through they are not allowed by the LDAPv3 RFCs. */ static int is_valid_attr( char *a ) { for ( ; *a; a++ ) { if ( !isascii( *a ) ) { return( 0 ); } else if ( !isalnum( *a ) ) { switch ( *a ) { case '-': case '.': case ';': case ':': case '_': break; /* valid */ default: return( 0 ); } } } return( 1 ); } static char * find_star( char *s ) { for ( ; *s; ++s ) { switch ( *s ) { case '*': return s; case '\\': ++s; if ( hexchar2int(s[0]) >= 0 && hexchar2int(s[1]) >= 0 ) ++s; default: break; } } return NULL; } static int put_simple_filter( BerElement *ber, char *str ) { char *s, *s2, *s3, filterop; char *value; unsigned long ftype; int rc, len; char *oid; /* for v3 extended filter */ int dnattr; /* for v3 extended filter */ LDAPDebug( LDAP_DEBUG_TRACE, "put_simple_filter \"%s\"\n", str, 0, 0 ); rc = -1; /* pessimistic */ if (( str = nsldapi_strdup( str )) == NULL ) { return( rc ); } if ( (s = strchr( str, '=' )) == NULL ) { goto free_and_return; } value = s + 1; *s-- = '\0'; filterop = *s; if ( filterop == '<' || filterop == '>' || filterop == '~' || filterop == ':' ) { *s = '\0'; } if ( ! is_valid_attr( str ) ) { goto free_and_return; } switch ( filterop ) { case '<': ftype = LDAP_FILTER_LE; break; case '>': ftype = LDAP_FILTER_GE; break; case '~': ftype = LDAP_FILTER_APPROX; break; case ':': /* extended filter - v3 only */ /* * extended filter looks like this: * * [type][':dn'][':'oid]':='value * * where one of type or :oid is required. * */ ftype = LDAP_FILTER_EXTENDED; s2 = s3 = NULL; if ( (s2 = strrchr( str, ':' )) == NULL ) { goto free_and_return; } if ( strcasecmp( s2, ":dn" ) == 0 ) { oid = NULL; dnattr = 1; *s2 = '\0'; } else { oid = s2 + 1; dnattr = 0; *s2 = '\0'; if ( (s3 = strrchr( str, ':' )) != NULL ) { if ( strcasecmp( s3, ":dn" ) == 0 ) { dnattr = 1; } else { goto free_and_return; } *s3 = '\0'; } } if ( (rc = ber_printf( ber, "t{", ftype )) == -1 ) { goto free_and_return; } if ( oid != NULL ) { if ( (rc = ber_printf( ber, "ts", LDAP_TAG_MRA_OID, oid )) == -1 ) { goto free_and_return; } } if ( *str != '\0' ) { if ( (rc = ber_printf( ber, "ts", LDAP_TAG_MRA_TYPE, str )) == -1 ) { goto free_and_return; } } if (( len = unescape_filterval( value )) < 0 || ( rc = ber_printf( ber, "totb}", LDAP_TAG_MRA_VALUE, value, len, LDAP_TAG_MRA_DNATTRS, dnattr )) == -1 ) { goto free_and_return; } rc = 0; goto free_and_return; break; default: if ( find_star( value ) == NULL ) { ftype = LDAP_FILTER_EQUALITY; } else if ( strcmp( value, "*" ) == 0 ) { ftype = LDAP_FILTER_PRESENT; } else { rc = put_substring_filter( ber, str, value ); goto free_and_return; } break; } if ( ftype == LDAP_FILTER_PRESENT ) { rc = ber_printf( ber, "ts", ftype, str ); } else if (( len = unescape_filterval( value )) >= 0 ) { rc = ber_printf( ber, "t{so}", ftype, str, value, len ); } if ( rc != -1 ) { rc = 0; } free_and_return: NSLDAPI_FREE( str ); return( rc ); } /* * Undo in place both LDAPv2 (RFC-1960) and LDAPv3 (hexadecimal) escape * sequences within the null-terminated string 'val'. The resulting value * may contain null characters. * * If 'val' contains invalid escape sequences we return -1. * Otherwise the length of the unescaped value is returned. */ static int unescape_filterval( char *val ) { int escape, firstdigit, ival; char *s, *d; escape = 0; for ( s = d = val; *s; s++ ) { if ( escape ) { /* * first try LDAPv3 escape (hexadecimal) sequence */ if (( ival = hexchar2int( *s )) < 0 ) { if ( firstdigit ) { /* * LDAPv2 (RFC1960) escape sequence */ *d++ = *s; escape = 0; } else { return(-1); } } if ( firstdigit ) { *d = ( ival<<4 ); firstdigit = 0; } else { *d++ |= ival; escape = 0; } } else if ( *s != '\\' ) { *d++ = *s; escape = 0; } else { escape = 1; firstdigit = 1; } } return( d - val ); } /* * convert character 'c' that represents a hexadecimal digit to an integer. * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned. * otherwise the converted value is returned. */ static int hexchar2int( char c ) { if ( c >= '0' && c <= '9' ) { return( c - '0' ); } if ( c >= 'A' && c <= 'F' ) { return( c - 'A' + 10 ); } if ( c >= 'a' && c <= 'f' ) { return( c - 'a' + 10 ); } return( -1 ); } static int put_substring_filter( BerElement *ber, char *type, char *val ) { char *nextstar, gotstar = 0; unsigned long ftype; int len; LDAPDebug( LDAP_DEBUG_TRACE, "put_substring_filter \"%s=%s\"\n", type, val, 0 ); if ( ber_printf( ber, "t{s{", LDAP_FILTER_SUBSTRINGS, type ) == -1 ) { return( -1 ); } for ( ; val != NULL; val = nextstar ) { if ( (nextstar = find_star( val )) != NULL ) { *nextstar++ = '\0'; } if ( gotstar == 0 ) { ftype = LDAP_SUBSTRING_INITIAL; } else if ( nextstar == NULL ) { ftype = LDAP_SUBSTRING_FINAL; } else { ftype = LDAP_SUBSTRING_ANY; } if ( *val != '\0' ) { if (( len = unescape_filterval( val )) < 0 || ber_printf( ber, "to", ftype, val, len ) == -1 ) { return( -1 ); } } gotstar = 1; } if ( ber_printf( ber, "}}" ) == -1 ) { return( -1 ); } return( 0 ); } int LDAP_CALL ldap_search_st( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, struct timeval *timeout, LDAPMessage **res ) { return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly, NULL, NULL, timeout, -1, -1, res )); } int LDAP_CALL ldap_search_s( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPMessage **res ) { return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly, NULL, NULL, NULL, -1, -1, res )); } int LDAP_CALL ldap_search_ext_s( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, struct timeval *timeoutp, int sizelimit, LDAPMessage **res ) { return( nsldapi_search_s( ld, base, scope, filter, attrs, attrsonly, serverctrls, clientctrls, timeoutp, nsldapi_timeval2ldaplimit( timeoutp, -1 ), sizelimit, res )); } static int nsldapi_search_s( LDAP *ld, const char *base, int scope, const char *filter, char **attrs, int attrsonly, LDAPControl **serverctrls, LDAPControl **clientctrls, struct timeval *localtimeoutp, int timelimit, /* -1 means use ld->ld_timelimit */ int sizelimit, /* -1 means use ld->ld_sizelimit */ LDAPMessage **res ) { int err, msgid; /* * It is an error to pass in a zero'd timeval. */ if ( localtimeoutp != NULL && localtimeoutp->tv_sec == 0 && localtimeoutp->tv_usec == 0 ) { if ( ld != NULL ) { LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL ); } if ( res != NULL ) { *res = NULL; } return( LDAP_PARAM_ERROR ); } if (( err = nsldapi_search( ld, base, scope, filter, attrs, attrsonly, serverctrls, clientctrls, timelimit, sizelimit, &msgid )) != LDAP_SUCCESS ) { if ( res != NULL ) { *res = NULL; } return( err ); } if ( ldap_result( ld, msgid, 1, localtimeoutp, res ) == -1 ) { /* * Error. ldap_result() sets *res to NULL for us. */ return( LDAP_GET_LDERRNO( ld, NULL, NULL ) ); } if ( LDAP_GET_LDERRNO( ld, NULL, NULL ) == LDAP_TIMEOUT ) { (void) ldap_abandon( ld, msgid ); err = LDAP_TIMEOUT; LDAP_SET_LDERRNO( ld, err, NULL, NULL ); if ( res != NULL ) { *res = NULL; } return( err ); } return( ldap_result2error( ld, *res, 0 ) ); }