/** * * Copyright (c) 1999-2004 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 the DB software and any accompanying software that uses the DB 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 SLEEPYCAT 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. * * * $Revision$ */ #ifndef _HAS_CSOCKET_ #define _HAS_CSOCKET_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBSSL #include #include #include #endif /* HAVE_LIBSSL */ #ifdef __sun #include #include #endif /* __sun */ #include #include #include #include #include #include #ifndef CS_STRING # ifdef _HAS_CSTRING_ # define CS_STRING Cstring # else # define CS_STRING string # endif /* _HAS_CSTRING_ */ #endif /* CS_STRING */ #ifndef CS_DEBUG #ifdef __DEBUG__ # define CS_DEBUG( f ) cerr << __FILE__ << ":" << __LINE__ << " " << f << endl #else # define CS_DEBUG(f) (void)0 #endif /* __DEBUG__ */ #endif /* CS_DEBUG */ #ifdef __DEBUG__ # define PERROR( f ) __Perror( f ) #else # define PERROR( f ) (void)0 #endif /* __DEBUG__ */ using namespace std; #ifndef _NO_CSOCKET_NS // some people may not want to use a namespace namespace Csocket { #endif /* _NO_CSOCKET_NS */ const u_int CS_BLOCKSIZE = 4096; template inline void CS_Delete( T * & p ) { if( p ) { delete p; p = NULL; } } #ifdef HAVE_LIBSSL enum ECompType { CT_NONE = 0, CT_ZLIB = 1, CT_RLE = 2 }; /** * @brief You HAVE to call this in order to use the SSL library * @return true on success */ bool InitSSL( ECompType eCompressionType = CT_NONE ); void SSLErrors( const char *filename, u_int iLineNum ); #endif /* HAVE_LIBSSL */ // wrappers for FD_SET and such to work in templates inline void TFD_ZERO( fd_set *set ) { FD_ZERO( set ); } inline void TFD_SET( int iSock, fd_set *set ) { FD_SET( iSock, set ); } inline bool TFD_ISSET( int iSock, fd_set *set ) { if ( FD_ISSET( iSock, set ) ) return( true ); return( false ); } inline void TFD_CLR( int iSock, fd_set *set ) { FD_CLR( iSock, set ); } void __Perror( const CS_STRING & s ); unsigned long long millitime(); bool GetHostByName( const CS_STRING & sHostName, struct in_addr *paddr ); /** * @class CCron * @brief this is the main cron job class * * You should derive from this class, and override RunJob() with your code * @author Jim Hull */ class CCron { public: CCron() ; virtual ~CCron() {} //! This is used by the Job Manager, and not you directly void run(); /** * @TimeSequence how often to run in seconds * @iMaxCycles how many times to run, 0 makes it run forever */ void StartMaxCycles( int TimeSequence, u_int iMaxCycles ); //! starts and runs infinity amount of times void Start( int TimeSequence ); //! call this to turn off your cron, it will be removed void Stop(); //! pauses excution of your code in RunJob void Pause(); //! removes the pause on RunJon void UnPause(); int GetInterval() const; u_int GetMaxCycles() const; u_int GetCyclesLeft() const; //! returns true if cron is active bool isValid(); const CS_STRING & GetName() const; void SetName( const CS_STRING & sName ); protected: //! this is the method you should override virtual void RunJob(); time_t m_iTime; bool m_bActive, m_bPause; int m_iTimeSequence; u_int m_iMaxCycles, m_iCycles; CS_STRING m_sName; }; /** * @class Csock * @brief Basic Socket Class * The most basic level socket class * You can use this class directly for quick things * or use the socket manager * @see TSocketManager * @author Jim Hull */ class Csock { public: //! default constructor, sets a timeout of 60 seconds Csock( int itimeout = 60 ); /** * Advanced constructor, for creating a simple connection * * @param sHostname the hostname your are connecting to * @param iport the port you are connectint to * @param itimeout how long to wait before ditching the connection, default is 60 seconds */ Csock( const CS_STRING & sHostname, int iport, int itimeout = 60 ); // override this for accept sockets virtual Csock *GetSockObj( const CS_STRING & sHostname, int iPort ); virtual ~Csock(); enum ETConn { OUTBOUND = 0, //!< outbound connection LISTENER = 1, //!< a socket accepting connections INBOUND = 2 //!< an inbound connection, passed from LISTENER }; enum EFRead { READ_EOF = 0, //!< End Of File, done reading READ_ERR = -1, //!< Error on the socket, socket closed, done reading READ_EAGAIN = -2, //!< Try to get data again READ_CONNREFUSED = -3, //!< Connection Refused READ_TIMEDOUT = -4 //!< Connection timed out }; enum EFSelect { SEL_OK = 0, //!< Select passed ok SEL_TIMEOUT = -1, //!< Select timed out SEL_EAGAIN = -2, //!< Select wants you to try again SEL_ERR = -3 //!< Select recieved an error }; enum ESSLMethod { SSL23 = 0, SSL2 = 2, SSL3 = 3 }; Csock & operator<<( const CS_STRING & s ); Csock & operator<<( ostream & ( *io )( ostream & ) ); Csock & operator<<( int i ); Csock & operator<<( unsigned int i ); Csock & operator<<( long i ); Csock & operator<<( unsigned long i ); Csock & operator<<( unsigned long long i ); Csock & operator<<( float i ); Csock & operator<<( double i ); /** * Create the connection * * @param sBindHost the ip you want to bind to locally * @return true on success */ virtual bool Connect( const CS_STRING & sBindHost = "" ); /** * WriteSelect on this socket * Only good if JUST using this socket, otherwise use the TSocketManager */ virtual int WriteSelect(); /** * ReadSelect on this socket * Only good if JUST using this socket, otherwise use the TSocketManager */ virtual int ReadSelect(); /** * Listens for connections * * @param iPort the port to listen on * @param iMaxConns the maximum amount of connections to allow */ virtual bool Listen( int iPort, int iMaxConns = SOMAXCONN, const CS_STRING & sBindHost = "", u_int iTimeout = 0 ); //! Accept an inbound connection, this is used internally virtual int Accept( CS_STRING & sHost, int & iRPort ); //! Accept an inbound SSL connection, this is used internally and called after Accept virtual bool AcceptSSL(); //! This sets up the SSL Client, this is used internally virtual bool SSLClientSetup(); //! This sets up the SSL Server, this is used internally virtual bool SSLServerSetup(); /** * Create the SSL connection * * @param sBindhost the ip you want to bind to locally * @return true on success */ virtual bool ConnectSSL( const CS_STRING & sBindhost = "" ); /** * Write data to the socket * if not all of the data is sent, it will be stored on * an internal buffer, and tried again with next call to Write * if the socket is blocking, it will send everything, its ok to check ernno after this (nothing else is processed) * * @param data the data to send * @param len the length of data * */ virtual bool Write( const char *data, int len ); /** * convience function * @see Write( const char *, int ) */ virtual bool Write( const CS_STRING & sData ); /** * Read from the socket * Just pass in a pointer, big enough to hold len bytes * * @param data the buffer to read into * @param len the size of the buffer * * @return * Returns READ_EOF for EOF * Returns READ_ERR for ERROR * Returns READ_EAGAIN for Try Again ( EAGAIN ) * Returns READ_CONNREFUSED for connection refused * Returns READ_TIMEDOUT for a connection that timed out at the TCP level * Otherwise returns the bytes read into data */ virtual int Read( char *data, int len ); CS_STRING GetLocalIP(); CS_STRING GetRemoteIP(); //! Tells you if the socket is connected virtual bool IsConnected(); //! Sets the sock, telling it its connected (internal use only) virtual void SetIsConnected( bool b ); //! returns a reference to the sock int & GetRSock(); void SetRSock( int iSock ); int & GetWSock(); void SetWSock( int iSock ); void SetSock( int iSock ); int & GetSock(); //! resets the time counter void ResetTimer(); //! will pause/unpause reading on this socket void PauseRead(); void UnPauseRead(); bool IsReadPaused(); /** * this timeout isn't just connection timeout, but also timeout on * NOT recieving data, to disable this set it to 0 * then the normal TCP timeout will apply (basically TCP will kill a dead connection) * Set the timeout, set to 0 to never timeout */ enum { TMO_READ = 1, TMO_WRITE = 2, TMO_ACCEPT = 4, TMO_ALL = TMO_READ|TMO_WRITE|TMO_ACCEPT }; //! Currently this uses the same value for all timeouts, and iTimeoutType merely states which event will be checked //! for timeouts void SetTimeout( int iTimeout, u_int iTimeoutType = TMO_ALL ); void SetTimeoutType( u_int iTimeoutType ); int GetTimeout() const; u_int GetTimeoutType() const; //! returns true if the socket has timed out virtual bool CheckTimeout(); /** * pushes data up on the buffer, if a line is ready * it calls the ReadLine event */ virtual void PushBuff( const char *data, int len ); //! This gives access to the internal buffer, if your //! not going to use GetLine(), then you may want to clear this out //! (if its binary data and not many '\n' CS_STRING & GetInternalBuffer(); //! sets the max buffered threshold when enablereadline() is enabled void SetMaxBufferThreshold( u_int iThreshold ); u_int GetMaxBufferThreshold(); //! Returns the connection type from enum eConnType int GetType(); void SetType( int iType ); //! Returns a reference to the socket name const CS_STRING & GetSockName(); void SetSockName( const CS_STRING & sName ); //! Returns a reference to the host name const CS_STRING & GetHostName(); void SetHostName( const CS_STRING & sHostname ); //! Gets the starting time of this socket unsigned long long GetStartTime() const; //! Resets the start time void ResetStartTime(); //! Gets the amount of data read during the existence of the socket unsigned long long GetBytesRead() const; void ResetBytesRead(); //! Gets the amount of data written during the existence of the socket unsigned long long GetBytesWritten() const; void ResetBytesWritten(); //! Get Avg Read Speed in sample milliseconds (default is 1000 milliseconds or 1 second) double GetAvgRead( unsigned long long iSample = 1000 ); //! Get Avg Write Speed in sample milliseconds (default is 1000 milliseconds or 1 second) double GetAvgWrite( unsigned long long iSample = 1000 ); //! Returns the remote port int GetRemotePort(); //! Returns the local port int GetLocalPort(); //! Returns the port int GetPort(); void SetPort( int iPort ); //! just mark us as closed, the parent can pick it up void Close(); //! returns true if the socket is closed bool isClosed(); //! Set rather to NON Blocking IO on this socket, default is true void BlockIO( bool bBLOCK ); //! Use this to change your fd's to blocking or none blocking void NonBlockingIO(); //! if this connection type is ssl or not bool GetSSL(); void SetSSL( bool b ); #ifdef HAVE_LIBSSL //! Set the cipher type ( openssl cipher [to see ciphers available] ) void SetCipher( const CS_STRING & sCipher ); const CS_STRING & GetCipher(); //! Set the pem file location void SetPemLocation( const CS_STRING & sPemFile ); const CS_STRING & GetPemLocation(); void SetPemPass( const CS_STRING & sPassword ); const CS_STRING & GetPemPass() const; static int PemPassCB( char *buf, int size, int rwflag, void *pcSocket ); static int CertVerifyCB( int preverify_ok, X509_STORE_CTX *x509_ctx ); //! Set the SSL method type void SetSSLMethod( int iMethod ); int GetSSLMethod(); void SetSSLObject( SSL *ssl ); void SetCTXObject( SSL_CTX *sslCtx ); void SetFullSSLAccept(); SSL_SESSION * GetSSLSession(); #endif /* HAVE_LIBSSL */ //! Get the send buffer const CS_STRING & GetWriteBuffer(); void ClearWriteBuffer(); //! is SSL_accept finished ? bool FullSSLAccept(); //! is the ssl properly finished (from write no error) bool SslIsEstablished(); //! Use this to bind this socket to inetd bool ConnectInetd( bool bIsSSL = false, const CS_STRING & sHostname = "" ); //! Tie this guy to an existing real file descriptor bool ConnectFD( int iReadFD, int iWriteFD, const CS_STRING & sName, bool bIsSSL = false, ETConn eDirection = INBOUND ); //! Get the peer's X509 cert #ifdef HAVE_LIBSSL X509 *getX509(); //! //! Returns The Peers Public Key //! CS_STRING GetPeerPubKey(); bool RequiresClientCert(); void SetRequiresClientCert( bool bRequiresCert ); #endif /* HAVE_LIBSSL */ //! Set The INBOUND Parent sockname virtual void SetParentSockName( const CS_STRING & sParentName ); const CS_STRING & GetParentSockName(); /* * sets the rate at which we can send data * \param iBytes the amount of bytes we can write * \param iMilliseconds the amount of time we have to rate to iBytes */ virtual void SetRate( u_int iBytes, unsigned long long iMilliseconds ); u_int GetRateBytes(); unsigned long long GetRateTime(); //! This has a garbage collecter, and is used internall to call the jobs virtual void Cron(); //! insert a newly created cron virtual void AddCron( CCron * pcCron ); //! delete cron(s) by name virtual void DelCron( const CS_STRING & sName, bool bDeleteAll = true, bool bCaseSensitive = true ); //! delete cron by idx virtual void DelCron( u_int iPos ); //! delete cron by address virtual void DelCronByAddr( CCron *pcCron ); /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * Connected event */ virtual void Connected() {} /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * Disconnected event */ virtual void Disconnected() {} /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * Sock Timed out event */ virtual void Timeout() {} /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * Ready to read data event */ virtual void ReadData( const char *data, int len ) {} /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks. * With ReadLine, if your not going to use it IE a data stream, @see EnableReadLine() * * Ready to Read a full line event */ virtual void ReadLine( const CS_STRING & sLine ) {} //! set the value of m_bEnableReadLine to true, we don't want to store a buffer for ReadLine, unless we want it void EnableReadLine(); void DisableReadLine(); /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * This WARNING event is called when your buffer for readline exceeds the warning threshold * and triggers this event. Either Override it and do nothing, or @SetMaxBufferThreshold( int ) * This event will only get called if m_bEnableReadLine is enabled */ virtual void ReachedMaxBuffer(); /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * A sock error occured event */ virtual void SockError( int iErrno ) {} /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * * Incoming Connection Event * return false and the connection will fail * default returns true */ virtual bool ConnectionFrom( const CS_STRING & sHost, int iPort ) { return( true ); } /** * Override these functions for an easy interface when using the Socket Manager * Don't bother using these callbacks if you are using this class directly (without Socket Manager) * as the Socket Manager calls most of these callbacks * * Connection Refused Event * */ virtual void ConnectionRefused() {} /** * This gets called every iteration of Select() if the socket is ReadPaused */ virtual void ReadPaused() {} //! return the data imediatly ready for read virtual int GetPending(); ////////////////////////////////////////////////// private: int m_iReadSock, m_iWriteSock, m_itimeout, m_iport, m_iConnType, m_iTcount, m_iMethod, m_iRemotePort, m_iLocalPort; bool m_bssl, m_bIsConnected, m_bClosed, m_bBLOCK, m_bFullsslAccept; bool m_bsslEstablished, m_bEnableReadLine, m_bRequireClientCert, m_bPauseRead; CS_STRING m_shostname, m_sbuffer, m_sSockName, m_sPemFile, m_sCipherType, m_sParentName; CS_STRING m_sSend, m_sSSLBuffer, m_sPemPass, m_sLocalIP, m_sRemoteIP; unsigned long long m_iMaxMilliSeconds, m_iLastSendTime, m_iBytesRead, m_iBytesWritten, m_iStartTime; unsigned int m_iMaxBytes, m_iLastSend, m_iMaxStoredBufferLength, m_iTimeoutType; struct sockaddr_in m_address; #ifdef HAVE_LIBSSL SSL *m_ssl; SSL_CTX *m_ssl_ctx; SSL_METHOD *m_ssl_method; virtual void FREE_SSL(); virtual void FREE_CTX(); #endif /* HAVE_LIBSSL */ vector m_vcCrons; //! Create the socket virtual int SOCKET( bool bListen = false ); virtual void Init( const CS_STRING & sHostname, int iport, int itimeout = 60 ); }; /** * @class TSocketManager * @brief Best class to use to interact with the sockets * * handles SSL and NON Blocking IO * Its a template class since Csock derives need to be new'd correctly * Makes it easier to use overall * Rather then use it directly, you'll probably get more use deriving from it * Another thing to note, is that all sockets are deleted implicitly, so obviously you * cant pass in Csock classes created on the stack. For those of you who don't * know STL very well, the reason I did this is because whenever you add to certain stl containers * (ie vector, or map), its completely rebuilt using the copy constructor on each element. * That then means the constructor and destructor are called on every item in the container. * Not only is this more overhead then just moving pointers around, its dangerous as if you have * an object that is newed and deleted in the destructor the value of its pointer is copied in the * default copy constructor. This means everyone has to know better and create a copy constructor, * or I just make everyone new their object :) * * class CBlahSock : public TSocketManager * * @author Jim Hull */ template class TSocketManager : public vector { public: TSocketManager() : vector() { m_errno = SUCCESS; m_iCallTimeouts = millitime(); m_iSelectWait = 100000; // Default of 100 milliseconds } virtual ~TSocketManager() { Cleanup(); } void clear() { for( unsigned int i = 0; i < this->size(); i++ ) CS_Delete( (*this)[i] ); vector::clear(); } virtual void Cleanup() { for( u_int a = 0; a < m_vcCrons.size(); a++ ) CS_Delete( m_vcCrons[a] ); m_vcCrons.clear(); clear(); } enum EMessages { SUCCESS = 0, //! Select returned more then 1 fd ready for action SELECT_ERROR = -1, //! An Error Happened, Probably dead socket. That socket is returned if available SELECT_TIMEOUT = -2, //! Select Timeout SELECT_TRYAGAIN = -3 //! Select calls for you to try again }; /** * Create a connection * * \param sHostname the destination * \param iPort the destination port * \param sSockName the Socket Name ( should be unique ) * \param iTimeout the amount of time to try to connect * \param isSSL does the connection require a SSL layer * \param sBindHost the host to bind too * \return true on success */ virtual bool Connect( const CS_STRING & sHostname, int iPort , const CS_STRING & sSockName, int iTimeout = 60, bool isSSL = false, const CS_STRING & sBindHost = "", T *pcSock = NULL ) { // create the new object if ( !pcSock ) pcSock = new T( sHostname, iPort, iTimeout ); else { pcSock->SetHostName( sHostname ); pcSock->SetPort( iPort ); pcSock->SetTimeout( iTimeout ); } // make it NON-Blocking IO pcSock->BlockIO( false ); if ( !pcSock->Connect( sBindHost ) ) { if ( errno == ECONNREFUSED ) pcSock->ConnectionRefused(); CS_Delete( pcSock ); return( false ); } #ifdef HAVE_LIBSSL if ( isSSL ) { if ( !pcSock->ConnectSSL() ) { if ( errno == ECONNREFUSED ) pcSock->ConnectionRefused(); CS_Delete( pcSock ); return( false ); } } #endif /* HAVE_LIBSSL */ AddSock( pcSock, sSockName ); return( true ); } /** * Create a listening socket * * \param iPort the port to listen on * \param sSockName the name of the socket * \param isSSL if the sockets created require an ssl layer * \param iMaxConns the maximum amount of connections to accept * \return true on success */ virtual T * ListenHost( int iPort, const CS_STRING & sSockName, const CS_STRING & sBindHost, int isSSL = false, int iMaxConns = SOMAXCONN, T *pcSock = NULL, u_int iTimeout = 0 ) { if ( !pcSock ) pcSock = new T(); pcSock->BlockIO( false ); pcSock->SetSSL( isSSL ); if ( pcSock->Listen( iPort, iMaxConns, sBindHost, iTimeout ) ) { AddSock( pcSock, sSockName ); return( pcSock ); } CS_Delete( pcSock ); return( NULL ); } virtual bool ListenAll( int iPort, const CS_STRING & sSockName, int isSSL = false, int iMaxConns = SOMAXCONN, T *pcSock = NULL, u_int iTimeout = 0 ) { return( ListenHost( iPort, sSockName, "", isSSL, iMaxConns, pcSock, iTimeout ) ); } /* * @return the port number being listened on */ virtual u_short ListenRand( const CS_STRING & sSockName, const CS_STRING & sBindHost, int isSSL = false, int iMaxConns = SOMAXCONN, T *pcSock = NULL, u_int iTimeout = 0 ) { u_short iPort = 0; T *pNewSock = ListenHost( 0, sSockName, sBindHost, isSSL, iMaxConns, pcSock, iTimeout ); if ( pNewSock ) { int iSock = pNewSock->GetSock(); if ( iSock < 0 ) { CS_DEBUG( "Failed to attain a valid file descriptor" ); pNewSock->Close(); return( 0 ); } struct sockaddr_in mLocalAddr; socklen_t mLocalLen = sizeof(struct sockaddr); getsockname( iSock, (struct sockaddr *) &mLocalAddr, &mLocalLen ); iPort = ntohs( mLocalAddr.sin_port ); } return( iPort ); } virtual u_short ListenAllRand( const CS_STRING & sSockName, int isSSL = false, int iMaxConns = SOMAXCONN, T *pcSock = NULL, u_int iTimeout = 0 ) { return( ListenRand( sSockName, "", isSSL, iMaxConns, pcSock, iTimeout ) ); } /* * Best place to call this class for running, all the call backs are called * You should through this in your main while loop (long as its not blocking) * all the events are called as needed */ virtual void Loop () { map mpeSocks; Select( mpeSocks ); set spReadySocks; switch( m_errno ) { case SUCCESS: { for( typename map::iterator itSock = mpeSocks.begin(); itSock != mpeSocks.end(); itSock++ ) { T * pcSock = itSock->first; EMessages iErrno = itSock->second; if ( iErrno == SUCCESS ) { // read in data // if this is a char *buff; int iLen = 0; if ( pcSock->GetSSL() ) iLen = pcSock->GetPending(); if ( iLen > 0 ) { buff = (char *)malloc( iLen ); } else { iLen = CS_BLOCKSIZE; buff = (char *)malloc( CS_BLOCKSIZE ); } int bytes = pcSock->Read( buff, iLen ); if ( ( bytes != T::READ_TIMEDOUT ) && ( bytes != T::READ_CONNREFUSED ) && ( !pcSock->IsConnected() ) ) { pcSock->SetIsConnected( true ); pcSock->Connected(); } switch( bytes ) { case T::READ_EOF: { DelSockByAddr( pcSock ); break; } case T::READ_ERR: { pcSock->SockError( errno ); DelSockByAddr( pcSock ); break; } case T::READ_EAGAIN: break; case T::READ_CONNREFUSED: pcSock->ConnectionRefused(); DelSockByAddr( pcSock ); break; case T::READ_TIMEDOUT: pcSock->Timeout(); DelSockByAddr( pcSock ); break; default: { if ( T::TMO_READ & pcSock->GetTimeoutType() ) pcSock->ResetTimer(); // reset the timeout timer pcSock->PushBuff( buff, bytes ); pcSock->ReadData( buff, bytes ); break; } } // free up the buff free( buff ); } 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_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]->CheckTimeout() ) DelSock( i-- ); } } // run any Manager Crons we may have Cron(); } /* * Make this method virtual, so you can override it when a socket is added * Assuming you might want to do some extra stuff */ virtual void AddSock( T *pcSock, const CS_STRING & sSockName ) { pcSock->SetSockName( sSockName ); push_back( pcSock ); } //! returns a pointer to the FIRST sock found by port or NULL on no match virtual T * FindSockByRemotePort( int iPort ) { for( unsigned int i = 0; i < this->size(); i++ ) { if ( (*this)[i]->GetRemotePort() == iPort ) return( (*this)[i] ); } return( NULL ); } //! returns a pointer to the FIRST sock found by port or NULL on no match virtual T * FindSockByLocalPort( int iPort ) { for( unsigned int i = 0; i < this->size(); i++ ) if ( (*this)[i]->GetLocalPort() == iPort ) return( (*this)[i] ); return( NULL ); } //! returns a pointer to the FIRST sock found by name or NULL on no match virtual T * FindSockByName( const CS_STRING & sName ) { for( unsigned int i = 0; i < this->size(); i++ ) if ( (*this)[i]->GetSockName() == sName ) return( (*this)[i] ); return( NULL ); } virtual vector FindSocksByName( const CS_STRING & sName ) { vector vpSocks; for( unsigned int i = 0; i < this->size(); i++ ) if ( (*this)[i]->GetSockName() == sName ) vpSocks.push_back( (*this)[i] ); return( vpSocks ); } //! returns a vector of pointers to socks with sHostname as being connected virtual vector FindSocksByRemoteHost( const CS_STRING & sHostname ) { vector vpSocks; for( unsigned int i = 0; i < this->size(); i++ ) if ( (*this)[i]->GetHostName() == sHostname ) vpSocks.push_back( (*this)[i] ); return( vpSocks ); } //! return the last known error as set by this class int GetErrno() { return( m_errno ); } //! add a cronjob at the manager level virtual void AddCron( CCron *pcCron ) { m_vcCrons.push_back( pcCron ); } //! delete cron(s) by name virtual void DelCron( const CS_STRING & sName, bool bDeleteAll = true, bool bCaseSensitive = true ) { 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-- ); } } } //! delete cron by idx virtual void 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 ); } } //! delete cron by address virtual void 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; } } } //! Get the Select Timeout in MICROSECONDS ( 1000 == 1 millisecond ) u_int GetSelectTimeout() { return( m_iSelectWait ); } //! Set the Select Timeout in MICROSECODS ( 1000 == 1 millisecond ) //! Setting this to 0 will cause no timeout to happen, select will return instantly void SetSelectTimeout( u_int iTimeout ) { m_iSelectWait = iTimeout; } vector & GetCrons() { return( m_vcCrons ); } //! Delete a sock by addr //! its position is looked up //! the socket is deleted, the appropriate call backs are peformed //! and its instance is removed from the manager virtual void DelSockByAddr( T *pcSock ) { for( u_int a = 0; a < this->size(); a++ ) { if ( pcSock == (*this)[a] ) { DelSock( a ); return; } } } //! Delete a sock by position in the vector //! the socket is deleted, the appropriate call backs are peformed //! and its instance is removed from the manager //! deleting in a loop can be tricky, be sure you watch your position. //! ie for( u_int a = 0; a < size(); a++ ) DelSock( a-- ); virtual void DelSock( u_int iPos ) { if ( iPos >= this->size() ) { CS_DEBUG( "Invalid Sock Position Requested! [" << iPos << "]" ); return; } if ( (*this)[iPos]->IsConnected() ) (*this)[iPos]->Disconnected(); // only call disconnected event if connected event was called (IE IsConnected was set) CS_Delete( (*this)[iPos] ); this->erase( this->begin() + iPos ); } private: /** * fills a map of socks to a message for check * map is empty if none are ready, check GetErrno() for the error, if not SUCCESS Select() failed * each struct contains the socks error * @see GetErrno() */ virtual void Select( map & mpeSocks ) { mpeSocks.clear(); struct timeval tv; fd_set rfds, wfds; tv.tv_sec = 0; tv.tv_usec = m_iSelectWait; u_int iQuickReset = 1000; if ( m_iSelectWait == 0 ) iQuickReset = 0; TFD_ZERO( &rfds ); TFD_ZERO( &wfds ); // before we go any further, Process work needing to be done on the job for( unsigned int i = 0; i < this->size(); i++ ) { if ( (*this)[i]->isClosed() ) DelSock( i-- ); // close any socks that have requested it else (*this)[i]->Cron(); // call the Cron handler here } bool bHasWriteable = false; for( unsigned int i = 0; i < this->size(); i++ ) { T *pcSock = (*this)[i]; int & iRSock = pcSock->GetRSock(); int & iWSock = pcSock->GetWSock(); bool bIsReadPaused = pcSock->IsReadPaused(); if ( bIsReadPaused ) { pcSock->ReadPaused(); bIsReadPaused = pcSock->IsReadPaused(); // re-read it again, incase it changed status) } if ( ( iRSock < 0 ) || ( iWSock < 0 ) ) { SelectSock( mpeSocks, SUCCESS, pcSock ); continue; // invalid sock fd } if ( pcSock->GetType() != T::LISTENER ) { if ( ( pcSock->GetSSL() ) && ( pcSock->GetType() == T::INBOUND ) && ( !pcSock->FullSSLAccept() ) ) { tv.tv_usec = iQuickReset; // just make sure this returns quick incase we still need pending // try accept on this socket again if ( !pcSock->AcceptSSL() ) pcSock->Close(); } else if ( ( pcSock->IsConnected() ) && ( pcSock->GetWriteBuffer().empty() ) ) { if ( !bIsReadPaused ) TFD_SET( iRSock, &rfds ); } else if ( ( pcSock->GetSSL() ) && ( !pcSock->SslIsEstablished() ) && ( !pcSock->GetWriteBuffer().empty() ) ) { // do this here, cause otherwise ssl will cause a small // cpu spike waiting for the handshake to finish TFD_SET( iRSock, &rfds ); // resend this data if ( !pcSock->Write( "" ) ) { pcSock->Close(); } } else { if ( !bIsReadPaused ) TFD_SET( iRSock, &rfds ); TFD_SET( iWSock, &wfds ); bHasWriteable = true; } } else TFD_SET( iRSock, &rfds ); } // first check to see if any ssl sockets are ready for immediate read // a mini select() type deal for ssl for( unsigned int i = 0; i < this->size(); i++ ) { T *pcSock = (*this)[i]; 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() ) tv.tv_usec = iQuickReset; // this won't be a timeout, 1 ms pause to see if anything else is ready (IE if there is SSL data pending, don't wait too long) if ( bHasWriteable ) iSel = select(FD_SETSIZE, &rfds, &wfds, NULL, &tv); else iSel = select(FD_SETSIZE, &rfds, NULL, NULL, &tv); if ( iSel == 0 ) { if ( mpeSocks.empty() ) m_errno = SELECT_TIMEOUT; else m_errno = SUCCESS; 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; } // find out wich one is ready for( unsigned int i = 0; i < this->size(); i++ ) { T *pcSock = (*this)[i]; int & iRSock = pcSock->GetRSock(); int & iWSock = pcSock->GetWSock(); EMessages iErrno = SUCCESS; if ( ( iRSock < 0 ) || ( iWSock < 0 ) ) { // 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 ( TFD_ISSET( iWSock, &wfds ) ) { 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 ( TFD_ISSET( iRSock, &rfds ) ) { if ( iSel > 0 ) iErrno = SUCCESS; else iErrno = SELECT_ERROR; if ( pcSock->GetType() != T::LISTENER ) SelectSock( mpeSocks, iErrno, pcSock ); else // someone is coming in! { CS_STRING sHost; int port; int inSock = pcSock->Accept( sHost, port ); if ( inSock != -1 ) { if ( T::TMO_ACCEPT & pcSock->GetTimeoutType() ) pcSock->ResetTimer(); // let them now it got dinged // if we have a new sock, then add it T *NewpcSock = (T *)pcSock->GetSockObj( sHost, port ); if ( !NewpcSock ) NewpcSock = new T( sHost, port ); NewpcSock->BlockIO( false ); NewpcSock->SetType( T::INBOUND ); NewpcSock->SetRSock( inSock ); NewpcSock->SetWSock( inSock ); 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->SetRequiresClientCert( pcSock->RequiresClientCert() ); 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() ) { stringstream s; s << sHost << ":" << port; AddSock( NewpcSock, s.str() ); } else AddSock( NewpcSock, NewpcSock->GetSockName() ); } else CS_Delete( NewpcSock ); } } } } } //! internal use only virtual void SelectSock( map & mpeSocks, EMessages eErrno, T * pcSock ) { if ( mpeSocks.find( pcSock ) != mpeSocks.end() ) return; mpeSocks[pcSock] = eErrno; } //! these crons get ran and checked in Loop() virtual void Cron() { for( unsigned int 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(); } } EMessages m_errno; vector m_vcCrons; unsigned long long m_iCallTimeouts; u_int m_iSelectWait; }; //! basic socket class typedef TSocketManager CSocketManager; #ifndef _NO_CSOCKET_NS }; #endif /* _NO_CSOCKET_NS */ #endif /* _HAS_CSOCKET_ */