result.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* -*- 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 <sys/time.h>
#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 */