root/usr/src/lib/libldap5/sources/ldap/common/result.c
/*
 * 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 <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 */