/** @file * * Copyright (c) 1999-2012 Jim Hull * 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 #ifdef __NetBSD__ #include #endif /* __NetBSD__ */ #ifdef HAVE_LIBSSL #include #include #include #endif /* HAVE_LIBSSL */ #include #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 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::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::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(), 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 mpeSocks; Select( mpeSocks ); switch( m_errno ) { case SUCCESS: { for( std::map::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::iterator it; std::vector::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 CSocketManager::FindSocksByName( const CS_STRING & sName ) { std::vector vpSocks; for( unsigned int i = 0; i < this->size(); i++ ) if(( *this )[i]->GetSockName() == sName ) vpSocks.push_back(( *this )[i] ); return( vpSocks ); } std::vector CSocketManager::FindSocksByRemoteHost( const CS_STRING & sHostname ) { std::vector 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 & 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::const_iterator it; // This is safe, because we don't modify the vector. std::vector::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 & vCrons = pSock->GetCrons(); std::vector::const_iterator cit; std::vector::const_iterator cit_end = vCrons.end(); for( cit = vCrons.begin(); cit != cit_end; cit++ ) MinimizeTime( tNextRunTime, ( *cit )->GetNextRun() ); } std::vector::const_iterator cit; std::vector::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 & mpeSocks, EMessages eErrno, Csock * pcSock ) { if( mpeSocks.find( pcSock ) != mpeSocks.end() ) return; mpeSocks[pcSock] = eErrno; }