/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * 
 * The contents of this file are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 * 
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998-1999
 * the Initial Developer. All Rights Reserved.
 * 
 * Contributor(s):
 *   Sun Microsystems
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */

/*
 * ldapsinit.c
 */

#if defined(NET_SSL)

#if defined( _WINDOWS )
#include <windows.h>
#endif

/* XXX:mhein The following is a workaround for the redefinition of */
/*           const problem on OSF.  Fix to be provided by NSS */
/*           This is a pretty benign workaround for us which */
/*           should not cause problems in the future even if */
/*           we forget to take it out :-) */

#ifdef OSF1V4D
#ifndef __STDC__
#  define __STDC__
#endif /* __STDC__ */
#endif /* OSF1V4D */

#include <errno.h>
#include <nspr.h>
#include <cert.h>
#include <key.h>
#include <ssl.h>
#include <sslproto.h>
#include <sslerr.h>
#include <prnetdb.h>

#include <ldap.h>
#include <ldap_ssl.h>
#include <ldappr.h>
#include <pk11func.h>

/*
 * Macro that determines how many SSL options we support. As of June, 2002
 * NSS supports 14 options numbered 1-14 (see nss/ssl.h).  We allow some
 * room for expansion.
 */
#define LDAPSSL_MAX_SSL_OPTION		20

/*
 * Data structure to hold the standard NSPR I/O function pointers set by
 * libprldap.   We save them in our session data structure so we can call
 * them from our own I/O functions (we add functionality to support SSL
 * while using libprldap's functions as much as possible).
 */
typedef struct ldapssl_std_functions {
    LDAP_X_EXTIOF_CLOSE_CALLBACK		*lssf_close_fn;
    LDAP_X_EXTIOF_CONNECT_CALLBACK		*lssf_connect_fn;
    LDAP_X_EXTIOF_DISPOSEHANDLE_CALLBACK	*lssf_disposehdl_fn;
} LDAPSSLStdFunctions;



/*
 * LDAP session data structure.
 */
typedef struct ldapssl_session_info {
    int			lssei_using_pcks_fns;
    int			lssei_ssl_strength;
    PRBool		lssei_ssl_ready;
    PRBool		lssei_tls_init; 	/* indicates startTLS success */
    PRBool 		lssei_client_auth;
    PRBool		lssei_ssl_option_value[LDAPSSL_MAX_SSL_OPTION+1];
    PRBool		lssei_ssl_option_isset[LDAPSSL_MAX_SSL_OPTION+1];
    char		*lssei_certnickname;
    char        	*lssei_keypasswd;	/* if NULL, assume pre-auth. */
    LDAPSSLStdFunctions	lssei_std_functions;
    CERTCertDBHandle	*lssei_certdbh;
} LDAPSSLSessionInfo;


/*
 * LDAP socket data structure.
 */
typedef struct ldapssl_socket_info {
    LDAPSSLSessionInfo	*soi_sessioninfo;	/* session info */
} LDAPSSLSocketInfo;


/* 
 * XXXceb  This is a hack until the new IO functions are done.
 * this function MUST be called before ldapssl_enable_clientauth.
 * right now, this function is called in ldapssl_pkcs_init();
 */

static int using_pkcs_functions = 0;


void set_using_pkcs_functions( int val )
{
    using_pkcs_functions = val;
}


/*
 * Utility functions:
 */
static int set_ssl_options( PRFileDesc *sslfd, PRBool *optval,
	PRBool *optisset );
static void ldapssl_free_session_info( LDAPSSLSessionInfo **ssipp );
static void ldapssl_free_socket_info( LDAPSSLSocketInfo **soipp );
static char *ldapssl_libldap_compat_strdup(const char *s1);


/*
 *  SSL Stuff 
 */

static int ldapssl_AuthCertificate(void *sessionarg, PRFileDesc *fd,
	PRBool checkSig, PRBool isServer);

/*
 * client auth stuff
 */
static SECStatus get_clientauth_data( void *sessionarg, PRFileDesc *prfd,
	CERTDistNames *caNames,  CERTCertificate **pRetCert,
	SECKEYPrivateKey **pRetKey );
static SECStatus get_keyandcert( LDAPSSLSessionInfo *ssip,
	CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey,
	char **errmsgp );
static SECStatus check_clientauth_nicknames_and_passwd( LDAP *ld,
	LDAPSSLSessionInfo *ssip );
static char *get_keypassword( PK11SlotInfo *slot, PRBool retry,
	void *sessionarg );

/*
 * Static variables.
 */
/* SSL strength setting for new LDAPS sessions */
static int default_ssl_strength = LDAPSSL_AUTH_CERT;

/*
 * Arrays to track global defaults for SSL options. These are used for
 * new LDAPS sessions. For each option, we track both the option value
 * and a Boolean that indicates whether the value has been set using
 * the ldapssl_set_option() call. If an option has not been set, we
 * don't make any NSS calls to set it; that way, the default NSS option
 * values are used. Similar arrays are included in the LDAPSSLSessionInfo
 * structure so options can be set on a per-LDAP session basis as well.
 */
static PRBool default_ssl_option_value[LDAPSSL_MAX_SSL_OPTION+1] = {0};
static PRBool default_ssl_option_isset[LDAPSSL_MAX_SSL_OPTION+1] = {0};


/*
 * Like ldap_init(), except also install I/O routines from libsec so we
 * can support SSL.  If defsecure is non-zero, SSL is enabled for the
 * default connection as well.
 */
LDAP *
LDAP_CALL
ldapssl_init( const char *defhost, int defport, int defsecure )
{
    LDAP	*ld;


    if (0 ==defport)
	defport = LDAPS_PORT;
    
    if (( ld = ldap_init( defhost, defport )) == NULL ) {
	return( NULL );
    }

    if ( ldapssl_install_routines( ld ) < 0 || ldap_set_option( ld,
		LDAP_OPT_SSL, defsecure ? LDAP_OPT_ON : LDAP_OPT_OFF ) != 0 ) {
	PR_SetError( PR_GetError(), EINVAL );  /* XXXmcs: just a guess! */
	ldap_unbind( ld );
	return( NULL );
    }

    return( ld );
}


static int
ldapssl_close(int s, struct lextiof_socket_private *socketarg)
{
    PRLDAPSocketInfo	soi;
    LDAPSSLSocketInfo	*ssoip;
    LDAPSSLSessionInfo	*sseip;

    memset( &soi, 0, sizeof(soi));
    soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE;
    if ( prldap_get_socket_info( s, socketarg, &soi ) != LDAP_SUCCESS ) {
	return( -1 );
    }

    ssoip = (LDAPSSLSocketInfo *)soi.soinfo_appdata;
    sseip = ssoip->soi_sessioninfo;

    ldapssl_free_socket_info( (LDAPSSLSocketInfo **)&soi.soinfo_appdata );

    return( (*(sseip->lssei_std_functions.lssf_close_fn))( s, socketarg ));
}


static int
ldapssl_connect(const char *hostlist, int defport, int timeout,
	unsigned long options, struct lextiof_session_private *sessionarg,
	struct lextiof_socket_private **socketargp )
{
    int			intfd = -1;
    PRBool		secure;
    PRLDAPSessionInfo	sei;
    PRLDAPSocketInfo	soi;
    LDAPSSLSocketInfo	*ssoip = NULL;
    LDAPSSLSessionInfo	*sseip;
    PRFileDesc		*sslfd = NULL;

    /*
     * Determine if secure option is set.  Also, clear secure bit in options
     * the we pass to the standard connect() function (since it doesn't know
     * how to handle the secure option).
     */
    if ( 0 != ( options & LDAP_X_EXTIOF_OPT_SECURE )) {
	secure = PR_TRUE;
	options &= ~LDAP_X_EXTIOF_OPT_SECURE;
    } else {
	secure = PR_FALSE;
    }

    /*
     * Retrieve session info. so we can store a pointer to our session info.
     * in our socket info. later.
     */
    memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( prldap_get_session_info( NULL, sessionarg, &sei ) != LDAP_SUCCESS ) {
	return( -1 );
    }
    sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
    
    /*
     * Call the standard connect() callback to make the TCP connection.  If it
     * succeeds, *socketargp is set.
     */
    intfd = (*(sseip->lssei_std_functions.lssf_connect_fn))( hostlist, defport,
		timeout, options, sessionarg, socketargp );
    if ( intfd < 0 ) {
	return( intfd );
    }

    /*
     * Retrieve socket info. so we have the PRFileDesc.
     */
    memset( &soi, 0, sizeof(soi));
    soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE;
    if ( prldap_get_socket_info( intfd, *socketargp, &soi ) != LDAP_SUCCESS ) {
	goto close_socket_and_exit_with_error;
    }

    /*
     * Allocate a structure to hold our socket-specific data.
     */
    if ( NULL == ( ssoip = PR_Calloc( 1, sizeof( LDAPSSLSocketInfo )))) {
	goto close_socket_and_exit_with_error;
    }
    ssoip->soi_sessioninfo = sseip;

    /*
     * Add SSL layer and enable SSL.
     */
    if (( sslfd = SSL_ImportFD( NULL, soi.soinfo_prfd )) == NULL ) {
	goto close_socket_and_exit_with_error;
    }

    if ( SSL_OptionSet( sslfd, SSL_SECURITY, secure ) != SECSuccess ||
		SSL_OptionSet( sslfd, SSL_HANDSHAKE_AS_CLIENT, secure )
		!= SECSuccess || ( secure && SSL_ResetHandshake( sslfd,
		PR_FALSE ) != SECSuccess )) {
	goto close_socket_and_exit_with_error;
    }

    /*
     * Set hostname which will be retrieved (depending on ssl strength) when
     * using client or server auth.
     */
    if ( SSL_SetURL( sslfd, hostlist ) != SECSuccess ) {
	goto close_socket_and_exit_with_error;
    }

    /*
     * Set any SSL options that were modified by a previous call to
     * the ldapssl_set_option() function. 
     */
    if ( set_ssl_options( sslfd, sseip->lssei_ssl_option_value,
		sseip->lssei_ssl_option_isset ) < 0 ) {
	goto close_socket_and_exit_with_error;
    }

    /*
     * If using client auth., arrange to use a separate SSL session cache
     * for each distinct local client certificate nickname.
     */
    if ( sseip->lssei_client_auth && 
         sseip->lssei_certnickname && sseip->lssei_certnickname[0] &&
	 SSL_SetSockPeerID( sslfd, sseip->lssei_certnickname) != SECSuccess ) {
	goto close_socket_and_exit_with_error;
    }

    /*
     * Let the standard NSPR to LDAP layer know about the new socket and
     * our own socket-specific data.
     */
    soi.soinfo_prfd = sslfd;
    soi.soinfo_appdata = (void *)ssoip;
    if ( prldap_set_socket_info( intfd, *socketargp, &soi ) != LDAP_SUCCESS ) {
	goto close_socket_and_exit_with_error;
    }
    sslfd = NULL;	/* so we don't close the socket twice upon error */

    /*
     * Install certificate hook function.
     */
    SSL_AuthCertificateHook( soi.soinfo_prfd,
			     (SSLAuthCertificate)ldapssl_AuthCertificate, 
			     (void *)sseip );

    if ( SSL_GetClientAuthDataHook( soi.soinfo_prfd,
		get_clientauth_data, sseip->lssei_client_auth ? sseip : NULL ) != 0 ) {
	goto close_socket_and_exit_with_error;
    }

    return( intfd );	/* success */

close_socket_and_exit_with_error:
    if ( NULL != sslfd && sslfd != soi.soinfo_prfd ) {
	PR_Close( sslfd );
    }
    if ( NULL != ssoip ) {
	ldapssl_free_socket_info( &ssoip );
    }
    if ( intfd >= 0 && NULL != *socketargp ) {
	(*(sseip->lssei_std_functions.lssf_close_fn))( intfd, *socketargp );
    }
    return( -1 );
}


static void
ldapssl_disposehandle(LDAP *ld, struct lextiof_session_private *sessionarg)
{
    PRLDAPSessionInfo				sei;
    LDAPSSLSessionInfo				*sseip;
    LDAP_X_EXTIOF_DISPOSEHANDLE_CALLBACK	*disposehdl_fn;

    memset( &sei, 0, sizeof( sei ));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) {
	sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
	disposehdl_fn = sseip->lssei_std_functions.lssf_disposehdl_fn;
	ldapssl_free_session_info( &sseip );
	(*disposehdl_fn)( ld, sessionarg );
    }
}

static 
LDAPSSLSessionInfo *
ldapssl_alloc_sessioninfo()
{
    LDAPSSLSessionInfo	*ssip;
    
    /*
     * Allocate our own session information.
     */
    if ( NULL == ( ssip = (LDAPSSLSessionInfo *)PR_Calloc( 1,
		sizeof( LDAPSSLSessionInfo )))) {
	return( NULL );
    }

    /*
     * Initialize session info.
     * XXX: it would be nice to be able to set these on a per-session basis:
     *		lssei_using_pcks_fns
     *		lssei_certdbh
     */
    ssip->lssei_ssl_strength = default_ssl_strength;
    memcpy( ssip->lssei_ssl_option_value, default_ssl_option_value,
		sizeof(ssip->lssei_ssl_option_value));
    memcpy( ssip->lssei_ssl_option_isset, default_ssl_option_isset,
		sizeof(ssip->lssei_ssl_option_isset));
    ssip->lssei_using_pcks_fns = using_pkcs_functions;
    ssip->lssei_certdbh = CERT_GetDefaultCertDB();
    ssip->lssei_ssl_ready = PR_TRUE;
    
    return( ssip );
}

/*
 * Install I/O routines from libsec and NSPR into libldap to allow libldap
 * to do SSL.
 *
 * We rely on libprldap to provide most of the functions, and then we override
 * a few of them to support SSL.
 */
int
LDAP_CALL
ldapssl_install_routines( LDAP *ld )
{
    struct ldap_x_ext_io_fns	iofns;
    LDAPSSLSessionInfo		*ssip;
    PRLDAPSessionInfo		sei;

    /* install standard NSPR functions */
    if ( prldap_install_routines(
		ld,
		1 /* shared -- we have to assume it is */ )
		!= LDAP_SUCCESS ) {
	return( -1 );
    }

    /*
     * Allocate session information.
     */
     if ( (ssip = ldapssl_alloc_sessioninfo()) == NULL )
     {
     	ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL );
	return( -1 );
     }

    /*
     * override a few functions, saving a pointer to the standard function
     * in each case so we can call it from our SSL savvy functions.
     */
    memset( &iofns, 0, sizeof(iofns));
    iofns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE;
    if ( ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) < 0 ) {
	ldapssl_free_session_info( &ssip );
	return( -1 );
    }

    /* override socket, connect, and disposehandle */
    ssip->lssei_std_functions.lssf_connect_fn = iofns.lextiof_connect;
    iofns.lextiof_connect = ldapssl_connect;
    ssip->lssei_std_functions.lssf_close_fn = iofns.lextiof_close;
    iofns.lextiof_close = ldapssl_close;
    ssip->lssei_std_functions.lssf_disposehdl_fn = iofns.lextiof_disposehandle;
    iofns.lextiof_disposehandle = ldapssl_disposehandle;

    if ( ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns ) < 0 ) {
	ldapssl_free_session_info( &ssip );
	return( -1 );
    }

    /*
     * Store session info. for later retrieval.
     */
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    sei.seinfo_appdata = (void *)ssip;
    if ( prldap_set_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) {
	ldapssl_free_session_info( &ssip );
	return( -1 );
    }

    return( 0 );
}

/* Sets up SSL for the NSPR layered connection (or plain Text connection
 * with startTLS extended I/O request already acknowledged by the server)
 * Call this function after the call to ldaptls_setup()
 *
 * Returns an LDAP API result code (LDAP_SUCCESS if all goes well).
 */
static int
ldaptls_complete(LDAP *ld)
{
    PRBool		secure = 1;
    PRLDAPSessionInfo	sei;
    PRLDAPSocketInfo	soi;
    LDAPSSLSocketInfo	*ssoip = NULL;
    LDAPSSLSessionInfo	*sseip = NULL;
    PRFileDesc		*sslfd = NULL;
    LBER_SOCKET 	intfd = -1;
    int			rc = LDAP_LOCAL_ERROR;
    char 		*hostlist = NULL;
    struct lextiof_socket_private *socketargp = NULL;
        
    /*
     * Get hostlist from LDAP Handle
     */
    if ( ldap_get_option(ld, LDAP_OPT_HOST_NAME, &hostlist) < 0 ) {
    	rc = ldap_get_lderrno( ld, NULL, NULL );
		goto close_socket_and_exit_with_error;
    }
     
    /*
     * Get File Desc from current connection
     */
    if ( ldap_get_option(ld, LDAP_OPT_DESC, &intfd) < 0 ) {
		rc = ldap_get_lderrno( ld, NULL, NULL );
		goto close_socket_and_exit_with_error;
    }

     
    /*
     * Get Socket Arg Pointer
     */
    if ( ldap_get_option(ld, LDAP_X_OPT_SOCKETARG, &socketargp) < 0 ) {
		rc = ldap_get_lderrno( ld, NULL, NULL );
		goto close_socket_and_exit_with_error;
    }


    /*
     * Retrieve session info. so we can store a pointer to our session info.
     * in our socket info. later.
     */
    memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if (LDAP_SUCCESS != (rc = prldap_get_session_info(ld, NULL, &sei))) {
		goto close_socket_and_exit_with_error;
    }
    sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;

    /*
     * Retrieve socket info.
     */
    memset( &soi, 0, sizeof(soi));
    soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE;
    if (LDAP_SUCCESS !=
		(rc = prldap_get_socket_info(intfd, socketargp, &soi))) {
	goto close_socket_and_exit_with_error;
    } 

    /*
     * Allocate a structure to hold our socket-specific data.
     */
    if ( NULL == ( ssoip = PR_Calloc( 1, sizeof( LDAPSSLSocketInfo )))) {
	rc = LDAP_NO_MEMORY;
	goto close_socket_and_exit_with_error;
    }
    ssoip->soi_sessioninfo = sseip;

    /*
     * Add SSL layer and enable SSL.
     */
    if (( sslfd = SSL_ImportFD( NULL, soi.soinfo_prfd )) == NULL ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }
    
    if ( SSL_OptionSet( sslfd, SSL_SECURITY, secure ) != SECSuccess ||
	 SSL_OptionSet( sslfd, SSL_ENABLE_TLS, secure ) != SECSuccess ||
	 SSL_OptionSet( sslfd, SSL_HANDSHAKE_AS_CLIENT, secure ) != SECSuccess ||
	 ( secure && SSL_ResetHandshake( sslfd, PR_FALSE ) != SECSuccess ) ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }
    
    /*
     * Set hostname which will be retrieved (depending on ssl strength) when
     * using client or server auth.
     */
    if ( SSL_SetURL( sslfd, hostlist ) != SECSuccess ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }
    ldap_memfree(hostlist);
    hostlist = NULL;

    /*
     * Set any SSL options that were modified by a previous call to
     * the ldapssl_set_option() function. 
     */
    if ( set_ssl_options( sslfd, sseip->lssei_ssl_option_value,
		sseip->lssei_ssl_option_isset ) < 0 ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }

    /*
     * If using client auth., arrange to use a separate SSL session cache
     * for each distinct local client certificate nickname.
     */
    if ( sseip->lssei_client_auth && 
         sseip->lssei_certnickname && sseip->lssei_certnickname[0] &&
	 SSL_SetSockPeerID( sslfd, sseip->lssei_certnickname) != SECSuccess ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }

    /*
     * Let the standard NSPR to LDAP layer know about the new socket and
     * our own socket-specific data.
     */
    soi.soinfo_prfd = sslfd;
    soi.soinfo_appdata = (void *)ssoip;

    if ( LDAP_SUCCESS !=
		(rc = prldap_set_socket_info( intfd, socketargp, &soi ))) {
	goto close_socket_and_exit_with_error;
    }
    sslfd = NULL;	/* so we don't close the socket twice upon error */

    /*
     * Install certificate hook function.
     */
    SSL_AuthCertificateHook( soi.soinfo_prfd,
			     (SSLAuthCertificate)ldapssl_AuthCertificate, 
			     (void *)sseip );

    if ( SSL_GetClientAuthDataHook( soi.soinfo_prfd,
		 get_clientauth_data, sseip->lssei_client_auth ? sseip : NULL ) != 0 ) {
	rc = LDAP_LOCAL_ERROR;
	goto close_socket_and_exit_with_error;
    }

    return( LDAP_SUCCESS );	/* success */

  close_socket_and_exit_with_error:

      ldap_memfree(hostlist);
      hostlist = NULL;

      if ( NULL != sslfd && sslfd != soi.soinfo_prfd ) {
               PR_Close( sslfd );
      }
      if ( NULL != ssoip ) {
                ldapssl_free_socket_info( &ssoip );
      }
      if ( intfd >= 0 && NULL != socketargp && sseip != NULL ) {
                (*(sseip->lssei_std_functions.lssf_close_fn))( intfd,
                socketargp );
      }
      return( rc );
   
} /* ldaptls_complete() */



/*
 * Install I/O routines from libsec and NSPR into libldap to allow libldap
 * to do TLS.
 *
 * We rely on libprldap to provide most of the functions.
 * Returns an LDAP API result code (LDAP_SUCCESS if all goes well).
 */
static int
ldaptls_setup( LDAP *ld )
{

    int				rc = LDAP_LOCAL_ERROR;
    LDAPSSLSessionInfo		*ssip;
    PRLDAPSessionInfo		sei;
    
    /* Check for valid input parameters. */
    if ( ld == NULL ) {
	ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL );
	return( LDAP_PARAM_ERROR );
    }
    
    /* Check if NSPR Layer is Installed */
    if ( !prldap_is_installed(ld) ) {
       /* No NSPR Layer installed, 
        * Call prldap_import_connection() that installs the NSPR I/O layer
	* and imports connection details from Clear-text connection
	*/
	if (LDAP_SUCCESS != (rc = prldap_import_connection( ld ))) {
	    return( rc );
    	}
    }
    
	memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( (rc = prldap_get_session_info( ld, NULL, &sei )) == LDAP_SUCCESS ) {
        ssip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
    } else {
		return( rc );
	}
	
	if ( NULL == ssip ) {
		if ( (ssip = ldapssl_alloc_sessioninfo()) == NULL ) {
			ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL );
			return( LDAP_NO_MEMORY );
		}

		/*
		 * Store session info. for later retrieval.
		 */
		sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
		sei.seinfo_appdata = (void *)ssip;
		if (LDAP_SUCCESS != (rc = prldap_set_session_info( ld, NULL, &sei ))) {
			ldapssl_free_session_info( &ssip );
			return( rc );
		}
	}

	ssip->lssei_tls_init= PR_TRUE;

    return( LDAP_SUCCESS );
} /* ldaptls_setup()*/

/* Function; ldap_start_tls_s()
 * startTLS request
 * Returns an LDAP API result code (LDAP_SUCCESS if all goes well).
 *
 */
int
LDAP_CALL
ldap_start_tls_s(LDAP *ld,
		 LDAPControl **serverctrls,
		 LDAPControl **clientctrls)
{
	int rc = -1;
	int	version = LDAP_VERSION3;

	/* Error check on LDAP handle */
	if ( ld == NULL ) {
		ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL );
		return( LDAP_PARAM_ERROR );
	}
	
	
	/* Make sure we are dealing with LDAPv3 */
	if ( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ) < 0 ) {
		 return( ldap_get_lderrno( ld, NULL, NULL ));
	}
		
	/* Issue the Start TLS extended operation */
	rc = ldap_extended_operation_s( ld, LDAP_EXOP_START_TLS, NULL, serverctrls,
									clientctrls, NULL, NULL );
	if ( rc != LDAP_SUCCESS )
	{
		return( rc );
	}

	/* Initialize TLS and enable SSL on the LDAP session */
	if ( LDAP_SUCCESS == ( rc = ldaptls_setup( ld ) ) &&
	     LDAP_SUCCESS == ( rc = ldaptls_complete( ld ) )) {
		if (ldap_set_option( ld, LDAP_OPT_SSL, LDAP_OPT_ON ) < 0 ) {
			rc = ldap_get_lderrno( ld, NULL, NULL );
		}
	}

	if (LDAP_SUCCESS != rc) {
		/* Allow to proceed in clear text if secure session fails */
		ldap_set_option( ld, LDAP_OPT_SSL, LDAP_OPT_OFF );
	}

	return( rc );
}



/*ARGSUSED*/
int
LDAP_CALL
ldapssl_enable_clientauth( LDAP *ld, char *keynickname,
        char *keypasswd, char *certnickname )
{
    LDAPSSLSessionInfo		*ssip;
    PRLDAPSessionInfo		sei;
    int	new_session_allocated = 0;

   /*
    * Check parameters
    * allow keypasswd to be NULL in case PK11_SetPasswordFunc()
    * already set by the user to their own private pin callback.
    * there is no proper way to test if PK11_SetPasswordFunc()
    * callback is already set apart from NSS private interfaces
    */
    if ( certnickname == NULL ) {
		ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL,
			ldapssl_libldap_compat_strdup(
				"A non-NULL certnickname is required" ));
		return( -1 );
    }

    /*
     * Get session info. data structure.
     */
    memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) {
		ssip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
    } else {
    	return( -1 );
    }

    if ( NULL == ssip ) { /* Failed to get ssl session info pointer */
		/*
		 * Allocate our own session information.
		 */
		if ( NULL == ( ssip = ldapssl_alloc_sessioninfo())) {
			ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL );
			return( -1 );
		}
		/*
		 * Store session info. for later retrieval.
		 */
		sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
		sei.seinfo_appdata = (void *)ssip;
		if ( prldap_set_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) {
			return( -1 );
		}
		new_session_allocated = 1;
    }
    
    if ( !(ssip->lssei_ssl_ready) && !new_session_allocated ) {
	/* standard SSL setup has not yet done */
	ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL,
		ldapssl_libldap_compat_strdup(
		"An SSL-ready LDAP session handle is required" ));
	return( -1 );
    }

    /*
     * Update session info. data structure.
     */
    ssip->lssei_certnickname = PL_strdup( certnickname );
    if ( keypasswd ) {
	ssip->lssei_keypasswd = PL_strdup( keypasswd );
    } else {
    /* set lssei_using_pcks_fns to prevent our own PK11_SetPasswordFunc()
	 * callback being installed in get_keyandcert() if keypasswd is NULL
	 * workaround for now til NSS comes up with proper check interface 
	 */ 
	ssip->lssei_using_pcks_fns = 1;
	ssip->lssei_keypasswd = NULL;	/* assume pre-authenticated */
    }

    if ( NULL == ssip->lssei_certnickname ||
		( keypasswd && ( NULL == ssip->lssei_keypasswd ) ) ) {
	ldap_set_lderrno( ld, LDAP_NO_MEMORY, NULL, NULL );
	return( -1 );
    }

    if ( check_clientauth_nicknames_and_passwd( ld, ssip ) != SECSuccess ) {
	/* LDAP errno is set by check_clientauth_nicknames_and_passwd() */
	return( -1 );
    }

    /* Mark the session as client auth enabled */
    ssip->lssei_client_auth = PR_TRUE;

    return( LDAP_SUCCESS );
}


/*
 * Set the SSL strength for an existing SSL-enabled LDAP session handle.
 *
 * See the description of ldapssl_serverauth_init() above for valid
 * sslstrength values. If ld is NULL, the default for new LDAP session
 * handles is set.
 *
 * Returns 0 if all goes well and -1 if an error occurs.
 */
int
LDAP_CALL
ldapssl_set_strength( LDAP *ld, int sslstrength )
{
    int			rc = 0;	/* assume success */

    if ( sslstrength != LDAPSSL_AUTH_WEAK &&
		sslstrength != LDAPSSL_AUTH_CERT &&
		sslstrength != LDAPSSL_AUTH_CNCHECK ) {
	rc = -1;
    } else {
	if ( NULL == ld ) {	/* set default strength */
	    default_ssl_strength = sslstrength;
	} else {		/* set session-specific strength */
	    PRLDAPSessionInfo	sei;
	    LDAPSSLSessionInfo	*sseip;

	    memset( &sei, 0, sizeof( sei ));
	    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
	    if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) {
		sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
		sseip->lssei_ssl_strength = sslstrength;
	    } else {
		rc = -1;
	    }
	}
    }

    return( rc );
}


/*
 * Set SSL options for an existing SSL-enabled LDAP session handle.
 * If ld is NULL, the default options used for all future LDAP SSL sessions
 * are the ones affected. The option values are specific to the underlying
 * SSL provider; see ssl.h within the Network Security Services (NSS)
 * distribution for the options supported by NSS (the default SSL provider).
 *
 * This function should be called before any LDAP connections are created.
 *
 * Returns: 0 if all goes well.
 */
int
LDAP_CALL
ldapssl_set_option( LDAP *ld, int option, int on )
{
    int		rc = 0;	/* assume success */

    if ( option < 0 || option > LDAPSSL_MAX_SSL_OPTION ) {
	ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL );
	rc = -1; 
    } else {
	if ( NULL == ld ) {
	    /* set default options for new LDAP sessions */
	    default_ssl_option_value[option] = on;
	    default_ssl_option_isset[option] = PR_TRUE;
	} else {
	    /* set session options */
	    PRLDAPSessionInfo	sei;
	    LDAPSSLSessionInfo	*sseip;

	    memset( &sei, 0, sizeof( sei ));
	    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
	    if ( prldap_get_session_info( ld, NULL, &sei ) == LDAP_SUCCESS ) {
		sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
		sseip->lssei_ssl_option_value[option] = on;
		sseip->lssei_ssl_option_isset[option] = PR_TRUE;
	    } else {
		rc = -1;
	    }
	}
    }

    return( rc );
}


/*
 * Retrieve SSL options for an existing SSL-enabled LDAP session handle.
 * If ld is NULL, the default options to be used for all future LDAP SSL
 * sessions are retrieved. The option values are specific to the underlying
 * SSL provider; see ssl.h within the Network Security Services (NSS)
 * distribution for the options supported by NSS (the default SSL provider).
 *
 * Returns: 0 if all goes well.
 */
int
LDAP_CALL
ldapssl_get_option( LDAP *ld, int option, int *onp )
{
    int		rc = 0;	/* assume success */

    if ( option < 0 || option > LDAPSSL_MAX_SSL_OPTION || onp == NULL ) {
	ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, NULL );
	rc = -1; 
    } else {
	int		rv = 0, set_rv = 0;

	if ( NULL == ld ) {
	     /* return default options for new LDAP sessions */
	    if ( default_ssl_option_isset[option] ) {
		rv = default_ssl_option_value[option];
		set_rv = 1;
	    }
	} else {
	    /* return session options */
	    PRLDAPSessionInfo	sei;
	    LDAPSSLSessionInfo	*sseip;

	    memset( &sei, 0, sizeof( sei ));
	    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
	    if ( prldap_get_session_info( ld, NULL, &sei )
			== LDAP_SUCCESS ) {
		sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;
		if ( sseip->lssei_ssl_option_isset[option] ) {
		    rv = sseip->lssei_ssl_option_value[option];
		    set_rv = 1;
		}
	    } else {
		rc = -1;
	    }
	}

	if ( !set_rv ) {
	    PRBool	pron = PR_FALSE;
	    if ( rc == 0 && SSL_OptionGetDefault( (PRInt32)option, &pron )
			!= SECSuccess ) {
		rc = -1;
	    }
	    rv = pron;
	}

	*onp = rv;	/* always return a value */
    }

    return( rc );
}

#ifdef LDAPSSL_DEBUG
struct optitem {
	PRInt32		om_option;
	const char	*om_string;
} optmap[] = {
	{ SSL_SECURITY,			"SSL_SECURITY" },
	{ SSL_SOCKS, 			"SSL_SOCKS" },
	{ SSL_REQUEST_CERTIFICATE, 	"SSL_REQUEST_CERTIFICATE" },
	{ SSL_HANDSHAKE_AS_CLIENT, 	"SSL_HANDSHAKE_AS_CLIENT" },
	{ SSL_HANDSHAKE_AS_SERVER, 	"SSL_HANDSHAKE_AS_SERVER" },
	{ SSL_ENABLE_SSL2, 		"SSL_ENABLE_SSL2" },
	{ SSL_ENABLE_SSL3, 		"SSL_ENABLE_SSL3" },
	{ SSL_NO_CACHE, 		"SSL_NO_CACHE" },
	{ SSL_REQUIRE_CERTIFICATE, 	"SSL_REQUIRE_CERTIFICATE" },
	{ SSL_ENABLE_FDX, 		"SSL_ENABLE_FDX" },
	{ SSL_V2_COMPATIBLE_HELLO, 	"SSL_V2_COMPATIBLE_HELLO" },
	{ SSL_ENABLE_TLS, 		"SSL_ENABLE_TLS" },
	{ SSL_ROLLBACK_DETECTION, 	"SSL_ROLLBACK_DETECTION" },
	{ -1, NULL },
};
	
static const char *
sslopt2string( PRInt32 option )
{
    int		i;
    const	char *s = "unknown";

    for ( i = 0; optmap[i].om_option != -1; ++i ) {
	if ( optmap[i].om_option == option ) {
	    s = optmap[i].om_string;
	    break;
	}
    }
	
    return( s );
}
#endif /* LDAPSSL_DEBUG */

static int
set_ssl_options( PRFileDesc *sslfd, PRBool *optval, PRBool *optisset )
{
    SECStatus	secrc = SECSuccess;
    PRInt32	option;

    for ( option = 0;
		( secrc == SECSuccess ) && ( option < LDAPSSL_MAX_SSL_OPTION );
		++option ) {
	if ( optisset[ option ] ) {
#ifdef LDAPSSL_DEBUG
	    fprintf( stderr,
		    "set_ssl_options: setting option %d - %s to %d (%s)\n",
		    option, sslopt2string(option), optval[ option ],
		    optval[ option ] ? "ON" : "OFF" );
#endif /* LDAPSSL_DEBUG */
	
	    secrc = SSL_OptionSet( sslfd, option, optval[ option ] );
	}
    }

    if ( secrc == SECSuccess ) {
	return( 0 );
    }

    PR_SetError( PR_GetError(), EINVAL );	/* set OS error only */
    return( -1 );
}


static void
ldapssl_free_session_info( LDAPSSLSessionInfo **ssipp )
{
    if ( NULL != ssipp && NULL != *ssipp ) {
	if ( NULL != (*ssipp)->lssei_certnickname ) {
	    PL_strfree( (*ssipp)->lssei_certnickname );
	    (*ssipp)->lssei_certnickname = NULL;
	}
	if ( NULL != (*ssipp)->lssei_keypasswd ) {
	    PL_strfree( (*ssipp)->lssei_keypasswd );
	    (*ssipp)->lssei_keypasswd = NULL;
	}
	PR_Free( *ssipp );
	*ssipp = NULL;
    }
}


static void
ldapssl_free_socket_info( LDAPSSLSocketInfo **soipp )
{
    if ( NULL != soipp && NULL != *soipp ) {
	PR_Free( *soipp );
	*soipp = NULL;
    }
}


/* this function provides cert authentication.  This is called during 
 * the SSL_Handshake process.  Once the cert has been retrieved from
 * the server, the it is checked, using VerifyCertNow(), then 
 * the cn is checked against the host name, set with SSL_SetURL()
 */

static int
ldapssl_AuthCertificate(void *sessionarg, PRFileDesc *fd, PRBool checkSig,
	PRBool isServer)
{
    SECStatus		rv = SECFailure;
    LDAPSSLSessionInfo	*sseip;
    CERTCertificate	*cert;
    SECCertUsage	certUsage;
    char		*hostname = (char *)0;

    if (!sessionarg || !fd) {
	return rv;
    }

    sseip = (LDAPSSLSessionInfo *)sessionarg;

    if (LDAPSSL_AUTH_WEAK == sseip->lssei_ssl_strength ) { /* no check */
	return SECSuccess;
    }

    if ( isServer ) {
	certUsage = certUsageSSLClient;
    } else {
	certUsage = certUsageSSLServer;
    }
    cert = SSL_PeerCertificate( fd );
    
    rv = CERT_VerifyCertNow(sseip->lssei_certdbh, cert, checkSig,
			certUsage, NULL);

    if ( rv != SECSuccess || isServer ) {
	/* must destroy cert to avoid mem leak */
	CERT_DestroyCertificate(cert);
	return rv;
    }
  
    if ( LDAPSSL_AUTH_CNCHECK == sseip->lssei_ssl_strength ) {
	/* cert is OK.  This is the client side of an SSL connection.
	 * Now check the name field in the cert against the desired hostname.
	 * NB: This is our only defense against Man-In-The-Middle (MITM) 
	 * attacks!
	 */

	hostname = SSL_RevealURL( fd );

	if (hostname && hostname[0]) {
	  rv = CERT_VerifyCertName(cert, hostname);
	} else  {
	  rv = SECFailure;
     	}
        if (hostname) {
	  PL_strfree(hostname);
        }
	if (rv != SECSuccess) {
	  PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN);
        }
    }

    /* must destroy cert to avoid mem leak */
    CERT_DestroyCertificate(cert);
    return((int)rv);
}


/*
 * called during SSL client auth. when server wants our cert and key.
 * returns: SECSuccess if we succeeded and set *pRetCert and *pRetKey,
 *			SECFailure otherwise.
 * if SECFailure is returned SSL will proceed without sending a cert.
 */
/*ARGSUSED*/
static SECStatus
get_clientauth_data( void *sessionarg, PRFileDesc *prfd,
        CERTDistNames *caNames,  CERTCertificate **pRetCert,
        SECKEYPrivateKey **pRetKey )

{
    LDAPSSLSessionInfo	*ssip;

    if (( ssip = (LDAPSSLSessionInfo *)sessionarg ) == NULL ) {
	return( SECFailure );       /* client auth. not enabled */
    }

    return( get_keyandcert( ssip, pRetCert, pRetKey, NULL ));
}

static SECStatus
get_keyandcert( LDAPSSLSessionInfo *ssip,
	CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey,
	char **errmsgp )
{
    CERTCertificate		*cert;
    SECKEYPrivateKey	*key;

    if ( !ssip->lssei_using_pcks_fns && (NULL != ssip->lssei_keypasswd) ) {
	/*
	 * XXX: This function should be called only once, and probably
	 *      in one of the ldapssl_.*_init() calls.
	 */
	PK11_SetPasswordFunc( get_keypassword );
    }

    if (( cert = CERT_FindUserCertByUsage( CERT_GetDefaultCertDB(), 
    				ssip->lssei_certnickname, certUsageSSLClient, 
    				PR_FALSE, (void *)ssip )) == NULL ) {
	if ( errmsgp != NULL ) {
	    *errmsgp = "unable to find certificate";
	}
	return( SECFailure );
    }

    if (( key = PK11_FindKeyByAnyCert( cert, (void *)ssip )) == NULL ) {
	CERT_DestroyCertificate( cert );
	if ( errmsgp != NULL ) {
	    *errmsgp = "bad key or key password";
	}
	return( SECFailure );
    }

    *pRetCert = cert;
    *pRetKey = key;
    return( SECSuccess );
}


/* 
 * This function returns the password to NSS.
 * This function is enable through PK11_SetPasswordFunc
 * only if pkcs functions are not being used.
 */ 
/*ARGSUSED*/
static char *
get_keypassword( PK11SlotInfo *slot, PRBool retry, void *sessionarg )
{
    LDAPSSLSessionInfo	*ssip;

    if ( retry)
      return (NULL);

    ssip = (LDAPSSLSessionInfo *)sessionarg;
    if ( NULL == ssip || NULL == ssip->lssei_keypasswd ) {
	return( NULL );
    }

    return( PL_strdup(ssip->lssei_keypasswd) );
}


/*
 * performs some basic checks on clientauth cert and key/password
 *
 * XXXmcs: could perform additional checks... see servers/slapd/ssl.c
 *	1) check expiration
 *	2) check that public key in cert matches private key
 * see ns/netsite/ldap/servers/slapd/ssl.c:slapd_ssl_init() for example code.
 */
static SECStatus
check_clientauth_nicknames_and_passwd( LDAP *ld, LDAPSSLSessionInfo *ssip )
{
    char		*errmsg = NULL;
    CERTCertificate	*cert = NULL;
    SECKEYPrivateKey	*key = NULL;
    SECStatus		rv;

    rv = get_keyandcert( ssip, &cert, &key, &errmsg );

    if ( rv != SECSuccess ) {
    	if ( errmsg != NULL ) {
	    errmsg = ldapssl_libldap_compat_strdup( errmsg );
	}
	ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, errmsg );
	return( rv );
    }

    if ( cert != NULL ) {
	CERT_DestroyCertificate( cert );
    }
    if ( key != NULL ) {
	SECKEY_DestroyPrivateKey( key );
    }
    return( SECSuccess );
}


/*
 * A strdup() work-alike that uses libldap's memory allocator.
 */
static char *
ldapssl_libldap_compat_strdup(const char *s1)
{
    char	*s2;

    if ( NULL == s1 ) {
	s2 = NULL;
    } else {
	size_t	len = strlen( s1 );

	if ( NULL != ( s2 = (char *)ldap_x_malloc( len + 1 ))) {
	    strcpy( s2, s1 );
	}
    }

    return s2;
}



/* there are patches and kludges.  this is both.  force some linkers to 
 * link this stuff in
 */
int stubs_o_stuff( void )
{
    PRExplodedTime exploded;
    PLArenaPool pool;
  
    const char *name ="t";
    PRUint32 size = 0, align = 0;

    PR_ImplodeTime( &exploded );
    PL_InitArenaPool( &pool, name, size, align);
    PR_Cleanup();
    PR_fprintf((PRFileDesc*)stderr, "Bad IDEA!!");

    return 0;

}


/*
 * Import the file descriptor corresponding to the socket of an already
 * open LDAP connection into SSL, and update the socket and session
 * information accordingly. Returns 0 if all goes well.
 */
int 
LDAP_CALL 
ldapssl_import_fd ( LDAP *ld, int secure )
{
    PRLDAPSessionInfo				sei;
    PRLDAPSocketInfo				soi;
    LDAPSSLSocketInfo				*ssoip = NULL;
    LDAPSSLSessionInfo				*sseip;
    PRFileDesc						*sslfd = NULL;
	LBER_SOCKET						intfd = -1;
	char							*hostlist;
	struct lextiof_socket_private	*socketargp;

	/*
     * Get hostlist from LDAP Handle
     */
    if ( ldap_get_option(ld, LDAP_OPT_HOST_NAME, &hostlist) < 0 ) {
		return( -1 );
    }
	
	/*
     * Get File Desc from current connection
     */
    if ( ldap_get_option(ld, LDAP_OPT_DESC, &intfd) < 0 ) {
		return( -1 );
    }

    /*
     * Get Socket Arg Pointer
     */
    if ( ldap_get_option(ld, LDAP_X_OPT_SOCKETARG, &socketargp) < 0 ) {
		return( -1 );
    }
		
    /*
     * Retrieve session info. so we can store a pointer to our session info.
     * in our socket info. later.
     */
    memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( prldap_get_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) {
        return( -1 );
    }
    sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;


    /*
     * Retrieve socket info. so we have the PRFileDesc.
     */
    memset( &soi, 0, sizeof(soi));
    soi.soinfo_size = PRLDAP_SOCKETINFO_SIZE;
    if ( prldap_get_socket_info( intfd, socketargp, &soi ) != LDAP_SUCCESS ) {
        return( -1 );
    }

    /*
     * Allocate a structure to hold our socket-specific data.
     */
    if ( NULL == ( ssoip = PR_Calloc( 1, sizeof( LDAPSSLSocketInfo )))) {
        goto reset_socket_and_exit_with_error;
    }
    ssoip->soi_sessioninfo = sseip;

    /*
     * Add SSL layer and let the standard NSPR to LDAP layer and enable SSL.
     */
    if (( sslfd = SSL_ImportFD( NULL, soi.soinfo_prfd )) == NULL ) {
        goto reset_socket_and_exit_with_error;
    }

    if ( SSL_OptionSet( sslfd, SSL_SECURITY, secure ) != SECSuccess ||
				SSL_OptionSet( sslfd, SSL_ENABLE_TLS, secure ) ||
                SSL_OptionSet( sslfd, SSL_HANDSHAKE_AS_CLIENT, secure )
                != SECSuccess || ( secure && SSL_ResetHandshake( sslfd,
                PR_FALSE ) != SECSuccess )) {
        goto reset_socket_and_exit_with_error;
    }
	
	/*
     * Set hostname which will be retrieved (depending on ssl strength) when
     * using client or server auth.
     */
    if ( SSL_SetURL( sslfd, hostlist ) != SECSuccess ) {
		goto reset_socket_and_exit_with_error;
    }
	
	/*
     * Set any SSL options that were modified by a previous call to
     * the ldapssl_set_option() function. 
     */
    if ( set_ssl_options( sslfd, sseip->lssei_ssl_option_value,
						  sseip->lssei_ssl_option_isset ) < 0 ) {
		goto reset_socket_and_exit_with_error;
    }

    /*
     * If using client auth., arrange to use a separate SSL session cache
     * for each distinct local client certificate nickname.
     */
    if ( sseip->lssei_client_auth && 
         sseip->lssei_certnickname && sseip->lssei_certnickname[0] &&
	 SSL_SetSockPeerID( sslfd, sseip->lssei_certnickname) != SECSuccess ) {
	goto reset_socket_and_exit_with_error;
    }

    /*
     * Let the standard NSPR to LDAP layer know about the new socket and
     * our own socket-specific data.
     */
    soi.soinfo_prfd = sslfd;
    soi.soinfo_appdata = (void *)ssoip;
    if ( prldap_set_default_socket_info( ld, &soi ) != LDAP_SUCCESS ) {
        goto reset_socket_and_exit_with_error;
    }

    /*
     * Install certificate hook function.
     */
    if ( SSL_AuthCertificateHook( soi.soinfo_prfd,
                                  (SSLAuthCertificate)ldapssl_AuthCertificate,
                                  (void *)sseip) != 0 ) {
        goto reset_socket_and_exit_with_error;
    }

    if ( SSL_GetClientAuthDataHook( soi.soinfo_prfd,
                get_clientauth_data, sseip->lssei_certnickname ? sseip : NULL )
		!= 0 ) {
        goto reset_socket_and_exit_with_error;
    }

    return 0;

 reset_socket_and_exit_with_error:
    if ( NULL != sslfd ) {
        /*
         * "Unimport" the socket from SSL, i.e. get rid of the upper layer of
         * the file descriptor stack, which represents SSL.
         */
        soi.soinfo_prfd = sslfd;
        sslfd = PR_PopIOLayer( soi.soinfo_prfd, PR_TOP_IO_LAYER );
        sslfd->dtor( sslfd );
    }
    if ( NULL != ssoip ) {
        ldapssl_free_socket_info( &ssoip );
        soi.soinfo_appdata = NULL;
    }
    prldap_set_default_socket_info( ld, &soi );

    return( -1 );
}


/*
 * Reset an LDAP session from SSL to a non-secure status. Basically, 
 * this function undoes the work done by ldapssl_install_routines.
 * Returns 0 if all goes well.
 */
int 
LDAP_CALL 
ldapssl_reset_to_nonsecure ( LDAP *ld )
{
    PRLDAPSessionInfo   sei;
    LDAPSSLSessionInfo  *sseip;

    struct ldap_x_ext_io_fns    iofns;
    int rc = 0;

    /*
     * Retrieve session info.
     */
    memset( &sei, 0, sizeof(sei));
    sei.seinfo_size = PRLDAP_SESSIONINFO_SIZE;
    if ( prldap_get_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) {
        return( -1 );
    }
    sseip = (LDAPSSLSessionInfo *)sei.seinfo_appdata;

    if ( sseip != NULL ) {
        /*
         * Reset the standard extended io functions.
         */
        memset( &iofns, 0, sizeof(iofns));
        iofns.lextiof_size = LDAP_X_EXTIO_FNS_SIZE;
        if ( ldap_get_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns )
		< 0) {
            rc = -1;
            goto free_session_info;
        }

        /* reset socket, connect, and ioctl */
        iofns.lextiof_connect = sseip->lssei_std_functions.lssf_connect_fn;
        iofns.lextiof_close = sseip->lssei_std_functions.lssf_close_fn;
        iofns.lextiof_disposehandle =
			sseip->lssei_std_functions.lssf_disposehdl_fn;

        if ( ldap_set_option( ld, LDAP_X_OPT_EXTIO_FN_PTRS, (void *)&iofns )
		< 0) {
            rc = -1;
            goto free_session_info;
        }

free_session_info:
        ldapssl_free_session_info( &sseip );
        sei.seinfo_appdata = NULL;
        if ( prldap_set_session_info( ld, NULL, &sei ) != LDAP_SUCCESS ) {
            rc = -1;
        }
    } /* if ( sseip && *sseip ) */
       
    if ( ldap_set_option( ld, LDAP_OPT_SSL, LDAP_OPT_OFF ) < 0 ) {
        return (-1);
    }

    return rc;
}
#endif /* NET_SSL */
