netssltransport.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2016-1/
  • net/
  • netssltransport.cc
  • View
  • Commits
  • Open Download .zip Download (41 KB)
/**
 * @file netssltransport.cc
 *
 * @brief SSL driver for NetTransport
 *	NetSslTransport - a TCP with SSL subclass of NetTcpTransport
 *
 * Threading: underlying SSL library contains threading
 *
 * @invariants:
 *
 * Copyright (c) 2011 Perforce Software
 * Confidential.  All Rights Reserved.
 * @author Wendy Heffner
 *
 * Creation Date: August 19, 2011
 */

/**
 * NOTE:
 * The following file only defined if USE_SSL true.
 * The setting of this definition is controlled by
 * the Jamrules file.  If the jam build is specified
 * with -sSSL=yes this class will be defined.
 */
# ifdef USE_SSL

# define NEED_ERRNO
# define NEED_SIGNAL
# ifdef OS_NT
# define NEED_FILE
# endif
# define NEED_FCNTL
# define NEED_IOCTL
# define NEED_TYPES
# define NEED_SOCKET_IO
# define NEED_SLEEP

# ifdef OS_MPEIX
# define _SOCKET_SOURCE /* for sys/types.h */
# endif

# define GET_MJR_SSL_VERSION(x) \
    (((x) >> 12) & 0xfffffL)

# include <stdhdrs.h>

# ifndef OS_NT
# include <pthread.h>
# endif // not OS_NT

# include <error.h>
# include <strbuf.h>
# include <md5.h>
# include "netaddrinfo.h"
# include <errorlog.h>
# include <debug.h>
# include <bitarray.h>
# include <tunable.h>
# include <error.h>

# include <enviro.h>
# include <msgrpc.h>
# include <timer.h>
# include "datetime.h"
# include "filesys.h"
# include "pathsys.h"

# include <keepalive.h>
# include "netsupport.h"
# include "netport.h"
# include "netportparser.h"
# include "netconnect.h"
# include "nettcpendpoint.h"
# include "nettcptransport.h"
# include "netselect.h"
# include "netdebug.h"


extern "C"
{
    // OpenSSL
# include "openssl/bio.h"
# include "openssl/ssl.h"
# include "openssl/err.h"
# if !( defined( OPENSSL_VERSION_TEXT ) && defined( OPENSSL_VERSION_NUMBER ))
# include "openssl/opensslv.h"
# endif

   // For strerror
# include <stdio.h>
}
# include "netsslcredentials.h"
# include "netsslendpoint.h"
# include "netssltransport.h"
# include "netsslmacros.h"


////////////////////////////////////////////////////////////////////////////
//  Defines and Globals                                                   //
////////////////////////////////////////////////////////////////////////////

/*
 * Current Cypher Suite Configuration:
 * Primary:   AES256-SHA        SSLv3 Kx=RSA      Au=RSA  Enc=AES(256)      Mac=SHA1
 * Secondary: CAMELLIA256-SHA   SSLv3 Kx=RSA      Au=RSA  Enc=Camellia(256) Mac=SHA1
 */
# define SSL_PRIMARY_CIPHER_SUITE "AES256-SHA"
# define SSL_SECONDARY_CIPHER_SUITE "CAMELLIA256-SHA"

const char *P4SslVersionString = OPENSSL_VERSION_TEXT;
unsigned long sCompileVersion =  OPENSSL_VERSION_NUMBER;
unsigned long sVersion1_0_0 =  0x1000000f;
const char *sVerStr1_0_0 = "1.0.0";
# ifdef OS_NT
static HANDLE *mutexArray = NULL;
# else
static pthread_mutex_t *mutexArray = NULL;
# endif

////////////////////////////////////////////////////////////////////////////
//  MutexLocker                                                           //
////////////////////////////////////////////////////////////////////////////
# ifdef OS_NT
# define ONE_SECOND 1000
class MutexLocker
{
    public:
	MutexLocker( HANDLE &mutex );
	~MutexLocker();

    private:
	DWORD   retval;
	HANDLE &mutex;
};

MutexLocker::MutexLocker( HANDLE &theLock )
    : mutex(theLock)
{
	retval = WaitForSingleObject( mutex, ONE_SECOND );
	if( retval != WAIT_OBJECT_0 )
	    DEBUGPRINT( SSLDEBUG_ERROR,
		    "Unable to acquire lock on windows." );
}

MutexLocker::~MutexLocker()
{
	if( retval == WAIT_OBJECT_0 )
	    ReleaseMutex( mutex );
}

# endif // OS_NT


////////////////////////////////////////////////////////////////////////////
//  Global                                                                //
////////////////////////////////////////////////////////////////////////////
# ifdef OS_NT
HANDLE                    sClientMutex = CreateMutex (NULL, FALSE, NULL);
HANDLE                    sServerMutex = CreateMutex (NULL, FALSE, NULL);
# endif // OS_NT

/**
 * MillisecondDifference
 *
 * @brief Utility to find the difference between two DateTimeHighPrecision
 * Note: Assumes that the difference is bounded by at most one second
 * since this is used to measure passage of time in a select with a 1/2 sec
 * timeout.
 *
 * @param lop left operator in subtraction
 * @param rop right operator in subtraction
 * @return count of milliseconds difference
 */
int
MillisecondDifference(const DateTimeHighPrecision &lop,
		      const DateTimeHighPrecision &rop)
{
    int sec = lop.Seconds() - rop.Seconds();
    int leftMs = (sec * 1000) + (lop.Nanos() / 1000000);
    int rightMs = (rop.Nanos() / 1000000);
    return (leftMs - rightMs);
}

////////////////////////////////////////////////////////////////////////////
//  NetSslTransport - STATIC MEMBERS                                      //
////////////////////////////////////////////////////////////////////////////
SSL_CTX *NetSslTransport::sServerCtx = NULL;
SSL_CTX *NetSslTransport::sClientCtx = NULL;


/**
 * NetSslTransport::constructor
 *
 * @brief constructor
 * @invariant assures that bio and ssl are NULL prior to use
 *
 * @param int socket
 * @param bool flag indicating if server or client side
 */
NetSslTransport::NetSslTransport( int t, bool fromClient )
    : NetTcpTransport( t, fromClient )
{
	this->bio = NULL;
	this->ssl = NULL;
	this->clientNotSsl = false;
	cipherSuite.Set("encrypted");
}

NetSslTransport::NetSslTransport( int t, bool fromClient,
				NetSslCredentials &cred )
    : NetTcpTransport( t, fromClient ), credentials(cred)
{
	this->bio = NULL;
	this->ssl = NULL;
	this->clientNotSsl = false;
	cipherSuite.Set("encrypted");
}
NetSslTransport::~NetSslTransport()
{
	Close();
}

/**
 * NetSslTransport::SslClientInit
 *
 * @brief static method to initialize the SSL client-side CTX structure
 *
 * @param error structure
 * @return none
 */
void
NetSslTransport::SslClientInit(Error *e)
{
	if( sClientCtx )
	    return;

# ifdef OS_NT
	/*
	 * Windows multi-threaded so must synchronize
	 * CTX creation, since we only want one.
	 */
	MutexLocker locker( sClientMutex );
	/*
	 * Now that we have the lock see if any
	 * thread created the CTX while we were
	 * waiting.
	 */
# endif // OS_NT

	if( !sClientCtx )
	{
#ifdef OS_NT
	    InitLockCallbacks( e );
	    if( e->Test())
		return;
#endif // OS_NT

	    TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
		"NetSslTransport::SslClientInit - Initializing client CTX structure." );


	    ValidateRuntimeVsCompiletimeSSLVersion( e );
	    if( e->Test() )
	    {
		TRANSPORT_PRINT( SSLDEBUG_ERROR,
			"Version mismatch between compile OpenSSL version and runtime OpenSSL version." );
		return;
	    }

	    /*
	     * Added due to job084753: Swarm is a web app that reuses processes.
	     * For some reason in this environment the SSL error stack is not removed
	     * when the process is reused so we need to explicitly remove any previous
	     * state prior to setting up a new SSL Context.
	     */
	    ERR_remove_thread_state(NULL);
	    // probably cannot check for error return from this call :-)

	    SSL_load_error_strings();
	    SSLCHECKERROR( e,
                       "NetSslTransport::SslClientInit SSL_load_error_strings",
                       MsgRpc::SslInit,
                       fail );
	    ERR_load_BIO_strings();
	    SSLCHECKERROR( e, "NetSslTransport::SslClientInit ERR_load_BIO_strings",
                       MsgRpc::SslInit,
                       fail );
	    if ( !SSL_library_init() )
	    {
		// executable not compiled supporting SSL
		// need to link with open SSL libraries
		e->Set(MsgRpc::SslNoSsl);
		return;
	    }
	    SSLCHECKERROR( e, "NetSslTransport::SslClientInit SSL_library_init",
                       MsgRpc::SslInit,
                       fail );

	    // WSAstartup code in NetTcpEndPoint constructor

	    sClientCtx = SSL_CTX_new( TLSv1_method() );
	    SSLNULLHANDLER( sClientCtx, e,
                        "NetSslTransport::SslClientInit SSL_CTX_new",
                        fail );

	    SSL_CTX_set_mode(
		sClientCtx,
		SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER );
	    SSLLOGFUNCTION( "NetSslTransport::SslClientInit SSL_CTX_set_mode" );
	}
	return;

fail:
	e->Set( MsgRpc::SslCtx ) << "the connecting client";
	return;
}

void
NetSslTransport::GetPeerFingerprint(StrBuf &value)
{
	if( !isAccepted && credentials.GetFingerprint() &&
		credentials.GetFingerprint()->Length() )
	    value.Set( credentials.GetFingerprint()->Text() );
	else
	    value.Clear();
}


/**
 * NetSslTransport::SslServerInit
 *
 * @brief One time initialization for server side of connection for SSL.
 * @invariant Assures that the server-side SSL CTX structure has been initialized.
 *
 * @param error structure
 * @return nothing
 */
void
NetSslTransport::SslServerInit(StrPtr *hostname, Error *e)
{
	if( sServerCtx )
	    return;

# ifdef OS_NT
	/*
	 * Windows multi-threaded so must synchronize
	 * CTX creation, since we only want one.
	 */
	MutexLocker locker(sServerMutex);
	/*
	 * Now that we have the lock see if any
	 * thread created the CTX while we were
	 * waiting.
	 */
# endif // OS_NT

	if( !sServerCtx )
	{
#ifdef OS_NT
	    InitLockCallbacks( e );
	    if( e->Test())
		return;
#endif // OS_NT

	    TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
		"NetSslTransport::SslServerInit - Initializing server CTX structure." );

	    /*
	     * Added due to job084753: Swarm is a web app that reuses client processes.
	     * See the SslClientInit code for more info.
	     *
	     * Adding the fix to the SslClientInit here on the server side to be
	     * symmetric and to make sure that later we do not see a similar problem
	     * here. (Currently, we have not seen this issue on the server side.)
	     */
	    ERR_remove_thread_state(NULL);
	    // probably cannot check for error return from this call :-)

	    SSL_load_error_strings();
	    SSLCHECKERROR( e,
                       "NetSslTransport::SslServerInit SSL_load_error_strings",
                       MsgRpc::SslInit,
                       fail );
	    ERR_load_BIO_strings();
	    SSLCHECKERROR( e, "NetSslTransport::SslServerInit ERR_load_BIO_strings",
                       MsgRpc::SslInit,
                       fail );
	    if ( !SSL_library_init() )
	    {
		// executable not compiled supporting SSL
		// need to link with open SSL libraries
		e->Set(MsgRpc::SslNoSsl);
		return;
	    }
	    SSLCHECKERROR( e, "NetSslTransport::SslServerInit SSL_library_init",
                       MsgRpc::SslInit,
                       fail );

	    /* Set up cert and key:
	     * Note that we have already verified in RpcService::Listen
	     * that sslKey and sslCert exist in P4SSLDIR and are valid.
	     * Since we are doing lazy init of the SslCtx the
	     * files could have been deleted before hitting this code.  We will do
	     * another verify check here when we finally load the credentials
	     * for the first read/write.
	     */
	    credentials.ReadCredentials(e);
	    P4CHECKERROR( e, "NetSslTransport::SslServerInit ReadCredentials", fail );


	    sServerCtx = SSL_CTX_new( TLSv1_method() );
	    SSLNULLHANDLER( sServerCtx, e,
                        "NetSslTransport::SslServerInit SSL_CTX_new",
                        fail );

	    SSL_CTX_set_mode(
		sServerCtx,
		SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER );
	    SSLLOGFUNCTION( "NetSslTransport::SslServerInit SSL_CTX_set_mode" );


	    SSL_CTX_use_PrivateKey( sServerCtx, credentials.GetPrivateKey() );
	    SSLLOGFUNCTION(
		"NetSslTransport::SslServerInit SSL_CTX_use_PrivateKey" );
	    credentials.SetOwnKey(false);
	    /*
	     * Note: if want key passphrase protected then need to implement
	     * a callback function to supply the passphrase:
	     *     int passwd_cb(char *buf, int size, int flag, void *userdata);
	     *
	     * Alternatively strip the passphrase protection off the key via
	     *     cp server.key server.key.org
	     *     openssl [rsa|dsa] -in server.key.org -out server.key
	     */
	    SSL_CTX_use_certificate( sServerCtx, credentials.GetCertificate() );
	    SSLLOGFUNCTION(
		"NetSslTransport::SslServerInit SSL_CTX_use_certificate" );
	    credentials.SetOwnCert(false);

	    /*
	     * Set context to not verify certificate authentication with CA.
	     */
	    SSL_CTX_set_verify( sServerCtx, SSL_VERIFY_NONE, NULL /* no callback */);
	    SSLLOGFUNCTION(
		"NetSslTransport::SslServerInit SSL_CTX_set_verify server ctx" );

	    /*
	     * NOTE: The way this code is written there is no CA check on the cert.
	     * If want to do this then SSL_CTX_load_verify_locations(sServerCtx, NULL, sslCACert);
	     * The certificate authority certificate directory must be hashed:
	     *     c_rehash /path/to/certfolder
	     */
	}

	return;

fail:
	e->Set( MsgRpc::SslCtx ) << "the accepting server";
	return;
}

/**
 * NetSslTransport::DoHandshake
 *
 * @brief transport level method to invoke SSL handshake and provide debugging output
 * @invariant if SSL and CTX structures are non-null handshake has completed successfully
 *
 * @param error structure
 * @return none
 */
void
NetSslTransport::DoHandshake( Error *e )
{
	int sslRetval;

	if(ssl)
	    return;

	/*
	 * Lazy initialization: If no SSL context has been created for
	 * this type of connection (server side or client side) then do it
	 * now, then use it to create an SSL structure for this connection.
	 */
	if( this->isAccepted )
	{
	    ssl = SSL_new( sServerCtx );
	    SSLNULLHANDLER( ssl, e, "NetSslTransport::DoHandshake SSL_new", fail );
	    if ( p4tunable.Get( P4TUNE_SSL_SECONDARY_SUITE ) )
	    {
		SSL_set_cipher_list( ssl, SSL_SECONDARY_CIPHER_SUITE );
		SSLLOGFUNCTION( "NetSslTransport::DoHandshake SSL_set_cipher_list secondary" );
	    } else
	    {
		SSL_set_cipher_list( ssl, SSL_PRIMARY_CIPHER_SUITE );
		SSLLOGFUNCTION( "NetSslTransport::DoHandshake SSL_set_cipher_list primary" );
	    }
	}
	else
	{
	    ssl = SSL_new( sClientCtx );
	    SSLNULLHANDLER( ssl, e, "NetSslTransport::DoHandshake SSL_new", fail );
	}

	/*
	 * If debugging output configured, dump prioritized list of
	 * supported cipher-suites.
	 */
	if( SSLDEBUG_TRANS )
	{
	    int priority = 0;
	    bool shouldContinue = true;
	    p4debug.printf( "List of Cipher Suites supported:\n" );
	    while( shouldContinue )
	    {
		const char *cipherStr = SSL_get_cipher_list( ssl, priority );
		priority++;
		shouldContinue = (cipherStr) ? true : false;
		if( shouldContinue )
		{
		    p4debug.printf( "  Priority %d: %s\n", priority, cipherStr );
		}
	    }
	}

	/* Create a bio for this new socket */
	bio = BIO_new_socket( t, BIO_NOCLOSE );
	SSLNULLHANDLER( bio, e, "NetSslTransport::DoHandshake BIO_new_socket",
                    fail );

	/**
	 * Hooks the BIO up with the SSL *. Note once this is called then the
	 * SSL * owns the BIOs and will cleanup their allocation in an SSL_free
	 * if the reference count becomes zero.
	 */
	SSL_set_bio( ssl, bio, bio );
	SSLLOGFUNCTION( "NetSslTransport::DoHandshake SSL_set_bio" );

	if( !SslHandshake(e) )
	    goto fail;

	if( !isAccepted )
	{

	    X509 *serverCert = SSL_get_peer_certificate( ssl );
	    credentials.SetCertificate( serverCert, e );

	    if ( e->Test() )
	    {
		X509_free( serverCert );
		goto failNoRead;
	    }
	    else
	    {
		SSLLOGFUNCTION( credentials.GetFingerprint()->Text() );
	    }


	    if( SSLDEBUG_TRANS )
	    {
		char *str = NULL;
		// print out CERT info from server

		p4debug.printf( "Server certificate:" );
		str = X509_NAME_oneline( X509_get_subject_name( serverCert ), 0,
				     0 );
		SSLNULLHANDLER( str, e, "connect X509_get_subject_name", fail );
		p4debug.printf( "\t subject: %s\n", str );
		free( str );
		str = X509_NAME_oneline( X509_get_issuer_name( serverCert ), 0,
				     0 );
		SSLNULLHANDLER( str, e, "connect X509_get_issuer_name", fail );
		p4debug.printf( "\t issuer: %s\n", str );
		free( str );

	    }
	    X509_free( serverCert );
	    SSLLOGFUNCTION( "X509_free" );
	}

	return;

	/* Error Handling */
fail:
	lastRead = 1;

failNoRead:
	TRANSPORT_PRINT( SSLDEBUG_ERROR,
		"NetSslTransport::DoHandshake In fail error code." );
	if(ssl)
	{
	    SSL_free(ssl);
	    SSLLOGFUNCTION( "NetSslTransport::DoHandshake SSL_free" );
	    bio = NULL;
	    ssl = NULL;
	}
	// NET_CLOSE_SOCKET(t);
	if( isAccepted )
	{
	    TRANSPORT_PRINT( SSLDEBUG_ERROR,
		    "NetSslTransport::DoHandshake failed on server side.");
	    if( !e->Test() )
		e->Set( MsgRpc::SslAccept ) << "";
	}
	else
	{
	    TRANSPORT_PRINT( SSLDEBUG_ERROR,
		    "NetSslTransport::DoHandshake failed on client side.");
	    if( !e->Test() )
		e->Set( MsgRpc::SslConnect ) << GetPortParser().String() << "";
	}

}

/**
 * NetSslTransport::SslHandshake
 *
 * @brief low level method to invoke ssl handshake and handle asynchronous behavior of
 * the BIO structure.
 *
 * @param error structure
 * @return bool true = success, false = fail
 */
bool
NetSslTransport::SslHandshake( Error *e )
{
	bool      done = false;
	int       readable = isAccepted? 1 : 0;
	int       writable = isAccepted? 0 : 1;
	int       counter = 0;
	/* select timeout */
	const int tv = HALF_SECOND;
	int	  sslClientTimeoutMs
	    = p4tunable.Get( P4TUNE_SSL_CLIENT_TIMEOUT ) * 1000;
	DateTimeHighPrecision dtBeforeSelect, dtAfterSelect;
	int maxwait = p4tunable.Get( P4TUNE_NET_MAXWAIT ) * 1000;
	if( maxwait && 
	    ( sslClientTimeoutMs == 0 || maxwait < sslClientTimeoutMs ) )
	    sslClientTimeoutMs = maxwait;

	while ( !done )
	{
	    // Perform an SSL handshake.
	    int ret = 0;
	    int errorRet = 0;
	    if( isAccepted )
		ret = SSL_accept(ssl);
	    else
		ret = SSL_connect( ssl );

	    switch( errorRet = SSL_get_error( ssl, ret ) )
	    {
	    case SSL_ERROR_NONE:
		done = true;
		break;

	    case SSL_ERROR_WANT_READ:
		{
		    readable = 1;
		    writable = 0;
# ifdef WSAEWOULDBLOCK
	            int error = WSAGetLastError();

# else
	            int error = errno;
# endif
	            dtBeforeSelect.Now();
		    int selectRet =
			    selector->Select( readable, writable, tv );
		    dtAfterSelect.Now();
		    counter += MillisecondDifference(dtAfterSelect, dtBeforeSelect);

		    if( selectRet < 0 )
		    {
			e->Sys( "select", "socket" );
			return false;
		    }
		    /*
		     * Changed to prevent a bad guy forming a DOS attack on
		     * Linux by running nc against a p4d.  Previous code
		     * executed a tight loop calling SSL_accept and erroring
		     * out with a WANT_WRITE and a EAGAIN error. We stop this
		     * by sleeping for a millisecond before trying again.
		     * Some OSs return EWOULDBLOCK under this same condition.
		     *
		     * Note POSIX 2008 spec prevents the select call in this
		     * case statement from returning prematurely with EAGAIN,
		     * but Ubuntu seems to follow an earlier version of the
		     * spec. It appears that Mac OS X is complient since this
		     * problem does not exist on that platform.
		     */
# ifdef WSAEWOULDBLOCK
		    if ( WSAEWOULDBLOCK == error)
# else
		    if(( EAGAIN == error ) || ( EWOULDBLOCK == error) )
# endif
		    {

			if( counter > 10 )
			{
			    // Timeout code for new client using SSL going against old server.
			    if(!isAccepted && (counter > sslClientTimeoutMs)) {
				TRANSPORT_PRINTF( SSLDEBUG_ERROR,"NetSslTransport::SslHandshake failed on client side: %d", errorRet);
				e->Set( MsgRpc::SslConnect) << GetPortParser().String();
				Close();
				return false;
			    }
			    msleep(1);
			    counter++;
			}
			else
			{
			    TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
				"NetSslTransport::SslHandshake WANT_READ with EAGAIN or EWOULDBLOCK");
			}
		    }
		}
		break;

	    case SSL_ERROR_WANT_WRITE:
		readable = 0;
		writable = 1;
		if( selector->Select( readable, writable, tv ) < 0 )
		{
		    e->Sys( "select", "socket" );
		    return false;
		}
		TRANSPORT_PRINTF( SSLDEBUG_FUNCTION,"NetSslTransport::SslHandshake WANT_WRITE ret=%d", ret);
		break;
	    case SSL_ERROR_SSL:
		/* underlying protocol error dump error to 
		 * debug output
		 */
		char sslError[256];
		ERR_error_string( ERR_get_error(), sslError );
		TRANSPORT_PRINTF( SSLDEBUG_ERROR, "Handshake Failed: %s", sslError );
		e->Net( "ssl handshake", sslError);
		return false;
		break;
	    default:
		StrBuf errBuf;
		if( Error::IsNetError() )
		{
		    StrBuf tmp;
		    Error::StrNetError( tmp );
		    errBuf.Set( " (" );
		    errBuf.Append( &tmp );
		    errBuf.Append( ")" );
		}
		if( isAccepted )
		{
		    TRANSPORT_PRINTF( SSLDEBUG_ERROR,
			    "NetSslTransport::SslHandshake failed on server side: %d",
			    errorRet );
		    e->Set( MsgRpc::SslAccept) << errBuf;
		}
		else
		{
		    TRANSPORT_PRINTF( SSLDEBUG_ERROR,
		 	"NetSslTransport::SslHandshake failed on client side: %d",
		 	errorRet);
		    e->Set( MsgRpc::SslConnect) << GetPortParser().String() << errBuf;
		}
		return false;
	    } // end switch - last SSL error
	} // end while - keep going until the handshake is done.
	return true;
}


/*
 * NetSslTransport::SendOrReceive() - send or receive data as ready
 *
 * If data to write and no room to read, block writing.
 * If room to read and no data to write, block reading.
 * If both data to write and room to read, block until one is ready.
 *
 * If neither data to write nor room to read, don't call this!
 *
 * Brain-dead version of NetSslSelector::Select() indicates both
 * read/write are ready.  To avoid blocking on read, we only do so
 * if not instructed to write.
 *
 * Returns 1 if either data read or written.
 * Returns 0 if neither, meaning EOF or error.
 */

int
NetSslTransport::SendOrReceive( NetIoPtrs &io, Error *se, Error *re )
{
	// Hacky solution to allow us to send out a cleartext error
	// message to the client if they are attempting a cleartext
	// connection to our SSL server.
	if( clientNotSsl )
	{
	    int retval = NetTcpTransport::SendOrReceive( io, se, re );
	    Close();
	    return retval;
	}
	if( t < 0 )
	{
	    TRANSPORT_PRINT( SSLDEBUG_ERROR,"NetSslTransport::SendOrReceive connection "
		    "closed, returning w/o doing anything." );
	    // Socket has been closed don't try to use
	    return 0;
	}
	StrBuf buf;
	int  doRead = 0;
	int  doWrite = 0;

	int dataReady;
	int maxwait = p4tunable.Get( P4TUNE_NET_MAXWAIT );
	Timer waitTime;
	if( maxwait )
	{
	    maxwait *= 1000;
	    waitTime.Start();
	}

	int  readable = 0;
	int  writable = 0;

	/* flags set by check_availability(  ) that poll for I/O status */
	bool can_read = false;
	bool can_write = false;

	/* flags to mark all the combinations of why we're blocking */
	bool read_waiton_write = false;
	bool read_waiton_read = false;
	bool write_waiton_write = false;
	bool write_waiton_read = false;

	/* select timeout */
	int  tv;

	/* ssl status */
	int  sslError;
	int  sslPending;
	long errErrorNum;
	char errErrorStr[256];

	// Lazy call of handshake code.
	if( !ssl )
	{
	    DoHandshake( se );
	    if( se->Test() )
	    {
		re = se;
		goto end;
	    }
	}

	for ( ;; )
	{
	    doRead = io.recvPtr != io.recvEnd && !re->Test();
	    doWrite = io.sendPtr != io.sendEnd && !se->Test();
	    sslPending = 0;

	    if( !doRead && !doWrite )
	    {
		// cannot read or write
		return 0;
	    }
	    /*
	     * If we enter this loop and are supposed to read AND
	     * there is something already in the SSL read buffers
	     * then don't wait in the select call for something
	     * to appear in the kernel buffers.
	     */
	    sslPending = SSL_pending( ssl );


	    /*
	     * Complex logic to check OS level buffers for
	     * read or write:
	     * For Write:
	     * - if entered this function to write then always check
	     * - if entered to read but handshake is blocking operation
	     *   then write before continue writing
	     * DO NOT check write otherwise since will cause tight loop
	     * burning CPU on interactive commands.
	     *
	     * For Read:
	     * - if entered this function to read then always check
	     * - if entered to write but handshake is blocking operation
	     *   then read before continue writing
	     */
	    readable =
		( doRead || write_waiton_read || read_waiton_read ) ? 1 : 0;
	    writable =
		( doWrite || write_waiton_write || read_waiton_write ) ? 1 : 0;

	    if (readable && sslPending)
		tv = 0;
	    else if( (readable && breakCallback) || maxwait )
		tv = HALF_SECOND;
	    else
		tv = -1;


	    if( ( dataReady = selector->Select( readable, writable, tv ) < 0 ) )
	    {
		re->Sys( "select", "socket" );
		return 0;
	    }

	    if( !dataReady && maxwait && waitTime.Time() >= maxwait )
	    {
	        lastRead = 0;
	        TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL SendOrReceive maxwait expired." );
	        if( doRead )
	            re->Set( MsgRpc::MaxWait ) << "receive" << ( maxwait / 1000 );
	        else
	            se->Set( MsgRpc::MaxWait ) << "send" << ( maxwait / 1000 );
		return 0;
	    }

	    // Before checking for data do the callback isalive test.
	    // If the user defined IsAlive() call returns a zero
	    // value then the user wants this request to terminate.

	    if( doRead && breakCallback && !breakCallback->IsAlive() )
	    {
		lastRead = 0;
		re->Set( MsgRpc::Break );
		return 0;
	    }

	    if( SSLDEBUG_BUFFER )
	    {
		DateTimeHighPrecision dt;
		char buftime[ DTHighPrecisionBufSize ];

		dt.Now();
		dt.Fmt( buftime );

		p4debug.printf(
		    "State status:	time: %s\n"
		    "\tsslPending         %d - is something in the SSL read buffer?\n"
		    "\treadable           %d - is something in the OS read buffer?\n"
		    "\twritable           %d - is there available room OS write buffer?\n"
		    "\tdoRead             %d - we have room in P4rpc read buffer\n"
		    "\tdoWrite            %d - we have stuff to write in P4rpc write buffer\n"
		    "\twrite_waiton_write %d - ssl write buffer not available, try again when net net write buffer ready\n"
		    "\twrite_waiton_read  %d - ssl write buffer not available due to handshake, try again when net read buffer ready\n"
		    "\tread_waiton_write  %d - ssl read buffer not available due to handshake, try again when net write buffer ready\n"
		    "\tread_waiton_read   %d - ssl read buffer not available, try again when net read buffer ready\n",
		    buftime, sslPending, readable, writable, doRead, doWrite,
		    write_waiton_write, write_waiton_read, read_waiton_write,
		    read_waiton_read );
	    }

	    /* This "if" statement reads data. It will only be entered if
	     * the following conditions are all true:
	     * 1. we're not in the middle of a write
	     * 2. there's space left in the recv buffer
	     * 3. the kernel write buffer is available signaling that a handshake
	     *    write has completed (this write had been blocking our read, and
	     *    now we are free to continue it), or either SSL or OS says we can
	     *    read regardless of whether we're blocking for availability to read.
	     */
	    if( !( write_waiton_read || write_waiton_write ) && doRead
            && ( sslPending || readable || ( writable && read_waiton_write )) )
	    {
		/* clear the flags since we'll set them based on the I/O call's
		 * return
		 */
		read_waiton_read = false;
		read_waiton_write = false;

		/* read into the buffer after the current position */

		int l = SSL_read( ssl, io.recvPtr, io.recvEnd - io.recvPtr );
		SSLLOGFUNCTION( "NetSslTransport::SendOrReceive SSL_read" );

		switch ( sslError = SSL_get_error( ssl, l ) )
		{
		case SSL_ERROR_NONE:
		    /*
		     * no errors occurred.  update the new buffer size and signal
		     * that "have data" by returning 1.
		     */
		    if( l > 0 )
			TRANSPORT_PRINTF( SSLDEBUG_TRANS,
			    "NetSslTransport::SendOrReceive recv %d bytes",
			    l );

		    lastRead = 1;
		    io.recvPtr += l;
		    return 1;

		    break;
		case SSL_ERROR_ZERO_RETURN:
		    /* connection closed */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL_read returned SSL_ERROR_ZERO_RETURN" );
		    goto end;
		    break;
		case SSL_ERROR_WANT_READ:
		    /*
		     * we need to retry the read after kernel buffer available for
		     * reading
		     */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL_read returned SSL_ERROR_WANT_READ" );
		    read_waiton_read = true;
		    break;
		case SSL_ERROR_WANT_WRITE:
		    /*
		     * we need to retry the read after kernel buffer is available for
		     * writing
		     */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL_read returned SSL_ERROR_WANT_WRITE" );
		    read_waiton_write = true;
		    break;
		case SSL_ERROR_SYSCALL:
		    /*
		     * an I/O error occurred check underlying SSL ERR for info
		     * if none then report errno
		     */
		    errErrorNum = ERR_get_error();
	            if ( errErrorNum == 0 )
	            {
	        	if( l == 0)
	        	{
	        	    TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL_read encountered an EOF." );
	        	    re->Net( "read", "SSL_read encountered an EOF." );
	        	}
	        	else if ( l < 0 )
	        	{
	        	    Error::StrError(buf, errno);
	        	    TRANSPORT_PRINTF( SSLDEBUG_ERROR, "SSL_read encountered a system error: %s", buf.Text() );
	        	    re->Net( "read", buf.Text() );
	        	}
	        	else
	        	{
	        	    TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
	        		    "SSL_read claims SSL_ERROR_SYSCALL but returns data." );
	        	    if( l > 0 )
	        		TRANSPORT_PRINTF( SSLDEBUG_TRANS,
	        			"NetSslTransport::SendOrReceive recv %d bytes\n",
	        			l );
	        	    lastRead = 1;
	        	    io.recvPtr += l;
	        	    return 1;
	        	}
	            }
	            else
	            {
	        	ERR_error_string( errErrorNum, errErrorStr );
	        	TRANSPORT_PRINTF( SSLDEBUG_ERROR,
	        		"SSL_read encountered a syscall ERR: %s", errErrorStr );
	        	re->Net( "read", errErrorStr );
	            }
	            re->Set( MsgRpc::SslRecv );
	            goto end;
	            break;
		default:
		    if( l == 0 )
		    {
			TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
			    "SSL_read attempted on closed connection." );
			if( doWrite )
			{
			    // Error: connection was closed but we had stuff to write
			    re->Net( "read", "socket" );
			    re->Set( MsgRpc::SslRecv );
			}
			/*
			 * Connection closed but did not shutdown SSL cleanly.
			 * The p4 proxy will do this if the command issued by
			 * its client does not require forwarding to the p4d server.
			 */
			goto end;
		    }

		    /* ERROR */
		    TRANSPORT_PRINTF( SSLDEBUG_ERROR,
			    "SSL_read returned unknown error: %d",
                                  sslError );
		    re->Net( "read", "socket" );
		    re->Set( MsgRpc::SslRecv );
		    goto end;
		}
	    }

	    /* This "if" statement writes data. It will only be entered if
	     * the following conditions are all true:
	     * 1. we're not in the middle of a read
	     * 2. there's room in the buffer
	     * 3. the kernel read buffer is available signaling that a handshake
	     *    read has completed (this rad had been blocking our write, and
	     *    now we are free to continue it), or OS says we can write regardless
	     *    of whether we're blocking for availability to write.
	     */
	    if( !( read_waiton_write || read_waiton_read ) && doWrite
            && ( writable || ( readable && write_waiton_read )) )
	    {
		/* clear the flags */
		write_waiton_read = false;
		write_waiton_write = false;

		/* perform the write from the start of the buffer */
		int l = SSL_write( ssl, io.sendPtr, io.sendEnd - io.sendPtr );
		SSLLOGFUNCTION( "NetSslTransport::SendOrReceive SSL_write" );

		switch ( sslError = SSL_get_error( ssl, l ) )
		{
		case SSL_ERROR_NONE:
		    /*
		     * no errors occurred.  update the new buffer size and signal
		     * that "have data" by returning 1.
		     */
		    if( l > 0 )
			TRANSPORT_PRINTF( SSLDEBUG_TRANS,
				"NetSslTransport send %d bytes\n", l );
		    lastRead = 0;
		    io.sendPtr += l;
		    return 1;

		    break;
		case SSL_ERROR_ZERO_RETURN:
		    /* connection closed */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR,
			    "SSL_write returned SSL_ERROR_ZERO_RETURN" );
		    goto end;
		case SSL_ERROR_WANT_READ:
		    /*
		     * we need to retry the write after A is available for
		     * reading
		     */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR,
			    "SSL_write returned SSL_ERROR_WANT_READ" );
		    write_waiton_read = true;
		    break;
		case SSL_ERROR_WANT_WRITE:
		    /*
		     * we need to retry the write after A is available for
		     * writing
		     */
		    TRANSPORT_PRINT( SSLDEBUG_ERROR,
			    "SSL_write returned SSL_ERROR_WANT_WRITE" );
		    write_waiton_write = true;
		    break;
		case SSL_ERROR_SYSCALL:
		    /*
		     * an I/O error occurred check underlying SSL ERR for info
		     * if none then report errno
		     */
		    errErrorNum = ERR_get_error();
	            if ( errErrorNum == 0 )
	            {
	        	if( l == 0)
	        	{
	        	    TRANSPORT_PRINT( SSLDEBUG_ERROR, "SSL_write encountered an EOF." );
	        	    se->Net( "write", "SSL_write encountered an EOF." );
	        	}
	        	else if ( l < 0 )
	        	{
	        	    Error::StrError(buf, errno);
	        	    TRANSPORT_PRINTF( SSLDEBUG_ERROR,
	        		    "SSL_write encountered a system error: %s", buf.Text() );
	        	    se->Net( "write", buf.Text() );
	        	}
	        	else
	        	{
	        	    TRANSPORT_PRINT( SSLDEBUG_ERROR,
	        		    "SSL_write claims SSL_ERROR_SYSCALL but returns data." );
	        	    if( l > 0 )
	        		TRANSPORT_PRINTF( SSLDEBUG_TRANS, "NetSslTransport send %d bytes", l );
	        	    lastRead = 0;
	        	    io.sendPtr += l;
	        	    return 1;
	        	}
	            }
	            else
	            {
	        	ERR_error_string( errErrorNum, errErrorStr );
	        	TRANSPORT_PRINTF( SSLDEBUG_ERROR,
	        		"SSL_write encountered a syscall ERR: %s", errErrorStr );
	        	se->Net( "write", errErrorStr );
	            }
	            se->Set( MsgRpc::SslSend );
	            goto end;
		    break;
		default:
		    if( l == 0 )
		    {
			/*
			 * Connection closed but did not shutdown SSL cleanly.
			 * The p4 proxy will do this if the command issued by
			 * its client does not require forwarding to the p4d server.
			 */
			TRANSPORT_PRINT( SSLDEBUG_FUNCTION,
			    "SSL_write attempted on closed connection." );
			goto end;
		    }

		    /* ERROR */
		    TRANSPORT_PRINTF( SSLDEBUG_ERROR,
			    "SSL_write returned unknown error: %d",
                                  sslError );
		    se->Net( "write", "socket" );
		    se->Set( MsgRpc::SslSend );
		    goto end;
		}
	    }
	}

end:
	Close();
	return 0;
}


/**
 * NetSslTransport::Close
 *
 * @brief handles teardown of the SSL connection and deletion of the
 * of the SSL and BIO structures, closes socket
 * @invariant SSL and BIO are NULL if the connection is closed
 *
 * @return none
 */
void
NetSslTransport::Close( void )
{
	int ret;
	if( t < 0 )
	    return;

	TRANSPORT_PRINTF( SSLDEBUG_CONNECT, "NetSslTransport %s closing %s",
                           GetAddress( RAF_PORT )->Text(),
                           GetPeerAddress( RAF_PORT )->Text() );

	// Avoid TIME_WAIT on the server by reading the EOF after
	// the last message sent by the client.  Getting the EOF
	// means we've received the TH_FIN packet, which means we
	// don't have to send our own on close().  He who sends
	// a TH_FIN goes into the 2 minute TIME_WAIT imposed by TCP.

	TRANSPORT_PRINTF( SSLDEBUG_TRANS, "NetSslTransport lastRead=%d", lastRead );

	if( lastRead )
	{
	    int  r = 1;
	    int  w = 0;
	    char buf[1];

	    if( selector->Select( r, w, -1 ) >= 0 && r )
		read( t, buf, 1 );
	}

	if (ssl)
	{
	    if( SSL_get_shutdown( ssl ) & SSL_RECEIVED_SHUTDOWN )
	    {
		// clean shutdown
		SSL_shutdown( ssl );
		SSLLOGFUNCTION( "NetSslTransport::Close SSL_shutdown" );
	    }
	    else
	    {
		/*
		 * An error is causing this shutdown, so we remove session
		 * from cache.
		 */
		SSL_clear( ssl );
		SSLLOGFUNCTION( "NetSslTransport::Close SSL_clear" );
	    }
	    BIO_pop( bio );
	    SSLLOGFUNCTION( "NetSslTransport::Close BIO_pop" );
	    SSL_free( ssl );
	    SSLLOGFUNCTION( "NetSslTransport::Close SSL_free" );
	}

	bio = NULL;
	ssl = NULL;

	if( lastRead )
	{
	    int  r = 1;
	    int  w = 0;
	    char buf[1];

	    if( selector->Select( r, w, -1 ) >= 0 && r )
		read( t, buf, 1 );
	}

	NET_CLOSE_SOCKET(t);
}


void
NetSslTransport::ClientMismatch( Error *e )
{
	if ( CheckForHandshake(t) == PeekCleartext )
	{
	    TRANSPORT_PRINT( SSLDEBUG_ERROR,
		    "Handshake peek appears not to be for SSL.");
	    // this is a non-ssl connection
	    e->Set( MsgRpc::SslCleartext );
	    clientNotSsl = true;
	}
}

void
NetSslTransport::ValidateRuntimeVsCompiletimeSSLVersion( Error *e )
{
	StrBuf sb;
	GetVersionString( sb, SSLeay() );
	TRANSPORT_PRINTF( SSLDEBUG_ERROR,
		"OpenSSL runtime version %s", sb.Text() );
	sb.Clear();
	GetVersionString( sb, OPENSSL_VERSION_NUMBER );
	TRANSPORT_PRINTF( SSLDEBUG_ERROR,
		"OpenSSL compile version %s", sb.Text() );

	if ( GET_MJR_SSL_VERSION(SSLeay()) < GET_MJR_SSL_VERSION(sVersion1_0_0) )
	{
	    e->Set(MsgRpc::SslLibMismatch) << sVerStr1_0_0;
	}
}

void
NetSslTransport::GetVersionString( StrBuf &sb, unsigned long version )
{
	/* Version numbering explanation from OpenSSL openssl/opensslv.h file:
	 *
	 * Numeric release version identifier:
	 * MNNFFPPS: major minor fix patch status
	 * The status nibble has one of the values 0 for development, 1 to e for betas
	 * 1 to 14, and f for release.  The patch level is exactly that.
	 * For example:
	 * 0.9.3-dev          0x00903000
	 * 0.9.3-beta1        0x00903001
	 * 0.9.3-beta2-dev    0x00903002
	 * 0.9.3-beta2        0x00903002 (same as ...beta2-dev)
	 * 0.9.3              0x0090300f
	 * 0.9.3a             0x0090301f
	 * 0.9.4              0x0090400f
	 * 1.2.3z             0x102031af
	 */
	unsigned long M = (version >> 28) & ~0xfffffff0L;
	unsigned long N = (version >> 20) & ~0xffffff00L;
	unsigned long P = (version >> 12) & ~0xffffff00L;
	sb << M << "." << N << "." << P;
}

////////////////////////////////////////////////////////////////////////////
//  OpenSSL Dynamic Locking Callback Functions                            //
////////////////////////////////////////////////////////////////////////////
// Dynamic locking code only being used on NT in 2012.1, will be used on
// other platforms in 2012.2 (by that time I will include pthreads to the
// HPUX build). In 2012.1 HPUX has many compile errors for pthreads.
#ifndef OS_HPUX

static void LockingFunction( int mode, int n, const char *file, int line )
{
# ifdef OS_NT
    if (mode & CRYPTO_LOCK)
    {
	WaitForSingleObject( mutexArray[n], INFINITE );
    }
    else
    {
	ReleaseMutex( mutexArray[n] );
    }
# else
    if( mode & CRYPTO_LOCK )
    {
	pthread_mutex_lock( &mutexArray[n] );
    }
    else
    {
	pthread_mutex_unlock( &mutexArray[n] );
    }
# endif // OS_NT
}

/**
 * OpenSSL uniq id function.
 *
 * @return    thread id
 */
static unsigned long IdFunction( void )
{
# ifdef OS_NT
    return ((unsigned long) GetCurrentThreadId());
# else
    return ((unsigned long) pthread_self());
# endif
}

/**
 * OpenSSL allocate and initialize dynamic crypto lock.
 *
 * @param    file    source file name
 * @param    line    source file line number
 */
static struct CRYPTO_dynlock_value *
DynCreateFunction( const char *file, int line )
{
    struct CRYPTO_dynlock_value *value;

    value = (struct CRYPTO_dynlock_value *) malloc(
	    sizeof(struct CRYPTO_dynlock_value) );
    if( !value )
    {
	goto err;
    }
# ifdef OS_NT
    value->mutex = CreateMutex( NULL, FALSE, NULL );
# else
    pthread_mutex_init( &value->mutex, NULL );
# endif // OS_NT
    return value;

    err: return (NULL);
}

/**
 * OpenSSL dynamic locking function.
 *
 * @param    mode    lock mode
 * @param    l        lock structure pointer
 * @param    file    source file name
 * @param    line    source file line number
 * @return    none
 */
static void DynLockFunction(
        int mode,
        struct CRYPTO_dynlock_value *l,
        const char *file,
        int line )
{
# ifdef OS_NT
    if (mode & CRYPTO_LOCK)
    {
	WaitForSingleObject( l->mutex, INFINITE );
    }
    else
    {
	ReleaseMutex( l->mutex );
    }
# else
    if( mode & CRYPTO_LOCK )
    {
	pthread_mutex_lock( &l->mutex );
    }
    else
    {
	pthread_mutex_unlock( &l->mutex );
    }
# endif // OS_NT
}

/**
 * OpenSSL destroy dynamic crypto lock.
 *
 * @param    l        lock structure pointer
 * @param    file    source file name
 * @param    line    source file line number
 * @return    none
 */

static void DynDestroyFunction(
        struct CRYPTO_dynlock_value *l,
        const char *file,
        int line )
{
# ifdef OS_NT
    CloseHandle(l->mutex);
# else
    pthread_mutex_destroy( &l->mutex );
# endif // OS_NT
    free( l );
}

static int InitLockCallbacks( Error *e )
{
	int i;
	int numlocks = CRYPTO_num_locks();

	/* static locks area */
# ifdef OS_NT
	mutexArray = (HANDLE *) malloc( CRYPTO_num_locks() * sizeof(HANDLE) );
# else
	mutexArray = (pthread_mutex_t *) malloc( CRYPTO_num_locks() * sizeof(pthread_mutex_t) );
# endif // OS_NT

	if( mutexArray == NULL )
	{
	    e->Set(MsgRpc::Operat) << "malloc";
	    return -1;
	}

	for ( i = 0; i < numlocks; i++ )
	{
# ifdef OS_NT
	    mutexArray[i] = CreateMutex( NULL, FALSE, NULL );
# else
	    pthread_mutex_init( &mutexArray[i], NULL );
# endif // OS_NT
	}
	/* static locks callbacks */
	CRYPTO_set_locking_callback( LockingFunction );
	CRYPTO_set_id_callback( IdFunction );
	/* dynamic locks callbacks */
	CRYPTO_set_dynlock_create_callback( DynCreateFunction );
	CRYPTO_set_dynlock_lock_callback( DynLockFunction );
	CRYPTO_set_dynlock_destroy_callback( DynDestroyFunction );

	return (0);
}

/**
 * Cleanup TLS library.
 *
 * @return    0
 */
static int ShudownLockCallbacks( void )
{
    int i;
    int numlocks = CRYPTO_num_locks();

    if( mutexArray == NULL )
    {
	return (0);
    }

    CRYPTO_set_dynlock_create_callback( NULL );
    CRYPTO_set_dynlock_lock_callback( NULL );
    CRYPTO_set_dynlock_destroy_callback( NULL );

    CRYPTO_set_locking_callback( NULL );
    CRYPTO_set_id_callback( NULL );

    for ( i = 0; i < numlocks; i++ )
    {
# ifdef OS_NT
	CloseHandle( mutexArray[i] );
# else
	pthread_mutex_destroy( &mutexArray[i] );
# endif // OS_NT
    }

    return (0);
}

# endif // !OS_HPUX

# endif //USE_SSL
# Change User Description Committed
#1 19472 Liz Lam Initial add of the 2016.1 p4/p4api source code.