root/usr/src/lib/libldap5/sources/ldap/common/os-ip.c
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * The contents of this file are subject to the Netscape Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 *
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation. Portions created by Netscape are
 * Copyright (C) 1998-1999 Netscape Communications Corporation. All
 * Rights Reserved.
 *
 * Contributor(s):
 */
/*
 *  Copyright (c) 1995 Regents of the University of Michigan.
 *  All rights reserved.
 */
/*
 *  os-ip.c -- platform-specific TCP & UDP related code
 */

#if 0
#ifndef lint
static char copyright[] = "@(#) Copyright (c) 1995 Regents of the University of Michigan.\nAll rights reserved.\n";
#endif
#endif

#include "ldap-int.h"
#ifdef LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED
#include <signal.h>
#endif

#ifdef NSLDAPI_HAVE_POLL
#include <poll.h>
#endif


#ifdef _WINDOWS
#define NSLDAPI_INVALID_OS_SOCKET( s )  ((s) == INVALID_SOCKET)
#else
#define NSLDAPI_INVALID_OS_SOCKET( s )  ((s) < 0 )
#endif


#define NSLDAPI_POLL_ARRAY_GROWTH  5  /* grow arrays 5 elements at a time */


/*
 * Structures and union for tracking status of network sockets
 */
#ifdef NSLDAPI_HAVE_POLL
struct nsldapi_os_statusinfo {          /* used with native OS poll() */
        struct pollfd           *ossi_pollfds;
        int                     ossi_pollfds_size;
};
#else /* NSLDAPI_HAVE_POLL */
struct nsldapi_os_statusinfo {          /* used with native OS select() */
        fd_set                  ossi_readfds;
        fd_set                  ossi_writefds;
        fd_set                  ossi_use_readfds;
        fd_set                  ossi_use_writefds;
};
#endif /* else NSLDAPI_HAVE_POLL */

struct nsldapi_cb_statusinfo {          /* used with ext. I/O poll() callback */
    LDAP_X_PollFD               *cbsi_pollfds;
    int                         cbsi_pollfds_size;
};

/*
 * NSLDAPI_CB_POLL_MATCH() evaluates to non-zero (true) if the Sockbuf *sdp
 * matches the LDAP_X_PollFD pollfd.
 */
#ifdef _WINDOWS
#define NSLDAPI_CB_POLL_SD_CAST         (unsigned int)
#else
#define NSLDAPI_CB_POLL_SD_CAST
#endif
#if defined(LDAP_SASLIO_HOOKS)
#define NSLDAPI_CB_POLL_MATCH( sbp, pollfd ) \
    ( ((sbp)->sb_sd == NSLDAPI_CB_POLL_SD_CAST ((pollfd).lpoll_fd)) && \
    (((sbp)->sb_sasl_fns.lbextiofn_socket_arg == (pollfd).lpoll_socketarg) || \
    ((sbp)->sb_ext_io_fns.lbextiofn_socket_arg == (pollfd).lpoll_socketarg) ) )
#else
#define NSLDAPI_CB_POLL_MATCH( sbp, pollfd ) \
    ((sbp)->sb_sd == NSLDAPI_CB_POLL_SD_CAST ((pollfd).lpoll_fd) && \
    (sbp)->sb_ext_io_fns.lbextiofn_socket_arg == (pollfd).lpoll_socketarg)
#endif


struct nsldapi_iostatus_info {
        int                             ios_type;
#define NSLDAPI_IOSTATUS_TYPE_OSNATIVE          1   /* poll() or select() */
#define NSLDAPI_IOSTATUS_TYPE_CALLBACK          2   /* poll()-like */
        int                             ios_read_count;
        int                             ios_write_count;
        union {
            struct nsldapi_os_statusinfo        ios_osinfo;
            struct nsldapi_cb_statusinfo        ios_cbinfo;
        } ios_status;
};


#ifdef NSLDAPI_HAVE_POLL
static int nsldapi_add_to_os_pollfds( int fd,
    struct nsldapi_os_statusinfo *pip, short events );
static int nsldapi_clear_from_os_pollfds( int fd,
    struct nsldapi_os_statusinfo *pip, short events );
static int nsldapi_find_in_os_pollfds( int fd,
    struct nsldapi_os_statusinfo *pip, short revents );
#endif /* NSLDAPI_HAVE_POLL */

static int nsldapi_iostatus_init_nolock( LDAP *ld );
static int nsldapi_add_to_cb_pollfds( Sockbuf *sb,
    struct nsldapi_cb_statusinfo *pip, short events );
static int nsldapi_clear_from_cb_pollfds( Sockbuf *sb,
    struct nsldapi_cb_statusinfo *pip, short events );
static int nsldapi_find_in_cb_pollfds( Sockbuf *sb,
    struct nsldapi_cb_statusinfo *pip, short revents );


#ifdef irix
#ifndef _PR_THREADS
/*
 * XXXmcs: on IRIX NSPR's poll() and select() wrappers will crash if NSPR
 * has not been initialized.  We work around the problem by bypassing
 * the NSPR wrapper functions and going directly to the OS' functions.
 */
#define NSLDAPI_POLL            _poll
#define NSLDAPI_SELECT          _select
extern int _poll(struct pollfd *fds, unsigned long nfds, int timeout);
extern int _select(int nfds, fd_set *readfds, fd_set *writefds,
        fd_set *exceptfds, struct timeval *timeout);
#else /* _PR_THREADS */
#define NSLDAPI_POLL            poll
#define NSLDAPI_SELECT          select
#endif /* else _PR_THREADS */
#else /* irix */
#define NSLDAPI_POLL            poll
#define NSLDAPI_SELECT          select
#endif /* else irix */


static LBER_SOCKET nsldapi_os_socket( LDAP *ld, int secure, int domain,
        int type, int protocol );
static int nsldapi_os_ioctl( LBER_SOCKET s, int option, int *statusp );
static int nsldapi_os_connect_with_to( LBER_SOCKET s, struct sockaddr *name,
        int namelen, LDAP *ld);

/*
 * Function typedefs used by nsldapi_try_each_host()
 */
typedef LBER_SOCKET (NSLDAPI_SOCKET_FN)( LDAP *ld, int secure, int domain,
            int type, int protocol );
typedef int (NSLDAPI_IOCTL_FN)( LBER_SOCKET s, int option, int *statusp );
typedef int (NSLDAPI_CONNECT_WITH_TO_FN )( LBER_SOCKET s, struct sockaddr *name,
        int namelen, LDAP *ld);
typedef int (NSLDAPI_CONNECT_FN )( LBER_SOCKET s, struct sockaddr *name,
        int namelen );
typedef int (NSLDAPI_CLOSE_FN )( LBER_SOCKET s );

static int nsldapi_try_each_host( LDAP *ld, const char *hostlist, int defport,
        int secure, NSLDAPI_SOCKET_FN *socketfn, NSLDAPI_IOCTL_FN *ioctlfn,
        NSLDAPI_CONNECT_WITH_TO_FN *connectwithtofn,
        NSLDAPI_CONNECT_FN *connectfn, NSLDAPI_CLOSE_FN *closefn );


static int
nsldapi_os_closesocket( LBER_SOCKET s )
{
        int     rc;

#ifdef _WINDOWS
        rc = closesocket( s );
#else
        rc = close( s );
#endif
        return( rc );
}


static LBER_SOCKET
nsldapi_os_socket( LDAP *ld, int secure, int domain, int type, int protocol )
{
        int             s, invalid_socket;
        char            *errmsg = NULL;

        if ( secure ) {
                LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL,
                            nsldapi_strdup( dgettext(TEXT_DOMAIN,
                                "secure mode not supported") ));
                return( -1 );
        }

        s = socket( domain, type, protocol );

        /*
         * if the socket() call failed or it returned a socket larger
         * than we can deal with, return a "local error."
         */
        if ( NSLDAPI_INVALID_OS_SOCKET( s )) {
                errmsg = dgettext(TEXT_DOMAIN, "unable to create a socket");
                invalid_socket = 1;
        } else {        /* valid socket -- check for overflow */
                invalid_socket = 0;
#if !defined(NSLDAPI_HAVE_POLL) && !defined(_WINDOWS)
                /* not on Windows and do not have poll() */
                if ( s >= FD_SETSIZE ) {
                        errmsg = "can't use socket >= FD_SETSIZE";
                }
#endif
        }

        if ( errmsg != NULL ) { /* local socket error */
                if ( !invalid_socket ) {
                        nsldapi_os_closesocket( s );
                }
                errmsg = nsldapi_strdup( errmsg );
                LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL, errmsg );
                return( -1 );
        }

        return( s );
}



/*
 * Non-blocking connect call function
 */
static int
nsldapi_os_connect_with_to(LBER_SOCKET sockfd, struct sockaddr *saptr,
        int salen, LDAP *ld)
{
#ifndef _WINDOWS
        int             flags;
#endif /* _WINDOWS */
        int             n, error;
        int             len;
        fd_set          rset, wset;
        struct timeval  tval;
#ifdef _WINDOWS
        int             nonblock = 1;
        int             block = 0;
        fd_set          eset;
#endif /* _WINDOWS */
        int             msec = ld->ld_connect_timeout; /* milliseconds */
        int             continue_on_intr = 0;
#ifdef _SOLARIS_SDK
        hrtime_t        start_time = 0, tmp_time, tv_time; /* nanoseconds */
#else
        long            start_time = 0, tmp_time; /* seconds */
#endif


        LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_connect_nonblock timeout: %d (msec)\n",
                msec, 0, 0);

#ifdef _WINDOWS
        ioctlsocket(sockfd, FIONBIO, &nonblock);
#else
        flags = fcntl(sockfd, F_GETFL, 0);
        fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
#endif /* _WINDOWS */

        error = 0;
        if ((n = connect(sockfd, saptr, salen)) < 0)
#ifdef _WINDOWS
                if ((n != SOCKET_ERROR) &&  (WSAGetLastError() != WSAEWOULDBLOCK)) {
#else
                if (errno != EINPROGRESS) {
#endif /* _WINDOWS */
#ifdef LDAP_DEBUG
                        if ( ldap_debug & LDAP_DEBUG_TRACE ) {
                                perror("connect");
                        }
#endif
                        return (-1);
                }

        /* success */
        if (n == 0)
                goto done;

        FD_ZERO(&rset);
        FD_SET(sockfd, &rset);
        wset = rset;

#ifdef _WINDOWS
        eset = rset;
#endif /* _WINDOWS */

        if (msec < 0 && msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) {
                LDAPDebug( LDAP_DEBUG_TRACE, "Invalid timeout value detected.."
                        "resetting connect timeout to default value "
                        "(LDAP_X_IO_TIMEOUT_NO_TIMEOUT\n", 0, 0, 0);
                msec = LDAP_X_IO_TIMEOUT_NO_TIMEOUT;
        } else {
                if (msec != 0) {
                        tval.tv_sec = msec / MILLISEC;
                        tval.tv_usec = (MICROSEC / MILLISEC) *
                                            (msec % MILLISEC);
#ifdef _SOLARIS_SDK
                        start_time = gethrtime();
                        tv_time = MSEC2NSEC(msec);
#else
                        start_time = (long)time(NULL);
#endif
                } else {
                        tval.tv_sec = 0;
                        tval.tv_usec = 0;
                }
        }

        /* if timeval structure == NULL, select will block indefinitely */
        /*                      != NULL, and value == 0, select will */
        /*                               not block */
        /* Windows is a bit quirky on how it behaves w.r.t nonblocking */
        /* connects.  If the connect fails, the exception fd, eset, is */
        /* set to show the failure.  The first argument in select is */
        /* ignored */

#ifdef _WINDOWS
        if ((n = select(sockfd +1, &rset, &wset, &eset,
                (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? &tval : NULL)) == 0) {
                errno = WSAETIMEDOUT;
                return (-1);
        }
        /* if wset is set, the connect worked */
        if (FD_ISSET(sockfd, &wset) || FD_ISSET(sockfd, &rset)) {
                len = sizeof(error);
                if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len)
                        < 0)
                        return (-1);
                goto done;
        }

        /* if eset is set, the connect failed */
        if (FD_ISSET(sockfd, &eset)) {
                return (-1);
        }

        /* failure on select call */
        if (n == SOCKET_ERROR) {
                perror("select error: SOCKET_ERROR returned");
                return (-1);
        }
#else
        /*
         * if LDAP_BITOPT_RESTART and select() is interrupted
         * try again.
         */
        do {
                continue_on_intr = 0;
                if ((n = select(sockfd +1, &rset, &wset, NULL,
                        (msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) ? \
                            &tval : NULL)) == 0) {
                        errno = ETIMEDOUT;
                        return (-1);
                }
                if (n < 0) {
                        if ((ld->ld_options & LDAP_BITOPT_RESTART) &&
                            (errno == EINTR)) {
                                continue_on_intr = 1;
                                errno = 0;
                                FD_ZERO(&rset);
                                FD_SET(sockfd, &rset);
                                wset = rset;
                                /* honour the timeout */
                                if ((msec != LDAP_X_IO_TIMEOUT_NO_TIMEOUT) &&
                                    (msec !=  0)) {
#ifdef _SOLARIS_SDK
                                        tmp_time = gethrtime();
                                        if ((tv_time -=
                                            (tmp_time - start_time)) <= 0) {
#else
                                        tmp_time = (long)time(NULL);
                                        if ((tval.tv_sec -=
                                            (tmp_time - start_time)) <= 0) {
#endif
                                                /* timeout */
                                                errno = ETIMEDOUT;
                                                return (-1);
                                        }
#ifdef _SOLARIS_SDK
                                        tval.tv_sec = tv_time / NANOSEC;
                                        tval.tv_usec = (tv_time % NANOSEC) /
                                                        (NANOSEC / MICROSEC);
#endif
                                        start_time = tmp_time;
                                }
                        } else {
#ifdef LDAP_DEBUG
                                perror("select error: ");
#endif
                                return (-1);
                        }
                }
        } while (continue_on_intr == 1);

        if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
                len = sizeof(error);
                if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (char *)&error, &len)
                        < 0)
                        return (-1);
#ifdef LDAP_DEBUG
        } else if ( ldap_debug & LDAP_DEBUG_TRACE ) {
                perror("select error: sockfd not set");
#endif
        }
#endif /* _WINDOWS */

done:
#ifdef _WINDOWS
        ioctlsocket(sockfd, FIONBIO, &block);
#else
        fcntl(sockfd, F_SETFL, flags);
#endif /* _WINDOWS */

        if (error) {
                errno = error;
                return (-1);
        }

        return (0);
}


static int
nsldapi_os_ioctl( LBER_SOCKET s, int option, int *statusp )
{
        int             err;
#ifdef _WINDOWS
        u_long          iostatus;
#endif

        if ( FIONBIO != option ) {
                return( -1 );
        }

#ifdef _WINDOWS
        iostatus = *(u_long *)statusp;
        err = ioctlsocket( s, FIONBIO, &iostatus );
#else
        err = ioctl( s, FIONBIO, (caddr_t)statusp );
#endif

        return( err );
}


int
nsldapi_connect_to_host( LDAP *ld, Sockbuf *sb, const char *hostlist,
                int defport, int secure, char **krbinstancep )
/*
 * "defport" must be in host byte order
 * zero is returned upon success, -1 if fatal error, -2 EINPROGRESS
 * if -1 is returned, ld_errno is set
 */
{
        int             s;

        LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_connect_to_host: %s, port: %d\n",
            NULL == hostlist ? "NULL" : hostlist, defport, 0 );

        /*
         * If an extended I/O connect callback has been defined, just use it.
         */
        if ( NULL != ld->ld_extconnect_fn ) {
                unsigned long connect_opts = 0;

                if ( ld->ld_options & LDAP_BITOPT_ASYNC) {
                        connect_opts |= LDAP_X_EXTIOF_OPT_NONBLOCKING;
                }
                if ( secure ) {
                        connect_opts |= LDAP_X_EXTIOF_OPT_SECURE;
                }
                s = ld->ld_extconnect_fn( hostlist, defport,
                    ld->ld_connect_timeout, connect_opts,
                    ld->ld_ext_session_arg,
                    &sb->sb_ext_io_fns.lbextiofn_socket_arg
#ifdef _SOLARIS_SDK
                    , NULL );
#else
                    );
#endif  /* _SOLARIS_SDK */

        } else {
                s = nsldapi_try_each_host( ld, hostlist,
                        defport, secure, nsldapi_os_socket,
                        nsldapi_os_ioctl, nsldapi_os_connect_with_to,
                        NULL, nsldapi_os_closesocket );
        }

        if ( s < 0 ) {
                LDAP_SET_LDERRNO( ld, LDAP_CONNECT_ERROR, NULL, NULL );
                return( -1 );
        }

        sb->sb_sd = s;

        /*
         * Set krbinstancep (canonical name of host for use by Kerberos).
         */
#ifdef KERBEROS
        char    *p;

        if (( *krbinstancep = nsldapi_host_connected_to( sb )) != NULL
            && ( p = strchr( *krbinstancep, '.' )) != NULL ) {
                *p = '\0';
        }
#else /* KERBEROS */
        *krbinstancep = NULL;
#endif /* KERBEROS */

        return( 0 );
}


/*
 * Returns a socket number if successful and -1 if an error occurs.
 */
static int
nsldapi_try_each_host( LDAP *ld, const char *hostlist,
        int defport, int secure, NSLDAPI_SOCKET_FN *socketfn,
        NSLDAPI_IOCTL_FN *ioctlfn, NSLDAPI_CONNECT_WITH_TO_FN *connectwithtofn,
        NSLDAPI_CONNECT_FN *connectfn, NSLDAPI_CLOSE_FN *closefn )
{
        int                     rc, i, s, err, connected, use_hp;
        int                     parse_err, port;
        struct sockaddr_in      sin;
        nsldapi_in_addr_t       address;
        char                    **addrlist, *ldhpbuf, *ldhpbuf_allocd;
        char                    *host;
        LDAPHostEnt             ldhent, *ldhp;
        struct hostent          *hp;
        struct ldap_x_hostlist_status   *status;
#ifdef GETHOSTBYNAME_BUF_T
        GETHOSTBYNAME_BUF_T     hbuf;
        struct hostent          hent;
#endif /* GETHOSTBYNAME_BUF_T */

        connected = 0;
        parse_err = ldap_x_hostlist_first( hostlist, defport, &host, &port,
            &status );
        while ( !connected && LDAP_SUCCESS == parse_err && host != NULL ) {
                ldhpbuf_allocd = NULL;
                ldhp = NULL;
                hp = NULL;
                s = 0;
                use_hp = 0;
                addrlist = NULL;


                if (( address = inet_addr( host )) == -1 ) {
                        if ( ld->ld_dns_gethostbyname_fn == NULL ) {
                                if (( hp = GETHOSTBYNAME( host, &hent, hbuf,
                                    sizeof(hbuf), &err )) != NULL ) {
                                        addrlist = hp->h_addr_list;
                                }
                        } else {
                                /*
                                 * DNS callback installed... use it.
                                 */
#ifdef GETHOSTBYNAME_buf_t
                                /* avoid allocation by using hbuf if large enough */
                                if ( sizeof( hbuf ) < ld->ld_dns_bufsize ) {
                                        ldhpbuf = ldhpbuf_allocd
                                            = NSLDAPI_MALLOC( ld->ld_dns_bufsize );
                                } else {
                                        ldhpbuf = (char *)hbuf;
                                }
#else /* GETHOSTBYNAME_buf_t */
                                ldhpbuf = ldhpbuf_allocd = NSLDAPI_MALLOC(
                                    ld->ld_dns_bufsize );
#endif /* else GETHOSTBYNAME_buf_t */

                                if ( ldhpbuf == NULL ) {
                                        LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY,
                                            NULL, NULL );
                                        ldap_memfree( host );
                                        ldap_x_hostlist_statusfree( status );
                                        return( -1 );
                                }

                                if (( ldhp = ld->ld_dns_gethostbyname_fn( host,
                                    &ldhent, ldhpbuf, ld->ld_dns_bufsize, &err,
                                    ld->ld_dns_extradata )) != NULL ) {
                                        addrlist = ldhp->ldaphe_addr_list;
                                }
                        }

                        if ( addrlist == NULL ) {
                                LDAP_SET_LDERRNO( ld, LDAP_CONNECT_ERROR, NULL, NULL );
                                LDAP_SET_ERRNO( ld, EHOSTUNREACH );  /* close enough */
                                if ( ldhpbuf_allocd != NULL ) {
                                        NSLDAPI_FREE( ldhpbuf_allocd );
                                }
                                ldap_memfree( host );
                                ldap_x_hostlist_statusfree( status );
                                return( -1 );
                        }
                        use_hp = 1;
                }

                rc = -1;
                for ( i = 0; !use_hp || ( addrlist[ i ] != 0 ); i++ ) {
                        if ( -1 == ( s = (*socketfn)( ld, secure, AF_INET,
                                        SOCK_STREAM, 0 ))) {
                                if ( ldhpbuf_allocd != NULL ) {
                                        NSLDAPI_FREE( ldhpbuf_allocd );
                                }
                                ldap_memfree( host );
                                ldap_x_hostlist_statusfree( status );
                                return( -1 );
                        }

                        if ( ld->ld_options & LDAP_BITOPT_ASYNC ) {
                                int     iostatus = 1;

                                err = (*ioctlfn)( s, FIONBIO, &iostatus );
                                if ( err == -1 ) {
                                        LDAPDebug( LDAP_DEBUG_ANY,
                                            "FIONBIO ioctl failed on %d\n",
                                            s, 0, 0 );
                                }
                        }

                        (void)memset( (char *)&sin, 0, sizeof( struct sockaddr_in ));
                        sin.sin_family = AF_INET;
                        sin.sin_port = htons( (unsigned short)port );

                        SAFEMEMCPY( (char *) &sin.sin_addr.s_addr,
                            ( use_hp ? (char *) addrlist[ i ] :
                            (char *) &address ), sizeof( sin.sin_addr.s_addr) );

                        {
#ifdef LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED
/*
 * Block all of the signals that might interrupt connect() since there
 * is an OS bug that causes connect() to fail if it is restarted.  Look in
 * ns/netsite/ldap/include/portable.h for the definition of
 * LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED
 */
                                sigset_t        ints_off, oldset;

                                sigemptyset( &ints_off );
                                sigaddset( &ints_off, SIGALRM );
                                sigaddset( &ints_off, SIGIO );
                                sigaddset( &ints_off, SIGCLD );

                                sigprocmask( SIG_BLOCK, &ints_off, &oldset );
#endif /* LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED */

                                if ( NULL != connectwithtofn  ) {
                                        err = (*connectwithtofn)(s,
                                                (struct sockaddr *)&sin,
                                                sizeof(struct sockaddr_in),
                                                ld);
                                } else {
                                        err = (*connectfn)(s,
                                                (struct sockaddr *)&sin,
                                                sizeof(struct sockaddr_in));
                                }
#ifdef LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED
/*
 * restore original signal mask
 */
                                sigprocmask( SIG_SETMASK, &oldset, 0 );
#endif /* LDAP_CONNECT_MUST_NOT_BE_INTERRUPTED */

                        }
                        if ( err >= 0 ) {
                                connected = 1;
                                rc = 0;
                                break;
                        } else {
                                if ( ld->ld_options & LDAP_BITOPT_ASYNC) {
#ifdef _WINDOWS
                                        if (err == -1 && WSAGetLastError() == WSAEWOULDBLOCK)
                                                LDAP_SET_ERRNO( ld, EWOULDBLOCK );
#endif /* _WINDOWS */
                                        err = LDAP_GET_ERRNO( ld );
                                        if ( NSLDAPI_ERRNO_IO_INPROGRESS( err )) {
                                                LDAPDebug( LDAP_DEBUG_TRACE, "connect would block...\n",
                                                           0, 0, 0 );
                                                rc = -2;
                                                break;
                                        }
                                }

#ifdef LDAP_DEBUG
                                if ( ldap_debug & LDAP_DEBUG_TRACE ) {
                                        perror( (char *)inet_ntoa( sin.sin_addr ));
                                }
#endif
                                (*closefn)( s );
                                if ( !use_hp ) {
                                        break;
                                }
                        }
                }

                ldap_memfree( host );
                parse_err = ldap_x_hostlist_next( &host, &port, status );
        }

        if ( ldhpbuf_allocd != NULL ) {
                NSLDAPI_FREE( ldhpbuf_allocd );
        }
        ldap_memfree( host );
        ldap_x_hostlist_statusfree( status );

        if ( connected ) {
                LDAPDebug( LDAP_DEBUG_TRACE, "sd %d connected to: %s\n",
                    s, inet_ntoa( sin.sin_addr ), 0 );
        }

        return( rc == 0 ? s : -1 );
}


void
nsldapi_close_connection( LDAP *ld, Sockbuf *sb )
{
        if ( ld->ld_extclose_fn == NULL ) {
                nsldapi_os_closesocket( sb->sb_sd );
        } else {
                ld->ld_extclose_fn( sb->sb_sd,
                            sb->sb_ext_io_fns.lbextiofn_socket_arg );
        }
}


#ifdef  KERBEROS
char *
nsldapi_host_connected_to( Sockbuf *sb )
{
        struct hostent          *hp;
        char                    *p;
        int                     len;
        struct sockaddr_in      sin;

        (void)memset( (char *)&sin, 0, sizeof( struct sockaddr_in ));
        len = sizeof( sin );
        if ( getpeername( sb->sb_sd, (struct sockaddr *)&sin, &len ) == -1 ) {
            return( NULL );
        }

        /*
         * do a reverse lookup on the addr to get the official hostname.
         * this is necessary for kerberos to work right, since the official
         * hostname is used as the kerberos instance.
         */
#error XXXmcs: need to use DNS callbacks here
        if (( hp = gethostbyaddr((char *) &sin.sin_addr,
            sizeof( sin.sin_addr ), AF_INET)) != NULL ) {
            if ( hp->h_name != NULL ) {
                return( nsldapi_strdup( hp->h_name ));
            }
        }

        return( NULL );
}
#endif /* KERBEROS */


/*
 * Returns 0 if all goes well and -1 if an error occurs (error code set in ld)
 * Also allocates initializes ld->ld_iostatus if needed..
 */
int
nsldapi_iostatus_interest_write( LDAP *ld, Sockbuf *sb )
{
        NSLDAPIIOStatus *iosp;

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );

        if ( ld->ld_iostatus == NULL
            && nsldapi_iostatus_init_nolock( ld ) < 0 ) {
                LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
                return( -1 );
        }

        iosp = ld->ld_iostatus;

        if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                if ( nsldapi_add_to_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, POLLOUT )) {
                        ++iosp->ios_write_count;
                }
#else /* NSLDAPI_HAVE_POLL */
                if ( !FD_ISSET( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo.ossi_writefds )) {
                        FD_SET( sb->sb_sd,
                            &iosp->ios_status.ios_osinfo.ossi_writefds );
                        ++iosp->ios_write_count;
                }
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                if ( nsldapi_add_to_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, LDAP_X_POLLOUT )) {
                        ++iosp->ios_write_count;
                }

        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_interest_write: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );

        return( 0 );
}


/*
 * Returns 0 if all goes well and -1 if an error occurs (error code set in ld)
 * Also allocates initializes ld->ld_iostatus if needed..
 */
int
nsldapi_iostatus_interest_read( LDAP *ld, Sockbuf *sb )
{
        NSLDAPIIOStatus *iosp;

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );

        if ( ld->ld_iostatus == NULL
            && nsldapi_iostatus_init_nolock( ld ) < 0 ) {
                LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
                return( -1 );
        }

        iosp = ld->ld_iostatus;

        if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                if ( nsldapi_add_to_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, POLLIN )) {
                        ++iosp->ios_read_count;
                }
#else /* NSLDAPI_HAVE_POLL */
                if ( !FD_ISSET( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo.ossi_readfds )) {
                        FD_SET( sb->sb_sd,
                            &iosp->ios_status.ios_osinfo.ossi_readfds );
                        ++iosp->ios_read_count;
                }
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                if ( nsldapi_add_to_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, LDAP_X_POLLIN )) {
                        ++iosp->ios_read_count;
                }
        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_interest_read: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );

        return( 0 );
}


/*
 * Returns 0 if all goes well and -1 if an error occurs (error code set in ld)
 * Also allocates initializes ld->ld_iostatus if needed..
 */
int
nsldapi_iostatus_interest_clear( LDAP *ld, Sockbuf *sb )
{
        NSLDAPIIOStatus *iosp;

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );

        if ( ld->ld_iostatus == NULL
            && nsldapi_iostatus_init_nolock( ld ) < 0 ) {
                LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
                return( -1 );
        }

        iosp = ld->ld_iostatus;

        if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                if ( nsldapi_clear_from_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, POLLOUT )) {
                        --iosp->ios_write_count;
                }
                if ( nsldapi_clear_from_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, POLLIN )) {
                        --iosp->ios_read_count;
                }
#else /* NSLDAPI_HAVE_POLL */
                if ( FD_ISSET( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo.ossi_writefds )) {
                        FD_CLR( sb->sb_sd,
                            &iosp->ios_status.ios_osinfo.ossi_writefds );
                        --iosp->ios_write_count;
                }
                if ( FD_ISSET( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo.ossi_readfds )) {
                        FD_CLR( sb->sb_sd,
                            &iosp->ios_status.ios_osinfo.ossi_readfds );
                        --iosp->ios_read_count;
                }
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                if ( nsldapi_clear_from_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, LDAP_X_POLLOUT )) {
                        --iosp->ios_write_count;
                }
                if ( nsldapi_clear_from_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, LDAP_X_POLLIN )) {
                        --iosp->ios_read_count;
                }
        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_interest_clear: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );

        return( 0 );
}


/*
 * Return a non-zero value if sb is ready for write.
 */
int
nsldapi_iostatus_is_write_ready( LDAP *ld, Sockbuf *sb )
{
        int             rc;
        NSLDAPIIOStatus *iosp;

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );
        iosp = ld->ld_iostatus;

        if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                /*
                 * if we are using poll() we do something a little tricky: if
                 * any bits in the socket's returned events field other than
                 * POLLIN (ready for read) are set, we return true.  This
                 * is done so we notice when a server closes a connection
                 * or when another error occurs.  The actual error will be
                 * noticed later when we call write() or send().
                 */
                rc = nsldapi_find_in_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, ~POLLIN );

#else /* NSLDAPI_HAVE_POLL */
                rc = FD_ISSET( sb->sb_sd,
                        &iosp->ios_status.ios_osinfo.ossi_use_writefds );
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                rc = nsldapi_find_in_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, ~LDAP_X_POLLIN );

        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_is_write_ready: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
                rc = 0;
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
        return( rc );
}


/*
 * Return a non-zero value if sb is ready for read.
 */
int
nsldapi_iostatus_is_read_ready( LDAP *ld, Sockbuf *sb )
{
        int             rc;
        NSLDAPIIOStatus *iosp;

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );
        iosp = ld->ld_iostatus;

        if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                /*
                 * if we are using poll() we do something a little tricky: if
                 * any bits in the socket's returned events field other than
                 * POLLOUT (ready for write) are set, we return true.  This
                 * is done so we notice when a server closes a connection
                 * or when another error occurs.  The actual error will be
                 * noticed later when we call read() or recv().
                 */
                rc = nsldapi_find_in_os_pollfds( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo, ~POLLOUT );

#else /* NSLDAPI_HAVE_POLL */
                rc = FD_ISSET( sb->sb_sd,
                    &iosp->ios_status.ios_osinfo.ossi_use_readfds );
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                rc = nsldapi_find_in_cb_pollfds( sb,
                    &iosp->ios_status.ios_cbinfo, ~LDAP_X_POLLOUT );

        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_is_read_ready: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
                rc = 0;
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
        return( rc );
}


/*
 * Allocated and initialize ld->ld_iostatus if not already done.
 * Should be called with LDAP_IOSTATUS_LOCK locked.
 * Returns 0 if all goes well and -1 if not (sets error in ld)
 */
static int
nsldapi_iostatus_init_nolock( LDAP *ld )
{
        NSLDAPIIOStatus *iosp;

        if ( ld->ld_iostatus != NULL ) {
                return( 0 );
        }

        if (( iosp = (NSLDAPIIOStatus *)NSLDAPI_CALLOC( 1,
            sizeof( NSLDAPIIOStatus ))) == NULL ) {
                LDAP_SET_LDERRNO( ld, LDAP_NO_MEMORY, NULL, NULL );
                return( -1 );
        }

        if ( ld->ld_extpoll_fn == NULL ) {
                iosp->ios_type = NSLDAPI_IOSTATUS_TYPE_OSNATIVE;
#ifndef NSLDAPI_HAVE_POLL
                FD_ZERO( &iosp->ios_status.ios_osinfo.ossi_readfds );
                FD_ZERO( &iosp->ios_status.ios_osinfo.ossi_writefds );
#endif /* !NSLDAPI_HAVE_POLL */

        } else {
                iosp->ios_type = NSLDAPI_IOSTATUS_TYPE_CALLBACK;
        }

        ld->ld_iostatus = iosp;
        return( 0 );
}


void
nsldapi_iostatus_free( LDAP *ld )
{
        if ( ld == NULL ) {
                return;
        }


        /* clean up classic I/O compatibility glue */
        if ( ld->ld_io_fns_ptr != NULL ) {
                if ( ld->ld_ext_session_arg != NULL ) {
                        NSLDAPI_FREE( ld->ld_ext_session_arg );
                }
                NSLDAPI_FREE( ld->ld_io_fns_ptr );
        }

        /* clean up I/O status tracking info. */
        if ( ld->ld_iostatus != NULL ) {
                NSLDAPIIOStatus *iosp = ld->ld_iostatus;

                if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL
                        if ( iosp->ios_status.ios_osinfo.ossi_pollfds
                            != NULL ) {
                                NSLDAPI_FREE(
                                    iosp->ios_status.ios_osinfo.ossi_pollfds );
                        }
#endif /* NSLDAPI_HAVE_POLL */

                } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                        if ( iosp->ios_status.ios_cbinfo.cbsi_pollfds
                            != NULL ) {
                                NSLDAPI_FREE(
                                    iosp->ios_status.ios_cbinfo.cbsi_pollfds );
                        }
                } else {
                        LDAPDebug( LDAP_DEBUG_ANY,
                            "nsldapi_iostatus_free: unknown I/O type %d\n",
                             iosp->ios_type, 0, 0 );
                }

                NSLDAPI_FREE( iosp );
        }
}


static int
nsldapi_get_select_table_size( void )
{
        static int      tblsize = 0;    /* static */

        if ( tblsize == 0 ) {
#if defined(_WINDOWS) || defined(XP_OS2)
                tblsize = FOPEN_MAX; /* ANSI spec. */
#else
#ifdef USE_SYSCONF
                tblsize = sysconf( _SC_OPEN_MAX );
#else /* USE_SYSCONF */
                tblsize = getdtablesize();
#endif /* else USE_SYSCONF */
#endif /* else _WINDOWS */

                if ( tblsize >= FD_SETSIZE ) {
                        /*
                         * clamp value so we don't overrun the fd_set structure
                         */
                        tblsize = FD_SETSIZE - 1;
                }
        }

        return( tblsize );
}

static int
nsldapi_tv2ms( struct timeval *tv )
{
        if ( tv == NULL ) {
                return( -1 );   /* infinite timout for poll() */
        }

        return( tv->tv_sec * 1000 + tv->tv_usec / 1000 );
}


int
nsldapi_iostatus_poll( LDAP *ld, struct timeval *timeout )
{
        int                     rc;
        NSLDAPIIOStatus         *iosp;

        LDAPDebug( LDAP_DEBUG_TRACE, "nsldapi_iostatus_poll\n", 0, 0, 0 );

        LDAP_MUTEX_LOCK( ld, LDAP_IOSTATUS_LOCK );
        iosp = ld->ld_iostatus;

        if ( iosp == NULL ||
            ( iosp->ios_read_count <= 0 && iosp->ios_read_count <= 0 )) {
                rc = 0;         /* simulate a timeout */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_OSNATIVE ) {
#ifdef NSLDAPI_HAVE_POLL

                rc = NSLDAPI_POLL( iosp->ios_status.ios_osinfo.ossi_pollfds,
                    iosp->ios_status.ios_osinfo.ossi_pollfds_size,
                    nsldapi_tv2ms( timeout ));

#else /* NSLDAPI_HAVE_POLL */

                /* two (potentially large) struct copies */
                iosp->ios_status.ios_osinfo.ossi_use_readfds
                    = iosp->ios_status.ios_osinfo.ossi_readfds;
                iosp->ios_status.ios_osinfo.ossi_use_writefds
                    = iosp->ios_status.ios_osinfo.ossi_writefds;

#ifdef HPUX9
                rc = NSLDAPI_SELECT( nsldapi_get_select_table_size(),
                    (int *)&iosp->ios_status.ios_osinfo.ossi_use_readfds
                    (int *)&iosp->ios_status.ios_osinfo.ossi_use_writefds,
                    NULL, timeout );
#else
                rc = NSLDAPI_SELECT( nsldapi_get_select_table_size(),
                    &iosp->ios_status.ios_osinfo.ossi_use_readfds,
                    &iosp->ios_status.ios_osinfo.ossi_use_writefds,
                    NULL, timeout );
#endif /* else HPUX9 */
#endif /* else NSLDAPI_HAVE_POLL */

        } else if ( iosp->ios_type == NSLDAPI_IOSTATUS_TYPE_CALLBACK ) {
                /*
                 * We always pass the session extended I/O argument to
                 * the extended poll() callback.
                 */
                rc = ld->ld_extpoll_fn(
                    iosp->ios_status.ios_cbinfo.cbsi_pollfds,
                    iosp->ios_status.ios_cbinfo.cbsi_pollfds_size,
                    nsldapi_tv2ms( timeout ), ld->ld_ext_session_arg );

        } else {
                LDAPDebug( LDAP_DEBUG_ANY,
                    "nsldapi_iostatus_poll: unknown I/O type %d\n",
                     iosp->ios_type, 0, 0 );
                rc = 0; /* simulate a timeout (what else to do?) */
        }

        LDAP_MUTEX_UNLOCK( ld, LDAP_IOSTATUS_LOCK );
        return( rc );
}


#ifdef NSLDAPI_HAVE_POLL
/*
 * returns 1 if "fd" was added to pollfds.
 * returns 1 if some of the bits in "events" were added to pollfds.
 * returns 0 if no changes were made.
 */
static int
nsldapi_add_to_os_pollfds( int fd, struct nsldapi_os_statusinfo *pip,
        short events )
{
        int     i, openslot;

        /* first we check to see if "fd" is already in our pollfds */
        openslot = -1;
        for ( i = 0; i < pip->ossi_pollfds_size; ++i ) {
                if ( pip->ossi_pollfds[ i ].fd == fd ) {
                        if (( pip->ossi_pollfds[ i ].events & events )
                            != events ) {
                                pip->ossi_pollfds[ i ].events |= events;
                                return( 1 );
                        } else {
                                return( 0 );
                        }
                }
                if ( pip->ossi_pollfds[ i ].fd == -1 && openslot == -1 ) {
                        openslot = i;   /* remember for later */
                }
        }

        /*
         * "fd" is not currently being poll'd on -- add to array.
         * if we need to expand the pollfds array, we do it in increments of
         * NSLDAPI_POLL_ARRAY_GROWTH (#define near the top of this file).
         */
        if ( openslot == -1 ) {
                struct pollfd   *newpollfds;

                if ( pip->ossi_pollfds_size == 0 ) {
                        newpollfds = (struct pollfd *)NSLDAPI_MALLOC(
                            NSLDAPI_POLL_ARRAY_GROWTH
                            * sizeof( struct pollfd ));
                } else {
                        newpollfds = (struct pollfd *)NSLDAPI_REALLOC(
                            pip->ossi_pollfds, (NSLDAPI_POLL_ARRAY_GROWTH
                            + pip->ossi_pollfds_size)
                            * sizeof( struct pollfd ));
                }
                if ( newpollfds == NULL ) { /* XXXmcs: no way to return err! */
                        return( 0 );
                }
                pip->ossi_pollfds = newpollfds;
                openslot = pip->ossi_pollfds_size;
                pip->ossi_pollfds_size += NSLDAPI_POLL_ARRAY_GROWTH;
                for ( i = openslot + 1; i < pip->ossi_pollfds_size; ++i ) {
                        pip->ossi_pollfds[ i ].fd = -1;
                        pip->ossi_pollfds[ i ].events =
                            pip->ossi_pollfds[ i ].revents = 0;
                }
        }
        pip->ossi_pollfds[ openslot ].fd = fd;
        pip->ossi_pollfds[ openslot ].events = events;
        pip->ossi_pollfds[ openslot ].revents = 0;
        return( 1 );
}


/*
 * returns 1 if any "events" from "fd" were removed from pollfds
 * returns 0 of "fd" wasn't in pollfds or if events did not overlap.
 */
static int
nsldapi_clear_from_os_pollfds( int fd, struct nsldapi_os_statusinfo *pip,
    short events )
{
        int     i;

        for ( i = 0; i < pip->ossi_pollfds_size; ++i ) {
                if ( pip->ossi_pollfds[i].fd == fd ) {
                        if (( pip->ossi_pollfds[ i ].events & events ) != 0 ) {
                                pip->ossi_pollfds[ i ].events &= ~events;
                                if ( pip->ossi_pollfds[ i ].events == 0 ) {
                                        pip->ossi_pollfds[i].fd = -1;
                                }
                                return( 1 );    /* events overlap */
                        } else {
                                return( 0 );    /* events do not overlap */
                        }
                }
        }

        return( 0 );    /* "fd" was not found */
}


/*
 * returns 1 if any "revents" from "fd" were set in pollfds revents field.
 * returns 0 if not.
 */
static int
nsldapi_find_in_os_pollfds( int fd, struct nsldapi_os_statusinfo *pip,
        short revents )
{
        int     i;

        for ( i = 0; i < pip->ossi_pollfds_size; ++i ) {
                if ( pip->ossi_pollfds[i].fd == fd ) {
                        if (( pip->ossi_pollfds[ i ].revents & revents ) != 0 ) {
                                return( 1 );    /* revents overlap */
                        } else {
                                return( 0 );    /* revents do not overlap */
                        }
                }
        }

        return( 0 );    /* "fd" was not found */
}
#endif /* NSLDAPI_HAVE_POLL */


/*
 * returns 1 if "sb" was added to pollfds.
 * returns 1 if some of the bits in "events" were added to pollfds.
 * returns 0 if no changes were made.
 */
static int
nsldapi_add_to_cb_pollfds( Sockbuf *sb, struct nsldapi_cb_statusinfo *pip,
    short events )
{
        int     i, openslot;

        /* first we check to see if "sb" is already in our pollfds */
        openslot = -1;
        for ( i = 0; i < pip->cbsi_pollfds_size; ++i ) {
                if ( NSLDAPI_CB_POLL_MATCH( sb, pip->cbsi_pollfds[ i ] )) {
                        if (( pip->cbsi_pollfds[ i ].lpoll_events & events )
                            != events ) {
                                pip->cbsi_pollfds[ i ].lpoll_events |= events;
                                return( 1 );
                        } else {
                                return( 0 );
                        }
                }
                if ( pip->cbsi_pollfds[ i ].lpoll_fd == -1 && openslot == -1 ) {
                        openslot = i;   /* remember for later */
                }
        }

        /*
         * "sb" is not currently being poll'd on -- add to array.
         * if we need to expand the pollfds array, we do it in increments of
         * NSLDAPI_POLL_ARRAY_GROWTH (#define near the top of this file).
         */
        if ( openslot == -1 ) {
                LDAP_X_PollFD   *newpollfds;

                if ( pip->cbsi_pollfds_size == 0 ) {
                        newpollfds = (LDAP_X_PollFD *)NSLDAPI_MALLOC(
                            NSLDAPI_POLL_ARRAY_GROWTH
                            * sizeof( LDAP_X_PollFD ));
                } else {
                        newpollfds = (LDAP_X_PollFD *)NSLDAPI_REALLOC(
                            pip->cbsi_pollfds, (NSLDAPI_POLL_ARRAY_GROWTH
                            + pip->cbsi_pollfds_size)
                            * sizeof( LDAP_X_PollFD ));
                }
                if ( newpollfds == NULL ) { /* XXXmcs: no way to return err! */
                        return( 0 );
                }
                pip->cbsi_pollfds = newpollfds;
                openslot = pip->cbsi_pollfds_size;
                pip->cbsi_pollfds_size += NSLDAPI_POLL_ARRAY_GROWTH;
                for ( i = openslot + 1; i < pip->cbsi_pollfds_size; ++i ) {
                        pip->cbsi_pollfds[ i ].lpoll_fd = -1;
                        pip->cbsi_pollfds[ i ].lpoll_socketarg = NULL;
                        pip->cbsi_pollfds[ i ].lpoll_events =
                            pip->cbsi_pollfds[ i ].lpoll_revents = 0;
                }
        }
        pip->cbsi_pollfds[ openslot ].lpoll_fd = sb->sb_sd;
        pip->cbsi_pollfds[ openslot ].lpoll_socketarg =
            sb->sb_ext_io_fns.lbextiofn_socket_arg;
        pip->cbsi_pollfds[ openslot ].lpoll_events = events;
        pip->cbsi_pollfds[ openslot ].lpoll_revents = 0;
        return( 1 );
}


/*
 * returns 1 if any "events" from "sb" were removed from pollfds
 * returns 0 of "sb" wasn't in pollfds or if events did not overlap.
 */
static int
nsldapi_clear_from_cb_pollfds( Sockbuf *sb,
    struct nsldapi_cb_statusinfo *pip, short events )
{
        int     i;

        for ( i = 0; i < pip->cbsi_pollfds_size; ++i ) {
                if ( NSLDAPI_CB_POLL_MATCH( sb, pip->cbsi_pollfds[ i ] )) {
                        if (( pip->cbsi_pollfds[ i ].lpoll_events
                            & events ) != 0 ) {
                                pip->cbsi_pollfds[ i ].lpoll_events &= ~events;
                                if ( pip->cbsi_pollfds[ i ].lpoll_events
                                    == 0 ) {
                                        pip->cbsi_pollfds[i].lpoll_fd = -1;
                                }
                                return( 1 );    /* events overlap */
                        } else {
                                return( 0 );    /* events do not overlap */
                        }
                }
        }

        return( 0 );    /* "sb" was not found */
}


/*
 * returns 1 if any "revents" from "sb" were set in pollfds revents field.
 * returns 0 if not.
 */
static int
nsldapi_find_in_cb_pollfds( Sockbuf *sb, struct nsldapi_cb_statusinfo *pip,
    short revents )
{
        int     i;

        for ( i = 0; i < pip->cbsi_pollfds_size; ++i ) {
                if ( NSLDAPI_CB_POLL_MATCH( sb, pip->cbsi_pollfds[ i ] )) {
                        if (( pip->cbsi_pollfds[ i ].lpoll_revents
                            & revents ) != 0 ) {
                                return( 1 );    /* revents overlap */
                        } else {
                                return( 0 );    /* revents do not overlap */
                        }
                }
        }

        return( 0 );    /* "sb" was not found */
}


/*
 * Install read and write functions into lber layer / sb
 */
int
nsldapi_install_lber_extiofns( LDAP *ld, Sockbuf *sb )
{
        struct lber_x_ext_io_fns        lberiofns;

        memset( &lberiofns, 0, sizeof(struct lber_x_ext_io_fns) );
        if ( NULL != sb ) {
                lberiofns.lbextiofn_size = LBER_X_EXTIO_FNS_SIZE;
                lberiofns.lbextiofn_read = ld->ld_extread_fn;
                lberiofns.lbextiofn_write = ld->ld_extwrite_fn;
                lberiofns.lbextiofn_writev = ld->ld_extwritev_fn;
                lberiofns.lbextiofn_socket_arg = ld->ld_ext_session_arg;

                if ( ber_sockbuf_set_option( sb, LBER_SOCKBUF_OPT_EXT_IO_FNS,
                    &lberiofns ) != 0 ) {
                        return( LDAP_LOCAL_ERROR );
                }
        }

        return( LDAP_SUCCESS );
}


/*
 ******************************************************************************
 * One struct and several functions to bridge the gap between new extended
 * I/O functions that are installed using ldap_set_option( ...,
 * LDAP_OPT_EXTIO_FN_PTRS, ... ) and the original "classic" I/O functions
 * (installed using LDAP_OPT_IO_FN_PTRS) follow.
 *
 * Our basic strategy is to use the new extended arg to hold a pointer to a
 *    structure that contains a pointer to the LDAP * (which contains pointers
 *    to the old functions so we can call them) as well as a pointer to an
 *    LBER_SOCKET to hold the socket used by the classic functions (the new
 *    functions use a simple int for the socket).
 */
typedef struct nsldapi_compat_socket_info {
    LBER_SOCKET         csi_socket;
    LDAP                *csi_ld;
} NSLDAPICompatSocketInfo;

static int LDAP_CALLBACK
nsldapi_ext_compat_read( int s, void *buf, int len,
        struct lextiof_socket_private *arg )
{
        NSLDAPICompatSocketInfo *csip = (NSLDAPICompatSocketInfo *)arg;
        struct ldap_io_fns      *iofns = csip->csi_ld->ld_io_fns_ptr;

        return( iofns->liof_read( csip->csi_socket, buf, len ));
}


static int LDAP_CALLBACK
nsldapi_ext_compat_write( int s, const void *buf, int len,
        struct lextiof_socket_private *arg  )
{
        NSLDAPICompatSocketInfo *csip = (NSLDAPICompatSocketInfo *)arg;
        struct ldap_io_fns      *iofns = csip->csi_ld->ld_io_fns_ptr;

        return( iofns->liof_write( csip->csi_socket, buf, len ));
}


static int LDAP_CALLBACK
nsldapi_ext_compat_poll( LDAP_X_PollFD fds[], int nfds, int timeout,
        struct lextiof_session_private *arg )
{
        NSLDAPICompatSocketInfo *csip = (NSLDAPICompatSocketInfo *)arg;
        struct ldap_io_fns      *iofns = csip->csi_ld->ld_io_fns_ptr;
        fd_set                  readfds, writefds;
        int                     i, rc, maxfd = 0;
        struct timeval          tv, *tvp;

        /*
         * Prepare fd_sets for select()
         */
        FD_ZERO( &readfds );
        FD_ZERO( &writefds );
        for ( i = 0; i < nfds; ++i ) {
                if ( fds[ i ].lpoll_fd < 0 ) {
                        continue;
                }

                if ( fds[ i ].lpoll_fd >= FD_SETSIZE ) {
                        LDAP_SET_ERRNO( csip->csi_ld, EINVAL );
                        return( -1 );
                }

                if ( 0 != ( fds[i].lpoll_events & LDAP_X_POLLIN )) {
                        FD_SET( fds[i].lpoll_fd, &readfds );
                }

                if ( 0 != ( fds[i].lpoll_events & LDAP_X_POLLOUT )) {
                        FD_SET( fds[i].lpoll_fd, &writefds );
                }

                fds[i].lpoll_revents = 0;       /* clear revents */

                if ( fds[i].lpoll_fd >= maxfd ) {
                        maxfd = fds[i].lpoll_fd;
                }
        }

        /*
         * select() using callback.
         */
        ++maxfd;
        if ( timeout == -1 ) {
                tvp = NULL;
        } else {
                tv.tv_sec = timeout / 1000;
                tv.tv_usec = 1000 * ( timeout - tv.tv_sec * 1000 );
                tvp = &tv;
        }
        rc = iofns->liof_select( maxfd, &readfds, &writefds, NULL, tvp );
        if ( rc <= 0 ) {        /* timeout or fatal error */
                return( rc );
        }

        /*
         * Use info. in fd_sets to populate poll() revents.
         */
        for ( i = 0; i < nfds; ++i ) {
                if ( fds[ i ].lpoll_fd < 0 ) {
                        continue;
                }

                if ( 0 != ( fds[i].lpoll_events & LDAP_X_POLLIN )
                    && FD_ISSET( fds[i].lpoll_fd, &readfds )) {
                        fds[i].lpoll_revents |= LDAP_X_POLLIN;
                }

                if ( 0 != ( fds[i].lpoll_events & LDAP_X_POLLOUT )
                    && FD_ISSET( fds[i].lpoll_fd, &writefds )) {
                        fds[i].lpoll_revents |= LDAP_X_POLLOUT;
                }

                /* XXXmcs: any other cases to deal with?  LDAP_X_POLLERR? */
        }

        return( rc );
}


static LBER_SOCKET
nsldapi_compat_socket( LDAP *ld, int secure, int domain, int type,
        int protocol )
{
        int             s;

        s = ld->ld_io_fns_ptr->liof_socket( domain, type, protocol );

        if ( s >= 0 ) {
                char                            *errmsg = NULL;

#ifdef NSLDAPI_HAVE_POLL
                if ( ld->ld_io_fns_ptr->liof_select != NULL
                            && s >= FD_SETSIZE ) {
                        errmsg = dgettext(TEXT_DOMAIN,
                                "can't use socket >= FD_SETSIZE");
                }
#elif !defined(_WINDOWS) /* not on Windows and do not have poll() */
                if ( s >= FD_SETSIZE ) {
                        errmsg = "can't use socket >= FD_SETSIZE";
                }
#endif

                if ( NULL == errmsg && secure &&
                            ld->ld_io_fns_ptr->liof_ssl_enable( s ) < 0 ) {
                        errmsg = dgettext(TEXT_DOMAIN,
                                "failed to enable secure mode");
                    }

                if ( NULL != errmsg ) {
                        if ( NULL == ld->ld_io_fns_ptr->liof_close ) {
                                nsldapi_os_closesocket( s );
                        } else {
                                ld->ld_io_fns_ptr->liof_close( s );
                        }
                        LDAP_SET_LDERRNO( ld, LDAP_LOCAL_ERROR, NULL,
                                    nsldapi_strdup( errmsg ));
                        return( -1 );
                }
        }

        return( s );
}


/*
 * Note: timeout is ignored because we have no way to pass it via
 * the old I/O callback interface.
 */
static int LDAP_CALLBACK
nsldapi_ext_compat_connect( const char *hostlist, int defport, int timeout,
        unsigned long options, struct lextiof_session_private *sessionarg,
        struct lextiof_socket_private **socketargp
#ifdef _SOLARIS_SDK
        , void **not_used )
#else
        )
#endif  /* _SOLARIS_SDK */
{
        NSLDAPICompatSocketInfo         *defcsip;
        struct ldap_io_fns              *iofns;
        int                             s, secure;
        NSLDAPI_SOCKET_FN               *socketfn;
        NSLDAPI_IOCTL_FN                *ioctlfn;
        NSLDAPI_CONNECT_WITH_TO_FN      *connectwithtofn;
        NSLDAPI_CONNECT_FN              *connectfn;
        NSLDAPI_CLOSE_FN                *closefn;

        defcsip = (NSLDAPICompatSocketInfo *)sessionarg;
        iofns = defcsip->csi_ld->ld_io_fns_ptr;

        if ( 0 != ( options & LDAP_X_EXTIOF_OPT_SECURE )) {
                if ( NULL == iofns->liof_ssl_enable ) {
                        LDAP_SET_ERRNO( defcsip->csi_ld, EINVAL );
                        return( -1 );
                }
                secure = 1;
        } else {
                secure = 0;
        }

        socketfn = ( iofns->liof_socket == NULL ) ?
                    nsldapi_os_socket : nsldapi_compat_socket;
        ioctlfn = ( iofns->liof_ioctl == NULL ) ?
                    nsldapi_os_ioctl : (NSLDAPI_IOCTL_FN *)(iofns->liof_ioctl);
        if ( NULL == iofns->liof_connect ) {
                connectwithtofn = nsldapi_os_connect_with_to;
                connectfn = NULL;
        } else {
                connectwithtofn = NULL;
                connectfn = iofns->liof_connect;
        }
        closefn = ( iofns->liof_close == NULL ) ?
                    nsldapi_os_closesocket : iofns->liof_close;

        s = nsldapi_try_each_host( defcsip->csi_ld, hostlist, defport,
                        secure, socketfn, ioctlfn, connectwithtofn,
                        connectfn, closefn );

        if ( s >= 0 ) {
                NSLDAPICompatSocketInfo         *csip;

                if (( csip = (NSLDAPICompatSocketInfo *)NSLDAPI_CALLOC( 1,
                    sizeof( NSLDAPICompatSocketInfo ))) == NULL ) {
                        (*closefn)( s );
                        LDAP_SET_LDERRNO( defcsip->csi_ld, LDAP_NO_MEMORY,
                                NULL, NULL );
                        return( -1 );
                }

                csip->csi_socket = s;
                csip->csi_ld = defcsip->csi_ld;
                *socketargp = (void *)csip;

                /*
                 * We always return 1, which is a valid but not unique socket
                 * (file descriptor) number.  The extended I/O functions only
                 * require that the combination of the void *arg and the int
                 * socket be unique.  Since we allocate the
                 * NSLDAPICompatSocketInfo that we assign to arg, we meet
                 * that requirement.
                 */
                s = 1;
        }

        return( s );
}


static int LDAP_CALLBACK
nsldapi_ext_compat_close( int s, struct lextiof_socket_private *arg )
{
        NSLDAPICompatSocketInfo *csip = (NSLDAPICompatSocketInfo *)arg;
        struct ldap_io_fns      *iofns = csip->csi_ld->ld_io_fns_ptr;
        int                     rc;

        rc = iofns->liof_close( csip->csi_socket );

        NSLDAPI_FREE( csip );

        return( rc );
}

/*
 * Install the I/O functions.
 * Return an LDAP error code (LDAP_SUCCESS if all goes well).
 */
int
nsldapi_install_compat_io_fns( LDAP *ld, struct ldap_io_fns *iofns )
{
        NSLDAPICompatSocketInfo         *defcsip;

        if (( defcsip = (NSLDAPICompatSocketInfo *)NSLDAPI_CALLOC( 1,
            sizeof( NSLDAPICompatSocketInfo ))) == NULL ) {
                return( LDAP_NO_MEMORY );
        }

        defcsip->csi_socket = -1;
        defcsip->csi_ld = ld;

        if ( ld->ld_io_fns_ptr != NULL ) {
                (void)memset( (char *)ld->ld_io_fns_ptr, 0,
                    sizeof( struct ldap_io_fns ));
        } else if (( ld->ld_io_fns_ptr = (struct ldap_io_fns *)NSLDAPI_CALLOC(
            1, sizeof( struct ldap_io_fns ))) == NULL ) {
                NSLDAPI_FREE( defcsip );
                return( LDAP_NO_MEMORY );
        }

        /* struct copy */
        *(ld->ld_io_fns_ptr) = *iofns;

        ld->ld_extio_size = LBER_X_EXTIO_FNS_SIZE;
        ld->ld_ext_session_arg = defcsip;
        ld->ld_extread_fn = nsldapi_ext_compat_read;
        ld->ld_extwrite_fn = nsldapi_ext_compat_write;
        ld->ld_extpoll_fn = nsldapi_ext_compat_poll;
        ld->ld_extconnect_fn = nsldapi_ext_compat_connect;
        ld->ld_extclose_fn = nsldapi_ext_compat_close;

        return( nsldapi_install_lber_extiofns( ld, ld->ld_sbp ));
}
/*
 * end of compat I/O functions
 ******************************************************************************
 */
#ifdef _SOLARIS_SDK
/*
 * _ns_gethostbyaddr is a helper function for the ssl layer so that
 * it can use the ldap layer's gethostbyaddr resolver.
 */

LDAPHostEnt *
_ns_gethostbyaddr(LDAP *ld, const char *addr, int length, int type,
        LDAPHostEnt *result, char *buffer, int buflen, int *statusp,
        void *extradata)
{
        if (ld == NULL || ld->ld_dns_gethostbyaddr_fn == NULL)
                return (NULL);
        return (ld->ld_dns_gethostbyaddr_fn(addr, length, type,
                result, buffer, buflen, statusp, extradata));
}

#endif  /* _SOLARIS_SDK */