Files
znc/src/Csocket.cpp
Alexey Sokolov 3aa6b581fc Csock: able use non-int number of secs for timer.
This patch happened in upstream csocket too, so update won't break
anything.
2012-03-20 09:03:44 +07:00

3564 lines
88 KiB
C++

/** @file
*
* Copyright (c) 1999-2012 Jim Hull <csocket@jimloco.com>
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
* Redistributions in any form must be accompanied by information on how to obtain
* complete source code for this software and any accompanying software that uses this software.
* The source code must either be included in the distribution or be available for no more than
* the cost of distribution plus a nominal fee, and must be freely redistributable
* under reasonable conditions. For an executable file, complete source code means the source
* code for all modules it contains. It does not include source code for modules or files
* that typically accompany the major components of the operating system on which the executable file runs.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*
*/
/***
* doing this because there seems to be a bug that is losing the "short" on htons when in optimize mode turns into a macro
* gcc 4.3.4
*/
#if defined(__OPTIMIZE__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 3
#pragma GCC diagnostic warning "-Wconversion"
#endif /* defined(__OPTIMIZE__) && __GNUC__ == 4 && __GNUC_MINOR__ >= 3 */
#include <znc/Csocket.h>
#ifdef __NetBSD__
#include <sys/param.h>
#endif /* __NetBSD__ */
#ifdef HAVE_LIBSSL
#include <stdio.h>
#include <openssl/conf.h>
#include <openssl/engine.h>
#endif /* HAVE_LIBSSL */
#include <list>
#define CS_SRANDBUFFER 128
using namespace std;
#define CREATE_ARES_VER( a, b, c ) ((a<<16)|(b<<8)|c)
#ifndef _NO_CSOCKET_NS // some people may not want to use a namespace
namespace Csocket
{
#endif /* _NO_CSOCKET_NS */
static int g_iCsockSSLIdx = 0; //!< this get setup once in InitSSL
int GetCsockClassIdx()
{
return( g_iCsockSSLIdx );
}
#ifdef _WIN32
#if defined(_WIN32) && (!defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600))
//! thanks to KiNgMaR @ #znc for this wrapper
static int inet_pton( int af, const char *src, void *dst )
{
sockaddr_storage aAddress;
int iAddrLen = sizeof( sockaddr_storage );
memset( &aAddress, 0, iAddrLen );
char *pTmp = strdup( src );
aAddress.ss_family = af; // this is important:
// The function fails if the sin_family member of the SOCKADDR_IN structure is not set to AF_INET or AF_INET6.
int iRet = WSAStringToAddressA( pTmp, af, NULL, ( sockaddr * )&aAddress, &iAddrLen );
free( pTmp );
if( iRet == 0 )
{
if( af == AF_INET6 )
memcpy( dst, &(( sockaddr_in6 * )&aAddress )->sin6_addr, sizeof( in6_addr ) );
else
memcpy( dst, &(( sockaddr_in * )&aAddress )->sin_addr, sizeof( in_addr ) );
return( 1 );
}
return( -1 );
}
#endif
static inline void set_non_blocking( cs_sock_t fd )
{
u_long iOpts = 1;
ioctlsocket( fd, FIONBIO, &iOpts );
}
/*
* not used by anything anymore
static inline void set_blocking(cs_sock_t fd)
{
u_long iOpts = 0;
ioctlsocket( fd, FIONBIO, &iOpts );
}
*/
static inline void set_close_on_exec( cs_sock_t fd )
{
// TODO add this for windows
// see http://gcc.gnu.org/ml/java-patches/2002-q1/msg00696.html
// for infos on how to do this
}
#else
static inline void set_non_blocking( cs_sock_t fd )
{
int fdflags = fcntl( fd, F_GETFL, 0 );
if( fdflags < 0 )
return; // Ignore errors
fcntl( fd, F_SETFL, fdflags|O_NONBLOCK );
}
/*
* not used by anything anymore
static inline void set_blocking(cs_sock_t fd)
{
int fdflags = fcntl(fd, F_GETFL, 0);
if ( fdflags < 0 )
return; // Ignore errors
fdflags &= ~O_NONBLOCK;
fcntl( fd, F_SETFL, fdflags );
}
*/
static inline void set_close_on_exec( cs_sock_t fd )
{
int fdflags = fcntl( fd, F_GETFD, 0 );
if( fdflags < 0 )
return; // Ignore errors
fcntl( fd, F_SETFD, fdflags|FD_CLOEXEC );
}
#endif /* _WIN32 */
void CSSockAddr::SinFamily()
{
#ifdef HAVE_IPV6
m_saddr6.sin6_family = PF_INET6;
#endif /* HAVE_IPV6 */
m_saddr.sin_family = PF_INET;
}
void CSSockAddr::SinPort( u_short iPort )
{
#ifdef HAVE_IPV6
m_saddr6.sin6_port = htons( iPort );
#endif /* HAVE_IPV6 */
m_saddr.sin_port = htons( iPort );
}
void CSSockAddr::SetIPv6( bool b )
{
#ifndef HAVE_IPV6
if( b )
{
CS_DEBUG( "-DHAVE_IPV6 must be set during compile time to enable this feature" );
m_bIsIPv6 = false;
return;
}
#endif /* HAVE_IPV6 */
m_bIsIPv6 = b;
SinFamily();
}
#ifdef HAVE_LIBSSL
Csock *GetCsockFromCTX( X509_STORE_CTX *pCTX )
{
Csock *pSock = NULL;
SSL *pSSL = ( SSL * )X509_STORE_CTX_get_ex_data( pCTX, SSL_get_ex_data_X509_STORE_CTX_idx() );
if( pSSL )
pSock = ( Csock * )SSL_get_ex_data( pSSL, GetCsockClassIdx() );
return( pSock );
}
#endif /* HAVE_LIBSSL */
#ifdef USE_GETHOSTBYNAME
// this issue here is getaddrinfo has a significant behavior difference when dealing with round robin dns on an
// ipv4 network. This is not desirable IMHO. so when this is compiled without ipv6 support backwards compatibility
// is maintained.
static int __GetHostByName( const CS_STRING & sHostName, struct in_addr *paddr, u_int iNumRetries )
{
int iReturn = HOST_NOT_FOUND;
struct hostent *hent = NULL;
#ifdef __linux__
char hbuff[2048];
struct hostent hentbuff;
int err;
for( u_int a = 0; a < iNumRetries; a++ )
{
memset(( char * )hbuff, '\0', 2048 );
iReturn = gethostbyname_r( sHostName.c_str(), &hentbuff, hbuff, 2048, &hent, &err );
if( iReturn == 0 )
break;
if( iReturn != TRY_AGAIN )
{
CS_DEBUG( "gethostyname_r: " << hstrerror( h_errno ) );
break;
}
}
if( !hent && iReturn == 0 )
iReturn = HOST_NOT_FOUND;
#else
for( u_int a = 0; a < iNumRetries; a++ )
{
iReturn = HOST_NOT_FOUND;
hent = gethostbyname( sHostName.c_str() );
if( hent )
{
iReturn = 0;
break;
}
if( h_errno != TRY_AGAIN )
{
#ifndef _WIN32
CS_DEBUG( "gethostyname: " << hstrerror( h_errno ) );
#endif /* _WIN32 */
break;
}
}
#endif /* __linux__ */
if( iReturn == 0 )
memcpy( &paddr->s_addr, hent->h_addr_list[0], sizeof( paddr->s_addr ) );
return( iReturn == TRY_AGAIN ? EAGAIN : iReturn );
}
#endif /* !USE_GETHOSTBYNAME */
#ifdef HAVE_C_ARES
void Csock::FreeAres()
{
if( m_pARESChannel )
{
ares_destroy( m_pARESChannel );
m_pARESChannel = NULL;
}
}
#endif /* HAVE_C_ARES */
#ifdef HAVE_C_ARES
static void AresHostCallback( void *pArg, int status, int timeouts, struct hostent *hent )
{
Csock *pSock = ( Csock * )pArg;
if( status == ARES_SUCCESS && hent && hent->h_addr_list[0] != NULL )
{
CSSockAddr *pSockAddr = pSock->GetCurrentAddr();
if( hent->h_addrtype == AF_INET )
{
pSock->SetIPv6( false );
memcpy( pSockAddr->GetAddr(), hent->h_addr_list[0], sizeof( *( pSockAddr->GetAddr() ) ) );
}
#ifdef HAVE_IPV6
else if( hent->h_addrtype == AF_INET6 )
{
pSock->SetIPv6( true );
memcpy( pSockAddr->GetAddr6(), hent->h_addr_list[0], sizeof( *( pSockAddr->GetAddr6() ) ) );
}
#endif /* HAVE_IPV6 */
else
{
status = ARES_ENOTFOUND;
}
}
else
{
CS_DEBUG( ares_strerror( status ) );
if( status == ARES_SUCCESS )
{
CS_DEBUG( "Received ARES_SUCCESS without any useful reply, using NODATA instead" );
status = ARES_ENODATA;
}
}
pSock->SetAresFinished( status );
}
#endif /* HAVE_C_ARES */
CGetAddrInfo::CGetAddrInfo( const CS_STRING & sHostname, Csock *pSock, CSSockAddr & csSockAddr )
: m_pSock( pSock ), m_csSockAddr( csSockAddr )
{
m_sHostname = sHostname;
m_pAddrRes = NULL;
m_iRet = ETIMEDOUT;
}
CGetAddrInfo::~CGetAddrInfo()
{
if( m_pAddrRes )
freeaddrinfo( m_pAddrRes );
m_pAddrRes = NULL;
}
void CGetAddrInfo::Init()
{
memset(( struct addrinfo * )&m_cHints, '\0', sizeof( m_cHints ) );
m_cHints.ai_family = m_csSockAddr.GetAFRequire();
m_cHints.ai_socktype = SOCK_STREAM;
m_cHints.ai_protocol = IPPROTO_TCP;
#ifdef AI_ADDRCONFIG
// this is suppose to eliminate host from appearing that this system can not support
m_cHints.ai_flags = AI_ADDRCONFIG;
#endif /* AI_ADDRCONFIG */
if( m_pSock && ( m_pSock->GetType() == Csock::LISTENER || m_pSock->GetConState() == Csock::CST_BINDVHOST ) )
{
// when doing a dns for bind only, set the AI_PASSIVE flag as suggested by the man page
m_cHints.ai_flags |= AI_PASSIVE;
}
}
int CGetAddrInfo::Process()
{
m_iRet = getaddrinfo( m_sHostname.c_str(), NULL, &m_cHints, &m_pAddrRes );
if( m_iRet == EAI_AGAIN )
return( EAGAIN );
else if( m_iRet == 0 )
return( 0 );
return( ETIMEDOUT );
}
int CGetAddrInfo::Finish()
{
if( m_iRet == 0 && m_pAddrRes )
{
std::list<struct addrinfo *> lpTryAddrs;
bool bFound = false;
for( struct addrinfo *pRes = m_pAddrRes; pRes; pRes = pRes->ai_next )
{
// pass through the list building out a lean list of candidates to try. AI_CONFIGADDR doesn't always seem to work
#ifdef __sun
if(( pRes->ai_socktype != SOCK_STREAM ) || ( pRes->ai_protocol != IPPROTO_TCP && pRes->ai_protocol != IPPROTO_IP ) )
#else
if(( pRes->ai_socktype != SOCK_STREAM ) || ( pRes->ai_protocol != IPPROTO_TCP ) )
#endif /* __sun work around broken impl of getaddrinfo */
continue;
if(( m_csSockAddr.GetAFRequire() != CSSockAddr::RAF_ANY ) && ( pRes->ai_family != m_csSockAddr.GetAFRequire() ) )
continue; // they requested a special type, so be certain we woop past anything unwanted
lpTryAddrs.push_back( pRes );
}
for( std::list<struct addrinfo *>::iterator it = lpTryAddrs.begin(); it != lpTryAddrs.end(); )
{
// cycle through these, leaving the last iterator for the outside caller to call, so if there is an error it can call the events
struct addrinfo * pRes = *it;
bool bTryConnect = false;
if( pRes->ai_family == AF_INET )
{
if( m_pSock )
m_pSock->SetIPv6( false );
m_csSockAddr.SetIPv6( false );
struct sockaddr_in *pTmp = ( struct sockaddr_in * )pRes->ai_addr;
memcpy( m_csSockAddr.GetAddr(), &( pTmp->sin_addr ), sizeof( *( m_csSockAddr.GetAddr() ) ) );
if( m_pSock && m_pSock->GetConState() == Csock::CST_DESTDNS && m_pSock->GetType() == Csock::OUTBOUND )
{
bTryConnect = true;
}
else
{
bFound = true;
break;
}
}
#ifdef HAVE_IPV6
else if( pRes->ai_family == AF_INET6 )
{
if( m_pSock )
m_pSock->SetIPv6( true );
m_csSockAddr.SetIPv6( true );
struct sockaddr_in6 *pTmp = ( struct sockaddr_in6 * )pRes->ai_addr;
memcpy( m_csSockAddr.GetAddr6(), &( pTmp->sin6_addr ), sizeof( *( m_csSockAddr.GetAddr6() ) ) );
if( m_pSock && m_pSock->GetConState() == Csock::CST_DESTDNS && m_pSock->GetType() == Csock::OUTBOUND )
{
bTryConnect = true;
}
else
{
bFound = true;
break;
}
}
#endif /* HAVE_IPV6 */
it++; // increment the iterator her so we know if its the last element or not
if( bTryConnect && it != lpTryAddrs.end() )
{
// save the last attempt for the outer loop, the issue then becomes that the error is thrown on the last failure
if( m_pSock->CreateSocksFD() && m_pSock->Connect() )
{
m_pSock->SetSkipConnect( true ); // this tells the socket that the connection state has been started
bFound = true;
break;
}
m_pSock->CloseSocksFD();
}
else if( bTryConnect )
{
bFound = true;
}
}
if( bFound ) // the data pointed to here is invalid now, but the pointer itself is a good test
{
return( 0 );
}
}
return( ETIMEDOUT );
}
int GetAddrInfo( const CS_STRING & sHostname, Csock *pSock, CSSockAddr & csSockAddr )
{
#ifdef USE_GETHOSTBYNAME
if( pSock )
pSock->SetIPv6( false );
csSockAddr.SetIPv6( false );
int iRet = __GetHostByName( sHostname, csSockAddr.GetAddr(), 3 );
return( iRet );
#else
CGetAddrInfo cInfo( sHostname, pSock, csSockAddr );
cInfo.Init();
int iRet = cInfo.Process();
if( iRet != 0 )
return( iRet );
return( cInfo.Finish() );
#endif /* USE_GETHOSTBYNAME */
}
int Csock::ConvertAddress( const struct sockaddr_storage * pAddr, socklen_t iAddrLen, CS_STRING & sIP, u_short * piPort )
{
char szHostname[NI_MAXHOST];
char szServ[NI_MAXSERV];
int iRet = getnameinfo(( const struct sockaddr * )pAddr, iAddrLen, szHostname, NI_MAXHOST, szServ, NI_MAXSERV, NI_NUMERICHOST|NI_NUMERICSERV );
if( iRet == 0 )
{
sIP = szHostname;
if( piPort )
*piPort = ( u_short )atoi( szServ );
}
return( iRet );
}
bool InitCsocket()
{
#ifdef _WIN32
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
if( iResult != NO_ERROR )
return( false );
#endif /* _WIN32 */
#ifdef HAVE_C_ARES
#if ARES_VERSION >= CREATE_ARES_VER( 1, 6, 1 )
if( ares_library_init( ARES_LIB_INIT_ALL ) != 0 )
return( false );
#endif /* ARES_VERSION >= CREATE_ARES_VER( 1, 6, 1 ) */
#endif /* HAVE_C_ARES */
#ifdef HAVE_LIBSSL
if( !InitSSL() )
return( false );
#endif /* HAVE_LIBSSL */
return( true );
}
void ShutdownCsocket()
{
#ifdef HAVE_LIBSSL
ERR_remove_state( 0 );
ENGINE_cleanup();
CONF_modules_unload( 1 );
ERR_free_strings();
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
#endif /* HAVE_LIBSSL */
#ifdef HAVE_C_ARES
#if ARES_VERSION >= CREATE_ARES_VER( 1, 6, 1 )
ares_library_cleanup();
#endif /* ARES_VERSION >= CREATE_ARES_VER( 1, 6, 1 ) */
#endif /* HAVE_C_ARES */
#ifdef _WIN32
WSACleanup();
#endif /* _WIN32 */
}
#ifdef HAVE_LIBSSL
bool InitSSL( ECompType eCompressionType )
{
SSL_load_error_strings();
if( SSL_library_init() != 1 )
{
CS_DEBUG( "SSL_library_init() failed!" );
return( false );
}
#ifndef _WIN32
if( access( "/dev/urandom", R_OK ) == 0 )
RAND_load_file( "/dev/urandom", 1024 );
else if( access( "/dev/random", R_OK ) == 0 )
RAND_load_file( "/dev/random", 1024 );
else
{
CS_DEBUG( "Unable to locate entropy location! Tried /dev/urandom and /dev/random" );
return( false );
}
#endif /* _WIN32 */
COMP_METHOD *cm = NULL;
if( CT_ZLIB & eCompressionType )
{
cm = COMP_zlib();
if( cm )
SSL_COMP_add_compression_method( CT_ZLIB, cm );
}
if( CT_RLE & eCompressionType )
{
cm = COMP_rle();
if( cm )
SSL_COMP_add_compression_method( CT_RLE, cm );
}
// setting this up once in the begining
g_iCsockSSLIdx = SSL_get_ex_new_index( 0, ( void * )"CsockGlobalIndex", NULL, NULL, NULL );
return( true );
}
void SSLErrors( const char *filename, u_int iLineNum )
{
unsigned long iSSLError = 0;
while(( iSSLError = ERR_get_error() ) != 0 )
{
CS_DEBUG( "at " << filename << ":" << iLineNum );
char szError[512];
memset(( char * ) szError, '\0', 512 );
ERR_error_string_n( iSSLError, szError, 511 );
if( *szError )
CS_DEBUG( szError );
}
}
#endif /* HAVE_LIBSSL */
void CSAdjustTVTimeout( struct timeval & tv, long iTimeoutMS )
{
if( iTimeoutMS >= 0 )
{
long iCurTimeout = tv.tv_usec / 1000;
iCurTimeout += tv.tv_sec * 1000;
if( iCurTimeout > iTimeoutMS )
{
tv.tv_sec = iTimeoutMS / 1000;
tv.tv_usec = iTimeoutMS % 1000;
}
}
}
#define CS_UNKNOWN_ERROR "Unknown Error"
static const char * CS_StrError( int iErrno, char * pszBuff, size_t uBuffLen )
{
#if defined( sgi ) || defined(__sun) || defined(_WIN32) || (defined(__NetBSD_Version__) && __NetBSD_Version__ < 4000000000)
return( strerror( iErrno ) );
#else
memset( pszBuff, '\0', uBuffLen );
#if !defined( _GNU_SOURCE )
if( strerror_r( iErrno, pszBuff, uBuffLen ) == 0 )
return( pszBuff );
#else
return( strerror_r( iErrno, pszBuff, uBuffLen ) );
#endif /* (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !defined( _GNU_SOURCE ) */
#endif /* defined( sgi ) || defined(__sun) || defined(_WIN32) || (defined(__NetBSD_Version__) && __NetBSD_Version__ < 4000000000) */
return( CS_UNKNOWN_ERROR );
}
void __Perror( const CS_STRING & s, const char *pszFile, unsigned int iLineNo )
{
char szBuff[0xff];
std::cerr << s << "(" << pszFile << ":" << iLineNo << "): " << CS_StrError( GetSockError(), szBuff, 0xff ) << endl;
}
unsigned long long millitime()
{
unsigned long long iTime = 0;
#ifdef _WIN32
struct timeb tm;
ftime( &tm );
iTime = tm.time * 1000;
iTime += tm.millitm;
#else
struct timeval tv;
gettimeofday( &tv, NULL );
iTime = ( unsigned long long )tv.tv_sec * 1000;
iTime += (( unsigned long long )tv.tv_usec / 1000 );
#endif /* _WIN32 */
return( iTime );
}
#ifndef _NO_CSOCKET_NS // some people may not want to use a namespace
}
using namespace Csocket;
#endif /* _NO_CSOCKET_NS */
CCron::CCron()
{
m_iCycles = 0;
m_iMaxCycles = 0;
m_bActive = true;
timerclear( &m_tTime );
m_tTimeSequence.tv_sec = 60;
m_tTimeSequence.tv_usec = 0;
m_bPause = false;
m_bRunOnNextCall = false;
}
void CCron::run( timeval & tNow )
{
if( m_bPause )
return;
if( !timerisset( &tNow ) )
gettimeofday( &tNow, NULL );
if(( m_bActive ) && ( !timercmp( &tNow, &m_tTime, < ) || m_bRunOnNextCall ) )
{
m_bRunOnNextCall = false; // Setting this here because RunJob() could set it back to true
RunJob();
if(( m_iMaxCycles > 0 ) && ( ++m_iCycles >= m_iMaxCycles ) )
m_bActive = false;
else
timeradd( &tNow, &m_tTimeSequence, &m_tTime );
}
}
void CCron::StartMaxCycles( double fTimeSequence, u_int iMaxCycles )
{
timeval tNow;
m_tTimeSequence.tv_sec = (time_t) fTimeSequence;
m_tTimeSequence.tv_usec = (suseconds_t) ((fTimeSequence - (time_t) fTimeSequence) * 1000000);
gettimeofday( &tNow, NULL );
timeradd( &tNow, &m_tTimeSequence, &m_tTime );
m_iMaxCycles = iMaxCycles;
}
void CCron::StartMaxCycles( const timeval& tTimeSequence, u_int iMaxCycles )
{
timeval tNow;
m_tTimeSequence = tTimeSequence;
gettimeofday( &tNow, NULL );
timeradd( &tNow, &m_tTimeSequence, &m_tTime );
m_iMaxCycles = iMaxCycles;
}
void CCron::Start( double fTimeSequence )
{
StartMaxCycles( fTimeSequence, 0 );
}
void CCron::Start( const timeval& tTimeSequence )
{
StartMaxCycles( tTimeSequence, 0 );
}
void CCron::Stop()
{
m_bActive = false;
}
void CCron::Pause()
{
m_bPause = true;
}
void CCron::UnPause()
{
m_bPause = false;
}
timeval CCron::GetInterval() const { return( m_tTimeSequence ); }
u_int CCron::GetMaxCycles() const { return( m_iMaxCycles ); }
u_int CCron::GetCyclesLeft() const { return(( m_iMaxCycles > m_iCycles ? ( m_iMaxCycles - m_iCycles ) : 0 ) ); }
bool CCron::isValid() { return( m_bActive ); }
const CS_STRING & CCron::GetName() const { return( m_sName ); }
void CCron::SetName( const CS_STRING & sName ) { m_sName = sName; }
void CCron::RunJob() { CS_DEBUG( "This should be overridden" ); }
bool CSMonitorFD::GatherFDsForSelect( std::map< int, short > & miiReadyFds, long & iTimeoutMS )
{
iTimeoutMS = -1; // don't bother changing anything in the default implementation
for( std::map< int, short >::iterator it = m_miiMonitorFDs.begin(); it != m_miiMonitorFDs.end(); ++it )
{
miiReadyFds[it->first] = it->second;
}
return( m_bEnabled );
}
bool CSMonitorFD::CheckFDs( const std::map< int, short > & miiReadyFds )
{
std::map< int, short > miiTriggerdFds;
for( std::map< int, short >::iterator it = m_miiMonitorFDs.begin(); it != m_miiMonitorFDs.end(); ++it )
{
std::map< int, short >::const_iterator itFD = miiReadyFds.find( it->first );
if( itFD != miiReadyFds.end() )
miiTriggerdFds[itFD->first] = itFD->second;
}
if( miiTriggerdFds.size() )
return( FDsThatTriggered( miiTriggerdFds ) );
return( m_bEnabled );
}
CSockCommon::~CSockCommon()
{
// delete any left over crons
CleanupCrons();
CleanupFDMonitors();
}
void CSockCommon::CleanupCrons()
{
for( size_t a = 0; a < m_vcCrons.size(); a++ )
CS_Delete( m_vcCrons[a] );
m_vcCrons.clear();
}
void CSockCommon::CleanupFDMonitors()
{
for( size_t a = 0; a < m_vcMonitorFD.size(); a++ )
CS_Delete( m_vcMonitorFD[a] );
m_vcMonitorFD.clear();
}
void CSockCommon::CheckFDs( const std::map< int, short > & miiReadyFds )
{
for( size_t uMon = 0; uMon < m_vcMonitorFD.size(); ++uMon )
{
if( !m_vcMonitorFD[uMon]->IsEnabled() || !m_vcMonitorFD[uMon]->CheckFDs( miiReadyFds ) )
m_vcMonitorFD.erase( m_vcMonitorFD.begin() + uMon-- );
}
}
void CSockCommon::AssignFDs( std::map< int, short > & miiReadyFds, struct timeval * tvtimeout )
{
for( size_t uMon = 0; uMon < m_vcMonitorFD.size(); ++uMon )
{
long iTimeoutMS = -1;
if( m_vcMonitorFD[uMon]->IsEnabled() && m_vcMonitorFD[uMon]->GatherFDsForSelect( miiReadyFds, iTimeoutMS ) )
{
CSAdjustTVTimeout( *tvtimeout, iTimeoutMS );
}
else
{
CS_Delete( m_vcMonitorFD[uMon] );
m_vcMonitorFD.erase( m_vcMonitorFD.begin() + uMon-- );
}
}
}
void CSockCommon::Cron()
{
timeval tNow;
timerclear( &tNow );
for( vector<CCron *>::size_type a = 0; a < m_vcCrons.size(); a++ )
{
CCron *pcCron = m_vcCrons[a];
if( !pcCron->isValid() )
{
CS_Delete( pcCron );
m_vcCrons.erase( m_vcCrons.begin() + a-- );
}
else
pcCron->run( tNow );
}
}
void CSockCommon::AddCron( CCron * pcCron )
{
m_vcCrons.push_back( pcCron );
}
void CSockCommon::DelCron( const CS_STRING & sName, bool bDeleteAll, bool bCaseSensitive )
{
for( u_int a = 0; a < m_vcCrons.size(); a++ )
{
int ( *Cmp )( const char *, const char * ) = ( bCaseSensitive ? strcmp : strcasecmp );
if( Cmp( m_vcCrons[a]->GetName().c_str(), sName.c_str() ) == 0 )
{
m_vcCrons[a]->Stop();
CS_Delete( m_vcCrons[a] );
m_vcCrons.erase( m_vcCrons.begin() + a-- );
if( !bDeleteAll )
break;
}
}
}
void CSockCommon::DelCron( u_int iPos )
{
if( iPos < m_vcCrons.size() )
{
m_vcCrons[iPos]->Stop();
CS_Delete( m_vcCrons[iPos] );
m_vcCrons.erase( m_vcCrons.begin() + iPos );
}
}
void CSockCommon::DelCronByAddr( CCron *pcCron )
{
for( u_int a = 0; a < m_vcCrons.size(); a++ )
{
if( m_vcCrons[a] == pcCron )
{
m_vcCrons[a]->Stop();
CS_Delete( m_vcCrons[a] );
m_vcCrons.erase( m_vcCrons.begin() + a );
return;
}
}
}
Csock::Csock( int iTimeout ) : CSockCommon()
{
#ifdef HAVE_LIBSSL
m_pCerVerifyCB = NULL;
#endif /* HAVE_LIBSSL */
Init( "", 0, iTimeout );
}
Csock::Csock( const CS_STRING & sHostname, u_short iport, int iTimeout ) : CSockCommon()
{
#ifdef HAVE_LIBSSL
m_pCerVerifyCB = NULL;
#endif /* HAVE_LIBSSL */
Init( sHostname, iport, iTimeout );
}
// override this for accept sockets
Csock *Csock::GetSockObj( const CS_STRING & sHostname, u_short iPort )
{
return( NULL );
}
#ifdef _WIN32
#define CS_CLOSE closesocket
#else
#define CS_CLOSE close
#endif /* _WIN32 */
Csock::~Csock()
{
#ifdef _WIN32
// prevent successful closesocket() calls and such from
// overwriting any possible previous errors.
int iOldError = ::WSAGetLastError();
#endif /* _WIN32 */
#ifdef HAVE_C_ARES
if( m_pARESChannel )
ares_cancel( m_pARESChannel );
FreeAres();
#endif /* HAVE_C_ARES */
#ifdef HAVE_LIBSSL
FREE_SSL();
FREE_CTX();
#endif /* HAVE_LIBSSL */
CloseSocksFD();
#ifdef _WIN32
::WSASetLastError( iOldError );
#endif /* _WIN32 */
}
void Csock::CloseSocksFD()
{
if( m_iReadSock != m_iWriteSock )
{
if( m_iReadSock != CS_INVALID_SOCK )
CS_CLOSE( m_iReadSock );
if( m_iWriteSock != CS_INVALID_SOCK )
CS_CLOSE( m_iWriteSock );
}
else if( m_iReadSock != CS_INVALID_SOCK )
CS_CLOSE( m_iReadSock );
m_iReadSock = CS_INVALID_SOCK;
m_iWriteSock = CS_INVALID_SOCK;
}
void Csock::Dereference()
{
m_iWriteSock = m_iReadSock = CS_INVALID_SOCK;
#ifdef HAVE_LIBSSL
m_ssl = NULL;
m_ssl_ctx = NULL;
#endif /* HAVE_LIBSSL */
// don't delete and erase, just erase since they were moved to the copied sock
m_vcCrons.clear();
m_vcMonitorFD.clear();
Close( CLT_DEREFERENCE );
}
void Csock::Copy( const Csock & cCopy )
{
m_iTcount = cCopy.m_iTcount;
m_iLastCheckTimeoutTime = cCopy.m_iLastCheckTimeoutTime;
m_uPort = cCopy.m_uPort;
m_iRemotePort = cCopy.m_iRemotePort;
m_iLocalPort = cCopy.m_iLocalPort;
m_iReadSock = cCopy.m_iReadSock;
m_iWriteSock = cCopy.m_iWriteSock;
m_iTimeout = cCopy.m_iTimeout;
m_iMaxConns = cCopy.m_iMaxConns;
m_iConnType = cCopy.m_iConnType;
m_iMethod = cCopy.m_iMethod;
m_bUseSSL = cCopy.m_bUseSSL;
m_bIsConnected = cCopy.m_bIsConnected;
m_bsslEstablished = cCopy.m_bsslEstablished;
m_bEnableReadLine = cCopy.m_bEnableReadLine;
m_bPauseRead = cCopy.m_bPauseRead;
m_shostname = cCopy.m_shostname;
m_sbuffer = cCopy.m_sbuffer;
m_sSockName = cCopy.m_sSockName;
m_sPemFile = cCopy.m_sPemFile;
m_sCipherType = cCopy.m_sCipherType;
m_sParentName = cCopy.m_sParentName;
m_sSend = cCopy.m_sSend;
m_sPemPass = cCopy.m_sPemPass;
m_sLocalIP = cCopy.m_sLocalIP;
m_sRemoteIP = cCopy.m_sRemoteIP;
m_eCloseType = cCopy.m_eCloseType;
m_iMaxMilliSeconds = cCopy.m_iMaxMilliSeconds;
m_iLastSendTime = cCopy.m_iLastSendTime;
m_iBytesRead = cCopy.m_iBytesRead;
m_iBytesWritten = cCopy.m_iBytesWritten;
m_iStartTime = cCopy.m_iStartTime;
m_iMaxBytes = cCopy.m_iMaxBytes;
m_iLastSend = cCopy.m_iLastSend;
m_iMaxStoredBufferLength = cCopy.m_iMaxStoredBufferLength;
m_iTimeoutType = cCopy.m_iTimeoutType;
m_address = cCopy.m_address;
m_bindhost = cCopy.m_bindhost;
m_bIsIPv6 = cCopy.m_bIsIPv6;
m_bSkipConnect = cCopy.m_bSkipConnect;
#ifdef HAVE_C_ARES
FreeAres(); // Not copying this state, but making sure its nulled out
m_iARESStatus = -1; // set it to unitialized
m_pCurrAddr = NULL;
#endif /* HAVE_C_ARES */
#ifdef HAVE_LIBSSL
m_iRequireClientCertFlags = cCopy.m_iRequireClientCertFlags;
m_sSSLBuffer = cCopy.m_sSSLBuffer;
FREE_SSL();
FREE_CTX(); // be sure to remove anything that was already here
m_ssl = cCopy.m_ssl;
m_ssl_ctx = cCopy.m_ssl_ctx;
m_pCerVerifyCB = cCopy.m_pCerVerifyCB;
#endif /* HAVE_LIBSSL */
CleanupCrons();
CleanupFDMonitors();
m_vcCrons = cCopy.m_vcCrons;
m_vcMonitorFD = cCopy.m_vcMonitorFD;
m_eConState = cCopy.m_eConState;
m_sBindHost = cCopy.m_sBindHost;
m_iCurBindCount = cCopy.m_iCurBindCount;
m_iDNSTryCount = cCopy.m_iDNSTryCount;
}
Csock & Csock::operator<<( const CS_STRING & s )
{
Write( s );
return( *this );
}
Csock & Csock::operator<<( ostream & ( *io )( ostream & ) )
{
Write( "\r\n" );
return( *this );
}
Csock & Csock::operator<<( int i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( unsigned int i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( long i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( unsigned long i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( unsigned long long i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( float i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
Csock & Csock::operator<<( double i )
{
stringstream s;
s << i;
Write( s.str() );
return( *this );
}
bool Csock::Connect()
{
if( m_bSkipConnect )
{
// this was already called, so skipping now. this is to allow easy pass through
if( m_eConState != CST_OK )
{
m_eConState = ( GetSSL() ? CST_CONNECTSSL : CST_OK );
}
return( true );
}
#ifndef _WIN32
set_non_blocking( m_iReadSock );
#else
if( !GetIPv6() )
set_non_blocking( m_iReadSock );
// non-blocking sockets on Win32 do *not* return ENETUNREACH/EHOSTUNREACH if there's no IPv6 gateway.
// we need those error codes for the v4 fallback in GetAddrInfo!
#endif /* _WIN32 */
m_iConnType = OUTBOUND;
int ret = -1;
if( !GetIPv6() )
ret = connect( m_iReadSock, ( struct sockaddr * )m_address.GetSockAddr(), m_address.GetSockAddrLen() );
#ifdef HAVE_IPV6
else
ret = connect( m_iReadSock, ( struct sockaddr * )m_address.GetSockAddr6(), m_address.GetSockAddrLen6() );
#endif /* HAVE_IPV6 */
#ifndef _WIN32
if( ret == -1 && GetSockError() != EINPROGRESS )
#else
if( ret == -1 && GetSockError() != EINPROGRESS && GetSockError() != WSAEWOULDBLOCK )
#endif /* _WIN32 */
{
CS_DEBUG( "Connect Failed. ERRNO [" << GetSockError() << "] FD [" << m_iReadSock << "]" );
return( false );
}
#ifdef _WIN32
// do what we didn't do above since connect() is now over!
if( GetIPv6() )
set_non_blocking( m_iReadSock );
#endif /* _WIN32 */
if( m_eConState != CST_OK )
{
m_eConState = ( GetSSL() ? CST_CONNECTSSL : CST_OK );
}
return( true );
}
bool Csock::Listen( u_short iPort, int iMaxConns, const CS_STRING & sBindHost, u_int iTimeout, bool bDetach )
{
m_iConnType = LISTENER;
m_iTimeout = iTimeout;
m_sBindHost = sBindHost;
m_iMaxConns = iMaxConns;
SetConState( Csock::CST_OK );
if( !m_sBindHost.empty() )
{
if( bDetach )
{
int iRet = GetAddrInfo( m_sBindHost, m_address );
if( iRet == ETIMEDOUT )
{
CallSockError( EADDRNOTAVAIL );
return( false );
}
else if( iRet == EAGAIN )
{
SetConState( Csock::CST_BINDVHOST );
return( true );
}
}
else
{
// if not detaching, then must block to do DNS resolution, so might as well use internal resolver
if( ::GetAddrInfo( m_sBindHost, this, m_address ) != 0 )
{
CallSockError( EADDRNOTAVAIL );
return( false );
}
}
}
m_iReadSock = m_iWriteSock = CreateSocket( true );
if( m_iReadSock == CS_INVALID_SOCK )
{
CallSockError( EBADF );
return( false );
}
#ifdef HAVE_IPV6
#ifdef _WIN32
# ifndef IPPROTO_IPV6
# define IPPROTO_IPV6 41 /* define for apps with _WIN32_WINNT < 0x0501 (XP) */
# endif /* !IPPROTO_IPV6 */
/* check for IPV6_V6ONLY support at runtime */
OSVERSIONINFOW lvi = { sizeof( OSVERSIONINFOW ), 0 };
if( ::GetVersionExW( &lvi ) && lvi.dwMajorVersion >= 6 ) // IPV6_V6ONLY is supported on Windows Vista or later.
{
#endif /* _WIN32 */
if( GetIPv6() )
{
// per RFC3493#5.3
const int on = ( m_address.GetAFRequire() == CSSockAddr::RAF_INET6 ? 1 : 0 );
if( setsockopt( m_iReadSock, IPPROTO_IPV6, IPV6_V6ONLY, ( char * )&on, sizeof( on ) ) != 0 )
PERROR( "IPV6_V6ONLY" );
}
#ifdef _WIN32
}
#endif /* _WIN32 */
#endif /* HAVE_IPV6 */
m_address.SinFamily();
m_address.SinPort( iPort );
if( !GetIPv6() )
{
if( bind( m_iReadSock, ( struct sockaddr * ) m_address.GetSockAddr(), m_address.GetSockAddrLen() ) == -1 )
{
CallSockError( GetSockError() );
return( false );
}
}
#ifdef HAVE_IPV6
else
{
if( bind( m_iReadSock, ( struct sockaddr * ) m_address.GetSockAddr6(), m_address.GetSockAddrLen6() ) == -1 )
{
CallSockError( GetSockError() );
return( false );
}
}
#endif /* HAVE_IPV6 */
if( listen( m_iReadSock, iMaxConns ) == -1 )
{
CallSockError( GetSockError() );
return( false );
}
// set it none blocking
set_non_blocking( m_iReadSock );
if( m_uPort == 0 || m_sBindHost.size() )
{
struct sockaddr_storage cAddr;
socklen_t iAddrLen = sizeof( cAddr );
if( getsockname( m_iReadSock, ( struct sockaddr * )&cAddr, &iAddrLen ) == 0 )
{
ConvertAddress( &cAddr, iAddrLen, m_sBindHost, &m_uPort );
}
}
Listening( m_sBindHost, m_uPort );
return( true );
}
cs_sock_t Csock::Accept( CS_STRING & sHost, u_short & iRPort )
{
cs_sock_t iSock = CS_INVALID_SOCK;
struct sockaddr_storage cAddr;
socklen_t iAddrLen = sizeof( cAddr );
iSock = accept( m_iReadSock, ( struct sockaddr * )&cAddr, &iAddrLen );
if( iSock != CS_INVALID_SOCK && getpeername( iSock, ( struct sockaddr * )&cAddr, &iAddrLen ) == 0 )
{
ConvertAddress( &cAddr, iAddrLen, sHost, &iRPort );
}
if( iSock != CS_INVALID_SOCK )
{
// Make it close-on-exec
set_close_on_exec( iSock );
// make it none blocking
set_non_blocking( iSock );
if( !ConnectionFrom( sHost, iRPort ) )
{
CS_CLOSE( iSock );
iSock = CS_INVALID_SOCK;
}
}
return( iSock );
}
bool Csock::AcceptSSL()
{
#ifdef HAVE_LIBSSL
if( !m_ssl )
if( !SSLServerSetup() )
return( false );
int err = SSL_accept( m_ssl );
if( err == 1 )
{
return( true );
}
int sslErr = SSL_get_error( m_ssl, err );
if( sslErr == SSL_ERROR_WANT_READ || sslErr == SSL_ERROR_WANT_WRITE )
return( true );
SSLErrors( __FILE__, __LINE__ );
#endif /* HAVE_LIBSSL */
return( false );
}
bool Csock::SSLClientSetup()
{
#ifdef HAVE_LIBSSL
m_bUseSSL = true;
FREE_SSL();
FREE_CTX();
#ifdef _WIN64
if( m_iReadSock != ( int )m_iReadSock || m_iWriteSock != ( int )m_iWriteSock )
{
// sanity check the FD to be sure its compatible with openssl
CS_DEBUG( "ERROR: sockfd larger than OpenSSL can handle" );
return( false );
}
#endif /* _WIN64 */
switch( m_iMethod )
{
case SSL3:
m_ssl_ctx = SSL_CTX_new( SSLv3_client_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv3_client_method failed!" );
return( false );
}
break;
case TLS1:
m_ssl_ctx = SSL_CTX_new( TLSv1_client_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... TLSv1_client_method failed!" );
return( false );
}
break;
case SSL2:
#ifndef OPENSSL_NO_SSL2
m_ssl_ctx = SSL_CTX_new( SSLv2_client_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv2_client_method failed!" );
return( false );
}
break;
#endif
/* Fall through if SSL2 is disabled */
case SSL23:
default:
m_ssl_ctx = SSL_CTX_new( SSLv23_client_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv23_client_method failed!" );
return( false );
}
break;
}
SSL_CTX_set_default_verify_paths( m_ssl_ctx );
if( !m_sPemFile.empty() )
{
// are we sending a client cerificate ?
SSL_CTX_set_default_passwd_cb( m_ssl_ctx, PemPassCB );
SSL_CTX_set_default_passwd_cb_userdata( m_ssl_ctx, ( void * )this );
//
// set up the CTX
if( SSL_CTX_use_certificate_file( m_ssl_ctx, m_sPemFile.c_str() , SSL_FILETYPE_PEM ) <= 0 )
{
CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" );
SSLErrors( __FILE__, __LINE__ );
}
if( SSL_CTX_use_PrivateKey_file( m_ssl_ctx, m_sPemFile.c_str(), SSL_FILETYPE_PEM ) <= 0 )
{
CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" );
SSLErrors( __FILE__, __LINE__ );
}
}
m_ssl = SSL_new( m_ssl_ctx );
if( !m_ssl )
return( false );
SSL_set_rfd( m_ssl, ( int )m_iReadSock );
SSL_set_wfd( m_ssl, ( int )m_iWriteSock );
SSL_set_verify( m_ssl, SSL_VERIFY_PEER, ( m_pCerVerifyCB ? m_pCerVerifyCB : CertVerifyCB ) );
SSL_set_ex_data( m_ssl, GetCsockClassIdx(), this );
SSLFinishSetup( m_ssl );
return( true );
#else
return( false );
#endif /* HAVE_LIBSSL */
}
bool Csock::SSLServerSetup()
{
#ifdef HAVE_LIBSSL
m_bUseSSL = true;
FREE_SSL();
FREE_CTX();
#ifdef _WIN64
if( m_iReadSock != ( int )m_iReadSock || m_iWriteSock != ( int )m_iWriteSock )
{
// sanity check the FD to be sure its compatible with openssl
CS_DEBUG( "ERROR: sockfd larger than OpenSSL can handle" );
return( false );
}
#endif /* _WIN64 */
switch( m_iMethod )
{
case SSL3:
m_ssl_ctx = SSL_CTX_new( SSLv3_server_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv3_server_method failed!" );
return( false );
}
break;
case TLS1:
m_ssl_ctx = SSL_CTX_new( TLSv1_server_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... TLSv1_server_method failed!" );
return( false );
}
break;
#ifndef OPENSSL_NO_SSL2
case SSL2:
m_ssl_ctx = SSL_CTX_new( SSLv2_server_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv2_server_method failed!" );
return( false );
}
break;
#endif
/* Fall through if SSL2 is disabled */
case SSL23:
default:
m_ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
if( !m_ssl_ctx )
{
CS_DEBUG( "WARNING: MakeConnection .... SSLv23_server_method failed!" );
return( false );
}
break;
}
SSL_CTX_set_default_verify_paths( m_ssl_ctx );
// set the pemfile password
SSL_CTX_set_default_passwd_cb( m_ssl_ctx, PemPassCB );
SSL_CTX_set_default_passwd_cb_userdata( m_ssl_ctx, ( void * )this );
if( m_sPemFile.empty() || access( m_sPemFile.c_str(), R_OK ) != 0 )
{
CS_DEBUG( "There is a problem with [" << m_sPemFile << "]" );
return( false );
}
//
// set up the CTX
if( SSL_CTX_use_certificate_chain_file( m_ssl_ctx, m_sPemFile.c_str() ) <= 0 )
{
CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" );
SSLErrors( __FILE__, __LINE__ );
return( false );
}
if( SSL_CTX_use_PrivateKey_file( m_ssl_ctx, m_sPemFile.c_str(), SSL_FILETYPE_PEM ) <= 0 )
{
CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" );
SSLErrors( __FILE__, __LINE__ );
return( false );
}
// check to see if this pem file contains a DH structure for use with DH key exchange
// https://github.com/znc/znc/pull/46
FILE *dhParamsFile = fopen( m_sPemFile.c_str(), "r" );
if( !dhParamsFile )
{
CS_DEBUG( "There is a problem with [" << m_sPemFile << "]" );
return( false );
}
DH * dhParams = PEM_read_DHparams( dhParamsFile, NULL, NULL, NULL );
fclose( dhParamsFile );
if( dhParams )
{
SSL_CTX_set_options( m_ssl_ctx, SSL_OP_SINGLE_DH_USE );
if( !SSL_CTX_set_tmp_dh( m_ssl_ctx, dhParams ) )
{
CS_DEBUG( "Error setting ephemeral DH parameters from [" << m_sPemFile << "]" );
SSLErrors( __FILE__, __LINE__ );
DH_free( dhParams );
return( false );
}
DH_free( dhParams );
}
else
{
// Presumably PEM_read_DHparams failed, as there was no DH structure. Clearing those errors here so they are removed off the stack
ERR_clear_error();
}
if( SSL_CTX_set_cipher_list( m_ssl_ctx, m_sCipherType.c_str() ) <= 0 )
{
CS_DEBUG( "Could not assign cipher [" << m_sCipherType << "]" );
return( false );
}
//
// setup the SSL
m_ssl = SSL_new( m_ssl_ctx );
if( !m_ssl )
return( false );
// Call for client Verification
SSL_set_rfd( m_ssl, ( int )m_iReadSock );
SSL_set_wfd( m_ssl, ( int )m_iWriteSock );
SSL_set_accept_state( m_ssl );
if( m_iRequireClientCertFlags )
{
SSL_set_verify( m_ssl, m_iRequireClientCertFlags, ( m_pCerVerifyCB ? m_pCerVerifyCB : CertVerifyCB ) );
SSL_set_ex_data( m_ssl, GetCsockClassIdx(), this );
}
SSLFinishSetup( m_ssl );
return( true );
#else
return( false );
#endif /* HAVE_LIBSSL */
}
bool Csock::StartTLS()
{
if( m_iConnType == INBOUND )
return( AcceptSSL() );
if( m_iConnType == OUTBOUND )
return( ConnectSSL() );
CS_DEBUG( "Invalid connection type with StartTLS" );
return( false );
}
bool Csock::ConnectSSL()
{
#ifdef HAVE_LIBSSL
if( m_iReadSock == CS_INVALID_SOCK )
return( false ); // this should be long passed at this point
if( !m_ssl && !SSLClientSetup() )
return( false );
bool bPass = true;
int iErr = SSL_connect( m_ssl );
if( iErr != 1 )
{
int sslErr = SSL_get_error( m_ssl, iErr );
bPass = false;
if( sslErr == SSL_ERROR_WANT_READ || sslErr == SSL_ERROR_WANT_WRITE )
bPass = true;
#ifdef _WIN32
else if( sslErr == SSL_ERROR_SYSCALL && iErr < 0 && GetLastError() == WSAENOTCONN )
{
// this seems to be an issue with win32 only. I've seen it happen on slow connections
// the issue is calling this before select(), which isn't a problem on unix. Allowing this
// to pass in this case is fine because subsequent ssl transactions will occur and the handshake
// will finish. At this point, its just instantiating the handshake.
bPass = true;
}
#endif /* _WIN32 */
}
else
bPass = true;
if( m_eConState != CST_OK )
m_eConState = CST_OK;
return( bPass );
#else
return( false );
#endif /* HAVE_LIBSSL */
}
bool Csock::AllowWrite( unsigned long long & iNOW ) const
{
if( m_iMaxBytes > 0 && m_iMaxMilliSeconds > 0 )
{
if( iNOW == 0 )
iNOW = millitime();
if( m_iLastSend < m_iMaxBytes )
return( true ); // allow sending if our out buffer was less than what we can send
if(( iNOW - m_iLastSendTime ) < m_iMaxMilliSeconds )
return( false );
}
return( true );
}
bool Csock::Write( const char *data, size_t len )
{
m_sSend.append( data, len );
if( m_sSend.empty() )
return( true );
if( m_eConState != CST_OK )
return( true );
// rate shaping
u_long iBytesToSend = 0;
#ifdef HAVE_LIBSSL
if( m_bUseSSL && m_sSSLBuffer.empty() && !m_bsslEstablished )
{
// to keep openssl from spinning, just initiate the connection with 1 byte so the connection establishes faster
iBytesToSend = 1;
}
else
#endif /* HAVE_LIBSSL */
if( m_iMaxBytes > 0 && m_iMaxMilliSeconds > 0 )
{
unsigned long long iNOW = millitime();
// figure out the shaping here
// if NOW - m_iLastSendTime > m_iMaxMilliSeconds then send a full length of ( iBytesToSend )
if(( iNOW - m_iLastSendTime ) > m_iMaxMilliSeconds )
{
m_iLastSendTime = iNOW;
iBytesToSend = m_iMaxBytes;
m_iLastSend = 0;
}
else // otherwise send m_iMaxBytes - m_iLastSend
iBytesToSend = m_iMaxBytes - m_iLastSend;
// take which ever is lesser
if( m_sSend.length() < iBytesToSend )
iBytesToSend = m_sSend.length();
// add up the bytes sent
m_iLastSend += iBytesToSend;
// so, are we ready to send anything ?
if( iBytesToSend == 0 )
return( true );
}
else
iBytesToSend = m_sSend.length();
#ifdef HAVE_LIBSSL
if( m_bUseSSL )
{
if( !m_ssl )
{
CS_DEBUG( "SSL object is NULL but m_bUseSSL is true" );
return( false );
}
if( m_sSSLBuffer.empty() ) // on retrying to write data, ssl wants the data in the SAME spot and the SAME size
m_sSSLBuffer.append( m_sSend.data(), iBytesToSend );
int iErr = SSL_write( m_ssl, m_sSSLBuffer.data(), ( int )m_sSSLBuffer.length() );
if( iErr < 0 && GetSockError() == ECONNREFUSED )
{
// If ret == -1, the underlying BIO reported an I/O error (man SSL_get_error)
ConnectionRefused();
return( false );
}
switch( SSL_get_error( m_ssl, iErr ) )
{
case SSL_ERROR_NONE:
m_bsslEstablished = true;
// all ok
break;
case SSL_ERROR_ZERO_RETURN:
{
// weird closer alert
return( false );
}
case SSL_ERROR_WANT_READ:
// retry
break;
case SSL_ERROR_WANT_WRITE:
// retry
break;
case SSL_ERROR_SSL:
{
SSLErrors( __FILE__, __LINE__ );
return( false );
}
}
if( iErr > 0 )
{
m_sSSLBuffer.clear();
m_sSend.erase( 0, iErr );
// reset the timer on successful write (we have to set it here because the write
// bit might not always be set, so need to trigger)
if( TMO_WRITE & GetTimeoutType() )
ResetTimer();
m_iBytesWritten += ( unsigned long long )iErr;
}
return( true );
}
#endif /* HAVE_LIBSSL */
#ifdef _WIN32
cs_ssize_t bytes = send( m_iWriteSock, m_sSend.data(), iBytesToSend, 0 );
#else
cs_ssize_t bytes = write( m_iWriteSock, m_sSend.data(), iBytesToSend );
#endif /* _WIN32 */
if( bytes == -1 && GetSockError() == ECONNREFUSED )
{
ConnectionRefused();
return( false );
}
#ifdef _WIN32
if( bytes <= 0 && GetSockError() != WSAEWOULDBLOCK )
return( false );
#else
if( bytes <= 0 && GetSockError() != EAGAIN )
return( false );
#endif /* _WIN32 */
// delete the bytes we sent
if( bytes > 0 )
{
m_sSend.erase( 0, bytes );
if( TMO_WRITE & GetTimeoutType() )
ResetTimer(); // reset the timer on successful write
m_iBytesWritten += ( unsigned long long )bytes;
}
return( true );
}
bool Csock::Write( const CS_STRING & sData )
{
return( Write( sData.c_str(), sData.length() ) );
}
cs_ssize_t Csock::Read( char *data, size_t len )
{
cs_ssize_t bytes = 0;
if( IsReadPaused() && SslIsEstablished() )
return( READ_EAGAIN ); // allow the handshake to complete first
#ifdef HAVE_LIBSSL
if( m_bUseSSL )
{
if( !m_ssl )
{
CS_DEBUG( "SSL object is NULL but m_bUseSSL is true" );
return( READ_ERR );
}
bytes = SSL_read( m_ssl, data, ( int )len );
if( bytes >= 0 )
m_bsslEstablished = true; // this means all is good in the realm of ssl
}
else
#endif /* HAVE_LIBSSL */
#ifdef _WIN32
bytes = recv( m_iReadSock, data, len, 0 );
#else
bytes = read( m_iReadSock, data, len );
#endif /* _WIN32 */
if( bytes == -1 )
{
if( GetSockError() == ECONNREFUSED )
return( READ_CONNREFUSED );
if( GetSockError() == ETIMEDOUT )
return( READ_TIMEDOUT );
if( GetSockError() == EINTR || GetSockError() == EAGAIN )
return( READ_EAGAIN );
#ifdef _WIN32
if( GetSockError() == WSAEWOULDBLOCK )
return( READ_EAGAIN );
#endif /* _WIN32 */
#ifdef HAVE_LIBSSL
if( m_ssl )
{
int iErr = SSL_get_error( m_ssl, ( int )bytes );
if( iErr != SSL_ERROR_WANT_READ && iErr != SSL_ERROR_WANT_WRITE )
return( READ_ERR );
else
return( READ_EAGAIN );
}
#else
return( READ_ERR );
#endif /* HAVE_LIBSSL */
}
if( bytes > 0 ) // becareful not to add negative bytes :P
m_iBytesRead += ( unsigned long long )bytes;
return( bytes );
}
CS_STRING Csock::GetLocalIP()
{
if( !m_sLocalIP.empty() )
return( m_sLocalIP );
cs_sock_t iSock = GetSock();
if( iSock == CS_INVALID_SOCK )
return( "" );
struct sockaddr_storage cAddr;
socklen_t iAddrLen = sizeof( cAddr );
if( getsockname( iSock, ( struct sockaddr * )&cAddr, &iAddrLen ) == 0 )
{
ConvertAddress( &cAddr, iAddrLen, m_sLocalIP, &m_iLocalPort );
}
return( m_sLocalIP );
}
CS_STRING Csock::GetRemoteIP()
{
if( !m_sRemoteIP.empty() )
return( m_sRemoteIP );
cs_sock_t iSock = GetSock();
if( iSock == CS_INVALID_SOCK )
return( "" );
struct sockaddr_storage cAddr;
socklen_t iAddrLen = sizeof( cAddr );
if( getpeername( iSock, ( struct sockaddr * )&cAddr, &iAddrLen ) == 0 )
{
ConvertAddress( &cAddr, iAddrLen, m_sRemoteIP, &m_iRemotePort );
}
return( m_sRemoteIP );
}
bool Csock::IsConnected() const { return( m_bIsConnected ); }
void Csock::SetIsConnected( bool b ) { m_bIsConnected = b; }
cs_sock_t & Csock::GetRSock() { return( m_iReadSock ); }
void Csock::SetRSock( cs_sock_t iSock ) { m_iReadSock = iSock; }
cs_sock_t & Csock::GetWSock() { return( m_iWriteSock ); }
void Csock::SetWSock( cs_sock_t iSock ) { m_iWriteSock = iSock; }
void Csock::SetSock( cs_sock_t iSock ) { m_iWriteSock = iSock; m_iReadSock = iSock; }
cs_sock_t & Csock::GetSock() { return( m_iReadSock ); }
void Csock::ResetTimer() { m_iLastCheckTimeoutTime = 0; m_iTcount = 0; }
void Csock::PauseRead() { m_bPauseRead = true; }
bool Csock::IsReadPaused() { return( m_bPauseRead ); }
void Csock::UnPauseRead()
{
m_bPauseRead = false;
ResetTimer();
PushBuff( "", 0, true );
}
void Csock::SetTimeout( int iTimeout, u_int iTimeoutType )
{
m_iTimeoutType = iTimeoutType;
m_iTimeout = iTimeout;
}
void Csock::CallSockError( int iErrno, const CS_STRING & sDescription )
{
if( sDescription.size() )
SockError( iErrno, sDescription );
else
{
char szBuff[0xff];
SockError( iErrno, CS_StrError( iErrno, szBuff, 0xff ) );
}
}
void Csock::SetTimeoutType( u_int iTimeoutType ) { m_iTimeoutType = iTimeoutType; }
int Csock::GetTimeout() const { return m_iTimeout; }
u_int Csock::GetTimeoutType() const { return( m_iTimeoutType ); }
bool Csock::CheckTimeout( time_t iNow )
{
if( m_iLastCheckTimeoutTime == 0 )
{
m_iLastCheckTimeoutTime = iNow;
return( false );
}
if( IsReadPaused() )
return( false );
time_t iDiff = 0;
if( iNow > m_iLastCheckTimeoutTime )
iDiff = iNow - m_iLastCheckTimeoutTime;
else
{
// this is weird, but its possible if someone changes a clock and it went back in time, this essentially has to reset the last check
// the worst case scenario is the timeout is about to it and the clock changes, it would then cause
// this to pass over the last half the time
m_iLastCheckTimeoutTime = iNow;
}
if( m_iTimeout > 0 )
{
// this is basically to help stop a clock adjust ahead, stuff could reset immediatly on a clock jump
// otherwise
time_t iRealTimeout = m_iTimeout;
if( iRealTimeout <= 1 )
m_iTcount++;
else if( m_iTcount == 0 )
iRealTimeout /= 2;
if( iDiff >= iRealTimeout )
{
if( m_iTcount == 0 )
m_iLastCheckTimeoutTime = iNow - iRealTimeout;
if( m_iTcount++ >= 1 )
{
Timeout();
return( true );
}
}
}
return( false );
}
void Csock::PushBuff( const char *data, size_t len, bool bStartAtZero )
{
if( !m_bEnableReadLine )
return; // If the ReadLine event is disabled, just ditch here
size_t iStartPos = ( m_sbuffer.empty() || bStartAtZero ? 0 : m_sbuffer.length() - 1 );
if( data )
m_sbuffer.append( data, len );
while( !m_bPauseRead && GetCloseType() == CLT_DONT )
{
CS_STRING::size_type iFind = m_sbuffer.find( "\n", iStartPos );
if( iFind != CS_STRING::npos )
{
CS_STRING sBuff = m_sbuffer.substr( 0, iFind + 1 ); // read up to(including) the newline
m_sbuffer.erase( 0, iFind + 1 ); // erase past the newline
ReadLine( sBuff );
iStartPos = 0; // reset this back to 0, since we need to look for the next newline here.
}
else
break;
}
if( m_iMaxStoredBufferLength > 0 && m_sbuffer.length() > m_iMaxStoredBufferLength )
ReachedMaxBuffer(); // call the max read buffer event
}
CS_STRING & Csock::GetInternalReadBuffer() { return( m_sbuffer ); }
CS_STRING & Csock::GetInternalWriteBuffer() { return( m_sSend ); }
void Csock::SetMaxBufferThreshold( u_int iThreshold ) { m_iMaxStoredBufferLength = iThreshold; }
u_int Csock::GetMaxBufferThreshold() const { return( m_iMaxStoredBufferLength ); }
int Csock::GetType() const { return( m_iConnType ); }
void Csock::SetType( int iType ) { m_iConnType = iType; }
const CS_STRING & Csock::GetSockName() const { return( m_sSockName ); }
void Csock::SetSockName( const CS_STRING & sName ) { m_sSockName = sName; }
const CS_STRING & Csock::GetHostName() const { return( m_shostname ); }
void Csock::SetHostName( const CS_STRING & sHostname ) { m_shostname = sHostname; }
unsigned long long Csock::GetStartTime() const { return( m_iStartTime ); }
void Csock::ResetStartTime() { m_iStartTime = 0; }
unsigned long long Csock::GetBytesRead() const { return( m_iBytesRead ); }
void Csock::ResetBytesRead() { m_iBytesRead = 0; }
unsigned long long Csock::GetBytesWritten() const { return( m_iBytesWritten ); }
void Csock::ResetBytesWritten() { m_iBytesWritten = 0; }
double Csock::GetAvgRead( unsigned long long iSample )
{
unsigned long long iDifference = ( millitime() - m_iStartTime );
if( m_iBytesRead == 0 || iSample > iDifference )
return(( double )m_iBytesRead );
return((( double )m_iBytesRead / (( double )iDifference / ( double )iSample ) ) );
}
double Csock::GetAvgWrite( unsigned long long iSample )
{
unsigned long long iDifference = ( millitime() - m_iStartTime );
if( m_iBytesWritten == 0 || iSample > iDifference )
return(( double )m_iBytesWritten );
return((( double )m_iBytesWritten / (( double )iDifference / ( double )iSample ) ) );
}
u_short Csock::GetRemotePort()
{
if( m_iRemotePort > 0 )
return( m_iRemotePort );
GetRemoteIP();
return( m_iRemotePort );
}
u_short Csock::GetLocalPort()
{
if( m_iLocalPort > 0 )
return( m_iLocalPort );
GetLocalIP();
return( m_iLocalPort );
}
u_short Csock::GetPort() { return( m_uPort ); }
void Csock::SetPort( u_short iPort ) { m_uPort = iPort; }
void Csock::Close( ECloseType eCloseType )
{
m_eCloseType = eCloseType;
}
void Csock::NonBlockingIO()
{
set_non_blocking( m_iReadSock );
if( m_iReadSock != m_iWriteSock )
{
set_non_blocking( m_iWriteSock );
}
}
bool Csock::GetSSL() { return( m_bUseSSL ); }
void Csock::SetSSL( bool b ) { m_bUseSSL = b; }
#ifdef HAVE_LIBSSL
void Csock::SetCipher( const CS_STRING & sCipher ) { m_sCipherType = sCipher; }
const CS_STRING & Csock::GetCipher() { return( m_sCipherType ); }
void Csock::SetPemLocation( const CS_STRING & sPemFile ) { m_sPemFile = sPemFile; }
const CS_STRING & Csock::GetPemLocation() { return( m_sPemFile ); }
void Csock::SetPemPass( const CS_STRING & sPassword ) { m_sPemPass = sPassword; }
const CS_STRING & Csock::GetPemPass() const { return( m_sPemPass ); }
int Csock::PemPassCB( char *buf, int size, int rwflag, void *pcSocket )
{
Csock *pSock = ( Csock * )pcSocket;
const CS_STRING & sPassword = pSock->GetPemPass();
memset( buf, '\0', size );
strncpy( buf, sPassword.c_str(), size );
buf[size-1] = '\0';
return(( int )strlen( buf ) );
}
int Csock::CertVerifyCB( int preverify_ok, X509_STORE_CTX *x509_ctx )
{
/*
* A small quick example on how to get ahold of the Csock in the data portion of x509_ctx
Csock *pSock = GetCsockFromCTX( x509_ctx );
assert( pSock );
cerr << pSock->GetRemoteIP() << endl;
*/
/* return 1 always for now, probably want to add some code for cert verification */
return( 1 );
}
void Csock::SetSSLMethod( int iMethod ) { m_iMethod = iMethod; }
int Csock::GetSSLMethod() { return( m_iMethod ); }
void Csock::SetSSLObject( SSL *ssl ) { m_ssl = ssl; }
void Csock::SetCTXObject( SSL_CTX *sslCtx ) { m_ssl_ctx = sslCtx; }
SSL_SESSION * Csock::GetSSLSession()
{
if( m_ssl )
return( SSL_get_session( m_ssl ) );
return( NULL );
}
#endif /* HAVE_LIBSSL */
const CS_STRING & Csock::GetWriteBuffer() { return( m_sSend ); }
void Csock::ClearWriteBuffer() { m_sSend.clear(); }
bool Csock::SslIsEstablished() { return ( m_bsslEstablished ); }
bool Csock::ConnectInetd( bool bIsSSL, const CS_STRING & sHostname )
{
if( !sHostname.empty() )
m_sSockName = sHostname;
// set our hostname
if( m_sSockName.empty() )
{
struct sockaddr_in client;
socklen_t clen = sizeof( client );
if( getpeername( 0, ( struct sockaddr * )&client, &clen ) < 0 )
m_sSockName = "0.0.0.0:0";
else
{
stringstream s;
s << inet_ntoa( client.sin_addr ) << ":" << ntohs( client.sin_port );
m_sSockName = s.str();
}
}
return( ConnectFD( 0, 1, m_sSockName, bIsSSL, INBOUND ) );
}
bool Csock::ConnectFD( int iReadFD, int iWriteFD, const CS_STRING & sName, bool bIsSSL, ETConn eDirection )
{
if( eDirection == LISTENER )
{
CS_DEBUG( "You can not use a LISTENER type here!" );
return( false );
}
// set our socket type
SetType( eDirection );
// set the hostname
m_sSockName = sName;
// set the file descriptors
SetRSock( iReadFD );
SetWSock( iWriteFD );
// set it up as non-blocking io
NonBlockingIO();
if( bIsSSL )
{
if( eDirection == INBOUND && !AcceptSSL() )
return( false );
else if( eDirection == OUTBOUND && !ConnectSSL() )
return( false );
}
return( true );
}
#ifdef HAVE_LIBSSL
X509 *Csock::GetX509()
{
if( m_ssl )
return( SSL_get_peer_certificate( m_ssl ) );
return( NULL );
}
CS_STRING Csock::GetPeerPubKey()
{
CS_STRING sKey;
SSL_SESSION *pSession = GetSSLSession();
if( pSession && pSession->peer )
{
EVP_PKEY *pKey = X509_get_pubkey( pSession->peer );
if( pKey )
{
char *hxKey = NULL;
switch( pKey->type )
{
case EVP_PKEY_RSA:
{
hxKey = BN_bn2hex( pKey->pkey.rsa->n );
break;
}
case EVP_PKEY_DSA:
{
hxKey = BN_bn2hex( pKey->pkey.dsa->pub_key );
break;
}
default:
{
CS_DEBUG( "Not Prepared for Public Key Type [" << pKey->type << "]" );
break;
}
}
if( hxKey )
{
sKey = hxKey;
OPENSSL_free( hxKey );
}
EVP_PKEY_free( pKey );
}
}
return( sKey );
}
long Csock::GetPeerFingerprint( CS_STRING & sFP )
{
sFP.clear();
if( !m_ssl )
return( 0 );
X509* pCert = GetX509();
// Inspired by charybdis
if( pCert )
{
for( int i = 0; i < SHA_DIGEST_LENGTH; i++ )
{
char buf[3];
snprintf( buf, 3, "%02x", pCert->sha1_hash[i] );
sFP += buf;
}
X509_free( pCert );
}
return( SSL_get_verify_result( m_ssl ) );
}
unsigned int Csock::GetRequireClientCertFlags() { return( m_iRequireClientCertFlags ); }
void Csock::SetRequiresClientCert( bool bRequiresCert ) { m_iRequireClientCertFlags = ( bRequiresCert ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_PEER : 0 ); }
#endif /* HAVE_LIBSSL */
void Csock::SetParentSockName( const CS_STRING & sParentName ) { m_sParentName = sParentName; }
const CS_STRING & Csock::GetParentSockName() { return( m_sParentName ); }
void Csock::SetRate( u_int iBytes, unsigned long long iMilliseconds )
{
m_iMaxBytes = iBytes;
m_iMaxMilliSeconds = iMilliseconds;
}
u_int Csock::GetRateBytes() { return( m_iMaxBytes ); }
unsigned long long Csock::GetRateTime() { return( m_iMaxMilliSeconds ); }
void Csock::EnableReadLine() { m_bEnableReadLine = true; }
void Csock::DisableReadLine()
{
m_bEnableReadLine = false;
m_sbuffer.clear();
}
void Csock::ReachedMaxBuffer()
{
std::cerr << "Warning, Max Buffer length Warning Threshold has been hit" << endl;
std::cerr << "If you don't care, then set SetMaxBufferThreshold to 0" << endl;
}
time_t Csock::GetTimeSinceLastDataTransaction( time_t iNow )
{
if( m_iLastCheckTimeoutTime == 0 )
return( 0 );
return(( iNow > 0 ? iNow : time( NULL ) ) - m_iLastCheckTimeoutTime );
}
time_t Csock::GetNextCheckTimeout( time_t iNow )
{
if( iNow == 0 )
iNow = time( NULL );
time_t iTimeout = m_iTimeout;
time_t iDiff = iNow - m_iLastCheckTimeoutTime;
/* CheckTimeout() wants to be called after half the timeout */
if( m_iTcount == 0 )
iTimeout /= 2;
if( iDiff > iTimeout )
iTimeout = 0;
else
iTimeout -= iDiff;
return( iNow + iTimeout );
}
int Csock::GetPending()
{
#ifdef HAVE_LIBSSL
if( m_ssl )
{
// in v23 method, the pending function is initialized to ssl_undefined_const_function
// which throws SSL_UNDEFINED_CONST_FUNCTION on to the error stack
// this is one of the more stupid things in openssl, it seems bizarre that even though SSL_pending
// returns an int, they don't bother returning in error to notify us, so basically
// we have to always clear errors here generated by SSL_pending, otherwise the stack could
// have a lame error on it causing SSL_write to fail in certain instances.
#if defined( OPENSSL_VERSION_NUMBER ) && OPENSSL_VERSION_NUMBER >= 0x00908000
ERR_set_mark();
int iBytes = SSL_pending( m_ssl );
ERR_pop_to_mark();
return( iBytes );
#else
int iBytes = SSL_pending( m_ssl );
ERR_clear_error(); // to get safer handling, upgrade your openssl version!
return( iBytes );
#endif /* OPENSSL_VERSION_NUMBER */
}
else
return( 0 );
#else
return( 0 );
#endif /* HAVE_LIBSSL */
}
bool Csock::CreateSocksFD()
{
if( m_iReadSock != CS_INVALID_SOCK )
return( true );
m_iReadSock = m_iWriteSock = CreateSocket();
if( m_iReadSock == CS_INVALID_SOCK )
return( false );
m_address.SinFamily();
m_address.SinPort( m_uPort );
return( true );
}
int Csock::GetAddrInfo( const CS_STRING & sHostname, CSSockAddr & csSockAddr )
{
#ifdef HAVE_IPV6
if( csSockAddr.GetAFRequire() != AF_INET && inet_pton( AF_INET6, sHostname.c_str(), csSockAddr.GetAddr6() ) > 0 )
{
SetIPv6( true );
return( 0 );
}
#endif /* HAVE_IPV6 */
if( inet_pton( AF_INET, sHostname.c_str(), csSockAddr.GetAddr() ) > 0 )
{
#ifdef HAVE_IPV6
SetIPv6( false );
#endif /* HAVE_IPV6 */
return( 0 );
}
#ifdef HAVE_C_ARES
// need to compute this up here
if( !m_pARESChannel )
{
if( ares_init( &m_pARESChannel ) != ARES_SUCCESS )
{
// TODO throw some debug?
FreeAres();
return( ETIMEDOUT );
}
m_pCurrAddr = &csSockAddr; // flag its starting
int iFamily = AF_INET;
#ifdef HAVE_IPV6
#if ARES_VERSION >= CREATE_ARES_VER( 1, 7, 5 )
// as of ares 1.7.5, it falls back to af_inet only when AF_UNSPEC is specified
// so this can finally let the code flow through as anticipated :)
iFamily = csSockAddr.GetAFRequire();
#else
// as of ares 1.6.0 if it fails on af_inet6, it falls back to af_inet,
// this code was here in the previous Csocket version, just adding the comment as a reminder
iFamily = csSockAddr.GetAFRequire() == CSSockAddr::RAF_ANY ? AF_INET6 : csSockAddr.GetAFRequire();
#endif /* CREATE_ARES_VER( 1, 7, 5 ) */
#endif /* HAVE_IPV6 */
ares_gethostbyname( m_pARESChannel, sHostname.c_str(), iFamily, AresHostCallback, this );
}
if( !m_pCurrAddr )
{
// this means its finished
FreeAres();
#ifdef HAVE_IPV6
if( GetType() != LISTENER && m_iARESStatus == ARES_SUCCESS && csSockAddr.GetAFRequire() == CSSockAddr::RAF_ANY && GetIPv6() )
{
// this means that ares_host returned an ipv6 host, so try a connect right away
if( CreateSocksFD() && Connect() )
{
SetSkipConnect( true );
}
#ifndef _WIN32
else if( GetSockError() == ENETUNREACH )
#else
else if( GetSockError() == WSAENETUNREACH || GetSockError() == WSAEHOSTUNREACH )
#endif /* !_WIN32 */
{
// the Connect() failed, so throw a retry back in with ipv4, and let it process normally
CS_DEBUG( "Failed ipv6 connection with PF_UNSPEC, falling back to ipv4" );
m_iARESStatus = -1;
CloseSocksFD();
SetAFRequire( CSSockAddr::RAF_INET );
return( GetAddrInfo( sHostname, csSockAddr ) );
}
}
#if ARES_VERSION < CREATE_ARES_VER( 1, 5, 3 )
if( m_iARESStatus != ARES_SUCCESS && csSockAddr.GetAFRequire() == CSSockAddr::RAF_ANY )
{
// this is a workaround for ares < 1.5.3 where the builtin retry on failed AF_INET6 isn't there yet
CS_DEBUG( "Retry for older version of c-ares with AF_INET only" );
// this means we tried previously with AF_INET6 and failed, so force AF_INET and retry
SetAFRequire( CSSockAddr::RAF_INET );
return( GetAddrInfo( sHostname, csSockAddr ) );
}
#endif /* ARES_VERSION < CREATE_ARES_VER( 1, 5, 3 ) */
#endif /* HAVE_IPV6 */
return( m_iARESStatus == ARES_SUCCESS ? 0 : ETIMEDOUT );
}
return( EAGAIN );
#else /* HAVE_C_ARES */
return( ::GetAddrInfo( sHostname, this, csSockAddr ) );
#endif /* HAVE_C_ARES */
}
int Csock::DNSLookup( EDNSLType eDNSLType )
{
if( eDNSLType == DNS_VHOST )
{
if( m_sBindHost.empty() )
{
if( m_eConState != CST_OK )
m_eConState = CST_DESTDNS; // skip binding, there is no vhost
return( 0 );
}
m_bindhost.SinFamily();
m_bindhost.SinPort( 0 );
}
int iRet = ETIMEDOUT;
if( eDNSLType == DNS_VHOST )
{
iRet = GetAddrInfo( m_sBindHost, m_bindhost );
#ifdef HAVE_IPV6
if( m_bindhost.GetIPv6() )
{
SetAFRequire( CSSockAddr::RAF_INET6 );
}
else
{
SetAFRequire( CSSockAddr::RAF_INET );
}
#endif /* HAVE_IPV6 */
}
else
{
iRet = GetAddrInfo( m_shostname, m_address );
}
if( iRet == 0 )
{
if( !CreateSocksFD() )
{
m_iDNSTryCount = 0;
return( ETIMEDOUT );
}
if( m_eConState != CST_OK )
m_eConState = (( eDNSLType == DNS_VHOST ) ? CST_BINDVHOST : CST_CONNECT );
m_iDNSTryCount = 0;
return( 0 );
}
else if( iRet == EAGAIN )
{
#ifndef HAVE_C_ARES
m_iDNSTryCount++;
if( m_iDNSTryCount > 20 )
{
m_iDNSTryCount = 0;
return( ETIMEDOUT );
}
#endif /* HAVE_C_ARES */
return( EAGAIN );
}
m_iDNSTryCount = 0;
return( ETIMEDOUT );
}
bool Csock::SetupVHost()
{
if( m_sBindHost.empty() )
{
if( m_eConState != CST_OK )
m_eConState = CST_DESTDNS;
return( true );
}
int iRet = -1;
if( !GetIPv6() )
iRet = bind( m_iReadSock, ( struct sockaddr * ) m_bindhost.GetSockAddr(), m_bindhost.GetSockAddrLen() );
#ifdef HAVE_IPV6
else
iRet = bind( m_iReadSock, ( struct sockaddr * ) m_bindhost.GetSockAddr6(), m_bindhost.GetSockAddrLen6() );
#endif /* HAVE_IPV6 */
if( iRet == 0 )
{
if( m_eConState != CST_OK )
m_eConState = CST_DESTDNS;
return( true );
}
m_iCurBindCount++;
if( m_iCurBindCount > 3 )
{
CS_DEBUG( "Failure to bind to " << m_sBindHost );
return( false );
}
return( true );
}
#ifdef HAVE_LIBSSL
void Csock::FREE_SSL()
{
if( m_ssl )
{
SSL_shutdown( m_ssl );
SSL_free( m_ssl );
}
m_ssl = NULL;
}
void Csock::FREE_CTX()
{
if( m_ssl_ctx )
SSL_CTX_free( m_ssl_ctx );
m_ssl_ctx = NULL;
}
#endif /* HAVE_LIBSSL */
cs_sock_t Csock::CreateSocket( bool bListen )
{
#ifdef HAVE_IPV6
cs_sock_t iRet = socket(( GetIPv6() ? PF_INET6 : PF_INET ), SOCK_STREAM, IPPROTO_TCP );
#else
cs_sock_t iRet = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
#endif /* HAVE_IPV6 */
if( iRet != CS_INVALID_SOCK )
{
set_close_on_exec( iRet );
if( bListen )
{
const int on = 1;
if( setsockopt( iRet, SOL_SOCKET, SO_REUSEADDR, ( char * )&on, sizeof( on ) ) != 0 )
PERROR( "SO_REUSEADDR" );
}
}
else
PERROR( "socket" );
return( iRet );
}
void Csock::Init( const CS_STRING & sHostname, u_short uPort, int iTimeout )
{
#ifdef HAVE_LIBSSL
m_ssl = NULL;
m_ssl_ctx = NULL;
m_iRequireClientCertFlags = 0;
#endif /* HAVE_LIBSSL */
m_iTcount = 0;
m_iReadSock = CS_INVALID_SOCK;
m_iWriteSock = CS_INVALID_SOCK;
m_iTimeout = iTimeout;
m_iMaxConns = SOMAXCONN;
m_bUseSSL = false;
m_bIsConnected = false;
m_uPort = uPort;
m_shostname = sHostname;
m_sbuffer.clear();
m_eCloseType = CLT_DONT;
m_iMethod = SSL23;
m_sCipherType = "ALL";
m_iMaxBytes = 0;
m_iMaxMilliSeconds = 0;
m_iLastSendTime = 0;
m_iLastSend = 0;
m_bsslEstablished = false;
m_bEnableReadLine = false;
m_iMaxStoredBufferLength = 1024;
m_iConnType = INBOUND;
m_iRemotePort = 0;
m_iLocalPort = 0;
m_iBytesRead = 0;
m_iBytesWritten = 0;
m_iStartTime = millitime();
m_bPauseRead = false;
m_iTimeoutType = TMO_ALL;
m_eConState = CST_OK; // default should be ok
m_iDNSTryCount = 0;
m_iCurBindCount = 0;
m_bIsIPv6 = false;
m_bSkipConnect = false;
m_iLastCheckTimeoutTime = 0;
#ifdef HAVE_C_ARES
m_pARESChannel = NULL;
m_pCurrAddr = NULL;
m_iARESStatus = -1;
#endif /* HAVE_C_ARES */
}
////////////////////////// CSocketManager //////////////////////////
CSocketManager::CSocketManager() : std::vector<Csock *>(), CSockCommon()
{
m_errno = SUCCESS;
m_iCallTimeouts = millitime();
m_iSelectWait = 100000; // Default of 100 milliseconds
m_iBytesRead = 0;
m_iBytesWritten = 0;
}
CSocketManager::~CSocketManager()
{
clear();
}
void CSocketManager::clear()
{
while( this->size() )
DelSock( 0 );
}
void CSocketManager::Cleanup()
{
CleanupCrons();
CleanupFDMonitors();
clear();
}
Csock * CSocketManager::GetSockObj( const CS_STRING & sHostname, u_short uPort, int iTimeout )
{
return( new Csock( sHostname, uPort, iTimeout ) );
}
void CSocketManager::Connect( const CSConnection & cCon, Csock * pcSock )
{
// create the new object
if( !pcSock )
pcSock = GetSockObj( cCon.GetHostname(), cCon.GetPort(), cCon.GetTimeout() );
else
{
pcSock->SetHostName( cCon.GetHostname() );
pcSock->SetPort( cCon.GetPort() );
pcSock->SetTimeout( cCon.GetTimeout() );
}
if( cCon.GetAFRequire() != CSSockAddr::RAF_ANY )
pcSock->SetAFRequire( cCon.GetAFRequire() );
// bind the vhost
pcSock->SetBindHost( cCon.GetBindHost() );
#ifdef HAVE_LIBSSL
pcSock->SetSSL( cCon.GetIsSSL() );
if( cCon.GetIsSSL() )
{
if( !cCon.GetPemLocation().empty() )
{
pcSock->SetPemLocation( cCon.GetPemLocation() );
pcSock->SetPemPass( cCon.GetPemPass() );
}
if( !cCon.GetCipher().empty() )
pcSock->SetCipher( cCon.GetCipher() );
}
#endif /* HAVE_LIBSSL */
pcSock->SetType( Csock::OUTBOUND );
pcSock->SetConState( Csock::CST_START );
AddSock( pcSock, cCon.GetSockName() );
}
bool CSocketManager::Listen( const CSListener & cListen, Csock * pcSock, u_short *piRandPort )
{
if( !pcSock )
pcSock = GetSockObj( "", 0 );
if( cListen.GetAFRequire() != CSSockAddr::RAF_ANY )
{
pcSock->SetAFRequire( cListen.GetAFRequire() );
#ifdef HAVE_IPV6
if( cListen.GetAFRequire() == CSSockAddr::RAF_INET6 )
pcSock->SetIPv6( true );
#endif /* HAVE_IPV6 */
}
#ifdef HAVE_IPV6
else
{
pcSock->SetIPv6( true );
}
#endif /* HAVE_IPV6 */
#ifdef HAVE_LIBSSL
pcSock->SetSSL( cListen.GetIsSSL() );
if( cListen.GetIsSSL() && !cListen.GetPemLocation().empty() )
{
pcSock->SetPemLocation( cListen.GetPemLocation() );
pcSock->SetPemPass( cListen.GetPemPass() );
pcSock->SetCipher( cListen.GetCipher() );
pcSock->SetRequireClientCertFlags( cListen.GetRequireClientCertFlags() );
}
#endif /* HAVE_LIBSSL */
if( piRandPort )
*piRandPort = 0;
bool bDetach = ( cListen.GetDetach() && !piRandPort ); // can't detach if we're waiting for the port to come up right now
if( pcSock->Listen( cListen.GetPort(), cListen.GetMaxConns(), cListen.GetBindHost(), cListen.GetTimeout(), bDetach ) )
{
AddSock( pcSock, cListen.GetSockName() );
if( piRandPort && cListen.GetPort() == 0 )
{
cs_sock_t iSock = pcSock->GetSock();
if( iSock == CS_INVALID_SOCK )
{
CS_DEBUG( "Failed to attain a valid file descriptor" );
pcSock->Close();
return( false );
}
struct sockaddr_in mLocalAddr;
socklen_t mLocalLen = sizeof( mLocalAddr );
getsockname( iSock, ( struct sockaddr * ) &mLocalAddr, &mLocalLen );
*piRandPort = ntohs( mLocalAddr.sin_port );
}
return( true );
}
CS_Delete( pcSock );
return( false );
}
bool CSocketManager::HasFDs() const
{
return( this->size() || m_vcMonitorFD.size() );
}
void CSocketManager::Loop()
{
for( u_int a = 0; a < this->size(); a++ )
{
Csock *pcSock = ( *this )[a];
if( pcSock->GetType() != Csock::OUTBOUND || pcSock->GetConState() == Csock::CST_OK )
continue;
if( pcSock->GetConState() == Csock::CST_DNS )
{
if( pcSock->DNSLookup( Csock::DNS_VHOST ) == ETIMEDOUT )
{
pcSock->CallSockError( EDOM, "DNS Lookup for bind host failed" );
DelSock( a-- );
continue;
}
}
if( pcSock->GetConState() == Csock::CST_BINDVHOST )
{
if( !pcSock->SetupVHost() )
{
pcSock->CallSockError( GetSockError(), "Failed to setup bind host" );
DelSock( a-- );
continue;
}
}
if( pcSock->GetConState() == Csock::CST_DESTDNS )
{
if( pcSock->DNSLookup( Csock::DNS_DEST ) == ETIMEDOUT )
{
pcSock->CallSockError( EADDRNOTAVAIL, "Unable to resolve requested address" );
DelSock( a-- );
continue;
}
}
if( pcSock->GetConState() == Csock::CST_CONNECT )
{
if( !pcSock->Connect() )
{
if( GetSockError() == ECONNREFUSED )
pcSock->ConnectionRefused();
else
pcSock->CallSockError( GetSockError() );
DelSock( a-- );
continue;
}
}
#ifdef HAVE_LIBSSL
if( pcSock->GetConState() == Csock::CST_CONNECTSSL )
{
if( pcSock->GetSSL() )
{
if( !pcSock->ConnectSSL() )
{
if( GetSockError() == ECONNREFUSED )
pcSock->ConnectionRefused();
else
pcSock->CallSockError( GetSockError() == 0 ? ECONNABORTED : GetSockError() );
DelSock( a-- );
continue;
}
}
}
#endif /* HAVE_LIBSSL */
}
std::map<Csock *, EMessages> mpeSocks;
Select( mpeSocks );
switch( m_errno )
{
case SUCCESS:
{
for( std::map<Csock *, EMessages>::iterator itSock = mpeSocks.begin(); itSock != mpeSocks.end(); itSock++ )
{
Csock * pcSock = itSock->first;
EMessages iErrno = itSock->second;
if( iErrno == SUCCESS )
{
// read in data
// if this is a
int iLen = 0;
if( pcSock->GetSSL() )
iLen = pcSock->GetPending();
if( iLen <= 0 )
iLen = CS_BLOCKSIZE;
CSCharBuffer cBuff( iLen );
cs_ssize_t bytes = pcSock->Read( cBuff(), iLen );
if( bytes != Csock::READ_TIMEDOUT && bytes != Csock::READ_CONNREFUSED && bytes != Csock::READ_ERR && !pcSock->IsConnected() )
{
pcSock->SetIsConnected( true );
pcSock->Connected();
}
switch( bytes )
{
case Csock::READ_EOF:
{
DelSockByAddr( pcSock );
break;
}
case Csock::READ_ERR:
{
pcSock->CallSockError( GetSockError() );
DelSockByAddr( pcSock );
break;
}
case Csock::READ_EAGAIN:
break;
case Csock::READ_CONNREFUSED:
pcSock->ConnectionRefused();
DelSockByAddr( pcSock );
break;
case Csock::READ_TIMEDOUT:
pcSock->Timeout();
DelSockByAddr( pcSock );
break;
default:
{
if( Csock::TMO_READ & pcSock->GetTimeoutType() )
pcSock->ResetTimer(); // reset the timeout timer
pcSock->ReadData( cBuff(), bytes ); // Call ReadData() before PushBuff() so that it is called before the ReadLine() event - LD 07/18/05
pcSock->PushBuff( cBuff(), bytes );
break;
}
}
}
else if( iErrno == SELECT_ERROR )
{
// a socket came back with an error
// usually means it was closed
DelSockByAddr( pcSock );
}
}
break;
}
case SELECT_TIMEOUT:
case SELECT_TRYAGAIN:
case SELECT_ERROR:
default :
break;
}
unsigned long long iMilliNow = millitime();
if(( iMilliNow - m_iCallTimeouts ) >= 1000 )
{
m_iCallTimeouts = iMilliNow;
// call timeout on all the sockets that recieved no data
for( unsigned int i = 0; i < this->size(); i++ )
{
if(( *this )[i]->GetConState() != Csock::CST_OK )
continue;
if(( *this )[i]->CheckTimeout(( time_t )( iMilliNow / 1000 ) ) )
DelSock( i-- );
}
}
// run any Manager Crons we may have
Cron();
}
void CSocketManager::DynamicSelectLoop( u_long iLowerBounds, u_long iUpperBounds, time_t iMaxResolution )
{
SetSelectTimeout( iLowerBounds );
if( m_errno == SELECT_TIMEOUT )
{
// only do this if the previous call to select was a timeout
timeval tMaxResolution;
timeval tNow;
tMaxResolution.tv_sec = iMaxResolution;
tMaxResolution.tv_usec = 0;
gettimeofday( &tNow, NULL );
timeval tSelectTimeout = GetDynamicSleepTime( tNow, tMaxResolution );
u_long iSelectTimeout = tSelectTimeout.tv_sec * 1000000 + tSelectTimeout.tv_usec;
iSelectTimeout = std::max( iLowerBounds, iSelectTimeout );
iSelectTimeout = std::min( iSelectTimeout, iUpperBounds );
if( iLowerBounds != iSelectTimeout )
SetSelectTimeout( iSelectTimeout );
}
Loop();
}
void CSocketManager::AddSock( Csock *pcSock, const CS_STRING & sSockName )
{
pcSock->SetSockName( sSockName );
this->push_back( pcSock );
}
Csock * CSocketManager::FindSockByRemotePort( u_short iPort )
{
for( unsigned int i = 0; i < this->size(); i++ )
{
if(( *this )[i]->GetRemotePort() == iPort )
return(( *this )[i] );
}
return( NULL );
}
Csock * CSocketManager::FindSockByLocalPort( u_short iPort )
{
for( unsigned int i = 0; i < this->size(); i++ )
if(( *this )[i]->GetLocalPort() == iPort )
return(( *this )[i] );
return( NULL );
}
Csock * CSocketManager::FindSockByName( const CS_STRING & sName )
{
std::vector<Csock *>::iterator it;
std::vector<Csock *>::iterator it_end = this->end();
for( it = this->begin(); it != it_end; it++ )
if(( *it )->GetSockName() == sName )
return( *it );
return( NULL );
}
Csock * CSocketManager::FindSockByFD( cs_sock_t iFD )
{
for( unsigned int i = 0; i < this->size(); i++ )
if(( *this )[i]->GetRSock() == iFD || ( *this )[i]->GetWSock() == iFD )
return(( *this )[i] );
return( NULL );
}
std::vector<Csock *> CSocketManager::FindSocksByName( const CS_STRING & sName )
{
std::vector<Csock *> vpSocks;
for( unsigned int i = 0; i < this->size(); i++ )
if(( *this )[i]->GetSockName() == sName )
vpSocks.push_back(( *this )[i] );
return( vpSocks );
}
std::vector<Csock *> CSocketManager::FindSocksByRemoteHost( const CS_STRING & sHostname )
{
std::vector<Csock *> vpSocks;
for( unsigned int i = 0; i < this->size(); i++ )
if(( *this )[i]->GetHostName() == sHostname )
vpSocks.push_back(( *this )[i] );
return( vpSocks );
}
void CSocketManager::DelSockByAddr( Csock *pcSock )
{
for( u_int a = 0; a < this->size(); a++ )
{
if( pcSock == ( *this )[a] )
{
DelSock( a );
return;
}
}
}
void CSocketManager::DelSock( u_int iPos )
{
if( iPos >= this->size() )
{
CS_DEBUG( "Invalid Sock Position Requested! [" << iPos << "]" );
return;
}
Csock * pSock = ( *this )[iPos];
if( pSock->GetCloseType() != Csock::CLT_DEREFERENCE )
{
if( pSock->IsConnected() )
pSock->Disconnected(); // only call disconnected event if connected event was called (IE IsConnected was set)
m_iBytesRead += pSock->GetBytesRead();
m_iBytesWritten += pSock->GetBytesWritten();
}
CS_Delete( pSock );
this->erase( this->begin() + iPos );
}
bool CSocketManager::SwapSockByIdx( Csock *pNewSock, u_long iOrginalSockIdx )
{
if( iOrginalSockIdx >= this->size() )
{
CS_DEBUG( "Invalid Sock Position Requested! [" << iOrginalSockIdx << "]" );
return( false );
}
Csock *pSock = ( *this )[iOrginalSockIdx];
pNewSock->Copy( *pSock );
pSock->Dereference();
( *this )[iOrginalSockIdx] = ( Csock * )pNewSock;
this->push_back(( Csock * )pSock ); // this allows it to get cleaned up
return( true );
}
bool CSocketManager::SwapSockByAddr( Csock *pNewSock, Csock *pOrigSock )
{
for( u_long a = 0; a < this->size(); a++ )
{
if(( *this )[a] == pOrigSock )
return( SwapSockByIdx( pNewSock, a ) );
}
return( false );
}
unsigned long long CSocketManager::GetBytesRead() const
{
// Start with the total bytes read from destroyed sockets
unsigned long long iRet = m_iBytesRead;
// Add in the outstanding bytes read from active sockets
for( u_int a = 0; a < this->size(); a++ )
iRet += ( *this )[a]->GetBytesRead();
return( iRet );
}
unsigned long long CSocketManager::GetBytesWritten() const
{
// Start with the total bytes written to destroyed sockets
unsigned long long iRet = m_iBytesWritten;
// Add in the outstanding bytes written to active sockets
for( u_int a = 0; a < this->size(); a++ )
iRet += ( *this )[a]->GetBytesWritten();
return( iRet );
}
void CSocketManager::FDSetCheck( int iFd, std::map< int, short > & miiReadyFds, ECheckType eType )
{
std::map< int, short >::iterator it = miiReadyFds.find( iFd );
if( it != miiReadyFds.end() )
it->second = ( short )( it->second | eType ); // TODO need to figure out why |= throws 'short int' from 'int' may alter its value
else
miiReadyFds[iFd] = eType;
}
bool CSocketManager::FDHasCheck( int iFd, std::map< int, short > & miiReadyFds, ECheckType eType )
{
std::map< int, short >::iterator it = miiReadyFds.find( iFd );
if( it != miiReadyFds.end() )
return(( it->second & eType ) );
return( false );
}
int CSocketManager::Select( std::map< int, short > & miiReadyFds, struct timeval *tvtimeout )
{
AssignFDs( miiReadyFds, tvtimeout );
#ifdef CSOCK_USE_POLL
if( miiReadyFds.empty() )
return( select( 0, NULL, NULL, NULL, tvtimeout ) );
struct pollfd * pFDs = ( struct pollfd * )malloc( sizeof( struct pollfd ) * miiReadyFds.size() );
size_t uCurrPoll = 0;
for( std::map< int, short >::iterator it = miiReadyFds.begin(); it != miiReadyFds.end(); ++it, ++uCurrPoll )
{
short iEvents = 0;
if( it->second & ECT_Read )
iEvents |= POLLIN;
if( it->second & ECT_Write )
iEvents |= POLLOUT;
pFDs[uCurrPoll].fd = it->first;
pFDs[uCurrPoll].events = iEvents;
pFDs[uCurrPoll].revents = 0;
}
int iTimeout = ( int )( tvtimeout->tv_usec / 1000 );
iTimeout += ( int )( tvtimeout->tv_sec * 1000 );
size_t uMaxFD = miiReadyFds.size();
int iRet = poll( pFDs, uMaxFD, iTimeout );
miiReadyFds.clear();
for( uCurrPoll = 0; uCurrPoll < uMaxFD; ++uCurrPoll )
{
short iEvents = 0;
if(( pFDs[uCurrPoll].revents & ( POLLIN|POLLERR|POLLHUP|POLLNVAL ) ) )
iEvents |= ECT_Read;
if(( pFDs[uCurrPoll].revents & POLLOUT ) )
iEvents |= ECT_Write;
std::map< int, short >::iterator it = miiReadyFds.find( pFDs[uCurrPoll].fd );
if( it != miiReadyFds.end() )
it->second = ( short )( it->second | iEvents ); // TODO need to figure out why |= throws 'short int' from 'int' may alter its value
else
miiReadyFds[pFDs[uCurrPoll].fd] = iEvents;
}
free( pFDs );
#else
fd_set rfds, wfds;
TFD_ZERO( &rfds );
TFD_ZERO( &wfds );
bool bHasWrite = false;
int iHighestFD = 0;
for( std::map< int, short >::iterator it = miiReadyFds.begin(); it != miiReadyFds.end(); ++it )
{
iHighestFD = std::max( it->first, iHighestFD );
if( it->second & ECT_Read )
{
TFD_SET( it->first, &rfds );
}
if( it->second & ECT_Write )
{
bHasWrite = true;
TFD_SET( it->first, &wfds );
}
}
int iRet = select( iHighestFD + 1, &rfds, ( bHasWrite ? &wfds : NULL ), NULL, tvtimeout );
if( iRet <= 0 )
miiReadyFds.clear();
else
{
for( std::map< int, short >::iterator it = miiReadyFds.begin(); it != miiReadyFds.end(); ++it )
{
if(( it->second & ECT_Read ) && !TFD_ISSET( it->first, &rfds ) )
it->second &= ~ECT_Read;
if(( it->second & ECT_Write ) && !TFD_ISSET( it->first, &wfds ) )
it->second &= ~ECT_Write;
}
}
#endif /* CSOCK_USE_POLL */
return( iRet );
}
void CSocketManager::Select( std::map<Csock *, EMessages> & mpeSocks )
{
mpeSocks.clear();
struct timeval tv;
std::map< int, short > miiReadyFds;
tv.tv_sec = m_iSelectWait / 1000000;
tv.tv_usec = m_iSelectWait % 1000000;
u_int iQuickReset = 1000;
if( m_iSelectWait == 0 )
iQuickReset = 0;
bool bHasAvailSocks = false;
unsigned long long iNOW = 0;
for( unsigned int i = 0; i < this->size(); i++ )
{
Csock *pcSock = ( *this )[i];
Csock::ECloseType eCloseType = pcSock->GetCloseType();
if( eCloseType == Csock::CLT_NOW || eCloseType == Csock::CLT_DEREFERENCE || ( eCloseType == Csock::CLT_AFTERWRITE && pcSock->GetWriteBuffer().empty() ) )
{
DelSock( i-- ); // close any socks that have requested it
continue;
}
else
pcSock->Cron(); // call the Cron handler here
cs_sock_t & iRSock = pcSock->GetRSock();
cs_sock_t & iWSock = pcSock->GetWSock();
#if !defined(CSOCK_USE_POLL) && !defined(_WIN32)
if( iRSock > FD_SETSIZE || iWSock > FD_SETSIZE )
{
CS_DEBUG( "FD is larger than select() can handle" );
DelSock( i-- );
continue;
}
#endif /* CSOCK_USE_POLL */
#ifdef HAVE_C_ARES
ares_channel pChannel = pcSock->GetAresChannel();
if( pChannel )
{
ares_socket_t aiAresSocks[1];
aiAresSocks[0] = ARES_SOCKET_BAD;
int iSockMask = ares_getsock( pChannel, aiAresSocks, 1 );
if( ARES_GETSOCK_READABLE( iSockMask, 0 ) )
FDSetCheck( aiAresSocks[0], miiReadyFds, ECT_Read );
if( ARES_GETSOCK_WRITABLE( iSockMask, 0 ) )
FDSetCheck( aiAresSocks[0], miiReadyFds, ECT_Write );
// let ares drop the timeout if it has something timing out sooner then whats in tv currently
ares_timeout( pChannel, &tv, &tv );
}
#endif /* HAVE_C_ARES */
if( pcSock->GetType() == Csock::LISTENER && pcSock->GetConState() == Csock::CST_BINDVHOST )
{
if( !pcSock->Listen( pcSock->GetPort(), pcSock->GetMaxConns(), pcSock->GetBindHost(), pcSock->GetTimeout(), true ) )
{
pcSock->Close();
DelSock( i-- );
}
continue;
}
pcSock->AssignFDs( miiReadyFds, &tv );
if( pcSock->GetConState() != Csock::CST_OK )
continue;
bHasAvailSocks = true;
bool bIsReadPaused = pcSock->IsReadPaused();
if( bIsReadPaused )
{
pcSock->ReadPaused();
bIsReadPaused = pcSock->IsReadPaused(); // re-read it again, incase it changed status)
}
if( iRSock == CS_INVALID_SOCK || iWSock == CS_INVALID_SOCK )
{
SelectSock( mpeSocks, SUCCESS, pcSock );
continue; // invalid sock fd
}
if( pcSock->GetType() != Csock::LISTENER )
{
bool bHasWriteBuffer = !pcSock->GetWriteBuffer().empty();
if( !bIsReadPaused )
FDSetCheck( iRSock, miiReadyFds, ECT_Read );
if( pcSock->AllowWrite( iNOW ) && ( !pcSock->IsConnected() || bHasWriteBuffer ) )
{
if( !pcSock->IsConnected() )
{
// set the write bit if not connected yet
FDSetCheck( iWSock, miiReadyFds, ECT_Write );
}
else if( bHasWriteBuffer && !pcSock->GetSSL() )
{
// always set the write bit if there is data to send when NOT ssl
FDSetCheck( iWSock, miiReadyFds, ECT_Write );
}
else if( bHasWriteBuffer && pcSock->GetSSL() && pcSock->SslIsEstablished() )
{
// ONLY set the write bit if there is data to send and the SSL handshake is finished
FDSetCheck( iWSock, miiReadyFds, ECT_Write );
}
}
if( pcSock->GetSSL() && !pcSock->SslIsEstablished() && bHasWriteBuffer )
{
// if this is an unestabled SSL session with data to send ... try sending it
// do this here, cause otherwise ssl will cause a small
// cpu spike waiting for the handshake to finish
// resend this data
if( !pcSock->Write( "" ) )
{
pcSock->Close();
}
// warning ... setting write bit in here causes massive CPU spinning on invalid SSL servers
// http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=631590
// however, we can set the select WAY down and it will retry quickly, but keep it from spinning at 100%
tv.tv_usec = iQuickReset;
tv.tv_sec = 0;
}
}
else
{
FDSetCheck( iRSock, miiReadyFds, ECT_Read );
}
if( pcSock->GetSSL() && pcSock->GetType() != Csock::LISTENER )
{
if(( pcSock->GetPending() > 0 ) && ( !pcSock->IsReadPaused() ) )
SelectSock( mpeSocks, SUCCESS, pcSock );
}
}
// old fashion select, go fer it
int iSel;
if( !mpeSocks.empty() ) // .1 ms pause to see if anything else is ready (IE if there is SSL data pending, don't wait too long)
{
tv.tv_usec = iQuickReset;
tv.tv_sec = 0;
}
else if( !this->empty() && !bHasAvailSocks )
{
tv.tv_usec = iQuickReset;
tv.tv_sec = 0;
}
iSel = Select( miiReadyFds, &tv );
if( iSel == 0 )
{
if( mpeSocks.empty() )
m_errno = SELECT_TIMEOUT;
else
m_errno = SUCCESS;
#ifdef HAVE_C_ARES
// run through ares channels and process timeouts
for( u_long uSock = 0; uSock < this->size(); ++uSock )
{
Csock *pcSock = this->at( uSock );
ares_channel pChannel = pcSock->GetAresChannel();
if( pChannel )
ares_process_fd( pChannel, ARES_SOCKET_BAD, ARES_SOCKET_BAD );
}
#endif /* HAVE_C_ARES */
return;
}
if(( iSel == -1 ) && ( errno == EINTR ) )
{
if( mpeSocks.empty() )
m_errno = SELECT_TRYAGAIN;
else
m_errno = SUCCESS;
return;
}
else if( iSel == -1 )
{
if( mpeSocks.empty() )
m_errno = SELECT_ERROR;
else
m_errno = SUCCESS;
return;
}
else
{
m_errno = SUCCESS;
}
CheckFDs( miiReadyFds );
// find out wich one is ready
for( unsigned int i = 0; i < this->size(); i++ )
{
Csock *pcSock = ( *this )[i];
#ifdef HAVE_C_ARES
ares_channel pChannel = pcSock->GetAresChannel();
if( pChannel )
{
ares_socket_t aiAresSocks[1];
aiAresSocks[0] = ARES_SOCKET_BAD;
ares_getsock( pChannel, aiAresSocks, 1 );
if( FDHasCheck( aiAresSocks[0], miiReadyFds, ECT_Read ) || FDHasCheck( aiAresSocks[0], miiReadyFds, ECT_Write ) )
ares_process_fd( pChannel, aiAresSocks[0], aiAresSocks[0] );
}
#endif /* HAVE_C_ARES */
pcSock->CheckFDs( miiReadyFds );
if( pcSock->GetConState() != Csock::CST_OK )
continue;
cs_sock_t & iRSock = pcSock->GetRSock();
cs_sock_t & iWSock = pcSock->GetWSock();
EMessages iErrno = SUCCESS;
if( iRSock == CS_INVALID_SOCK || iWSock == CS_INVALID_SOCK )
{
// trigger a success so it goes through the normal motions
// and an error is produced
SelectSock( mpeSocks, SUCCESS, pcSock );
continue; // watch for invalid socks
}
if( FDHasCheck( iWSock, miiReadyFds, ECT_Write ) )
{
if( iSel > 0 )
{
iErrno = SUCCESS;
if(( !pcSock->GetWriteBuffer().empty() ) && ( pcSock->IsConnected() ) )
{
// write whats in the socks send buffer
if( !pcSock->Write( "" ) )
{
// write failed, sock died :(
iErrno = SELECT_ERROR;
}
}
}
else
iErrno = SELECT_ERROR;
SelectSock( mpeSocks, iErrno, pcSock );
}
else if( FDHasCheck( iRSock, miiReadyFds, ECT_Read ) )
{
if( iSel > 0 )
iErrno = SUCCESS;
else
iErrno = SELECT_ERROR;
if( pcSock->GetType() != Csock::LISTENER )
SelectSock( mpeSocks, iErrno, pcSock );
else // someone is coming in!
{
CS_STRING sHost;
u_short port;
cs_sock_t inSock = pcSock->Accept( sHost, port );
if( inSock != CS_INVALID_SOCK )
{
if( Csock::TMO_ACCEPT & pcSock->GetTimeoutType() )
pcSock->ResetTimer(); // let them now it got dinged
// if we have a new sock, then add it
Csock *NewpcSock = ( Csock * )pcSock->GetSockObj( sHost, port );
if( !NewpcSock )
NewpcSock = GetSockObj( sHost, port );
NewpcSock->SetType( Csock::INBOUND );
NewpcSock->SetRSock( inSock );
NewpcSock->SetWSock( inSock );
NewpcSock->SetIPv6( pcSock->GetIPv6() );
bool bAddSock = true;
#ifdef HAVE_LIBSSL
//
// is this ssl ?
if( pcSock->GetSSL() )
{
NewpcSock->SetCipher( pcSock->GetCipher() );
NewpcSock->SetPemLocation( pcSock->GetPemLocation() );
NewpcSock->SetPemPass( pcSock->GetPemPass() );
NewpcSock->SetRequireClientCertFlags( pcSock->GetRequireClientCertFlags() );
bAddSock = NewpcSock->AcceptSSL();
}
#endif /* HAVE_LIBSSL */
if( bAddSock )
{
// set the name of the listener
NewpcSock->SetParentSockName( pcSock->GetSockName() );
NewpcSock->SetRate( pcSock->GetRateBytes(), pcSock->GetRateTime() );
if( NewpcSock->GetSockName().empty() )
{
std::stringstream s;
s << sHost << ":" << port;
AddSock( NewpcSock, s.str() );
}
else
AddSock( NewpcSock, NewpcSock->GetSockName() );
}
else
CS_Delete( NewpcSock );
}
#ifdef _WIN32
else if( GetSockError() != WSAEWOULDBLOCK )
#else /* _WIN32 */
else if( GetSockError() != EAGAIN )
#endif /* _WIN32 */
{
pcSock->CallSockError( GetSockError() );
}
}
}
}
}
inline void MinimizeTime( timeval& min, const timeval& another )
{
if( timercmp( &min, &another, > ) )
{
min = another;
}
}
timeval CSocketManager::GetDynamicSleepTime( const timeval& tNow, const timeval& tMaxResolution ) const
{
timeval tNextRunTime;
timeradd( &tNow, &tMaxResolution, &tNextRunTime );
std::vector<Csock *>::const_iterator it;
// This is safe, because we don't modify the vector.
std::vector<Csock *>::const_iterator it_end = this->end();
for( it = this->begin(); it != it_end; it++ )
{
Csock* pSock = *it;
if( pSock->GetConState() != Csock::CST_OK )
tNextRunTime = tNow; // this is in a nebulous state, need to let it proceed like normal
time_t iTimeoutInSeconds = pSock->GetTimeout();
if( iTimeoutInSeconds > 0 )
{
timeval tNextTimeout;
tNextTimeout.tv_sec = pSock->GetNextCheckTimeout( 0 ); // TODO convert socket timeouts to timeval too?
tNextTimeout.tv_usec = 0;
MinimizeTime( tNextRunTime, tNextTimeout );
}
const std::vector<CCron *> & vCrons = pSock->GetCrons();
std::vector<CCron *>::const_iterator cit;
std::vector<CCron *>::const_iterator cit_end = vCrons.end();
for( cit = vCrons.begin(); cit != cit_end; cit++ )
MinimizeTime( tNextRunTime, ( *cit )->GetNextRun() );
}
std::vector<CCron *>::const_iterator cit;
std::vector<CCron *>::const_iterator cit_end = m_vcCrons.end();
for( cit = m_vcCrons.begin(); cit != cit_end; cit++ )
MinimizeTime( tNextRunTime, ( *cit )->GetNextRun() );
timeval tReturnValue;
if( timercmp( &tNextRunTime, &tNow, < ) )
{
timerclear( &tReturnValue );
return( tReturnValue ); // smallest unit possible
}
timersub( &tNextRunTime, &tNow, &tReturnValue );
MinimizeTime( tReturnValue, tMaxResolution );
return( tReturnValue );
}
void CSocketManager::SelectSock( std::map<Csock *, EMessages> & mpeSocks, EMessages eErrno, Csock * pcSock )
{
if( mpeSocks.find( pcSock ) != mpeSocks.end() )
return;
mpeSocks[pcSock] = eErrno;
}