/* * Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* * Copyright (c) 1990 Regents of the University of Michigan. * All rights reserved. */ /* * result.c - wait for an ldap result */ #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" #ifdef _SOLARIS_SDK /* high resolution timer usage */ #include #endif static int check_response_queue( LDAP *ld, int msgid, int all, int do_abandon_check, LDAPMessage **result ); static int ldap_abandoned( LDAP *ld, int msgid ); static int ldap_mark_abandoned( LDAP *ld, int msgid ); static int wait4msg( LDAP *ld, int msgid, int all, int unlock_permitted, struct timeval *timeout, LDAPMessage **result ); static int read1msg( LDAP *ld, int msgid, int all, Sockbuf *sb, LDAPConn *lc, LDAPMessage **result ); static void check_for_refs( LDAP *ld, LDAPRequest *lr, BerElement *ber, int ldapversion, int *totalcountp, int *chasingcountp ); static int build_result_ber( LDAP *ld, BerElement **berp, LDAPRequest *lr ); static void merge_error_info( LDAP *ld, LDAPRequest *parentr, LDAPRequest *lr ); #if defined( CLDAP ) static int cldap_select1( LDAP *ld, struct timeval *timeout ); #endif static void link_pend( LDAP *ld, LDAPPend *lp ); #if 0 /* these functions are no longer used */ static void unlink_pend( LDAP *ld, LDAPPend *lp ); static int unlink_msg( LDAP *ld, int msgid, int all ); #endif /* 0 */ /* * ldap_result - wait for an ldap result response to a message from the * ldap server. If msgid is -1, any message will be accepted, otherwise * ldap_result will wait for a response with msgid. If all is 0 the * first message with id msgid will be accepted, otherwise, ldap_result * will wait for all responses with id msgid and then return a pointer to * the entire list of messages. This is only useful for search responses, * which can be of two message types (zero or more entries, followed by an * ldap result). The type of the first message received is returned. * When waiting, any messages that have been abandoned are discarded. * * Example: * ldap_result( s, msgid, all, timeout, result ) */ int LDAP_CALL ldap_result( LDAP *ld, int msgid, int all, struct timeval *timeout, LDAPMessage **result ) { int rc; LDAPDebug( LDAP_DEBUG_TRACE, "ldap_result\n", 0, 0, 0 ); if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) { return( -1 ); /* punt */ } LDAP_MUTEX_LOCK( ld, LDAP_RESULT_LOCK ); rc = nsldapi_result_nolock(ld, msgid, all, 1, timeout, result); LDAP_MUTEX_UNLOCK( ld, LDAP_RESULT_LOCK ); return( rc ); } int nsldapi_result_nolock( LDAP *ld, int msgid, int all, int unlock_permitted, struct timeval *timeout, LDAPMessage **result ) { int rc; LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_result_nolock (msgid=%d, all=%d)\n", msgid, all, 0 ); /* * First, look through the list of responses we have received on * this association and see if the response we're interested in * is there. If it is, return it. If not, call wait4msg() to * wait until it arrives or timeout occurs. */ if ( result == NULL ) { LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, NULL ); return( -1 ); } if ( check_response_queue( ld, msgid, all, 1, result ) != 0 ) { LDAP_SET_LDERRNO( ld, LDAP_SUCCESS, NULL, NULL ); rc = (*result)->lm_msgtype; } else { rc = wait4msg( ld, msgid, all, unlock_permitted, timeout, result ); } /* * XXXmcs should use cache function pointers to hook in memcache */ if ( ld->ld_memcache != NULL && NSLDAPI_SEARCH_RELATED_RESULT( rc ) && !((*result)->lm_fromcache )) { ldap_memcache_append( ld, (*result)->lm_msgid, (all || NSLDAPI_IS_SEARCH_RESULT( rc )), *result ); } return( rc ); } /* * Look through the list of queued responses for a message that matches the * criteria in the msgid and all parameters. msgid == LDAP_RES_ANY matches * all ids. * * If an appropriate message is found, a non-zero value is returned and the * message is dequeued and assigned to *result. * * If not, *result is set to NULL and this function returns 0. */ static int check_response_queue( LDAP *ld, int msgid, int all, int do_abandon_check, LDAPMessage **result ) { LDAPMessage *lm, *lastlm, *nextlm; LDAPRequest *lr; LDAPDebug( LDAP_DEBUG_TRACE, "=> check_response_queue (msgid=%d, all=%d)\n", msgid, all, 0 ); *result = NULL; lastlm = NULL; LDAP_MUTEX_LOCK( ld, LDAP_RESP_LOCK ); for ( lm = ld->ld_responses; lm != NULL; lm = nextlm ) { nextlm = lm->lm_next; if ( do_abandon_check && ldap_abandoned( ld, lm->lm_msgid ) ) { ldap_mark_abandoned( ld, lm->lm_msgid ); if ( lastlm == NULL ) { ld->ld_responses = lm->lm_next; } else { lastlm->lm_next = nextlm; } ldap_msgfree( lm ); continue; } if ( msgid == LDAP_RES_ANY || lm->lm_msgid == msgid ) { LDAPMessage *tmp; if ( all == 0 || (lm->lm_msgtype != LDAP_RES_SEARCH_RESULT && lm->lm_msgtype != LDAP_RES_SEARCH_REFERENCE && lm->lm_msgtype != LDAP_RES_SEARCH_ENTRY) ) break; for ( tmp = lm; tmp != NULL; tmp = tmp->lm_chain ) { if ( tmp->lm_msgtype == LDAP_RES_SEARCH_RESULT ) break; } if ( tmp == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); LDAPDebug( LDAP_DEBUG_TRACE, "<= check_response_queue NOT FOUND\n", 0, 0, 0 ); return( 0 ); /* no message to return */ } break; } lastlm = lm; } /* * if we did not find a message OR if the one we found is a result for * a request that is still pending, return failure. */ if ( lm == NULL || (( lr = nsldapi_find_request_by_msgid( ld, lm->lm_msgid )) != NULL && lr->lr_outrefcnt > 0 )) { LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); LDAPDebug( LDAP_DEBUG_TRACE, "<= check_response_queue NOT FOUND\n", 0, 0, 0 ); return( 0 ); /* no message to return */ } if ( all == 0 ) { if ( lm->lm_chain == NULL ) { if ( lastlm == NULL ) { ld->ld_responses = lm->lm_next; } else { lastlm->lm_next = lm->lm_next; } } else { if ( lastlm == NULL ) { ld->ld_responses = lm->lm_chain; ld->ld_responses->lm_next = lm->lm_next; } else { lastlm->lm_next = lm->lm_chain; lastlm->lm_next->lm_next = lm->lm_next; } } } else { if ( lastlm == NULL ) { ld->ld_responses = lm->lm_next; } else { lastlm->lm_next = lm->lm_next; } } if ( all == 0 ) { lm->lm_chain = NULL; } lm->lm_next = NULL; LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); *result = lm; LDAPDebug( LDAP_DEBUG_TRACE, "<= check_response_queue returning msgid %d type %d\n", lm->lm_msgid, lm->lm_msgtype, 0 ); return( 1 ); /* a message was found and returned in *result */ } static int wait4msg( LDAP *ld, int msgid, int all, int unlock_permitted, struct timeval *timeout, LDAPMessage **result ) { int rc; struct timeval tv, *tvp; #ifdef _SOLARIS_SDK hrtime_t start_time = 0, tmp_time, tv_time; #else long start_time = 0, tmp_time; #endif LDAPConn *lc, *nextlc; LDAPRequest *lr; #ifdef LDAP_DEBUG if ( timeout == NULL ) { LDAPDebug( LDAP_DEBUG_TRACE, "wait4msg (infinite timeout)\n", 0, 0, 0 ); } else { LDAPDebug( LDAP_DEBUG_TRACE, "wait4msg (timeout %ld sec, %ld usec)\n", timeout->tv_sec, timeout->tv_usec, 0 ); } #endif /* LDAP_DEBUG */ /* check the cache */ if ( ld->ld_cache_on && ld->ld_cache_result != NULL ) { /* if ( unlock_permitted ) LDAP_MUTEX_UNLOCK( ld ); */ LDAP_MUTEX_LOCK( ld, LDAP_CACHE_LOCK ); rc = (ld->ld_cache_result)( ld, msgid, all, timeout, result ); LDAP_MUTEX_UNLOCK( ld, LDAP_CACHE_LOCK ); /* if ( unlock_permitted ) LDAP_MUTEX_LOCK( ld ); */ if ( rc != 0 ) { return( rc ); } if ( ld->ld_cache_strategy == LDAP_CACHE_LOCALDB ) { LDAP_SET_LDERRNO( ld, LDAP_TIMEOUT, NULL, NULL ); return( 0 ); /* timeout */ } } /* * if we are looking for a specific msgid, check to see if it is * associated with a dead connection and return an error if so. */ if ( msgid != LDAP_RES_ANY && msgid != LDAP_RES_UNSOLICITED ) { LDAP_MUTEX_LOCK( ld, LDAP_REQ_LOCK ); if (( lr = nsldapi_find_request_by_msgid( ld, msgid )) == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); LDAP_SET_LDERRNO( ld, LDAP_PARAM_ERROR, NULL, nsldapi_strdup( dgettext(TEXT_DOMAIN, "unknown message id") )); return( -1 ); /* could not find request for msgid */ } if ( lr->lr_conn != NULL && lr->lr_conn->lconn_status == LDAP_CONNST_DEAD ) { nsldapi_free_request( ld, lr, 1 ); LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); LDAP_SET_LDERRNO( ld, LDAP_SERVER_DOWN, NULL, NULL ); return( -1 ); /* connection dead */ } LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); } if ( timeout == NULL ) { tvp = NULL; } else { tv = *timeout; tvp = &tv; #ifdef _SOLARIS_SDK start_time = gethrtime(); tv_time = ((hrtime_t)tv.tv_sec * NANOSEC + (hrtime_t)tv.tv_usec * (NANOSEC / MICROSEC)); #else start_time = (long)time( NULL ); #endif } rc = -2; while ( rc == -2 ) { #ifdef LDAP_DEBUG if ( ldap_debug & LDAP_DEBUG_TRACE ) { nsldapi_dump_connection( ld, ld->ld_conns, 1 ); nsldapi_dump_requests_and_responses( ld ); } #endif /* LDAP_DEBUG */ LDAP_MUTEX_LOCK( ld, LDAP_CONN_LOCK ); LDAP_MUTEX_LOCK( ld, LDAP_REQ_LOCK ); for ( lc = ld->ld_conns; lc != NULL; lc = lc->lconn_next ) { if ( lc->lconn_sb->sb_ber.ber_ptr < lc->lconn_sb->sb_ber.ber_end ) { rc = read1msg( ld, msgid, all, lc->lconn_sb, lc, result ); break; } } LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); LDAP_MUTEX_UNLOCK( ld, LDAP_CONN_LOCK ); if ( lc == NULL ) { rc = nsldapi_iostatus_poll( ld, tvp ); #if defined( LDAP_DEBUG ) && !defined( macintosh ) && !defined( DOS ) if ( rc == -1 ) { LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_iostatus_poll returned -1: errno %d\n", LDAP_GET_ERRNO( ld ), 0, 0 ); } #endif #if !defined( macintosh ) && !defined( DOS ) if ( rc == 0 || ( rc == -1 && (( ld->ld_options & LDAP_BITOPT_RESTART ) == 0 || LDAP_GET_ERRNO( ld ) != EINTR ))) { #else if ( rc == -1 || rc == 0 ) { #endif LDAP_SET_LDERRNO( ld, (rc == -1 ? LDAP_SERVER_DOWN : LDAP_TIMEOUT), NULL, NULL ); if ( rc == -1 ) { LDAP_MUTEX_LOCK( ld, LDAP_REQ_LOCK ); nsldapi_connection_lost_nolock( ld, NULL ); LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); } return( rc ); } if ( rc == -1 ) { rc = -2; /* select interrupted: loop */ } else { rc = -2; LDAP_MUTEX_LOCK( ld, LDAP_CONN_LOCK ); LDAP_MUTEX_LOCK( ld, LDAP_REQ_LOCK ); for ( lc = ld->ld_conns; rc == -2 && lc != NULL; lc = nextlc ) { nextlc = lc->lconn_next; if ( lc->lconn_status == LDAP_CONNST_CONNECTED && nsldapi_iostatus_is_read_ready( ld, lc->lconn_sb )) { rc = read1msg( ld, msgid, all, lc->lconn_sb, lc, result ); } else if (ld->ld_options & LDAP_BITOPT_ASYNC) { if ( lr && lc->lconn_status == LDAP_CONNST_CONNECTING && nsldapi_iostatus_is_write_ready( ld, lc->lconn_sb ) ) { rc = nsldapi_ber_flush( ld, lc->lconn_sb, lr->lr_ber, 0, 1 ); if ( rc == 0 ) { rc = LDAP_RES_BIND; lc->lconn_status = LDAP_CONNST_CONNECTED; lr->lr_ber->ber_end = lr->lr_ber->ber_ptr; lr->lr_ber->ber_ptr = lr->lr_ber->ber_buf; nsldapi_iostatus_interest_read( ld, lc->lconn_sb ); } else if ( rc == -1 ) { LDAP_SET_LDERRNO( ld, LDAP_SERVER_DOWN, NULL, NULL ); nsldapi_free_request( ld, lr, 0 ); nsldapi_free_connection( ld, lc, NULL, NULL, 0, 0 ); } } } } LDAP_MUTEX_UNLOCK( ld, LDAP_REQ_LOCK ); LDAP_MUTEX_UNLOCK( ld, LDAP_CONN_LOCK ); } } /* * It is possible that recursion occurred while chasing * referrals and as a result the message we are looking * for may have been placed on the response queue. Look * for it there before continuing so we don't end up * waiting on the network for a message that we already * received! */ if ( rc == -2 && check_response_queue( ld, msgid, all, 0, result ) != 0 ) { LDAP_SET_LDERRNO( ld, LDAP_SUCCESS, NULL, NULL ); rc = (*result)->lm_msgtype; } /* * honor the timeout if specified */ if ( rc == -2 && tvp != NULL ) { #ifdef _SOLARIS_SDK tmp_time = gethrtime(); if ((tv_time -= (tmp_time - start_time)) <= 0) { #else tmp_time = (long)time( NULL ); if (( tv.tv_sec -= ( tmp_time - start_time )) <= 0 ) { #endif rc = 0; /* timed out */ LDAP_SET_LDERRNO( ld, LDAP_TIMEOUT, NULL, NULL ); break; } #ifdef _SOLARIS_SDK tv.tv_sec = tv_time / NANOSEC; tv.tv_usec = (tv_time % NANOSEC) / (NANOSEC / MICROSEC); #endif LDAPDebug( LDAP_DEBUG_TRACE, "wait4msg: %ld secs to go\n", tv.tv_sec, 0, 0 ); start_time = tmp_time; } } return( rc ); } /* * read1msg() should be called with LDAP_CONN_LOCK and LDAP_REQ_LOCK locked. */ static int read1msg( LDAP *ld, int msgid, int all, Sockbuf *sb, LDAPConn *lc, LDAPMessage **result ) { BerElement *ber; LDAPMessage *new, *l, *prev, *chainprev, *tmp; ber_int_t id; ber_tag_t tag; ber_len_t len; int terrno, lderr, foundit = 0; LDAPRequest *lr; int rc, has_parent, message_can_be_returned; int manufactured_result = 0; LDAPDebug( LDAP_DEBUG_TRACE, "read1msg\n", 0, 0, 0 ); message_can_be_returned = 1; /* the usual case... */ /* * if we are not already in the midst of reading a message, allocate * a ber that is associated with this connection */ if ( lc->lconn_ber == NULLBER && nsldapi_alloc_ber_with_options( ld, &lc->lconn_ber ) != LDAP_SUCCESS ) { return( -1 ); } /* * ber_get_next() doesn't set errno on EOF, so we pre-set it to * zero to avoid getting tricked by leftover "EAGAIN" errors */ LDAP_SET_ERRNO( ld, 0 ); /* get the next message */ if ( (tag = ber_get_next( sb, &len, lc->lconn_ber )) != LDAP_TAG_MESSAGE ) { terrno = LDAP_GET_ERRNO( ld ); if ( terrno == EWOULDBLOCK || terrno == EAGAIN ) { return( -2 ); /* try again */ } LDAP_SET_LDERRNO( ld, (tag == LBER_DEFAULT ? LDAP_SERVER_DOWN : LDAP_LOCAL_ERROR), NULL, NULL ); if ( tag == LBER_DEFAULT ) { nsldapi_connection_lost_nolock( ld, sb ); } return( -1 ); } /* * Since we have received a complete message now, we pull this ber * out of the connection structure and never read into it again. */ ber = lc->lconn_ber; lc->lconn_ber = NULLBER; /* message id */ if ( ber_get_int( ber, &id ) == LBER_ERROR ) { LDAP_SET_LDERRNO( ld, LDAP_DECODING_ERROR, NULL, NULL ); return( -1 ); } /* if it's been abandoned, toss it */ if ( ldap_abandoned( ld, (int)id ) ) { ber_free( ber, 1 ); return( -2 ); /* continue looking */ } if ( id == LDAP_RES_UNSOLICITED ) { lr = NULL; } else if (( lr = nsldapi_find_request_by_msgid( ld, id )) == NULL ) { LDAPDebug( LDAP_DEBUG_ANY, "no request for response with msgid %ld (tossing)\n", id, 0, 0 ); ber_free( ber, 1 ); return( -2 ); /* continue looking */ } /* the message type */ if ( (tag = ber_peek_tag( ber, &len )) == LBER_ERROR ) { LDAP_SET_LDERRNO( ld, LDAP_DECODING_ERROR, NULL, NULL ); return( -1 ); } LDAPDebug( LDAP_DEBUG_TRACE, "got %s msgid %ld, original id %d\n", ( tag == LDAP_RES_SEARCH_ENTRY ) ? "ENTRY" : ( tag == LDAP_RES_SEARCH_REFERENCE ) ? "REFERENCE" : "RESULT", id, ( lr == NULL ) ? id : lr->lr_origid ); if ( lr != NULL ) { id = lr->lr_origid; lr->lr_res_msgtype = tag; } rc = -2; /* default is to keep looking (no response found) */ if ( id != LDAP_RES_UNSOLICITED && ( tag == LDAP_RES_SEARCH_REFERENCE || tag != LDAP_RES_SEARCH_ENTRY )) { int refchasing, reftotal, simple_request = 0; check_for_refs( ld, lr, ber, lc->lconn_version, &reftotal, &refchasing ); if ( refchasing > 0 || lr->lr_outrefcnt > 0 ) { /* * we're chasing one or more new refs... */ ber_free( ber, 1 ); ber = NULLBER; lr->lr_status = LDAP_REQST_CHASINGREFS; message_can_be_returned = 0; } else if ( tag != LDAP_RES_SEARCH_REFERENCE ) { /* * this request is complete... */ has_parent = ( lr->lr_parent != NULL ); if ( lr->lr_outrefcnt <= 0 && !has_parent ) { /* request without any refs */ simple_request = ( reftotal == 0 ); } /* * If this is not a child request and it is a bind * request, reset the connection's bind DN and * status based on the result of the operation. */ if ( !has_parent && LDAP_RES_BIND == lr->lr_res_msgtype && lr->lr_conn != NULL ) { if ( lr->lr_conn->lconn_binddn != NULL ) { NSLDAPI_FREE( lr->lr_conn->lconn_binddn ); } if ( LDAP_SUCCESS == nsldapi_parse_result( ld, lr->lr_res_msgtype, ber, &lderr, NULL, NULL, NULL, NULL ) && LDAP_SUCCESS == lderr ) { lr->lr_conn->lconn_bound = 1; lr->lr_conn->lconn_binddn = lr->lr_binddn; lr->lr_binddn = NULL; } else { lr->lr_conn->lconn_bound = 0; lr->lr_conn->lconn_binddn = NULL; } } /* * if this response is to a child request, we toss * the message contents and just merge error info. * into the parent. */ if ( has_parent ) { ber_free( ber, 1 ); ber = NULLBER; } while ( lr->lr_parent != NULL ) { merge_error_info( ld, lr->lr_parent, lr ); lr = lr->lr_parent; if ( --lr->lr_outrefcnt > 0 ) { break; /* not completely done yet */ } } /* * we recognize a request as complete when: * 1) it has no outstanding referrals * 2) it is not a child request * 3) we have received a result for the request (i.e., * something other than an entry or a reference). */ if ( lr->lr_outrefcnt <= 0 && lr->lr_parent == NULL && lr->lr_res_msgtype != LDAP_RES_SEARCH_ENTRY && lr->lr_res_msgtype != LDAP_RES_SEARCH_REFERENCE ) { id = lr->lr_msgid; tag = lr->lr_res_msgtype; LDAPDebug( LDAP_DEBUG_TRACE, "request %ld done\n", id, 0, 0 ); LDAPDebug( LDAP_DEBUG_TRACE, "res_errno: %d, res_error: <%s>, res_matched: <%s>\n", lr->lr_res_errno, lr->lr_res_error ? lr->lr_res_error : "", lr->lr_res_matched ? lr->lr_res_matched : "" ); if ( !simple_request ) { if ( ber != NULLBER ) { ber_free( ber, 1 ); ber = NULLBER; } if ( build_result_ber( ld, &ber, lr ) != LDAP_SUCCESS ) { rc = -1; /* fatal error */ } else { manufactured_result = 1; } } nsldapi_free_request( ld, lr, 1 ); } else { message_can_be_returned = 0; } } } if ( ber == NULLBER ) { return( rc ); } /* make a new ldap message */ if ( (new = (LDAPMessage*)NSLDAPI_CALLOC( 1, sizeof(struct ldapmsg) )) == NULL ) { LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY, NULL, NULL ); return( -1 ); } new->lm_msgid = (int)id; new->lm_msgtype = tag; new->lm_ber = ber; /* * if this is a search entry or if this request is complete (i.e., * there are no outstanding referrals) then add to cache and check * to see if we should return this to the caller right away or not. */ if ( message_can_be_returned ) { if ( ld->ld_cache_on ) { nsldapi_add_result_to_cache( ld, new ); } if ( msgid == LDAP_RES_ANY || id == msgid ) { if ( new->lm_msgtype == LDAP_RES_SEARCH_RESULT ) { /* * return the first response we have for this * search request later (possibly an entire * chain of messages). */ foundit = 1; } else if ( all == 0 || (new->lm_msgtype != LDAP_RES_SEARCH_REFERENCE && new->lm_msgtype != LDAP_RES_SEARCH_ENTRY) ) { *result = new; LDAP_SET_LDERRNO( ld, LDAP_SUCCESS, NULL, NULL ); return( tag ); } } } /* * if not, we must add it to the list of responses. if * the msgid is already there, it must be part of an existing * search response. */ prev = NULL; LDAP_MUTEX_LOCK( ld, LDAP_RESP_LOCK ); for ( l = ld->ld_responses; l != NULL; l = l->lm_next ) { if ( l->lm_msgid == new->lm_msgid ) break; prev = l; } /* not part of an existing search response */ if ( l == NULL ) { if ( foundit ) { LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); *result = new; LDAP_SET_LDERRNO( ld, LDAP_SUCCESS, NULL, NULL ); return( tag ); } new->lm_next = ld->ld_responses; ld->ld_responses = new; LDAPDebug( LDAP_DEBUG_TRACE, "adding new response id %d type %d (looking for id %d)\n", new->lm_msgid, new->lm_msgtype, msgid ); LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); if( message_can_be_returned ) POST( ld, new->lm_msgid, new ); return( -2 ); /* continue looking */ } LDAPDebug( LDAP_DEBUG_TRACE, "adding response id %d type %d (looking for id %d)\n", new->lm_msgid, new->lm_msgtype, msgid ); /* * part of a search response - add to end of list of entries * * the first step is to find the end of the list of entries and * references. after the following loop is executed, tmp points to * the last entry or reference in the chain. If there are none, * tmp points to the search result. */ chainprev = NULL; for ( tmp = l; tmp->lm_chain != NULL && ( tmp->lm_chain->lm_msgtype == LDAP_RES_SEARCH_ENTRY || tmp->lm_chain->lm_msgtype == LDAP_RES_SEARCH_REFERENCE ); tmp = tmp->lm_chain ) { chainprev = tmp; } /* * If this is a manufactured result message and a result is already * queued we throw away the one that is queued and replace it with * our new result. This is necessary so we don't end up returning * more than one result. */ if ( manufactured_result && tmp->lm_msgtype == LDAP_RES_SEARCH_RESULT ) { /* * the result is the only thing in the chain... replace it. */ new->lm_chain = tmp->lm_chain; new->lm_next = tmp->lm_next; if ( chainprev == NULL ) { if ( prev == NULL ) { ld->ld_responses = new; } else { prev->lm_next = new; } } else { chainprev->lm_chain = new; } if ( l == tmp ) { l = new; } ldap_msgfree( tmp ); } else if ( manufactured_result && tmp->lm_chain != NULL && tmp->lm_chain->lm_msgtype == LDAP_RES_SEARCH_RESULT ) { /* * entries or references are also present, so the result * is the next entry after tmp. replace it. */ new->lm_chain = tmp->lm_chain->lm_chain; new->lm_next = tmp->lm_chain->lm_next; ldap_msgfree( tmp->lm_chain ); tmp->lm_chain = new; } else if ( tmp->lm_msgtype == LDAP_RES_SEARCH_RESULT ) { /* * the result is the only thing in the chain... add before it. */ new->lm_chain = tmp; if ( chainprev == NULL ) { if ( prev == NULL ) { ld->ld_responses = new; } else { prev->lm_next = new; } } else { chainprev->lm_chain = new; } if ( l == tmp ) { l = new; } } else { /* * entries and/or references are present... add to the end * of the entry/reference part of the chain. */ new->lm_chain = tmp->lm_chain; tmp->lm_chain = new; } /* * return the first response or the whole chain if that's what * we were looking for.... */ if ( foundit ) { if ( all == 0 && l->lm_chain != NULL ) { /* * only return the first response in the chain */ if ( prev == NULL ) { ld->ld_responses = l->lm_chain; } else { prev->lm_next = l->lm_chain; } l->lm_chain = NULL; tag = l->lm_msgtype; } else { /* * return all of the responses (may be a chain) */ if ( prev == NULL ) { ld->ld_responses = l->lm_next; } else { prev->lm_next = l->lm_next; } } *result = l; LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); LDAP_SET_LDERRNO( ld, LDAP_SUCCESS, NULL, NULL ); return( tag ); } LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); return( -2 ); /* continue looking */ } /* * check for LDAPv2+ (UMich extension) or LDAPv3 referrals or references * errors are merged in "lr". */ static void check_for_refs( LDAP *ld, LDAPRequest *lr, BerElement *ber, int ldapversion, int *totalcountp, int *chasingcountp ) { int err, origerr; char *errstr, *matcheddn, **v3refs; LDAPDebug( LDAP_DEBUG_TRACE, "check_for_refs\n", 0, 0, 0 ); *chasingcountp = *totalcountp = 0; if ( ldapversion < LDAP_VERSION2 || ( lr->lr_parent == NULL && ( ld->ld_options & LDAP_BITOPT_REFERRALS ) == 0 )) { /* referrals are not supported or are disabled */ return; } if ( lr->lr_res_msgtype == LDAP_RES_SEARCH_REFERENCE ) { err = nsldapi_parse_reference( ld, ber, &v3refs, NULL ); origerr = LDAP_REFERRAL; /* a small lie... */ matcheddn = errstr = NULL; } else { err = nsldapi_parse_result( ld, lr->lr_res_msgtype, ber, &origerr, &matcheddn, &errstr, &v3refs, NULL ); } if ( err != LDAP_SUCCESS ) { /* parse failed */ return; } if ( origerr == LDAP_REFERRAL ) { /* ldapv3 */ if ( v3refs != NULL ) { err = nsldapi_chase_v3_refs( ld, lr, v3refs, ( lr->lr_res_msgtype == LDAP_RES_SEARCH_REFERENCE ), totalcountp, chasingcountp ); ldap_value_free( v3refs ); } } else if ( ldapversion == LDAP_VERSION2 && origerr != LDAP_SUCCESS ) { /* referrals may be present in the error string */ err = nsldapi_chase_v2_referrals( ld, lr, &errstr, totalcountp, chasingcountp ); } /* set LDAP errno, message, and matched string appropriately */ if ( lr->lr_res_error != NULL ) { NSLDAPI_FREE( lr->lr_res_error ); } lr->lr_res_error = errstr; if ( lr->lr_res_matched != NULL ) { NSLDAPI_FREE( lr->lr_res_matched ); } lr->lr_res_matched = matcheddn; if ( err == LDAP_SUCCESS && ( *chasingcountp == *totalcountp )) { if ( *totalcountp > 0 && ( origerr == LDAP_PARTIAL_RESULTS || origerr == LDAP_REFERRAL )) { /* substitute success for referral error codes */ lr->lr_res_errno = LDAP_SUCCESS; } else { /* preserve existing non-referral error code */ lr->lr_res_errno = origerr; } } else if ( err != LDAP_SUCCESS ) { /* error occurred while trying to chase referrals */ lr->lr_res_errno = err; } else { /* some referrals were not recognized */ lr->lr_res_errno = ( ldapversion == LDAP_VERSION2 ) ? LDAP_PARTIAL_RESULTS : LDAP_REFERRAL; } LDAPDebug( LDAP_DEBUG_TRACE, "check_for_refs: new result: msgid %d, res_errno %d, ", lr->lr_msgid, lr->lr_res_errno, 0 ); LDAPDebug( LDAP_DEBUG_TRACE, " res_error <%s>, res_matched <%s>\n", lr->lr_res_error ? lr->lr_res_error : "", lr->lr_res_matched ? lr->lr_res_matched : "", 0 ); LDAPDebug( LDAP_DEBUG_TRACE, "check_for_refs: %d new refs(s); chasing %d of them\n", *totalcountp, *chasingcountp, 0 ); } /* returns an LDAP error code and also sets it in LDAP * */ static int build_result_ber( LDAP *ld, BerElement **berp, LDAPRequest *lr ) { ber_len_t len; ber_int_t along; BerElement *ber; int err; if (( err = nsldapi_alloc_ber_with_options( ld, &ber )) != LDAP_SUCCESS ) { return( err ); } *berp = ber; if ( ber_printf( ber, "{it{ess}}", lr->lr_msgid, (long)lr->lr_res_msgtype, lr->lr_res_errno, lr->lr_res_matched ? lr->lr_res_matched : "", lr->lr_res_error ? lr->lr_res_error : "" ) == -1 ) { return( LDAP_ENCODING_ERROR ); } ber_reset( ber, 1 ); if ( ber_skip_tag( ber, &len ) == LBER_ERROR || ber_get_int( ber, &along ) == LBER_ERROR || ber_peek_tag( ber, &len ) == LBER_ERROR ) { return( LDAP_DECODING_ERROR ); } return( LDAP_SUCCESS ); } static void merge_error_info( LDAP *ld, LDAPRequest *parentr, LDAPRequest *lr ) { /* * Merge error information in "lr" with "parentr" error code and string. */ if ( lr->lr_res_errno == LDAP_PARTIAL_RESULTS ) { parentr->lr_res_errno = lr->lr_res_errno; if ( lr->lr_res_error != NULL ) { (void)nsldapi_append_referral( ld, &parentr->lr_res_error, lr->lr_res_error ); } } else if ( lr->lr_res_errno != LDAP_SUCCESS && parentr->lr_res_errno == LDAP_SUCCESS ) { parentr->lr_res_errno = lr->lr_res_errno; if ( parentr->lr_res_error != NULL ) { NSLDAPI_FREE( parentr->lr_res_error ); } parentr->lr_res_error = lr->lr_res_error; lr->lr_res_error = NULL; if ( NAME_ERROR( lr->lr_res_errno )) { if ( parentr->lr_res_matched != NULL ) { NSLDAPI_FREE( parentr->lr_res_matched ); } parentr->lr_res_matched = lr->lr_res_matched; lr->lr_res_matched = NULL; } } LDAPDebug( LDAP_DEBUG_TRACE, "merged parent (id %d) error info: ", parentr->lr_msgid, 0, 0 ); LDAPDebug( LDAP_DEBUG_TRACE, "result lderrno %d, error <%s>, matched <%s>\n", parentr->lr_res_errno, parentr->lr_res_error ? parentr->lr_res_error : "", parentr->lr_res_matched ? parentr->lr_res_matched : "" ); } #if defined( CLDAP ) #if !defined( macintosh ) && !defined( DOS ) && !defined( _WINDOWS ) && !defined(XP_OS2) /* XXXmcs: was revised to support extended I/O callbacks but never compiled! */ static int cldap_select1( LDAP *ld, struct timeval *timeout ) { int rc; static int tblsize = 0; NSLDAPIIOStatus *iosp = ld->ld_iostatus; if ( tblsize == 0 ) { #ifdef USE_SYSCONF tblsize = sysconf( _SC_OPEN_MAX ); #else /* USE_SYSCONF */ tblsize = getdtablesize(); #endif /* USE_SYSCONF */ } if ( tblsize >= FD_SETSIZE ) { /* * clamp value so we don't overrun the fd_set structure */ tblsize = FD_SETSIZE - 1; } if ( NSLDAPI_IOSTATUS_TYPE_OSNATIVE == iosp->ios_type ) { fd_set readfds; FD_ZERO( &readfds ); FD_SET( ld->ld_sbp->sb_sd, &readfds ); /* XXXmcs: UNIX platforms should use poll() */ rc = select( tblsize, &readfds, 0, 0, timeout ) ); } else if ( NSLDAPI_IOSTATUS_TYPE_CALLBACK == iosp->ios_type ) { LDAP_X_PollFD pollfds[ 1 ]; pollfds[0].lpoll_fd = ld->ld_sbp->sb_sd; pollfds[0].lpoll_arg = ld->ld_sbp->sb_arg; pollfds[0].lpoll_events = LDAP_X_POLLIN; pollfds[0].lpoll_revents = 0; rc = ld->ld_extpoll_fn( pollfds, 1, nsldapi_tv2ms( timeout ), ld->ld_ext_session_arg ); } else { LDAPDebug( LDAP_DEBUG_ANY, "nsldapi_iostatus_poll: unknown I/O type %d\n", rc = 0; /* simulate a timeout (what else to do?) */ } return( rc ); } #endif /* !macintosh */ #ifdef macintosh static int cldap_select1( LDAP *ld, struct timeval *timeout ) { /* XXXmcs: needs to be revised to support I/O callbacks */ return( tcpselect( ld->ld_sbp->sb_sd, timeout )); } #endif /* macintosh */ #if (defined( DOS ) && defined( WINSOCK )) || defined( _WINDOWS ) || defined(XP_OS2) /* XXXmcs: needs to be revised to support extended I/O callbacks */ static int cldap_select1( LDAP *ld, struct timeval *timeout ) { fd_set readfds; int rc; FD_ZERO( &readfds ); FD_SET( ld->ld_sbp->sb_sd, &readfds ); if ( NSLDAPI_IO_TYPE_STANDARD == ld->ldiou_type && NULL != ld->ld_select_fn ) { rc = ld->ld_select_fn( 1, &readfds, 0, 0, timeout ); } else if ( NSLDAPI_IO_TYPE_EXTENDED == ld->ldiou_type && NULL != ld->ld_extselect_fn ) { rc = ld->ld_extselect_fn( ld->ld_ext_session_arg, 1, &readfds, 0, 0, timeout ) ); } else { /* XXXmcs: UNIX platforms should use poll() */ rc = select( 1, &readfds, 0, 0, timeout ) ); } return( rc == SOCKET_ERROR ? -1 : rc ); } #endif /* WINSOCK || _WINDOWS */ #endif /* CLDAP */ int LDAP_CALL ldap_msgfree( LDAPMessage *lm ) { LDAPMessage *next; int type = 0; LDAPDebug( LDAP_DEBUG_TRACE, "ldap_msgfree\n", 0, 0, 0 ); for ( ; lm != NULL; lm = next ) { next = lm->lm_chain; type = lm->lm_msgtype; ber_free( lm->lm_ber, 1 ); NSLDAPI_FREE( (char *) lm ); } return( type ); } /* * ldap_msgdelete - delete a message. It returns: * 0 if the entire message was deleted * -1 if the message was not found, or only part of it was found */ int ldap_msgdelete( LDAP *ld, int msgid ) { LDAPMessage *lm, *prev; int msgtype; LDAPDebug( LDAP_DEBUG_TRACE, "ldap_msgdelete\n", 0, 0, 0 ); if ( !NSLDAPI_VALID_LDAP_POINTER( ld )) { return( -1 ); /* punt */ } prev = NULL; LDAP_MUTEX_LOCK( ld, LDAP_RESP_LOCK ); for ( lm = ld->ld_responses; lm != NULL; lm = lm->lm_next ) { if ( lm->lm_msgid == msgid ) break; prev = lm; } if ( lm == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); return( -1 ); } if ( prev == NULL ) ld->ld_responses = lm->lm_next; else prev->lm_next = lm->lm_next; LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); msgtype = ldap_msgfree( lm ); if ( msgtype == LDAP_RES_SEARCH_ENTRY || msgtype == LDAP_RES_SEARCH_REFERENCE ) { return( -1 ); } return( 0 ); } /* * return 1 if message msgid is waiting to be abandoned, 0 otherwise */ static int ldap_abandoned( LDAP *ld, int msgid ) { int i; LDAP_MUTEX_LOCK( ld, LDAP_ABANDON_LOCK ); if ( ld->ld_abandoned == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( 0 ); } for ( i = 0; ld->ld_abandoned[i] != -1; i++ ) if ( ld->ld_abandoned[i] == msgid ) { LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( 1 ); } LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( 0 ); } static int ldap_mark_abandoned( LDAP *ld, int msgid ) { int i; LDAP_MUTEX_LOCK( ld, LDAP_ABANDON_LOCK ); if ( ld->ld_abandoned == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( -1 ); } for ( i = 0; ld->ld_abandoned[i] != -1; i++ ) if ( ld->ld_abandoned[i] == msgid ) break; if ( ld->ld_abandoned[i] == -1 ) { LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( -1 ); } for ( ; ld->ld_abandoned[i] != -1; i++ ) { ld->ld_abandoned[i] = ld->ld_abandoned[i + 1]; } LDAP_MUTEX_UNLOCK( ld, LDAP_ABANDON_LOCK ); return( 0 ); } #ifdef CLDAP int cldap_getmsg( LDAP *ld, struct timeval *timeout, BerElement **ber ) { int rc; ber_tag_t tag; ber_len_t len; if ( ld->ld_sbp->sb_ber.ber_ptr >= ld->ld_sbp->sb_ber.ber_end ) { rc = cldap_select1( ld, timeout ); if ( rc == -1 || rc == 0 ) { LDAP_SET_LDERRNO( ld, (rc == -1 ? LDAP_SERVER_DOWN : LDAP_TIMEOUT), NULL, NULL ); return( rc ); } } /* get the next message */ if ( (tag = ber_get_next( ld->ld_sbp, &len, ber )) != LDAP_TAG_MESSAGE ) { LDAP_SET_LDERRNO( ld, (tag == LBER_DEFAULT ? LDAP_SERVER_DOWN : LDAP_LOCAL_ERROR), NULL, NULL ); return( -1 ); } return( tag ); } #endif /* CLDAP */ int nsldapi_post_result( LDAP *ld, int msgid, LDAPMessage *result ) { LDAPPend *lp; LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_post_result(ld=0x%x, msgid=%d, result=0x%x)\n", ld, msgid, result ); LDAP_MUTEX_LOCK( ld, LDAP_PEND_LOCK ); if( msgid == LDAP_RES_ANY ) { /* * Look for any pending request for which someone is waiting. */ for( lp = ld->ld_pend; lp != NULL; lp = lp->lp_next ) { if ( lp->lp_sema != NULL ) { break; } } /* * If we did't find a pending request, lp is NULL at this * point, and we will leave this function without doing * anything more -- which is exactly what we want to do. */ } else { /* * Look for a pending request specific to this message id */ for( lp = ld->ld_pend; lp != NULL; lp = lp->lp_next ) { if( lp->lp_msgid == msgid ) break; } if( lp == NULL ) { /* * No pending requests for this response... append to * our pending result list. */ LDAPPend *newlp; newlp = (LDAPPend *)NSLDAPI_CALLOC( 1, sizeof( LDAPPend )); if( newlp == NULL ) { LDAP_MUTEX_UNLOCK( ld, LDAP_PEND_LOCK ); LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY, NULL, NULL ); return (-1); } newlp->lp_msgid = msgid; newlp->lp_result = result; link_pend( ld, newlp ); } } if( lp != NULL ) { /* * Wake up a thread that is waiting for this result. */ lp->lp_msgid = msgid; lp->lp_result = result; LDAP_SEMA_POST( ld, lp ); } LDAP_MUTEX_UNLOCK( ld, LDAP_PEND_LOCK ); return (0); } static void link_pend( LDAP *ld, LDAPPend *lp ) { if (( lp->lp_next = ld->ld_pend ) != NULL ) { lp->lp_next->lp_prev = lp; } ld->ld_pend = lp; lp->lp_prev = NULL; } #if 0 /* these functions are no longer used */ static void unlink_pend( LDAP *ld, LDAPPend *lp ) { if ( lp->lp_prev == NULL ) { ld->ld_pend = lp->lp_next; } else { lp->lp_prev->lp_next = lp->lp_next; } if ( lp->lp_next != NULL ) { lp->lp_next->lp_prev = lp->lp_prev; } } static int unlink_msg( LDAP *ld, int msgid, int all ) { int rc; LDAPMessage *lm, *lastlm, *nextlm; lastlm = NULL; LDAP_MUTEX_LOCK( ld, LDAP_RESP_LOCK ); for ( lm = ld->ld_responses; lm != NULL; lm = nextlm ) { nextlm = lm->lm_next; if ( lm->lm_msgid == msgid ) { LDAPMessage *tmp; if ( all == 0 || (lm->lm_msgtype != LDAP_RES_SEARCH_RESULT && lm->lm_msgtype != LDAP_RES_SEARCH_REFERENCE && lm->lm_msgtype != LDAP_RES_SEARCH_ENTRY) ) break; for ( tmp = lm; tmp != NULL; tmp = tmp->lm_chain ) { if ( tmp->lm_msgtype == LDAP_RES_SEARCH_RESULT ) break; } if( tmp != NULL ) break; } lastlm = lm; } if( lm != NULL ) { if ( all == 0 ) { if ( lm->lm_chain == NULL ) { if ( lastlm == NULL ) ld->ld_responses = lm->lm_next; else lastlm->lm_next = lm->lm_next; } else { if ( lastlm == NULL ) { ld->ld_responses = lm->lm_chain; ld->ld_responses->lm_next = lm->lm_next; } else { lastlm->lm_next = lm->lm_chain; lastlm->lm_next->lm_next = lm->lm_next; } } } else { if ( lastlm == NULL ) ld->ld_responses = lm->lm_next; else lastlm->lm_next = lm->lm_next; } if ( all == 0 ) lm->lm_chain = NULL; lm->lm_next = NULL; rc = lm->lm_msgtype; } else { rc = -2; } LDAP_MUTEX_UNLOCK( ld, LDAP_RESP_LOCK ); return ( rc ); } #endif /* 0 */