diff --git a/Buffer.cpp b/Buffer.cpp new file mode 100644 index 00000000..0d140179 --- /dev/null +++ b/Buffer.cpp @@ -0,0 +1,52 @@ +#include "Buffer.h" + +CBufLine::CBufLine(const string& sPre, const string& sPost) { + m_sPre = sPre; + m_sPost = sPost; +} + +CBufLine::~CBufLine() {} + +void CBufLine::GetLine(const string& sTarget, string& sRet) { + sRet = m_sPre + sTarget + m_sPost; +} + +CBuffer::CBuffer(unsigned int uLineCount) { + m_uLineCount = uLineCount; +} + +CBuffer::~CBuffer() {} + +int CBuffer::AddLine(const string& sPre, const string& sPost) { + if (!m_uLineCount) { + return 0; + } + + if (size() >= m_uLineCount) { + erase(begin()); + } + + push_back(CBufLine(sPre, sPost)); + return size(); +} + +bool CBuffer::GetLine(const string& sTarget, string& sRet, unsigned int uIdx) { + if (uIdx >= size()) { + return false; + } + + (*this)[uIdx].GetLine(sTarget, sRet); + return true; +} + +bool CBuffer::GetNextLine(const string& sTarget, string& sRet) { + sRet = ""; + + if (!size()) { + return false; + } + + begin()->GetLine(sTarget, sRet); + erase(begin()); + return true; +} diff --git a/Buffer.h b/Buffer.h new file mode 100644 index 00000000..09ddf271 --- /dev/null +++ b/Buffer.h @@ -0,0 +1,45 @@ +#ifndef _BUFFER_H +#define _BUFFER_H + +#include +#include +using std::vector; +using std::string; + +class CBufLine { +public: + CBufLine(const string& sPre, const string& sPost); + virtual ~CBufLine(); + void GetLine(const string& sTarget, string& sRet); + +private: +protected: + string m_sPre; + string m_sPost; +}; + +class CBuffer : private vector { +public: + CBuffer(unsigned int uLineCount = 100); + virtual ~CBuffer(); + + int AddLine(const string& sPre, const string& sPost); + bool GetNextLine(const string& sTarget, string& sRet); + bool GetLine(const string& sTarget, string& sRet, unsigned int uIdx); + bool IsEmpty() { return empty(); } + void Clear() { clear(); } + + // Setters + void SetLineCount(unsigned int u) { m_uLineCount = u; } + // !Setters + + // Getters + unsigned int GetLineCount() const { return m_uLineCount; } + // !Getters +private: +protected: + unsigned int m_uLineCount; +}; + +#endif // !_BUFFER_H + diff --git a/Chan.cpp b/Chan.cpp new file mode 100644 index 00000000..d22fbdad --- /dev/null +++ b/Chan.cpp @@ -0,0 +1,311 @@ +#include "Chan.h" +#include "znc.h" +#include "User.h" +#include "Utils.h" + +void CChan::Reset() { + m_bWhoDone = false; + m_bIsOn = false; + m_uOpCount = 0; + m_uVoiceCount = 0; + m_uModes = 0; + m_uLimit = 0; + m_uClientRequests = 0; + ClearNicks(); +} + +void CChan::Joined() { +} + +void CChan::Cycle() const { + m_pUser->PutIRC("PART " + GetName() + "\r\nJOIN " + GetName() + " " + GetKey()); +} + +string CChan::GetModeString() const { + string sRet; + + if (m_uModes & Secret) { sRet += "s"; } + if (m_uModes & Private) { sRet += "p"; } + if (m_uModes & OpTopic) { sRet += "t"; } + if (m_uModes & InviteOnly) { sRet += "i"; } + if (m_uModes & NoMessages) { sRet += "n"; } + if (m_uModes & Moderated) { sRet += "m"; } + if (m_uLimit) { sRet += "l"; } + if (m_uModes & Key) { sRet += "k"; } + + return (sRet.empty()) ? sRet : ("+" + sRet); +} + +void CChan::SetModes(const string& sModes) { + m_uModes = 0; + m_uLimit = 0; + m_sKey = ""; + ModeChange(sModes); +} + +void CChan::IncClientRequests() { + m_uClientRequests++; +} + +bool CChan::DecClientRequests() { + if (!m_uClientRequests) { + return false; + } + + m_uClientRequests--; + return true; +} + +bool CChan::Who() { + if (m_bWhoDone) { + return false; + } + + m_pUser->PutIRC("WHO " + GetName()); + return true; +} + +void CChan::OnWho(const string& sNick, const string& sIdent, const string& sHost) { + CNick* pNick = FindNick(sNick); + + if (pNick) { + pNick->SetIdent(sIdent); + pNick->SetHost(sHost); + } +} + +void CChan::ModeChange(const string& sModes, const string& sOpNick) { + string sModeArg = CUtils::Token(sModes, 0); + string sArgs = CUtils::Token(sModes, 1, true); + bool bAdd = true; + +#ifdef _MODULES + CNick* pNick = FindNick(sOpNick); + if (pNick) { + m_pUser->GetModules().OnRawMode(*pNick, *this, sModeArg, sArgs); + } +#endif + + for (unsigned int a = 0; a < sModeArg.size(); a++) { + switch (sModeArg[a]) { + case '+': bAdd = true; break; + case '-': bAdd = false; break; + case 's': if (bAdd) { m_uModes |= Secret; } else { m_uModes &= ~Secret; } break; + case 'p': if (bAdd) { m_uModes |= Private; } else { m_uModes &= ~Private; } break; + case 'm': if (bAdd) { m_uModes |= Moderated; } else { m_uModes &= ~Moderated; } break; + case 'i': if (bAdd) { m_uModes |= InviteOnly; } else { m_uModes &= ~InviteOnly; } break; + case 'n': if (bAdd) { m_uModes |= NoMessages; } else { m_uModes &= ~NoMessages; } break; + case 't': if (bAdd) { m_uModes |= OpTopic; } else { m_uModes &= ~OpTopic; } break; + case 'l': if (bAdd) { m_uLimit = strtoul(GetModeArg(sArgs).c_str(), NULL, 10); } else { m_uLimit = 0; } break; + case 'k': if (bAdd) { m_uModes |= Key; SetKey(GetModeArg(sArgs)); } else { m_uModes &= ~Key; GetModeArg(sArgs); } break; + case 'o': OnOp(sOpNick, GetModeArg(sArgs), bAdd); break; + case 'v': OnVoice(sOpNick, GetModeArg(sArgs), bAdd); break; + case 'b': // Don't do anything with bans yet + case 'e': // Don't do anything with excepts yet + default: GetModeArg(sArgs); // Pop off an arg, assume new modes will have an argument + } + } +} + +string CChan::GetModeArg(string& sArgs) const { + string sRet = sArgs.substr(0, sArgs.find(' ')); + sArgs = (sRet.size() < sArgs.size()) ? sArgs.substr(sRet.size() +1) : ""; + return sRet; +} + +void CChan::ClearNicks() { + for (map::iterator a = m_msNicks.begin(); a != m_msNicks.end(); a++) { + delete a->second; + } + + m_msNicks.clear(); +} + +int CChan::AddNicks(const string& sNicks) { + if (IsOn()) { + return 0; + } + + int iRet = 0; + string sCurNick; + + for (unsigned int a = 0; a < sNicks.size(); a++) { + switch (sNicks[a]) { + case ' ': + if (AddNick(sCurNick)) { + iRet++; + } + + sCurNick = ""; + + break; + default: + sCurNick += sNicks[a]; + break; + } + } + + if (!sCurNick.empty()) { + if (AddNick(sCurNick)) { + iRet++; + } + } + + return iRet; +} + +bool CChan::AddNick(const string& sNick) { + const char* p = sNick.c_str(); + bool bIsOp = false; + bool bIsVoice = false; + + switch (*p) { + case '\0': + return false; + case '@': + bIsOp = true; + case '+': + if (!*++p) { + return false; + } + + bIsVoice = !bIsOp; + } + + CNick* pNick = FindNick(p); + if (!pNick) { + pNick = new CNick(p); + } + + if ((bIsOp) && (!pNick->IsOp())) { + IncOpCount(); + pNick->SetOp(true); + } else if ((bIsVoice) && (!pNick->IsVoice())) { + IncVoiceCount(); + pNick->SetVoice(true); + } + + m_msNicks[pNick->GetNick()] = pNick; + + return true; +} + +bool CChan::RemNick(const string& sNick) { + map::iterator it = m_msNicks.find(sNick); + if (it == m_msNicks.end()) { + return false; + } + + if (it->second->IsOp()) { + DecOpCount(); + } + + if (it->second->IsVoice()) { + DecVoiceCount(); + } + + delete it->second; + m_msNicks.erase(it); + + if ((m_msNicks.size() == 1) && (!m_msNicks.begin()->second->IsOp())) { + Cycle(); + } + + return true; +} + +bool CChan::ChangeNick(const string& sOldNick, const string& sNewNick) { + map::iterator it = m_msNicks.find(sOldNick); + + if (it == m_msNicks.end()) { + return false; + } + + // Rename this nick + it->second->SetNick(sNewNick); + + // Insert a new element into the map then erase the old one, do this to change the key + m_msNicks[sNewNick] = it->second; + m_msNicks.erase(it); + + return true; +} + +void CChan::OnOp(const string& sOpNick, const string& sNick, bool bOpped) { + CNick* pNick = FindNick(sNick); + + if (pNick) { + bool bNoChange = (pNick->IsOp() == bOpped); +#ifdef _MODULES + CNick* pOpNick = FindNick(sOpNick); + + if (pOpNick) { + if (bOpped) { + m_pUser->GetModules().OnOp(*pOpNick, *pNick, *this, bNoChange); + } else { + m_pUser->GetModules().OnDeop(*pOpNick, *pNick, *this, bNoChange); + } + } +#endif + + if (bNoChange) { + // If no change, return + return; + } + + pNick->SetOp(bOpped); + (bOpped) ? IncOpCount() : DecOpCount(); + } +} + +void CChan::OnVoice(const string& sOpNick, const string& sNick, bool bVoiced) { + CNick* pNick = FindNick(sNick); + + if (pNick) { + bool bNoChange = (pNick->IsVoice() == bVoiced); + +#ifdef _MODULES + CNick* pOpNick = FindNick(sOpNick); + + if (pOpNick) { + if (bVoiced) { + m_pUser->GetModules().OnVoice(*pOpNick, *pNick, *this, bNoChange); + } else { + m_pUser->GetModules().OnDevoice(*pOpNick, *pNick, *this, bNoChange); + } + } +#endif + + if (bNoChange) { + // If no change, return + return; + } + + pNick->SetVoice(bVoiced); + (bVoiced) ? IncVoiceCount() : DecVoiceCount(); + } +} + +CNick* CChan::FindNick(const string& sNick) const { + map::const_iterator it = m_msNicks.find(sNick); + return (it != m_msNicks.end()) ? it->second : NULL; +} + +int CChan::AddBuffer(const string& sLine) { + // Todo: revisit the buffering + if (!m_uBufferCount) { + return 0; + } + + if (m_vsBuffer.size() >= m_uBufferCount) { + m_vsBuffer.erase(m_vsBuffer.begin()); + } + + m_vsBuffer.push_back(sLine); + return m_vsBuffer.size(); +} + +void CChan::ClearBuffer() { + m_vsBuffer.clear(); +} + diff --git a/Chan.h b/Chan.h new file mode 100644 index 00000000..3f1beba6 --- /dev/null +++ b/Chan.h @@ -0,0 +1,123 @@ +#ifndef _CHAN_H +#define _CHAN_H + +#include "Nick.h" +#include +#include +using std::vector; +using std::map; + +// Forward Declarations +class CUser; +// !Forward Declarations + +class CChan { +public: + typedef enum { + Private = 1 << 0, + Secret = 1 << 1, + NoMessages = 1 << 2, + Moderated = 1 << 3, + OpTopic = 1 << 4, + InviteOnly = 1 << 5, + Key = 1 << 6 + } EMode; + + CChan(const string& sName, CUser* pUser, unsigned int uBufferCount = 0) { + m_sName = sName; + m_pUser = pUser; + m_bKeepBuffer = false; + m_uBufferCount = uBufferCount; + Reset(); + } + virtual ~CChan() { + ClearNicks(); + } + + void Reset(); + void Joined(); + void Cycle() const; + + void IncClientRequests(); + bool DecClientRequests(); + + bool Who(); + void OnWho(const string& sNick, const string& sIdent, const string& sHost); + + // Modes + void SetModes(const string& s); + void ModeChange(const string& sModes, const string& sNick = ""); + void OnOp(const string& sOpNick, const string& sNick, bool bOpped); + void OnVoice(const string& sOpNick, const string& sNick, bool bVoiced); + string GetModeString() const; + string GetModeArg(string& sArgs) const; + // !Modes + + // Nicks + void ClearNicks(); + CNick* FindNick(const string& sNick) const; + int AddNicks(const string& sNicks); + bool AddNick(const string& sNick); + bool RemNick(const string& sNick); + bool ChangeNick(const string& sOldNick, const string& sNewNick); + // !Nicks + + // Buffer + int AddBuffer(const string& sLine); + void ClearBuffer(); + // !Buffer + + // Setters + void SetIsOn(bool b) { m_bIsOn = b; if (!b) { Reset(); } else { Joined(); } } + void SetKey(const string& s) { m_sKey = s; } + void SetTopic(const string& s) { m_sTopic = s; } + void SetDefaultModes(const string& s) { m_sDefaultModes = s; } + void IncOpCount() { m_uOpCount++; } + void DecOpCount() { m_uOpCount -= (m_uOpCount > 0); } + void IncVoiceCount() { m_uVoiceCount++; } + void DecVoiceCount() { m_uVoiceCount -= (m_uVoiceCount > 0); } + void SetBufferCount(unsigned int u) { m_uBufferCount = u; } + void SetKeepBuffer(bool b) { m_bKeepBuffer = b; } + void SetWhoDone(bool b = true) { m_bWhoDone = b; } + // !Setters + + // Getters + const bool IsOn() const { return m_bIsOn; } + const string& GetName() const { return m_sName; } + unsigned int GetModes() const { return m_uModes; } + const string& GetKey() const { return m_sKey; } + unsigned int GetLimit() const { return m_uLimit; } + const string& GetTopic() const { return m_sTopic; } + const string& GetDefaultModes() const { return m_sDefaultModes; } + const vector& GetBans() const { return m_vsBans; } + const vector& GetBuffer() const { return m_vsBuffer; } + const map& GetNicks() const { return m_msNicks; } + unsigned int GetNickCount() const { return m_msNicks.size(); } + unsigned int GetOpCount() const { return m_uOpCount; } + unsigned int GetVoiceCount() const { return m_uVoiceCount; } + unsigned int GetBufferCount() const { return m_uBufferCount; } + bool HasMode(EMode eMode) const { return (m_uModes & eMode); } + bool KeepBuffer() const { return m_bKeepBuffer; } + // !Getters +private: +protected: + bool m_bIsOn; + bool m_bWhoDone; + bool m_bKeepBuffer; + string m_sName; + string m_sKey; + string m_sTopic; + CUser* m_pUser; + unsigned int m_uLimit; + unsigned int m_uModes; + string m_sDefaultModes; + vector m_vsBans; + map m_msNicks; // Todo: make this caseless (irc style) + unsigned int m_uBufferCount; + unsigned int m_uOpCount; + unsigned int m_uVoiceCount; + unsigned int m_uClientRequests; // Used to tell how many times a client tried to join this chan + vector m_vsBuffer; +}; + +#endif // !_CHAN_H diff --git a/CreatePem.sh b/CreatePem.sh new file mode 100755 index 00000000..145ad11d --- /dev/null +++ b/CreatePem.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +if [ "$1" = "" ]; then + echo CreatePem.sh file.pem + exit 1 +fi + +log () { + echo [`date`] $* +} + +log Creating Random File for key seed +dd if=/dev/urandom of=blah-1234.txt bs=1024k count=10 >/dev/null 2>&1 + +openssl genrsa -rand blah-1234.txt -out ${1}.key +openssl req -new -key ${1}.key -out ${1}.csr +openssl x509 -req -days 365 -in ${1}.csr -signkey ${1}.key -out ${1}.crt + +log Cleaning up +rm -f blah-1234.txt ${1}.csr +log done + +cat ${1}.key > $1 +cat ${1}.crt >> $1 + +rm ${1}.key ${1}.crt + +echo "Created $1" diff --git a/Csocket.h b/Csocket.h new file mode 100644 index 00000000..66050ab1 --- /dev/null +++ b/Csocket.h @@ -0,0 +1,2545 @@ +/** +* +* 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 +#endif /* HAVE_LIBSSL */ + +#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; } } + + // 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 ); + } + + inline void __Perror( const CS_STRING & s ) + { + char buff[512]; + memset( (char *)buff, '\0', 512 ); + if ( strerror_r( errno, buff, 511 ) == 0 ) + CS_DEBUG( s << ": " << buff ); + else + CS_DEBUG( s << ": Unknown Error Occured" ); + } + inline unsigned long long millitime() + { + struct timeval tv; + unsigned long long iTime = 0; + gettimeofday( &tv, NULL ); + iTime = (unsigned long long )tv.tv_sec * 1000; + iTime += ( (unsigned long long)tv.tv_usec / 1000 ); + return( iTime ); + } + +#ifdef HAVE_LIBSSL + inline void SSLErrors() + { + unsigned long iSSLError = 0; + while( ( iSSLError = ERR_get_error() ) != 0 ) + { + char szError[512]; + memset( (char *) szError, '\0', 512 ); + ERR_error_string_n( iSSLError, szError, 511 ); + if ( strlen( szError ) > 0 ) + CS_DEBUG( szError ); + } + } +#endif /* HAVE_LIBSSL */ + + inline bool GetHostByName( const CS_STRING & sHostName, struct in_addr *paddr ) + { + bool bRet = false; + struct hostent *hent = NULL; +#ifdef __linux__ + char hbuff[2048]; + struct hostent hentbuff; + + int err; + for( u_int a = 0; a < 20; a++ ) + { + memset( (char *)hbuff, '\0', 2048 ); + int iRet = gethostbyname_r( sHostName.c_str(), &hentbuff, hbuff, 2048, &hent, &err ); + + if ( iRet == 0 ) + { + bRet = true; + break; + } + + if ( iRet != TRY_AGAIN ) + break; + + PERROR( "gethostbyname_r" ); + } + + if ( !hent ) + bRet = false; +#else + hent = gethostbyname( sHostName.c_str() ); + PERROR( "gethostbyname" ); + + if ( hent ) + bRet = true; + +#endif /* __linux__ */ + + if ( bRet ) + bcopy( hent->h_addr_list[0], &paddr->s_addr, 4 ); + + return( bRet ); + } + + /** + * @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() + { + m_iCycles = 0; + m_iMaxCycles = 0; + m_bActive = true; + m_iTime = 0; + m_iTimeSequence = 60; + m_bPause = false; + } + virtual ~CCron() {} + + //! This is used by the Job Manager, and not you directly + void run() + { + if ( m_bPause ) + return; + + if ( ( m_bActive ) && ( time( NULL ) >= m_iTime ) ) + { + + RunJob(); + + if ( ( m_iMaxCycles > 0 ) && ( ++m_iCycles >= m_iMaxCycles ) ) + m_bActive = false; + else + m_iTime = time( NULL ) + m_iTimeSequence; + } + } + + /** + * @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 ) + { + m_iTimeSequence = TimeSequence; + m_iTime = time( NULL ) + m_iTimeSequence; + m_iMaxCycles = iMaxCycles; + } + + //! starts and runs infinity amount of times + void Start( int TimeSequence ) + { + m_iTimeSequence = TimeSequence; + m_iTime = time( NULL ) + m_iTimeSequence; + m_iMaxCycles = 0; + } + + //! call this to turn off your cron, it will be removed + void Stop() + { + m_bActive = false; + } + + //! pauses excution of your code in RunJob + void Pause() + { + m_bPause = true; + } + + //! removes the pause on RunJon + void UnPause() + { + m_bPause = false; + } + + int GetInterval() const { return( m_iTimeSequence ); } + u_int GetMaxCycles() const { return( m_iMaxCycles ); } + u_int GetCyclesLeft() const { return( ( m_iMaxCycles > m_iCycles ? ( m_iMaxCycles - m_iCycles ) : 0 ) ); } + + //! returns true if cron is active + bool isValid() { return( m_bActive ); } + + const CS_STRING & GetName() const { return( m_sName ); } + void SetName( const CS_STRING & sName ) { m_sName = sName; } + + protected: + + //! this is the method you should override + virtual void RunJob() { CS_DEBUG( "This should be overriden" ); } + + 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 ) + { + Init( "", 0, itimeout ); + } + /** + * Advanced constructor, for creating a simple connection + * + * sHostname the hostname your are connecting to + * iport the port you are connectint to + * itimeout how long to wait before ditching the connection, default is 60 seconds + */ + Csock( const CS_STRING & sHostname, int iport, int itimeout = 60 ) + { + Init( sHostname, iport, itimeout ); + } + + //! override this for accept sockets + //! returning NULL will cause it to return a new instance of + //! type T + virtual Csock *GetSockObj( const CS_STRING & sHostname, int iPort ) + { + return( NULL ); + } + + virtual ~Csock() + { + if ( m_iReadSock != m_iWriteSock ) + { + close( m_iReadSock ); + close( m_iWriteSock ); + } else + close( m_iReadSock ); + + m_iReadSock = -1; + m_iWriteSock = -1; + +#ifdef HAVE_LIBSSL + FREE_SSL(); + FREE_CTX(); + +#endif /* HAVE_LIBSSL */ + // delete any left over crons + for( unsigned int i = 0; i < m_vcCrons.size(); i++ ) + CS_Delete( m_vcCrons[i] ); + } + + 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 + + }; + + 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 ) + { + Write( s ); + return( *this ); + } + + Csock & operator<<( ostream & ( *io )( ostream & ) ) + { + Write( "\r\n" ); + return( *this ); + } + + Csock & operator<<( int i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( unsigned int i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( long i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( unsigned long i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( unsigned long long i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( float i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + Csock & operator<<( double i ) + { + stringstream s; + s << i; + Write( s.str() ); + return( *this ); + } + + /** + * Create the connection + * + * \param sBindHost the ip you want to bind to locally + * \return true on success + */ + virtual bool Connect( const CS_STRING & sBindHost = "" ) + { + // create the socket + m_iReadSock = m_iWriteSock = SOCKET(); + + if ( m_iReadSock == -1 ) + return( false ); + + m_address.sin_family = PF_INET; + m_address.sin_port = htons( m_iport ); + + if ( !GetHostByName( m_shostname, &(m_address.sin_addr) ) ) + return( false ); + + // bind to a hostname if requested + if ( !sBindHost.empty() ) + { + struct sockaddr_in vh; + + vh.sin_family = PF_INET; + vh.sin_port = htons( 0 ); + + if ( !GetHostByName( sBindHost, &(vh.sin_addr) ) ) + return( false ); + + // try to bind 3 times, otherwise exit failure + bool bBound = false; + for( int a = 0; a < 3; a++ ) + { + if ( bind( m_iReadSock, (struct sockaddr *) &vh, sizeof( vh ) ) == 0 ) + { + bBound = true; + break; + } + usleep( 5000 ); // quick pause, common lets BIND!)(!*! + } + + if ( !bBound ) + { + CS_DEBUG( "Failure to bind to " << sBindHost ); + return( false ); + } + } + + // set it none blocking + int fdflags = fcntl (m_iReadSock, F_GETFL, 0); + fcntl( m_iReadSock, F_SETFL, fdflags|O_NONBLOCK ); + m_iConnType = OUTBOUND; + + // connect + int ret = connect( m_iReadSock, (struct sockaddr *)&m_address, sizeof( m_address ) ); + if ( ( ret == -1 ) && ( errno != EINPROGRESS ) ) + { + CS_DEBUG( "Connect Failed. ERRNO [" << errno << "] FD [" << m_iReadSock << "]" ); + return( false ); + } + + if ( m_bBLOCK ) + { + // unset the flags afterwords, rather than have connect block + int fdflags = fcntl (m_iReadSock, F_GETFL, 0); + fdflags &= ~O_NONBLOCK; + fcntl( m_iReadSock, F_SETFL, fdflags ); + + } + + return( true ); + } + + /** + * WriteSelect on this socket + * Only good if JUST using this socket, otherwise use the TSocketManager + */ + virtual int WriteSelect() + { + struct timeval tv; + fd_set wfds; + + TFD_ZERO( &wfds ); + TFD_SET( m_iWriteSock, &wfds ); + + tv.tv_sec = m_itimeout; + tv.tv_usec = 0; + + int ret = select( FD_SETSIZE, NULL, &wfds, NULL, &tv ); + + if ( ret == 0 ) + return( SEL_TIMEOUT ); + + if ( ret == -1 ) + { + if ( errno == EINTR ) + return( SEL_EAGAIN ); + else + return( SEL_ERR ); + } + + return( SEL_OK ); + } + + /** + * ReadSelect on this socket + * Only good if JUST using this socket, otherwise use the TSocketManager + */ + virtual int ReadSelect() + { + struct timeval tv; + fd_set rfds; + + TFD_ZERO( &rfds ); + TFD_SET( m_iReadSock, &rfds ); + + tv.tv_sec = m_itimeout; + tv.tv_usec = 0; + + int ret = select( FD_SETSIZE, &rfds, NULL, NULL, &tv ); + + if ( ret == 0 ) + return( SEL_TIMEOUT ); + + if ( ret == -1 ) + { + if ( errno == EINTR ) + return( SEL_EAGAIN ); + else + return( SEL_ERR ); + } + + return( SEL_OK ); + } + + /** + * 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 ) + { + m_iReadSock = m_iWriteSock = SOCKET( true ); + m_iConnType = LISTENER; + m_itimeout = iTimeout; + + if ( m_iReadSock == -1 ) + return( false ); + + m_address.sin_family = PF_INET; + if ( sBindHost.empty() ) + m_address.sin_addr.s_addr = htonl( INADDR_ANY ); + else + { + if ( !GetHostByName( sBindHost, &(m_address.sin_addr) ) ) + return( false ); + } + m_address.sin_port = htons( iPort ); + bzero(&(m_address.sin_zero), 8); + + if ( bind( m_iReadSock, (struct sockaddr *) &m_address, sizeof( m_address ) ) == -1 ) + return( false ); + + if ( listen( m_iReadSock, iMaxConns ) == -1 ) + return( false ); + + if ( !m_bBLOCK ) + { + // set it none blocking + int fdflags = fcntl ( m_iReadSock, F_GETFL, 0); + fcntl( m_iReadSock, F_SETFL, fdflags|O_NONBLOCK ); + } + + return( true ); + } + + //! Accept an inbound connection, this is used internally + virtual int Accept( CS_STRING & sHost, int & iRPort ) + { + struct sockaddr_in client; + socklen_t clen = sizeof(struct sockaddr); + + int iSock = accept( m_iReadSock , (struct sockaddr *) &client, &clen ); + if ( iSock != -1 ) + { + if ( !m_bBLOCK ) + { + // make it none blocking + int fdflags = fcntl (iSock, F_GETFL, 0); + fcntl( iSock, F_SETFL, fdflags|O_NONBLOCK ); + } + + getpeername( iSock, (struct sockaddr *) &client, &clen ); + + sHost = inet_ntoa( client.sin_addr ); + iRPort = ntohs( client.sin_port ); + + if ( !ConnectionFrom( sHost, iRPort ) ) + { + close( iSock ); + iSock = -1; + } + + } + + return( iSock ); + } + + //! Accept an inbound SSL connection, this is used internally and called after Accept + virtual bool AcceptSSL() + { +#ifdef HAVE_LIBSSL + if ( !m_ssl ) + if ( !SSLServerSetup() ) + return( false ); + + + int err = SSL_accept( m_ssl ); + + if ( err == 1 ) + { + m_bFullsslAccept = true; + return( true ); + } + + m_bFullsslAccept = false; + + int sslErr = SSL_get_error( m_ssl, err ); + + if ( ( sslErr == SSL_ERROR_WANT_READ ) || ( sslErr == SSL_ERROR_WANT_WRITE ) ) + return( true ); + + SSLErrors(); + +#endif /* HAVE_LIBSSL */ + + return( false ); + } + + //! This sets up the SSL Client, this is used internally + virtual bool SSLClientSetup() + { +#ifdef HAVE_LIBSSL + m_bssl = true; + FREE_SSL(); + FREE_CTX(); + + SSLeay_add_ssl_algorithms(); + + switch( m_iMethod ) + { + case SSL2: + m_ssl_method = SSLv2_client_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv2_client_method failed!" ); + return( false ); + } + break; + + case SSL3: + m_ssl_method = SSLv3_client_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv3_client_method failed!" ); + return( false ); + } + break; + + case SSL23: + default: + m_ssl_method = SSLv23_client_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv23_client_method failed!" ); + return( false ); + } + break; + } + + SSL_load_error_strings (); + // wrap some warnings in here + m_ssl_ctx = SSL_CTX_new ( m_ssl_method ); + if ( !m_ssl_ctx ) + return( false ); + + 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(); + } + + 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(); + } + } + + m_ssl = SSL_new ( m_ssl_ctx ); + if ( !m_ssl ) + return( false ); + + SSL_set_rfd( m_ssl, m_iReadSock ); + SSL_set_wfd( m_ssl, m_iWriteSock ); + SSL_set_verify( m_ssl, SSL_VERIFY_PEER, CertVerifyCB ); + + return( true ); +#else + return( false ); + +#endif /* HAVE_LIBSSL */ + } + + //! This sets up the SSL Server, this is used internally + virtual bool SSLServerSetup() + { +#ifdef HAVE_LIBSSL + m_bssl = true; + FREE_SSL(); + FREE_CTX(); + + SSLeay_add_ssl_algorithms(); + + switch( m_iMethod ) + { + case SSL2: + m_ssl_method = SSLv2_server_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv2_server_method failed!" ); + return( false ); + } + break; + + case SSL3: + m_ssl_method = SSLv3_server_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv3_server_method failed!" ); + return( false ); + } + break; + + case SSL23: + default: + m_ssl_method = SSLv23_server_method(); + if ( !m_ssl_method ) + { + CS_DEBUG( "WARNING: MakeConnection .... SSLv23_server_method failed!" ); + return( false ); + } + break; + } + + SSL_load_error_strings (); + // wrap some warnings in here + m_ssl_ctx = SSL_CTX_new ( m_ssl_method ); + if ( !m_ssl_ctx ) + return( false ); + + // 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_file( m_ssl_ctx, m_sPemFile.c_str() , SSL_FILETYPE_PEM ) <= 0 ) + { + CS_DEBUG( "Error with PEM file [" << m_sPemFile << "]" ); + SSLErrors(); + 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(); + return( false ); + } + + 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, m_iReadSock ); + SSL_set_wfd( m_ssl, m_iWriteSock ); + SSL_set_accept_state( m_ssl ); + + if ( m_bRequireClientCert ) + SSL_set_verify( m_ssl, SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_PEER, CertVerifyCB ); + + return( true ); +#else + return( false ); +#endif /* HAVE_LIBSSL */ + } + + /** + * 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 = "" ) + { +#ifdef HAVE_LIBSSL + if ( m_iReadSock == -1 ) + if ( !Connect( sBindhost ) ) + return( false ); + + if ( !m_ssl ) + if ( !SSLClientSetup() ) + return( false ); + + bool bPass = true; + + if ( m_bBLOCK ) + { + int fdflags = fcntl ( m_iReadSock, F_GETFL, 0); + fcntl( m_iReadSock, F_SETFL, fdflags|O_NONBLOCK ); + } + + int iErr = SSL_connect( m_ssl ); + if ( iErr != 1 ) + { + int sslErr = SSL_get_error( m_ssl, iErr ); + + if ( ( sslErr != SSL_ERROR_WANT_READ ) && ( sslErr != SSL_ERROR_WANT_WRITE ) ) + bPass = false; + } else + bPass = true; + + if ( m_bBLOCK ) + { + // unset the flags afterwords, rather then have connect block + int fdflags = fcntl (m_iReadSock, F_GETFL, 0); + fdflags &= ~O_NONBLOCK; + fcntl( m_iReadSock, F_SETFL, fdflags ); + + } + + return( bPass ); +#else + return( false ); +#endif /* HAVE_LIBSSL */ + } + + + /** + * 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 ) + { + m_sSend.append( data, len ); + + if ( m_sSend.empty() ) + return( true ); + + if ( m_bBLOCK ) + { + if ( WriteSelect() != SEL_OK ) + return( false ); + + } + // rate shaping + u_int iBytesToSend = 0; + + 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_bssl ) + { + + 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(), m_sSSLBuffer.length() ); + + if ( ( iErr < 0 ) && ( errno == 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(); + 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) + ResetTimer(); + m_iBytesWritten += (unsigned long long)iErr; + } + + return( true ); + } +#endif /* HAVE_LIBSSL */ + int bytes = write( m_iWriteSock, m_sSend.data(), iBytesToSend ); + + if ( ( bytes == -1 ) && ( errno == ECONNREFUSED ) ) + { + ConnectionRefused(); + return( false ); + } + + if ( ( bytes <= 0 ) && ( errno != EAGAIN ) ) + return( false ); + + // delete the bytes we sent + if ( bytes > 0 ) + { + m_sSend.erase( 0, bytes ); + ResetTimer(); // reset the timer on successful write + m_iBytesWritten += (unsigned long long)bytes; + } + + return( true ); + } + + /** + * convience function + * @see Write( const char *, int ) + */ + virtual bool Write( const CS_STRING & sData ) + { + return( Write( sData.c_str(), sData.length() ) ); + } + + /** + * 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 + * Otherwise returns the bytes read into data + */ + virtual int Read( char *data, int len ) + { + + if ( m_bBLOCK ) + { + if ( ReadSelect() != SEL_OK ) + return( READ_ERR ); + } + + int bytes = 0; + memset( (char *)data, '\0', len ); + +#ifdef HAVE_LIBSSL + if ( m_bssl ) + bytes = SSL_read( m_ssl, data, len ); + else +#endif /* HAVE_LIBSSL */ + bytes = read( m_iReadSock, data, len ); + + if ( bytes == -1 ) + { + if ( errno == ECONNREFUSED ) + return( READ_CONNREFUSED ); + + if ( ( errno == EINTR ) || ( errno == EAGAIN ) ) + return( READ_EAGAIN ); +#ifdef HAVE_LIBSSL + if ( m_bssl ) + { + int iErr = SSL_get_error( m_ssl, 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 */ + } + + m_iBytesRead += (unsigned long long)bytes; + + return( bytes ); + } + + CS_STRING GetLocalIP() + { + if ( !m_sLocalIP.empty() ) + return( m_sLocalIP ); + + int iSock = GetSock(); + + if ( iSock < 0 ) + return( "" ); + + struct sockaddr_in mLocalAddr; + socklen_t mLocalLen = sizeof(struct sockaddr); + if ( getsockname( iSock, (struct sockaddr *) &mLocalAddr, &mLocalLen ) == 0 ) + m_sLocalIP = inet_ntoa( mLocalAddr.sin_addr ); + + return( m_sLocalIP ); + } + + CS_STRING GetRemoteIP() + { + if ( !m_sRemoteIP.empty() ) + return( m_sRemoteIP ); + + int iSock = GetSock(); + + if ( iSock <= 0 ) + { + cerr << "What the hell is wrong with my fd!?" << endl; + return( "" ); + } + + struct sockaddr_in mRemoteAddr; + socklen_t mRemoteLen = sizeof(struct sockaddr); + if ( getpeername( iSock, (struct sockaddr *) &mRemoteAddr, &mRemoteLen ) == 0 ) + m_sRemoteIP = inet_ntoa( mRemoteAddr.sin_addr ); + + return( m_sRemoteIP ); + } + + //! Tells you if the socket is connected + virtual bool IsConnected() { return( m_bIsConnected ); } + //! Sets the sock, telling it its connected (internal use only) + virtual void SetIsConnected( bool b ) { m_bIsConnected = b; } + + //! returns a reference to the sock + int & GetRSock() { return( m_iReadSock ); } + void SetRSock( int iSock ) { m_iReadSock = iSock; } + int & GetWSock() { return( m_iWriteSock ); } + void SetWSock( int iSock ) { m_iWriteSock = iSock; } + + void SetSock( int iSock ) { m_iWriteSock = iSock; m_iReadSock = iSock; } + int & GetSock() { return( m_iReadSock ); } + + //! resets the time counter + void ResetTimer() { m_iTcount = 0; } + + /** + * 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 + */ + void SetTimeout( int iTimeout ) { m_itimeout = iTimeout; } + int GetTimeout() const { return m_itimeout; } + + //! returns true if the socket has timed out + virtual bool CheckTimeout() + { + if ( m_itimeout > 0 ) + { + if ( m_iTcount >= m_itimeout ) + { + Timeout(); + return( true ); + } + + m_iTcount++; + } + return( false ); + } + + /** + * pushes data up on the buffer, if a line is ready + * it calls the ReadLine event + */ + virtual void PushBuff( const char *data, int len ) + { + if ( !m_bEnableReadLine ) + return; // If the ReadLine event is disabled, just ditch here + + if ( data ) + m_sbuffer.append( data, len ); + + while( true ) + { + u_int iFind = m_sbuffer.find( "\n" ); + + 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 ); + + } else + break; + } + + if ( ( m_iMaxStoredBufferLength > 0 ) && ( m_sbuffer.length() > m_iMaxStoredBufferLength ) ) + ReachedMaxBuffer(); // call the max read buffer event + + } + + //! 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() { return( m_sbuffer ); } + void SetMaxBufferThreshold( u_int iThreshold ) { m_iMaxStoredBufferLength = iThreshold; } + u_int GetMaxBufferThreshold() { return( m_iMaxStoredBufferLength ); } + + //! Returns the connection type from enum eConnType + int GetType() { return( m_iConnType ); } + void SetType( int iType ) { m_iConnType = iType; } + + //! Returns a reference to the socket name + const CS_STRING & GetSockName() { return( m_sSockName ); } + void SetSockName( const CS_STRING & sName ) { m_sSockName = sName; } + + //! Returns a reference to the host name + const CS_STRING & GetHostName() { return( m_shostname ); } + void SetHostName( const CS_STRING & sHostname ) { m_shostname = sHostname; } + + + //! Gets the starting time of this socket + unsigned long long GetStartTime() const { return( m_iStartTime ); } + //! Resets the start time + void ResetStartTime() { m_iStartTime = 0; } + + //! Gets the amount of data read during the existence of the socket + unsigned long long GetBytesRead() const { return( m_iBytesRead ); } + void ResetBytesRead() { m_iBytesRead = 0; } + + //! Gets the amount of data written during the existence of the socket + unsigned long long GetBytesWritten() const { return( m_iBytesWritten ); } + void ResetBytesWritten() { m_iBytesWritten = 0; } + + + //! Get Avg Read Speed in sample milliseconds (default is 1000 milliseconds or 1 second) + double GetAvgRead( unsigned long long iSample = 1000 ) + { + 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 ) ) ); + } + + //! Get Avg Write Speed in sample milliseconds (default is 1000 milliseconds or 1 second) + double GetAvgWrite( unsigned long long iSample = 1000 ) + { + unsigned long long iDifference = ( millitime() - m_iStartTime ); + + if ( ( m_iBytesWritten == 0 ) || ( iSample > iDifference ) ) + return( (double)m_iBytesRead ); + + return( ( (double)m_iBytesWritten / ( (double)iDifference / (double)iSample ) ) ); + } + + + //! Returns the remote port + int GetRemotePort() + { + if ( m_iRemotePort > 0 ) + return( m_iRemotePort ); + + int iSock = GetSock(); + + if ( iSock >= 0 ) + { + struct sockaddr_in mAddr; + socklen_t mLen = sizeof(struct sockaddr); + if ( getpeername( iSock, (struct sockaddr *) &mAddr, &mLen ) == 0 ) + m_iRemotePort = ntohs( mAddr.sin_port ); + } + + return( m_iRemotePort ); + } + + //! Returns the local port + int GetLocalPort() + { + if ( m_iLocalPort > 0 ) + return( m_iLocalPort ); + + int iSock = GetSock(); + + if ( iSock >= 0 ) + { + struct sockaddr_in mLocalAddr; + socklen_t mLocalLen = sizeof(struct sockaddr); + if ( getsockname( iSock, (struct sockaddr *) &mLocalAddr, &mLocalLen ) == 0 ) + m_iLocalPort = ntohs( mLocalAddr.sin_port ); + } + + return( m_iLocalPort ); + } + + //! Returns the port + int GetPort() { return( m_iport ); } + void SetPort( int iPort ) { m_iport = iPort; } + + //! just mark us as closed, the parent can pick it up + void Close() { m_bClosed = true; } + //! returns true if the socket is closed + bool isClosed() { return( m_bClosed ); } + + //! Set rather to NON Blocking IO on this socket, default is true + void BlockIO( bool bBLOCK ) { m_bBLOCK = bBLOCK; } + + //! Use this to change your fd's to blocking or none blocking + void NonBlockingIO() + { + int fdflags = fcntl ( m_iReadSock, F_GETFL, 0); + fcntl( m_iReadSock, F_SETFL, fdflags|O_NONBLOCK ); + + if ( m_iReadSock != m_iWriteSock ) + { + fdflags = fcntl ( m_iWriteSock, F_GETFL, 0); + fcntl( m_iWriteSock, F_SETFL, fdflags|O_NONBLOCK ); + } + + BlockIO( false ); + } + + //! if this connection type is ssl or not + bool GetSSL() { return( m_bssl ); } + void SetSSL( bool b ) { m_bssl = b; } + +#ifdef HAVE_LIBSSL + //! Set the cipher type ( openssl cipher [to see ciphers available] ) + void SetCipher( const CS_STRING & sCipher ) { m_sCipherType = sCipher; } + const CS_STRING & GetCipher() { return( m_sCipherType ); } + + //! Set the pem file location + void SetPemLocation( const CS_STRING & sPemFile ) { m_sPemFile = sPemFile; } + const CS_STRING & GetPemLocation() { return( m_sPemFile ); } + void SetPemPass( const CS_STRING & sPassword ) { m_sPemPass = sPassword; } + const CS_STRING & GetPemPass() const { return( m_sPemPass ); } + static int 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( strlen( buf ) ); + } + static int CertVerifyCB( int preverify_ok, X509_STORE_CTX *x509_ctx ) + { + /* return 1 always for now, probably want to add some code for cert verification */ + return( 1 ); + } + + //! Set the SSL method type + void SetSSLMethod( int iMethod ) { m_iMethod = iMethod; } + int GetSSLMethod() { return( m_iMethod ); } + + void SetSSLObject( SSL *ssl ) { m_ssl = ssl; } + void SetCTXObject( SSL_CTX *sslCtx ) { m_ssl_ctx = sslCtx; } + void SetFullSSLAccept() { m_bFullsslAccept = true; } + SSL_SESSION * GetSSLSession() { return( SSL_get_session( m_ssl ) ); } +#endif /* HAVE_LIBSSL */ + + //! Get the send buffer + const CS_STRING & GetSendBuff() { return( m_sSend ); } + + //! is SSL_accept finished ? + bool FullSSLAccept() { return ( m_bFullsslAccept ); } + //! is the ssl properly finished (from write no error) + bool SslIsEstablished() { return ( m_bsslEstablished ); } + + //! returns the underlying buffer + CS_STRING & GetBuffer() { return( m_sSend ); } + + //! Use this to bind this socket to inetd + bool ConnectInetd( bool bIsSSL = false, 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 ) ); + } + + //! 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 ) + { + 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 ); + } + + //! Get the peer's X509 cert +#ifdef HAVE_LIBSSL + X509 *getX509() + { + if ( m_ssl ) + return( SSL_get_peer_certificate( m_ssl ) ); + + return( NULL ); + } + + //! + //! Returns The Peers Public Key + //! + CS_STRING 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 ); + } + bool RequiresClientCert() { return( m_bRequireClientCert ); } + void SetRequiresClientCert( bool bRequiresCert ) { m_bRequireClientCert = bRequiresCert; } + +#endif /* HAVE_LIBSSL */ + + //! Set The INBOUND Parent sockname + virtual void SetParentSockName( const CS_STRING & sParentName ) { m_sParentName = sParentName; } + const CS_STRING & GetParentSockName() { return( m_sParentName ); } + + /* + * 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 ) + { + m_iMaxBytes = iBytes; + m_iMaxMilliSeconds = iMilliseconds; + } + + u_int GetRateBytes() { return( m_iMaxBytes ); } + unsigned long long GetRateTime() { return( m_iMaxMilliSeconds ); } + + + //! This has a garbage collecter, and is used internall to call the jobs + 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(); + } + } + + //! insert a newly created cron + 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; + } + } + } + + /** + * 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() { m_bEnableReadLine = true; } + void DisableReadLine() { m_bEnableReadLine = false; } + + /** + * 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() + { + cerr << "Warning, Max Buffer length Warning Threshold has been hit" << endl; + cerr << "If you don't care, then set SetMaxBufferThreshold to 0" << endl; + } + /** + * 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 is ONLY called on the LISTENER, returning false from this + * will cause the LISTENER to ABORT on the connection and disregard the + * object. + */ + virtual bool CreatedChild( Csock *pSock ) { return( true ); } + //! return the data imediatly ready for read + virtual int GetPending() + { +#ifdef HAVE_LIBSSL + if( m_ssl ) + return( SSL_pending( m_ssl ) ); + else + return( 0 ); +#else + return( 0 ); +#endif /* HAVE_LIBSSL */ + } + + ////////////////////////////////////////////////// + + 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; + 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; + + 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() + { + if ( m_ssl ) + { + SSL_shutdown( m_ssl ); + SSL_free( m_ssl ); + } + m_ssl = NULL; + } + + virtual void FREE_CTX() + { + if ( m_ssl_ctx ) + SSL_CTX_free( m_ssl_ctx ); + + m_ssl_ctx = NULL; + } + +#endif /* HAVE_LIBSSL */ + + vector m_vcCrons; + + //! Create the socket + virtual int SOCKET( bool bListen = false ) + { + int iRet = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); + + if ( ( iRet > -1 ) && ( bListen ) ) + { + const int on = 1; + + if ( setsockopt( iRet, SOL_SOCKET, SO_REUSEADDR, &on, sizeof( on ) ) != 0 ) + PERROR( "setsockopt" ); + + } else if ( iRet == -1 ) + PERROR( "socket" ); + + return( iRet ); + } + + virtual void Init( const CS_STRING & sHostname, int iport, int itimeout = 60 ) + { +#ifdef HAVE_LIBSSL + m_ssl = NULL; + m_ssl_ctx = NULL; +#endif /* HAVE_LIBSSL */ + m_iReadSock = -1; + m_iWriteSock = -1; + m_itimeout = itimeout; + m_bssl = false; + m_bIsConnected = false; + m_iport = iport; + m_shostname = sHostname; + m_iTcount = 0; + m_sbuffer.clear(); + m_bClosed = false; + m_bBLOCK = true; + m_iMethod = SSL23; + m_sCipherType = "ALL"; + m_iMaxBytes = 0; + m_iMaxMilliSeconds = 0; + m_iLastSendTime = 0; + m_iLastSend = 0; + m_bFullsslAccept = false; + m_bsslEstablished = false; + m_bEnableReadLine = false; + m_bRequireClientCert = false; + m_iMaxStoredBufferLength = 1024; + m_iConnType = INBOUND; + m_iRemotePort = 0; + m_iLocalPort = 0; + m_iBytesRead = 0; + m_iBytesWritten = 0; + m_iStartTime = millitime(); + } + }; + + /** + * @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 < 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; + + // mark that this sock was ready + spReadySocks.insert( pcSock ); + if ( iErrno == SUCCESS ) + { + pcSock->ResetTimer(); // reset the timeout timer + + // 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_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; + + default: + { + 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 < size(); i++ ) + { + if ( (*this)[i]->GetType() != T::LISTENER ) + { + // are we in the map of found socks ? + if ( spReadySocks.find( (*this)[i] ) == spReadySocks.end() ) + { + if ( (*this)[i]->CheckTimeout() ) + DelSock( i-- ); + } + } else + { + 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 < 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 < 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 < 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 < 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 < 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 MILLISECONDS + u_int GetSelectTimeout() { return( m_iSelectWait ); } + //! Set the Select Timeout in MILLISECONDS + 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 < 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 >= 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] ); + erase( 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; + + 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 < 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 < size(); i++ ) + { + + T *pcSock = (*this)[i]; + + if ( pcSock->GetType() != T::LISTENER ) + { + int & iRSock = pcSock->GetRSock(); + int & iWSock = pcSock->GetWSock(); + + if ( ( pcSock->GetSSL() ) && ( pcSock->GetType() == T::INBOUND ) && ( !pcSock->FullSSLAccept() ) ) + { + tv.tv_usec = 1000; // 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->GetSendBuff().empty() ) ) + { + TFD_SET( iRSock, &rfds ); + + } else if ( ( pcSock->GetSSL() ) && ( !pcSock->SslIsEstablished() ) && ( !pcSock->GetSendBuff().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 + { + TFD_SET( iRSock, &rfds ); + TFD_SET( iWSock, &wfds ); + bHasWriteable = true; + } + + } else + TFD_SET( pcSock->GetRSock(), &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 < size(); i++ ) + { + T *pcSock = (*this)[i]; + + if ( ( pcSock->GetSSL() ) && ( pcSock->GetType() != Csock::LISTENER ) ) + { + if ( pcSock->GetPending() > 0 ) + SelectSock( mpeSocks, SUCCESS, pcSock ); + } + } + + // old fashion select, go fer it + int iSel; + + if ( !mpeSocks.empty() ) + tv.tv_usec = 1000; // 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 < size(); i++ ) + { + T *pcSock = (*this)[i]; + int & iRSock = pcSock->GetRSock(); + int & iWSock = pcSock->GetWSock(); + EMessages iErrno = SUCCESS; + + if ( TFD_ISSET( iWSock, &wfds ) ) + { + if ( iSel > 0 ) + { + iErrno = SUCCESS; + if ( ( !pcSock->GetSendBuff().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 ) + { + 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 ( pcSock->CreatedChild( (T *)NewpcSock ) ) + { // last step, notify the listener that this socket was created succesfully + if ( NewpcSock->GetSockName().empty() ) + { + stringstream s; + s << sHost << ":" << port; + AddSock( NewpcSock, s.str() ); + + } else + AddSock( NewpcSock, NewpcSock->GetSockName() ); + } else + CS_Delete( NewpcSock ); + + + } 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_ */ + diff --git a/DCCBounce.cpp b/DCCBounce.cpp new file mode 100644 index 00000000..96eb7702 --- /dev/null +++ b/DCCBounce.cpp @@ -0,0 +1,130 @@ +#include "DCCBounce.h" + +void CDCCBounce::ReadLine(const string& sData) { + string sLine = sData; + + while ((CUtils::Right(sLine, 1) == "\r") || (CUtils::Right(sLine, 1) == "\n")) { + CUtils::RightChomp(sLine); + } + + DEBUG_ONLY(cout << GetSockName() << " <- [" << sLine << "]" << endl); + + PutPeer(sLine); +} + +void CDCCBounce::ReadData(const char* data, int len) { + if (m_pPeer) { + m_pPeer->Write(data, len); + } +} + +void CDCCBounce::Timeout() { + DEBUG_ONLY(cout << GetSockName() << " == Timeout()" << endl); + string sType = (m_bIsChat) ? "Chat" : "Xfer"; + + if (IsRemote()) { + string sHost = Csock::GetHostName(); + if (!sHost.empty()) { + sHost = " to [" + sHost + ":" + CUtils::ToString(Csock::GetPort()) + "]"; + } else { + sHost = "."; + } + + m_pUser->PutStatus("DCC " + sType + " Bounce (" + m_sRemoteNick + "): Timeout while connecting" + sHost); + } else { + m_pUser->PutStatus("DCC " + sType + " Bounce (" + m_sRemoteNick + "): Timeout waiting for incoming connection [" + Csock::GetLocalIP() + ":" + CUtils::ToString(Csock::GetLocalPort()) + "]"); + } +} + +void CDCCBounce::ConnectionRefused() { + DEBUG_ONLY(cout << GetSockName() << " == ConnectionRefused()" << endl); + + string sType = (m_bIsChat) ? "Chat" : "Xfer"; + string sHost = Csock::GetHostName(); + if (!sHost.empty()) { + sHost = " to [" + sHost + ":" + CUtils::ToString(Csock::GetPort()) + "]"; + } else { + sHost = "."; + } + + m_pUser->PutStatus("DCC " + sType + " Bounce (" + m_sRemoteNick + "): Connection Refused while connecting" + sHost); +} + +void CDCCBounce::SockError(int iErrno) { + DEBUG_ONLY(cout << GetSockName() << " == SockError(" << iErrno << ")" << endl); + string sType = (m_bIsChat) ? "Chat" : "Xfer"; + + if (IsRemote()) { + string sHost = Csock::GetHostName(); + if (!sHost.empty()) { + sHost = "[" + sHost + ":" + CUtils::ToString(Csock::GetPort()) + "]"; + } + + m_pUser->PutStatus("DCC " + sType + " Bounce (" + m_sRemoteNick + "): Socket error [" + string(strerror(iErrno)) + "]" + sHost); + } else { + m_pUser->PutStatus("DCC " + sType + " Bounce (" + m_sRemoteNick + "): Socket error [" + string(strerror(iErrno)) + "] [" + Csock::GetLocalIP() + ":" + CUtils::ToString(Csock::GetLocalPort()) + "]"); + } +} + +void CDCCBounce::Connected() { + DEBUG_ONLY(cout << GetSockName() << " == Connected()" << endl); + SetTimeout(0); +} + +void CDCCBounce::Disconnected() { + DEBUG_ONLY(cout << GetSockName() << " == Disconnected()" << endl); +} + +void CDCCBounce::Shutdown() { + m_pPeer = NULL; + DEBUG_ONLY(cerr << GetSockName() << " == Close(); because my peer told me to" << endl); + Close(); +} + +Csock* CDCCBounce::GetSockObj(const string& sHost, int iPort) { + Close(); + + if (!m_pManager) { + return NULL; + } + + if (m_sRemoteIP.empty()) { + m_sRemoteIP = sHost; + } + + CDCCBounce* pSock = new CDCCBounce(sHost, iPort, m_pUser, m_sRemoteNick, m_sRemoteIP, m_sFileName, m_bIsChat); + CDCCBounce* pRemoteSock = new CDCCBounce(sHost, iPort, m_pUser, m_sRemoteNick, m_sRemoteIP, m_sFileName, m_bIsChat); + pSock->SetPeer(pRemoteSock); + pRemoteSock->SetPeer(pSock); + pRemoteSock->SetRemote(true); + pSock->SetRemote(false); + + if (!m_pManager->Connect(m_sConnectIP, m_uRemotePort, "DCC::" + string((m_bIsChat) ? "Chat" : "XFER") + "::Remote::" + m_sRemoteNick, 60, false, m_sLocalIP, pRemoteSock)) { + pRemoteSock->Close(); + } + + pSock->SetSockName(GetSockName()); + pSock->SetTimeout(0); + return pSock; +} + +void CDCCBounce::PutServ(const string& sLine) { + DEBUG_ONLY(cout << GetSockName() << " -> [" << sLine << "]" << endl); + Write(sLine + "\r\n"); +} + +void CDCCBounce::PutPeer(const string& sLine) { + if (m_pPeer) { + m_pPeer->PutServ(sLine); + } else { + PutServ("*** Not connected yet ***"); + } +} + +unsigned short CDCCBounce::DCCRequest(const string& sNick, unsigned long uLongIP, unsigned short uPort, const string& sFileName, bool bIsChat, CUser* pUser, const string& sLocalIP, const string& sRemoteIP) { + CDCCBounce* pDCCBounce = new CDCCBounce(pUser, uLongIP, uPort, sFileName, sNick, sRemoteIP, sLocalIP, bIsChat); + unsigned short uListenPort = pUser->GetManager()->ListenAllRand("DCC::" + string((bIsChat) ? "Chat" : "Xfer") + "::Local::" + sNick, false, SOMAXCONN, pDCCBounce, 120); + + return uListenPort; +} + diff --git a/DCCBounce.h b/DCCBounce.h new file mode 100644 index 00000000..01b59d71 --- /dev/null +++ b/DCCBounce.h @@ -0,0 +1,98 @@ +#ifndef _DCCBOUNCE_H +#define _DCCBOUNCE_H + +#include "main.h" +#include "Utils.h" +#include "User.h" + +class CDCCBounce : public Csock { +public: + CDCCBounce(CUser* pUser, unsigned long uLongIP, unsigned short uPort, const string& sFileName, const string& sRemoteNick, const string& sRemoteIP, string sLocalIP, bool bIsChat = false) : Csock() { + m_uRemotePort = uPort; + m_sConnectIP = CUtils::GetIP(uLongIP); + m_sRemoteIP = sRemoteIP; + m_sFileName = sFileName; + m_sRemoteNick = sRemoteNick; + m_pUser = pUser; + m_pManager = pUser->GetManager(); + m_bIsChat = bIsChat; + m_sLocalIP = sLocalIP; + m_pPeer = NULL; + m_bIsRemote = false; + + if (bIsChat) { + EnableReadLine(); + } + } + + CDCCBounce(const string& sHostname, int iport, CUser* pUser, const string& sRemoteNick, const string& sRemoteIP, const string& sFileName, int itimeout = 60, bool bIsChat = false) : Csock(sHostname, iport, itimeout) { + m_uRemotePort = 0; + m_bIsChat = bIsChat; + m_pManager = pUser->GetManager(); + m_pUser = pUser; + m_pPeer = NULL; + m_sRemoteNick = sRemoteNick; + m_sFileName = sFileName; + m_sRemoteIP = sRemoteIP; + m_bIsRemote = false; + + if (bIsChat) { + EnableReadLine(); + } + } + virtual ~CDCCBounce() { + if (m_pPeer) { + m_pPeer->Shutdown(); + m_pPeer = NULL; + } + } + + static unsigned short DCCRequest(const string& sNick, unsigned long uLongIP, unsigned short uPort, const string& sFileName, bool bIsChat, CUser* pUser, const string& sLocalIP, const string& sRemoteIP); + + void ReadLine(const string& sData); + virtual void ReadData(const char* data, int len); + virtual void Timeout(); + virtual void ConnectionRefused(); + virtual void SockError(int iErrno); + virtual void Connected(); + virtual void Disconnected(); + void Shutdown(); + Csock* GetSockObj(const string& sHost, int iPort); + void PutServ(const string& sLine); + void PutPeer(const string& sLine); + bool IsPeerConnected() { return (m_pPeer) ? m_pPeer->IsConnected() : false; } + + // Setters + void SetPeer(CDCCBounce* p) { m_pPeer = p; } + void SetRemoteIP(const string& s) { m_sRemoteIP = s; } + void SetRemoteNick(const string& s) { m_sRemoteNick = s; } + void SetManager(TSocketManager* p) { m_pManager = p; } + void SetRemote(bool b) { m_bIsRemote = b; } + // !Setters + + // Getters + unsigned short GetUserPort() const { return m_uRemotePort; } + const string& GetRemoteIP() const { return m_sRemoteIP; } + const string& GetRemoteNick() const { return m_sRemoteNick; } + const string& GetFileName() const { return m_sFileName; } + CDCCBounce* GetPeer() const { return m_pPeer; } + TSocketManager* GetManager() const { return m_pManager; } + bool IsRemote() { return m_bIsRemote; } + // !Getters +private: +protected: + string m_sRemoteNick; + string m_sRemoteIP; + string m_sConnectIP; + string m_sLocalIP; + string m_sFileName; + CUser* m_pUser; + CDCCBounce* m_pPeer; + TSocketManager* m_pManager; + unsigned short m_uRemotePort; + bool m_bIsChat; + bool m_bIsRemote; +}; + +#endif // !_DCCBOUNCE_H + diff --git a/DCCSock.cpp b/DCCSock.cpp new file mode 100644 index 00000000..23bc7fe8 --- /dev/null +++ b/DCCSock.cpp @@ -0,0 +1,156 @@ +#include "DCCSock.h" + +void CDCCSock::ReadData(const char* data, int len) { + if (!m_pFile) { + DEBUG_ONLY(cout << "File not open! closing get." << endl); + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - File not open!"); + Close(); + } + + if (m_bSend) { + m_sSendBuf.append(data, len); + + while (m_sSendBuf.size() >= 4) { + unsigned int iRemoteSoFar; + memcpy(&iRemoteSoFar, m_sSendBuf.data(), 4); + iRemoteSoFar = ntohl(iRemoteSoFar); + + if (iRemoteSoFar >= m_uBytesSoFar) { + SendPacket(); + } + + m_sSendBuf.erase(0, 4); + } + } else { + m_pFile->Write(data, len); + m_uBytesSoFar += len; + unsigned long uSoFar = htonl(m_uBytesSoFar); + Write((char*) &uSoFar, sizeof(unsigned long)); + + if (m_uBytesSoFar >= m_uFileSize) { + Close(); + } + } +} + +void CDCCSock::ConnectionRefused() { + DEBUG_ONLY(cout << GetSockName() << " == ConnectionRefused()" << endl); + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Connection Refused."); +} + +void CDCCSock::Timeout() { + DEBUG_ONLY(cout << GetSockName() << " == Timeout()" << endl); + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Timed Out."); +} + +void CDCCSock::SockError(int iErrno) { + DEBUG_ONLY(cout << GetSockName() << " == SockError(" << iErrno << ")" << endl); + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Socket Error [" + CUtils::ToString(iErrno) + "]"); +} + +void CDCCSock::Connected() { + DEBUG_ONLY(cout << GetSockName() << " == Connected(" << GetRemoteIP() << ")" << endl); + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Transfer Started."); + + if (m_bSend) { + SendPacket(); + } + + SetTimeout(120); +} + +void CDCCSock::Disconnected() { + DEBUG_ONLY(cout << GetSockName() << " == Disconnected()" << endl); + if (m_uBytesSoFar > m_uFileSize) { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - TooMuchData!"); + } else if (m_uBytesSoFar == m_uFileSize) { + if (m_bSend) { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Completed! - Sent [" + m_sLocalFile + "] at [" + CUtils::ToKBytes(GetAvgWrite() / 1000.0) + "]"); + } else { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Completed! - Saved to [" + m_sLocalFile + "] at [" + CUtils::ToKBytes(GetAvgRead() / 1000.0) + "]"); + } + } else { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Incomplete!"); + } +} + +void CDCCSock::SendPacket() { + if (!m_pFile) { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - File closed prematurely."); + Close(); + return; + } + + char szBuf[4096]; + int iLen = m_pFile->Read(szBuf, 4096); + + if (iLen < 0) { + m_pUser->PutModule(m_sModuleName, ((m_bSend) ? "DCC -> [" : "DCC <- [") + m_sRemoteNick + "][" + m_sFileName + "] - Error reading from file."); + Close(); + return; + } + + if (iLen > 0) { + Write(szBuf, iLen); + m_uBytesSoFar += iLen; + } +} + +Csock* CDCCSock::GetSockObj(const string& sHost, int iPort) { + Close(); + + CDCCSock* pSock = new CDCCSock(m_pUser, m_sRemoteNick, m_sLocalFile, m_sModuleName, m_uFileSize, m_pFile); + pSock->SetSockName("DCC::SEND::" + m_sRemoteNick); + pSock->SetTimeout(120); + pSock->SetFileName(m_sFileName); + pSock->SetFileOffset(m_uBytesSoFar); + m_bNoDelFile = true; + + return pSock; +} + +CFile* CDCCSock::OpenFile(bool bWrite) { + if ((m_pFile) || (m_sLocalFile.empty())) { + m_pUser->PutModule(m_sModuleName, ((bWrite) ? "DCC <- [" : "DCC -> [") + m_sRemoteNick + "][" + m_sLocalFile + "] - Unable to open file."); + return false; + } + + m_pFile = new CFile(m_sLocalFile); + + if (bWrite) { + if (m_pFile->Exists()) { + delete m_pFile; + m_pFile = NULL; + m_pUser->PutModule(m_sModuleName, "DCC <- [" + m_sRemoteNick + "] - File already exists [" + m_sLocalFile + "]"); + return NULL; + } + + if (!m_pFile->Open(O_WRONLY | O_TRUNC | O_CREAT)) { + delete m_pFile; + m_pFile = NULL; + m_pUser->PutModule(m_sModuleName, "DCC <- [" + m_sRemoteNick + "] - Could not open file [" + m_sLocalFile + "]"); + return NULL; + } + } else { + if (!m_pFile->IsReg()) { + delete m_pFile; + m_pFile = NULL; + m_pUser->PutModule(m_sModuleName, "DCC -> [" + m_sRemoteNick + "] - Not a file [" + m_sLocalFile + "]"); + return NULL; + } + + if (!m_pFile->Open(O_RDONLY)) { + delete m_pFile; + m_pFile = NULL; + m_pUser->PutModule(m_sModuleName, "DCC -> [" + m_sRemoteNick + "] - Could not open file [" + m_sLocalFile + "]"); + return NULL; + } + + m_uFileSize = m_pFile->GetSize(); + } + + m_sFileName = m_pFile->GetShortName(); + + return m_pFile; +} + diff --git a/DCCSock.h b/DCCSock.h new file mode 100644 index 00000000..e06cd994 --- /dev/null +++ b/DCCSock.h @@ -0,0 +1,108 @@ +#ifndef _DCCSOCK_H +#define _DCCSOCK_H + +#include "main.h" +#include "Utils.h" +#include "User.h" + +class CDCCSock : public Csock { +public: + CDCCSock(CUser* pUser, const string& sRemoteNick, const string& sLocalFile, const string& sModuleName, unsigned long uFileSize = 0, CFile* pFile = NULL) : Csock() { + m_sRemoteNick = sRemoteNick; + m_uFileSize = uFileSize; + m_uRemotePort = 0; + m_uBytesSoFar = 0; + m_pUser = pUser; + m_pFile = pFile; + m_sLocalFile = sLocalFile; + m_sModuleName = sModuleName; + m_bSend = true; + m_bNoDelFile = false; + } + + CDCCSock(CUser* pUser, const string& sRemoteNick, const string& sRemoteIP, unsigned short uRemotePort, const string& sLocalFile, unsigned long uFileSize, const string& sModuleName) : Csock() { + m_sRemoteNick = sRemoteNick; + m_sRemoteIP = sRemoteIP; + m_uRemotePort = uRemotePort; + m_uFileSize = uFileSize; + m_uBytesSoFar = 0; + m_pUser = pUser; + m_pFile = NULL; + m_sLocalFile = sLocalFile; + m_sModuleName = sModuleName; + m_bSend = false; + m_bNoDelFile = false; + } + +/* CDCCSock(CUser* pUser, const string& sHostname, int iport, int itimeout = 60) : Csock(sHostname, iport, itimeout) { + m_uRemotePort = 0; + m_uBytesSoFar = 0; + m_uFileSize = 0; + m_pFile = NULL; + m_pUser = pUser; + m_bNoDelFile = false; + } +*/ + virtual ~CDCCSock() { + if ((m_pFile) && (!m_bNoDelFile)) { + m_pFile->Close(); + delete m_pFile; + } + } + + virtual void ReadData(const char* data, int len); + virtual void ConnectionRefused(); + virtual void SockError(int iErrno); + virtual void Timeout(); + virtual void Connected(); + virtual void Disconnected(); + void SendPacket(); + Csock* GetSockObj(const string& sHost, int iPort); + CFile* OpenFile(bool bWrite = true); + bool Seek(unsigned int uPos) { + if (m_pFile) { + if (m_pFile->Seek(uPos)) { + m_uBytesSoFar = uPos; + return true; + } + } + + return false; + } + + // Setters + void SetRemoteIP(const string& s) { m_sRemoteIP = s; } + void SetRemoteNick(const string& s) { m_sRemoteNick = s; } + void SetFileName(const string& s) { m_sFileName = s; } + void SetFileOffset(unsigned long u) { m_uBytesSoFar = u; } + // !Setters + + // Getters + unsigned short GetUserPort() const { return m_uRemotePort; } + const string& GetRemoteNick() const { return m_sRemoteNick; } + const string& GetFileName() const { return m_sFileName; } + const string& GetLocalFile() const { return m_sLocalFile; } + const string& GetModuleName() const { return m_sModuleName; } + CFile* GetFile() { return m_pFile; } + double GetProgress() const { return ((m_uFileSize) && (m_uBytesSoFar)) ? (double) (((double) m_uBytesSoFar / (double) m_uFileSize) *100.0) : 0; } + //const string& GetRemoteIP() const { return m_sRemoteIP; } + // !Getters +private: +protected: + string m_sRemoteNick; + string m_sRemoteIP; + string m_sFileName; + string m_sLocalFile; + string m_sSendBuf; + string m_sModuleName; + unsigned short m_uRemotePort; + unsigned long m_uFileSize; + unsigned long m_uBytesSoFar; + bool m_bSend; + bool m_bNoDelFile; + CFile* m_pFile; + CUser* m_pUser; +}; + +#endif // !_DCCSOCK_H + diff --git a/IRCSock.cpp b/IRCSock.cpp new file mode 100644 index 00000000..4b186253 --- /dev/null +++ b/IRCSock.cpp @@ -0,0 +1,742 @@ +#include "znc.h" +#include "IRCSock.h" +#include "DCCBounce.h" +#include "UserSock.h" + +CIRCSock::CIRCSock(CZNC* pZNC, CUser* pUser) : Csock() { + m_pZNC = pZNC; + m_pUser = pUser; + m_pUserSock = NULL; + m_uQueryBufferCount = 50; + m_bISpoofReleased = false; + m_bKeepNick = true; + m_bAuthed = false; + EnableReadLine(); + m_RawBuffer.SetLineCount(100); // This should be more than enough raws, especially since we are asking the server to resend MOTD + m_Nick.SetIdent(pUser->GetIdent()); + m_Nick.SetHost(pUser->GetVHost()); +} + +CIRCSock::~CIRCSock() { + for (map::iterator a = m_msChans.begin(); a != m_msChans.end(); a++) { + delete a->second; + } + + if (!m_bISpoofReleased) { + m_pZNC->ReleaseISpoof(); + } + + PutServ("QUIT :"); + m_msChans.clear(); +} + +void CIRCSock::ReadLine(const string& sData) { + string sLine = sData; + + while ((CUtils::Right(sLine, 1) == "\r") || (CUtils::Right(sLine, 1) == "\n")) { + CUtils::RightChomp(sLine); + } + + DEBUG_ONLY(cout << GetSockName() << " <- [" << sLine << "]" << endl); + +#ifdef _MODULES + if (m_pUser->GetModules().OnRaw(sLine)) { + return; + } +#endif + + if (strncasecmp(sLine.c_str(), "PING ", 5) == 0) { + PutServ("PONG " + sLine.substr(5)); + } else if (CUtils::WildCmp(":* * *", sLine.c_str())) { //"^:(\\S+) (\\d\\d\\d) (.*?) (.*)$", vCap)) { + string sCmd = CUtils::Token(sLine, 1); + + if ((sCmd.length() == 3) && (isdigit(sCmd[0])) && (isdigit(sCmd[1])) && (isdigit(sCmd[2]))) { + string sServer = CUtils::Token(sLine, 0); CUtils::LeftChomp(sServer); + unsigned int uRaw = strtoul(sCmd.c_str(), NULL, 10); + string sNick = CUtils::Token(sLine, 2); + string sRest = CUtils::Token(sLine, 3, true); + + switch (uRaw) { + case 1: {// :irc.server.com 001 nick :Welcome to the Internet Relay Network nick + SetTimeout(900); // Now that we are connected, let nature take its course + PutServ("WHO " + sNick); +#ifdef _MODULES + m_pUser->GetModules().OnIRCConnected(); +#endif + m_bAuthed = true; + m_pUser->PutStatus("Connected!"); + + if (m_pUserSock) { + string sClientNick = m_pUserSock->GetNick(); + if (strcasecmp(sClientNick.c_str(), sNick.c_str()) != 0) { + // If they connected with a nick that doesn't match the one we got on irc, then we need to update them + PutUser(":" + sClientNick + "!" + m_Nick.GetIdent() + "@" + m_Nick.GetHost() + " NICK :" + sNick); + } + } + + SetNick(sNick); + + m_RawBuffer.Clear(); + m_RawBuffer.AddLine(":" + sServer + " " + sCmd + " ", " " + sRest); + + // Now that we are connected, we need to join our chans + const vector& vChans = m_pUser->GetChans(); + + for (unsigned int a = 0; a < vChans.size(); a++) { + PutServ("JOIN " + vChans[a]->GetName() + " " + vChans[a]->GetKey()); + } + + m_pZNC->ReleaseISpoof(); + m_bISpoofReleased = true; + + break; + } + case 2: + case 3: + case 4: + case 5: + case 250: // highest connection count + case 251: // user count + case 252: // oper count + case 254: // channel count + case 255: // client count + case 265: // local users + case 266: // global users + m_RawBuffer.AddLine(":" + sServer + " " + sCmd + " ", " " + sRest); + break; + case 372: // motd + case 375: // begin motd + case 376: // end motd + break; + case 471: // :irc.server.net 471 nick #chan :Cannot join channel (+l) + case 473: // :irc.server.net 473 nick #chan :Cannot join channel (+i) + case 475: // :irc.server.net 475 nick #chan :Cannot join channel (+k) + if (m_pUserSock) { + CChan* pChan = m_pUser->FindChan(sRest.substr(0, sRest.find(' '))); + + if ((pChan) && (!pChan->IsOn())) { + if (!pChan->DecClientRequests()) { + return; + } + } + } + + break; + case 433: { + string sBadNick = CUtils::Token(sRest, 0); + string sConfNick = m_pUser->GetNick(); + + if (sNick == "*") { + string sAltNick = m_pUser->GetAltNick(); + + if (strcasecmp(sBadNick.c_str(), sConfNick.c_str()) == 0) { + if ((!sAltNick.empty()) && (strcasecmp(sConfNick.c_str(), sAltNick.c_str()) != 0)) { + PutServ("NICK " + sAltNick); + } else { + PutServ("NICK " + CUtils::Left(sConfNick, 8) + "-"); + } + } else if (strcasecmp(sBadNick.c_str(), sAltNick.c_str()) == 0) { + PutServ("NICK " + CUtils::Left(sConfNick, 8) + "-"); + } else if (strcasecmp(sBadNick.c_str(), string(CUtils::Left(sConfNick, 8) + "-").c_str()) == 0) { + PutServ("NICK " + CUtils::Left(sConfNick, 8) + "|"); + } else if (strcasecmp(sBadNick.c_str(), string(CUtils::Left(sConfNick, 8) + "|").c_str()) == 0) { + PutServ("NICK " + CUtils::Left(sConfNick, 8) + "^"); + } else { + char cLetter = 0; + if (sBadNick.empty()) { + Close(); + return; + } + + cLetter = CUtils::Right(sBadNick, 1)[0]; + + if (cLetter == 'z') { + Close(); + return; + } + + string sSend = "NICK " + CUtils::Left(sConfNick, 8) + cLetter++; + PutServ(sSend); + } + + return; + } else { + // :irc.server.net 433 mynick badnick :Nickname is already in use. + if ((m_bKeepNick) && (m_pUser->KeepNick())) { + if (strcasecmp(sBadNick.c_str(), sConfNick.c_str()) == 0) { + if ((!m_pUserSock) || (!m_pUserSock->DecKeepNickCounter())) { + return; + } + } + } + } + break; + } + case 315: { + // :irc.server.com 315 yournick #chan :End of /WHO list. + CChan* pChan = m_pUser->FindChan(CUtils::Token(sLine, 3)); + + if (pChan) { + pChan->SetWhoDone(); + } + } + case 352: { + // :irc.yourserver.com 352 yournick #chan ident theirhost.com irc.theirserver.com theirnick H :0 Real Name + string sNick = CUtils::Token(sLine, 7); + string sIdent = CUtils::Token(sLine, 4); + string sHost = CUtils::Token(sLine, 5); + + if (strcasecmp(sNick.c_str(), GetNick().c_str()) == 0) { + m_Nick.SetIdent(sIdent); + m_Nick.SetHost(sHost); + } + + const vector& vChans = m_pUser->GetChans(); + + for (unsigned int a = 0; a < vChans.size(); a++) { + vChans[a]->OnWho(sNick, sIdent, sHost); + } + + break; + } + case 324: { // MODE + CUtils::Trim(sRest); + CChan* pChan = m_pUser->FindChan(CUtils::Token(sRest, 0)); + + if (pChan) { + pChan->SetModes(CUtils::Token(sRest, 1, true)); + } + } + break; + case 353: { // NAMES + CUtils::Trim(sRest); + // Todo: allow for non @+= server msgs + CChan* pChan = m_pUser->FindChan(CUtils::Token(sRest, 1)); + if (pChan) { + string sNicks = CUtils::Token(sRest, 2, true); + if (CUtils::Left(sNicks, 1) == ":") { + CUtils::LeftChomp(sNicks); + } + + pChan->AddNicks(sNicks); + } + } + break; + case 366: { // end of names list + PutUser(sLine); // First send them the raw + + // :irc.server.com 366 nick #chan :End of /NAMES list. + CChan* pChan = m_pUser->FindChan(CUtils::Token(sRest, 0)); + + if (pChan) { + if (IsUserAttached()) { + const vector& vsBuffer = pChan->GetBuffer(); + + if (vsBuffer.size()) { + PutUser(":***!znc@znc.com PRIVMSG " + pChan->GetName() + " :Buffer Playback..."); + + for (unsigned int a = 0; a < vsBuffer.size(); a++) { + PutUser(vsBuffer[a]); + } + + if (!pChan->KeepBuffer()) { + pChan->ClearBuffer(); + } + + PutUser(":***!znc@znc.com PRIVMSG " + pChan->GetName() + " :Playback Complete."); + } + } + + if (!pChan->IsOn()) { + pChan->SetIsOn(true); + PutServ("MODE " + pChan->GetName()); + + // If we are the only one in the chan, set our default modes + if (pChan->GetNickCount() == 1) { + string sModes = pChan->GetDefaultModes(); + + if (sModes.empty()) { + sModes = m_pUser->GetDefaultChanModes(); + } + + if (!sModes.empty()) { + PutServ("MODE " + pChan->GetName() + " " + sModes); + } + } + } + } + + return; // return so we don't send them the raw twice + } + } + } else { //if (CUtils::WildCmp(":*!*@* * *", sLine.c_str())) { + string sNickMask = CUtils::Token(sLine, 0); + CUtils::LeftChomp(sNickMask); + + string sNick = CUtils::Token(sNickMask, 0, false, '!'); + string sCmd = CUtils::Token(sLine, 1); + string sRest = CUtils::Token(sLine, 2, true); + + if (strcasecmp(sCmd.c_str(), "NICK") == 0) { + string sNewNick = sRest; + if (CUtils::Left(sNewNick, 1) == ":") { + CUtils::LeftChomp(sNewNick); + } + + const vector& vChans = m_pUser->GetChans(); + for (unsigned int a = 0; a < vChans.size(); a++) { + vChans[a]->ChangeNick(sNick, sNewNick); + } + + if (strcasecmp(sNick.c_str(), GetNick().c_str()) == 0) { + SetNick(sNewNick); + if (strcasecmp(sNick.c_str(), m_pUser->GetNick().c_str()) == 0) { + // If the user changes his nick away from the config nick, we shut off keepnick for this session + m_bKeepNick = false; + } + } else if (strcasecmp(sNick.c_str(), m_pUser->GetNick().c_str()) == 0) { + KeepNick(); + } +#ifdef _MODULES + m_pUser->GetModules().OnNick(sNickMask, sNewNick); +#endif + } else if (strcasecmp(sCmd.c_str(), "QUIT") == 0) { + string sChan = sRest; + if (CUtils::Left(sChan, 1) == ":") { + CUtils::LeftChomp(sChan); + } + + // :nick!ident@host.com QUIT :message + CNick Nick(sNickMask); + + const vector& vChans = m_pUser->GetChans(); + for (unsigned int a = 0; a < vChans.size(); a++) { + vChans[a]->RemNick(sNick); + } + + if (strcasecmp(Nick.GetNick().c_str(), m_pUser->GetNick().c_str()) == 0) { + KeepNick(); + } + +#ifdef _MODULES + m_pUser->GetModules().OnQuit(Nick, sChan); +#endif + } else if (strcasecmp(sCmd.c_str(), "JOIN") == 0) { + string sChan = sRest; + if (CUtils::Left(sChan, 1) == ":") { + CUtils::LeftChomp(sChan); + } + + if (strcasecmp(sNick.c_str(), GetNick().c_str()) == 0) { + m_pUser->AddChan(sChan); + } + + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { + pChan->AddNick(sNickMask); +#ifdef _MODULES + m_pUser->GetModules().OnJoin(sNickMask, *pChan); +#endif + } + } else if (strcasecmp(sCmd.c_str(), "PART") == 0) { + string sChan = sRest; + if (CUtils::Left(sChan, 1) == ":") { + CUtils::LeftChomp(sChan); + } + + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { + pChan->RemNick(sNick); +#ifdef _MODULES + m_pUser->GetModules().OnPart(sNickMask, *pChan); +#endif + } + + if (strcasecmp(sNick.c_str(), GetNick().c_str()) == 0) { + m_pUser->DelChan(sChan); + } + } else if (strcasecmp(sCmd.c_str(), "MODE") == 0) { + string sChan = CUtils::Token(sRest, 0); + string sModes = CUtils::Token(sRest, 1, true); + + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { + pChan->ModeChange(sModes, sNick); + } + } else if (strcasecmp(sCmd.c_str(), "KICK") == 0) { + // :opnick!ident@host.com KICK #chan nick :msg + string sChan = CUtils::Token(sRest, 0); + string sKickedNick = CUtils::Token(sRest, 1); + string sMsg = CUtils::Token(sRest, 2, true); + CUtils::LeftChomp(sMsg); + + CChan* pChan = m_pUser->FindChan(sChan); + + if (pChan) { + pChan->RemNick(sKickedNick); +#ifdef _MODULES + m_pUser->GetModules().OnKick(sNickMask, sKickedNick, *pChan, sMsg); +#endif + } + + if (strcasecmp(GetNick().c_str(), sKickedNick.c_str()) == 0) { + string sKey; + + if (pChan) { + sKey = pChan->GetKey(); + pChan->SetIsOn(false); + } + + PutServ("JOIN " + sChan + " " + sKey); + } + } else if (strcasecmp(sCmd.c_str(), "NOTICE") == 0) { + // :nick!ident@host.com NOTICE #chan :Message + + CNick Nick(sNickMask); + + string sTarget = CUtils::Token(sRest, 0); + string sMsg = CUtils::Token(sRest, 1, true); + CUtils::LeftChomp(sMsg); + + if (CUtils::WildCmp("\001*\001", sMsg.c_str())) { + CUtils::LeftChomp(sMsg); + CUtils::RightChomp(sMsg); + + if (strcasecmp(sTarget.c_str(), GetNick().c_str()) == 0) { + if (OnCTCPReply(sNickMask, sMsg)) { + return; + } + } + + PutUser(":" + sNickMask + " NOTICE " + sTarget + " :\001" + sMsg + "\001"); + return; + } else { + if (strcasecmp(sTarget.c_str(), GetNick().c_str()) == 0) { + if (OnPrivNotice(sNickMask, sMsg)) { + return; + } + } else { + if (OnChanNotice(sNickMask, sTarget, sMsg)) { + return; + } + } + } + + PutUser(":" + sNickMask + " NOTICE " + sTarget + " :" + sMsg); + return; + } else if (strcasecmp(sCmd.c_str(), "PRIVMSG") == 0) { + // :nick!ident@host.com PRIVMSG #chan :Message + + CNick Nick(sNickMask); + string sTarget = CUtils::Token(sRest, 0); + string sMsg = CUtils::Token(sRest, 1, true); + + if (CUtils::Left(sMsg, 1) == ":") { + CUtils::LeftChomp(sMsg); + } + + if (CUtils::WildCmp("\001*\001", sMsg.c_str())) { + CUtils::LeftChomp(sMsg); + CUtils::RightChomp(sMsg); + + if (strcasecmp(sTarget.c_str(), GetNick().c_str()) == 0) { + if (OnPrivCTCP(sNickMask, sMsg)) { + return; + } + } else { + if (OnChanCTCP(sNickMask, sTarget, sMsg)) { + return; + } + } + + PutUser(":" + sNickMask + " PRIVMSG " + sTarget + " :\001" + sMsg + "\001"); + return; + } else { + if (strcasecmp(sTarget.c_str(), GetNick().c_str()) == 0) { + if (OnPrivMsg(sNickMask, sMsg)) { + return; + } + } else { + if (OnChanMsg(sNickMask, sTarget, sMsg)) { + return; + } + } + + PutUser(":" + sNickMask + " PRIVMSG " + sTarget + " :" + sMsg); + return; + } + } + } + } + + PutUser(sLine); +} + +void CIRCSock::KeepNick() { + const string& sConfNick = m_pUser->GetNick(); + + if ((m_bAuthed) && (m_bKeepNick) && (m_pUser->KeepNick()) && (strcasecmp(GetNick().c_str(), sConfNick.c_str()) != 0)) { + PutServ("NICK " + sConfNick); + } +} + +bool CIRCSock::OnCTCPReply(const string& sNickMask, string& sMessage) { +#ifdef _MODULES + if (m_pUser->GetModules().OnCTCPReply(sNickMask, sMessage)) { + return true; + } +#endif + + return false; +} + +bool CIRCSock::OnPrivCTCP(const string& sNickMask, string& sMessage) { +#ifdef _MODULES + if (m_pUser->GetModules().OnPrivCTCP(sNickMask, sMessage)) { + return true; + } +#endif + + // DCC CHAT chat 2453612361 44592 + if (strncasecmp(sMessage.c_str(), "DCC ", 4) == 0) { + string sType = CUtils::Token(sMessage, 1); + string sFile = CUtils::Token(sMessage, 2); + unsigned long uLongIP = strtoul(CUtils::Token(sMessage, 3).c_str(), NULL, 10); + unsigned short uPort = strtoul(CUtils::Token(sMessage, 4).c_str(), NULL, 10); + unsigned long uFileSize = strtoul(CUtils::Token(sMessage, 5).c_str(), NULL, 10); + + if (strcasecmp(sType.c_str(), "CHAT") == 0) { + if (m_pUserSock) { + CNick FromNick(sNickMask); + unsigned short uBNCPort = CDCCBounce::DCCRequest(FromNick.GetNick(), uLongIP, uPort, "", true, m_pUser, GetLocalIP(), CUtils::GetIP(uLongIP)); + + if (uBNCPort) { + PutUser(":" + sNickMask + " PRIVMSG " + GetNick() + " :\001DCC CHAT chat " + CUtils::ToString(CUtils::GetLongIP(GetLocalIP())) + " " + CUtils::ToString(uBNCPort) + "\001"); + } + } + } else if (strcasecmp(sType.c_str(), "SEND") == 0) { + // DCC SEND readme.txt 403120438 5550 1104 + CNick FromNick(sNickMask); + + unsigned short uBNCPort = CDCCBounce::DCCRequest(FromNick.GetNick(), uLongIP, uPort, sFile, false, m_pUser, GetLocalIP(), CUtils::GetIP(uLongIP)); + if (uBNCPort) { + PutUser(":" + sNickMask + " PRIVMSG " + GetNick() + " :\001DCC SEND " + sFile + " " + CUtils::ToString(CUtils::GetLongIP(GetLocalIP())) + " " + CUtils::ToString(uBNCPort) + " " + CUtils::ToString(uFileSize)); + } + } else if (strcasecmp(sType.c_str(), "RESUME") == 0) { + // Need to lookup the connection by port, filter the port, and forward to the user + CDCCBounce* pSock = (CDCCBounce*) m_pZNC->GetManager().FindSockByLocalPort(atoi(CUtils::Token(sMessage, 3).c_str())); + + if ((pSock) && (strncasecmp(pSock->GetSockName().c_str(), "DCC::", 5) == 0)) { + PutUser(":" + sNickMask + " PRIVMSG " + GetNick() + " :\001DCC " + sType + " " + sFile + " " + CUtils::ToString(pSock->GetUserPort()) + " " + CUtils::Token(sMessage, 4) + "\001"); + } + } else if (strcasecmp(sType.c_str(), "ACCEPT") == 0) { + // Need to lookup the connection by port, filter the port, and forward to the user + TSocketManager& Manager = m_pZNC->GetManager(); + + for (unsigned int a = 0; a < Manager.size(); a++) { + CDCCBounce* pSock = (CDCCBounce*) Manager[a]; + + if ((pSock) && (strncasecmp(pSock->GetSockName().c_str(), "DCC::", 5) == 0)) { + if (pSock->GetUserPort() == atoi(CUtils::Token(sMessage, 3).c_str())) { + PutUser(":" + sNickMask + " PRIVMSG " + GetNick() + " :\001DCC " + sType + " " + sFile + " " + CUtils::ToString(pSock->GetLocalPort()) + " " + CUtils::Token(sMessage, 4) + "\001"); + } + } + } + } + + return true; + } + + return false; +} + +bool CIRCSock::OnPrivNotice(const string& sNickMask, string& sMessage) { +#ifdef _MODULES + if (m_pUser->GetModules().OnPrivNotice(sNickMask, sMessage)) { + return true; + } +#endif + if (!m_pUserSock) { + // If the user is detached, add to the buffer + m_QueryBuffer.AddLine(":" + sNickMask + " NOTICE ", " :" + sMessage); + } + + return false; +} + +bool CIRCSock::OnPrivMsg(const string& sNickMask, string& sMessage) { +#ifdef _MODULES + if (m_pUser->GetModules().OnPrivMsg(sNickMask, sMessage)) { + return true; + } +#endif + if (!m_pUserSock) { + // If the user is detached, add to the buffer + m_QueryBuffer.AddLine(":" + sNickMask + " PRIVMSG ", " :" + sMessage); + } + + return false; +} + +bool CIRCSock::OnChanCTCP(const string& sNickMask, const string& sChan, string& sMessage) { + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { +#ifdef _MODULES + if (m_pUser->GetModules().OnChanCTCP(sNickMask, *pChan, sMessage)) { + return true; + } +#endif + } + + return false; +} + +bool CIRCSock::OnChanNotice(const string& sNickMask, const string& sChan, string& sMessage) { + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { +#ifdef _MODULES + if (m_pUser->GetModules().OnChanNotice(sNickMask, *pChan, sMessage)) { + return true; + } +#endif + if ((pChan->KeepBuffer()) || (!m_pUserSock)) { + pChan->AddBuffer(":" + sNickMask + " NOTICE " + sChan + " :" + sMessage); + } + } + + return false; +} + +bool CIRCSock::OnChanMsg(const string& sNickMask, const string& sChan, string& sMessage) { + CChan* pChan = m_pUser->FindChan(sChan); + if (pChan) { +#ifdef _MODULES + if (m_pUser->GetModules().OnChanMsg(sNickMask, *pChan, sMessage)) { + return true; + } +#endif + if ((pChan->KeepBuffer()) || (!m_pUserSock)) { + pChan->AddBuffer(":" + sNickMask + " PRIVMSG " + sChan + " :" + sMessage); + } + } + + return false; +} + +void CIRCSock::UserConnected(CUserSock* pUserSock) { + if (m_pUserSock) { + m_pUserSock->BouncedOff(); + } + + m_pUserSock = pUserSock; + + if (m_RawBuffer.IsEmpty()) { + PutUser(":irc.znc.com 001 " + m_pUserSock->GetNick() + " :- Welcome to ZNC -"); + } else { + unsigned int uIdx = 0; + string sLine; + + while (m_RawBuffer.GetLine(GetNick(), sLine, uIdx++)) { + PutUser(sLine); + } + } + + PutServ("MOTD"); + + const vector& vChans = m_pUser->GetChans(); + for (unsigned int a = 0; a < vChans.size(); a++) { + if (vChans[a]->IsOn()) { + PutUser(":" + m_Nick.GetNickMask() + " JOIN :" + vChans[a]->GetName()); + PutServ("NAMES " + vChans[a]->GetName()); + } + } + + //PutUser(""); + + string sBufLine; + while (m_QueryBuffer.GetNextLine(GetNick(), sBufLine)) { + PutUser(sBufLine); + } +} + +void CIRCSock::UserDisconnected() { + m_pUserSock = NULL; +} + +void CIRCSock::PutServ(const string& sLine) { + DEBUG_ONLY(cout << GetSockName() << " -> [" << sLine << "]" << endl); + Write(sLine + "\r\n"); +} + +void CIRCSock::PutUser(const string& sLine) { + if (m_pUserSock) { + m_pUserSock->PutServ(sLine); + } +} + +void CIRCSock::PutStatus(const string& sLine) { + if (m_pUserSock) { + m_pUserSock->PutStatus(sLine); + } +} + +void CIRCSock::SetNick(const string& sNick) { + m_Nick.SetNick(sNick); + + if (m_pUserSock) { + m_pUserSock->SetNick(sNick); + } +} + +void CIRCSock::Connected() { + DEBUG_ONLY(cout << GetSockName() << " == Connected()" << endl); + + CUserSock* pUserSock = (CUserSock*) m_pZNC->FindSockByName("USR::" + m_pUser->GetUserName()); + + if (pUserSock) { + m_pUserSock = pUserSock; + pUserSock->IRCConnected(this); + } + + if (!m_sPass.empty()) { + PutServ("PASS " + m_sPass); + } + + PutServ("NICK " + m_pUser->GetNick()); + PutServ("USER " + m_pUser->GetIdent() + " \"" + m_pUser->GetIdent() + "\" \"" + m_pUser->GetIdent() + "\" :" + m_pUser->GetRealName()); +} + +void CIRCSock::Disconnected() { + DEBUG_ONLY(cout << GetSockName() << " == Disconnected()" << endl); + m_pUser->PutStatus("Disconnected from IRC. Reconnecting..."); + +#ifdef _MODULES + m_pUser->GetModules().OnIRCDisconnected(); +#endif + + const vector& vChans = m_pUser->GetChans(); + for (unsigned int a = 0; a < vChans.size(); a++) { + vChans[a]->SetIsOn(false); + } + + if (m_pUserSock) { + m_pUserSock->IRCDisconnected(); + m_pUserSock = NULL; + } +} + +void CIRCSock::SockError(int iErrno) { + DEBUG_ONLY(cout << GetSockName() << " == SockError(" << iErrno << ")" << endl); + m_pUser->PutStatus("Disconnected from IRC. Reconnecting..."); +} + +void CIRCSock::Timeout() { + DEBUG_ONLY(cout << GetSockName() << " == Timeout()" << endl); + m_pUser->PutStatus("IRC connection timed out. Reconnecting..."); +} + +void CIRCSock::ConnectionRefused() { + DEBUG_ONLY(cout << GetSockName() << " == ConnectionRefused()" << endl); + m_pUser->PutStatus("Connection Refused. Reconnecting..."); +} + diff --git a/IRCSock.h b/IRCSock.h new file mode 100644 index 00000000..302f4951 --- /dev/null +++ b/IRCSock.h @@ -0,0 +1,72 @@ +#ifndef _IRCSOCK_H +#define _IRCSOCK_H + +#include "main.h" +#include "User.h" +#include "Chan.h" +#include "Buffer.h" + +// Forward Declarations +class CZNC; +class CUserSock; +// !Forward Declarations + +class CIRCSock : public Csock { +public: + CIRCSock(CZNC* pZNC, CUser* pUser); + virtual ~CIRCSock(); + + // Message Handlers + bool OnCTCPReply(const string& sNickMask, string& sMessage); + bool OnPrivCTCP(const string& sNickMask, string& sMessage); + bool OnChanCTCP(const string& sNickMask, const string& sChan, string& sMessage); + bool OnPrivMsg(const string& sNickMask, string& sMessage); + bool OnChanMsg(const string& sNickMask, const string& sChan, string& sMessage); + bool OnPrivNotice(const string& sNickMask, string& sMessage); + bool OnChanNotice(const string& sNickMask, const string& sChan, string& sMessage); + // !Message Handlers + + virtual void ReadLine(const string& sData); + virtual void Connected(); + virtual void Disconnected(); + virtual void ConnectionRefused(); + virtual void SockError(int iErrno); + virtual void Timeout(); + + void KeepNick(); + bool IsUserAttached() { return (m_pUserSock != NULL); } + void UserConnected(CUserSock* pUserSock); + void UserDisconnected(); + void PutServ(const string& sLine); + void PutUser(const string& sLine); + void PutStatus(const string& sLine); + + // Setters + void SetPass(const string& s) { m_sPass = s; } + // !Setters + + // Getters + string GetNickMask() const { + return m_Nick.GetNickMask(); + } + const string& GetNick() const { return m_Nick.GetNick(); } + const string& GetPass() const { return m_sPass; } + // !Getters +private: + void SetNick(const string& sNick); +protected: + bool m_bISpoofReleased; + bool m_bAuthed; + bool m_bKeepNick; + CZNC* m_pZNC; + CUser* m_pUser; + CNick m_Nick; + string m_sPass; + CBuffer m_RawBuffer; + CUserSock* m_pUserSock; + map m_msChans; + unsigned int m_uQueryBufferCount; + CBuffer m_QueryBuffer; +}; + +#endif // !_IRCSOCK_H diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..14f043b0 --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +CXX=g++ +TARGET=linux-gnu +MACHINE=i686 +CXXFLAGS=-s -O2 -fomit-frame-pointer -D_MODULES -Wall -D_GNU_SOURCE -DHAVE_LIBSSL +#CXXFLAGS=-Wall -ggdb -D_DEBUG -D_MODULES -D_GNU_SOURCE -DHAVE_LIBSSL +INCLUDES= +LIBS=-ldl -lssl -rdynamic + +OBJS=main.o znc.o User.o IRCSock.o UserSock.o DCCBounce.o DCCSock.o Chan.o Nick.o Server.o Modules.o md5.o Buffer.o Utils.o +SRCS=main.cpp znc.cpp User.cpp IRCSock.cpp UserSock.cpp DCCBounce.cpp DCCSock.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp md5.cpp Buffer.cpp Utils.cpp + +all: znc + +depend:: + cat /dev/null >.depend + g++ -M $(CXXFLAGS) $(SRCS) $(INCLUDES) >.depend + +znc: $(OBJS) + $(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $(LIBS) $(OBJS) + +znc-static: $(OBJS) + $(CXX) $(CXXFLAGS) -static -o $@ $(INCLUDES) $(OBJS) $(LIBS) + strip $@ + +clean: + rm -rf znc znc-static *.o core core.* + +include .depend + +.cpp.o: + $(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $*.o $*.cpp diff --git a/Modules.cpp b/Modules.cpp new file mode 100644 index 00000000..2c05fa0e --- /dev/null +++ b/Modules.cpp @@ -0,0 +1,594 @@ +#include "main.h" +#include "Modules.h" +#include "Utils.h" +#include "User.h" +#include "Nick.h" +#include "Chan.h" + +/////////////////// Timer /////////////////// +CTimer::CTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription) : CCron() { + SetName(sLabel); + m_sDescription = sDescription; + m_pModule = pModule; + + if (uCycles) { + StartMaxCycles(uInterval, uCycles); + } else { + Start(uInterval); + } +} + +CTimer::~CTimer() { + m_pModule->UnlinkTimer(this); +} + +void CTimer::SetModule(CModule* p) { m_pModule = p; } +void CTimer::SetDescription(const string& s) { m_sDescription = s; } +CModule* CTimer::GetModule() const { return m_pModule; } +const string& CTimer::GetDescription() const { return m_sDescription; } +/////////////////// !Timer /////////////////// + +CModule::CModule(void* pDLL, CUser* pUser, const string& sModName) { + m_pDLL = pDLL; + m_pManager = pUser->GetManager(); + m_pUser = pUser; + m_sModName = sModName; +} + +CModule::~CModule() { + while (m_vTimers.size()) { + RemTimer(m_vTimers[0]->GetName()); + } +} + +bool CModule::AddTimer(CTimer* pTimer) { + if ((!pTimer) || (FindTimer(pTimer->GetName()))) { + delete pTimer; + return false; + } + + m_pManager->AddCron(pTimer); + m_vTimers.push_back(pTimer); + return true; +} + +bool CModule::RemTimer(const string& sLabel) { + for (unsigned int a = 0; a < m_vTimers.size(); a++) { + CTimer* pTimer = m_vTimers[a]; + + if (strcasecmp(pTimer->GetName().c_str(), sLabel.c_str()) == 0) { + m_vTimers.erase(m_vTimers.begin() +a); + m_pManager->DelCronByAddr(pTimer); + return true; + } + } + + return false; +} + +bool CModule::UnlinkTimer(CTimer* pTimer) { + for (unsigned int a = 0; a < m_vTimers.size(); a++) { + if (pTimer == m_vTimers[a]) { + m_vTimers.erase(m_vTimers.begin() +a); + return true; + } + } + + return false; +} + +CTimer* CModule::FindTimer(const string& sLabel) { + for (unsigned int a = 0; a < m_vTimers.size(); a++) { + CTimer* pTimer = m_vTimers[a]; + if (strcasecmp(pTimer->GetName().c_str(), sLabel.c_str()) == 0) { + return pTimer; + } + } + + return NULL; +} + +void CModule::ListTimers() { + if (!m_vTimers.size()) { + PutModule("You have no timers running."); + return; + } + + CTable Table; + Table.AddColumn("Name"); + Table.AddColumn("Secs"); + Table.AddColumn("Cycles"); + Table.AddColumn("Description"); + + for (unsigned int a = 0; a < m_vTimers.size(); a++) { + CTimer* pTimer = (CTimer*) m_vTimers[a]; + unsigned int uCycles = pTimer->GetCyclesLeft(); + + Table.AddRow(); + Table.SetCell("Name", pTimer->GetName()); + Table.SetCell("Secs", CUtils::ToString(pTimer->GetInterval())); + Table.SetCell("Cycles", ((uCycles) ? CUtils::ToString(uCycles) : "INF")); + Table.SetCell("Description", pTimer->GetDescription()); + } + + if (Table.size()) { + unsigned int uTableIdx = 0; + string sLine; + + while (Table.GetLine(uTableIdx++, sLine)) { + PutModule(sLine); + } + } +} + +const string& CModule::GetModName() { return m_sModName; } +string CModule::GetModNick() { return ((m_pUser) ? m_pUser->GetStatusPrefix() : "*") + m_sModName; } + +string CModule::GetDescription() { return "Unknown"; } + +bool CModule::OnLoad(const string& sArgs) { return true; } +bool CModule::OnBoot() { return true; } +void CModule::OnUserAttached() {} +void CModule::OnUserDetached() {} +void CModule::OnIRCDisconnected() {} +void CModule::OnIRCConnected() {} + +bool CModule::OnDCCUserSend(const CNick& RemoteNick, unsigned long uLongIP, unsigned short uPort, const string& sFile, unsigned long uFileSize) { return false; } + +void CModule::OnOp(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) {} +void CModule::OnDeop(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) {} +void CModule::OnVoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) {} +void CModule::OnDevoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) {} +void CModule::OnRawMode(const CNick& OpNick, const CChan& Channel, const string& sModes, const string& sArgs) {} + +bool CModule::OnUserRaw(string& sLine) { return false; } +bool CModule::OnRaw(string& sLine) { return false; } + +bool CModule::OnStatusCommand(const string& sCommand) { return false; } +void CModule::OnModCommand(const string& sCommand) {} +void CModule::OnModNotice(const string& sMessage) {} +void CModule::OnModCTCP(const string& sMessage) {} + +void CModule::OnQuit(const CNick& Nick, const string& sMessage) {} +void CModule::OnNick(const CNick& Nick, const string& sNewNick) {} +void CModule::OnKick(const CNick& Nick, const string& sKickedNick, const CChan& Channel, const string& sMessage) {} +void CModule::OnJoin(const CNick& Nick, const CChan& Channel) {} +void CModule::OnPart(const CNick& Nick, const CChan& Channel) {} + +bool CModule::OnUserCTCPReply(const CNick& Nick, string& sMessage) { return false; } +bool CModule::OnCTCPReply(const CNick& Nick, string& sMessage) { return false; } +bool CModule::OnUserCTCP(const string& sTarget, string& sMessage) { return false; } +bool CModule::OnPrivCTCP(const CNick& Nick, string& sMessage) { return false; } +bool CModule::OnChanCTCP(const CNick& Nick, const CChan& Channel, string& sMessage) { return false; } +bool CModule::OnUserMsg(const string& sTarget, string& sMessage) { return false; } +bool CModule::OnPrivMsg(const CNick& Nick, string& sMessage) { return false; } +bool CModule::OnChanMsg(const CNick& Nick, const CChan& Channel, string& sMessage) { return false; } +bool CModule::OnUserNotice(const string& sTarget, string& sMessage) { return false; } +bool CModule::OnPrivNotice(const CNick& Nick, string& sMessage) { return false; } +bool CModule::OnChanNotice(const CNick& Nick, const CChan& Channel, string& sMessage) { return false; } + +void* CModule::GetDLL() { return m_pDLL; } +bool CModule::PutIRC(const string& sLine) { + return (m_pUser) ? m_pUser->PutIRC(sLine) : false; +} +bool CModule::PutUser(const string& sLine) { + return (m_pUser) ? m_pUser->PutUser(sLine) : false; +} +bool CModule::PutStatus(const string& sLine) { + return (m_pUser) ? m_pUser->PutStatus(sLine) : false; +} +bool CModule::PutModule(const string& sLine, const string& sIdent, const string& sHost) { + return (m_pUser) ? m_pUser->PutUser(":" + GetModNick() + "!" + sIdent + "@" + sHost + " PRIVMSG " + m_pUser->GetCurNick() + " :" + sLine) : false; +} +bool CModule::PutModNotice(const string& sLine, const string& sIdent, const string& sHost) { + return (m_pUser) ? m_pUser->PutUser(":" + GetModNick() + "!" + sIdent + "@" + sHost + " NOTICE " + m_pUser->GetCurNick() + " :" + sLine) : false; +} + +CModules::CModules() {} +CModules::~CModules() {} + +void CModules::UnloadAll() { + while (size()) { + string sRetMsg; + string sModName = (*this)[0]->GetModName(); + UnloadModule(sModName, sRetMsg); + } +} + +void CModules::OnIRCConnected() { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnIRCConnected(); + } +} + +bool CModules::OnLoad(const string& sArgs) { + for (unsigned int a = 0; a < size(); a++) { + if (!(*this)[a]->OnLoad(sArgs)) { + return false; + } + } + + return true; +} + +bool CModules::OnBoot() { + for (unsigned int a = 0; a < size(); a++) { + if (!(*this)[a]->OnBoot()) { + return false; + } + } + + return true; +} + +void CModules::OnUserAttached() { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnUserAttached(); + } +} + +void CModules::OnUserDetached() { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnUserDetached(); + } +} + +void CModules::OnIRCDisconnected() { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnIRCDisconnected(); + } +} + +bool CModules::OnDCCUserSend(const CNick& RemoteNick, unsigned long uLongIP, unsigned short uPort, const string& sFile, unsigned long uFileSize) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnDCCUserSend(RemoteNick, uLongIP, uPort, sFile, uFileSize)) { + return true; + } + } + + return false; +} + +void CModules::OnOp(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnOp(OpNick, Nick, Channel, bNoChange); + } +} + +void CModules::OnDeop(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnDeop(OpNick, Nick, Channel, bNoChange); + } +} + +void CModules::OnVoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnVoice(OpNick, Nick, Channel, bNoChange); + } +} + +void CModules::OnDevoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnDevoice(OpNick, Nick, Channel, bNoChange); + } +} + +void CModules::OnRawMode(const CNick& OpNick, const CChan& Channel, const string& sModes, const string& sArgs) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnRawMode(OpNick, Channel, sModes, sArgs); + } +} + +bool CModules::OnRaw(string& sLine) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnRaw(sLine)) { + return true; + } + } + + return false; +} + +bool CModules::OnUserRaw(string& sLine) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnUserRaw(sLine)) { + return true; + } + } + + return false; +} + +void CModules::OnQuit(const CNick& Nick, const string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnQuit(Nick, sMessage); + } +} + +void CModules::OnNick(const CNick& Nick, const string& sNewNick) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnNick(Nick, sNewNick); + } +} + +void CModules::OnKick(const CNick& Nick, const string& sKickedNick, const CChan& Channel, const string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnKick(Nick, sKickedNick, Channel, sMessage); + } +} + +void CModules::OnJoin(const CNick& Nick, const CChan& Channel) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnJoin(Nick, Channel); + } +} + +void CModules::OnPart(const CNick& Nick, const CChan& Channel) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnPart(Nick, Channel); + } +} + +bool CModules::OnUserCTCP(const string& sTarget, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnUserCTCP(sTarget, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnUserCTCPReply(const CNick& Nick, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnUserCTCPReply(Nick, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnCTCPReply(const CNick& Nick, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnCTCPReply(Nick, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnPrivCTCP(const CNick& Nick, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnPrivCTCP(Nick, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnChanCTCP(const CNick& Nick, const CChan& Channel, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnChanCTCP(Nick, Channel, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnUserMsg(const string& sTarget, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnUserMsg(sTarget, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnPrivMsg(const CNick& Nick, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnPrivMsg(Nick, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnChanMsg(const CNick& Nick, const CChan& Channel, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnChanMsg(Nick, Channel, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnUserNotice(const string& sTarget, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnUserNotice(sTarget, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnPrivNotice(const CNick& Nick, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnPrivNotice(Nick, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnChanNotice(const CNick& Nick, const CChan& Channel, string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnChanNotice(Nick, Channel, sMessage)) { + return true; + } + } + + return false; +} + +bool CModules::OnStatusCommand(const string& sCommand) { + for (unsigned int a = 0; a < size(); a++) { + if ((*this)[a]->OnStatusCommand(sCommand)) { + return true; + } + } + + return false; +} + +void CModules::OnModCommand(const string& sCommand) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnModCommand(sCommand); + } +} + +void CModules::OnModNotice(const string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnModNotice(sMessage); + } +} + +void CModules::OnModCTCP(const string& sMessage) { + for (unsigned int a = 0; a < size(); a++) { + (*this)[a]->OnModCTCP(sMessage); + } +} + +CModule* CModules::FindModule(const string& sModule) { + for (unsigned int a = 0; a < size(); a++) { + if (strcasecmp(sModule.c_str(), (*this)[a]->GetModName().c_str()) == 0) { + return (*this)[a]; + } + } + + return NULL; +} + +bool CModules::LoadModule(const string& sModule, const string& sArgs, CUser* pUser, const string& sPath, string& sRetMsg) { + sRetMsg = ""; + + if (!pUser) { + sRetMsg = "Unable to load module [" + sModule + "] Internal Error 1."; + return false; + } + + for (unsigned int a = 0; a < sModule.length(); a++) { + if (((sModule[a] < '0') || (sModule[a] > '9')) && ((sModule[a] < 'a') || (sModule[a] > 'z')) && ((sModule[a] < 'A') || (sModule[a] > 'Z')) && (sModule[a] != '_')) { + sRetMsg = "Unable to load module [" + sModule + "] module names can only be letters, numbers, or underscores."; + return false; + } + } + + if (FindModule(sModule) != NULL) { + sRetMsg = "Module [" + sModule + "] already loaded."; + return false; + } + + void* p = dlopen((pUser->GetModPath() + "/" + sModule + ".so").c_str(), RTLD_LAZY); + + if (!p) { + sRetMsg = "Unable to load module [" + sModule + "] [" + dlerror() + "]"; + return false; + } + + typedef double (*fpp)(); + fpp Version = (fpp) dlsym(p, "GetVersion"); + if (!Version) { + dlclose(p); + sRetMsg = "Could not find Version() in module [" + sModule + "]"; + return false; + } + + if (CModule::GetVersion() != Version()) { + dlclose(p); + sRetMsg = "Version mismatch in module [" + sModule + "] - You need to recompile your modules using this version of znc."; + return false; + } + + typedef CModule* (*fp)(void*, CUser* pUser, const string& sModName); + fp Load = (fp) dlsym(p, "Load"); + + if (!Load) { + dlclose(p); + sRetMsg = "Could not find Load() in module [" + sModule + "]"; + return false; + } + + CModule* pModule = Load(p, pUser, sModule); + push_back(pModule); + + if (!pModule->OnLoad(sArgs)) { + UnloadModule(sModule, sRetMsg); + sRetMsg = "Module [" + sModule + "] aborted."; + return false; + } + + sRetMsg = "Loaded module [" + sModule + "]"; + return true; +} + +bool CModules::UnloadModule(const string& sModule, string& sRetMsg) { + CModule* pModule = FindModule(sModule); + sRetMsg = ""; + + if (!pModule) { + sRetMsg = "Module [" + sModule + "] not loaded."; + return false; + } + + void* p = pModule->GetDLL(); + + if (p) { + typedef void (*fp)(CModule*); + fp Unload = (fp)dlsym(p, "Unload"); + + if (Unload) { + Unload(pModule); + + for (iterator it = begin(); it != end(); it++) { + if (*it == pModule) { + erase(it); + break; + } + } + + dlclose(p); + sRetMsg = "Module [" + sModule + "] unloaded"; + + return true; + } else { + sRetMsg = "Unable to unload module [" + sModule + "] could not find Unload()"; + return false; + } + } + + sRetMsg = "Unable to unload module [" + sModule + "]"; + return false; +} + +bool CModules::ReloadModule(const string& sModule, const string& sArgs, CUser* pUser, const string& sPath, string& sRetMsg) { + sRetMsg = ""; + if (!UnloadModule(sModule, sRetMsg)) { + return false; + } + + if (!LoadModule(sModule, sArgs, pUser, sPath, sRetMsg)) { + return false; + } + + sRetMsg = "Reloaded module [" + sModule + "]"; + return true; +} diff --git a/Modules.h b/Modules.h new file mode 100644 index 00000000..7c1eadcd --- /dev/null +++ b/Modules.h @@ -0,0 +1,174 @@ +#ifndef _MODULES_H +#define _MODULES_H + +#include +#include +#include +using std::vector; +using std::string; + +#define VERSION 0.022 +#define MODULEDEFS(CLASS) extern "C" { CModule* Load(void* p, CUser* pUser, const string& sModName); void Unload(CModule* pMod); double GetVersion(); } double GetVersion() { return VERSION; } CModule* Load(void* p, CUser* pUser, const string& sModName) { return new CLASS(p, pUser, sModName); } void Unload(CModule* pMod) { if (pMod) { delete pMod; } } +#define MODCONSTRUCTOR(CLASS) CLASS(void *pDLL, CUser* pUser, const string& sModName) : CModule(pDLL, pUser, sModName) + +// Forward Declarations +class CUser; +class CNick; +class CChan; +class Csock; +class CModule; +template class TSocketManager; +// !Forward Declarations + +class CTimer : public CCron { +public: + CTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription); + + virtual ~CTimer(); + + // Setters + void SetModule(CModule* p); + void SetDescription(const string& s); + // !Setters + + // Getters + CModule* GetModule() const; + const string& GetDescription() const; + // !Getters +private: +protected: + CModule* m_pModule; + string m_sDescription; +}; + +class CModule { +public: + CModule(void* pDLL, CUser* pUser, const string& sModName); + virtual ~CModule(); + + virtual string GetDescription(); + + virtual bool OnLoad(const string& sArgs); + virtual bool OnBoot(); + virtual void OnUserAttached(); + virtual void OnUserDetached(); + virtual void OnIRCDisconnected(); + virtual void OnIRCConnected(); + + virtual bool OnDCCUserSend(const CNick& RemoteNick, unsigned long uLongIP, unsigned short uPort, const string& sFile, unsigned long uFileSize); + + virtual void OnOp(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnDeop(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnVoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnDevoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnRawMode(const CNick& OpNick, const CChan& Channel, const string& sModes, const string& sArgs); + + virtual bool OnUserRaw(string& sLine); + virtual bool OnRaw(string& sLine); + + virtual bool OnStatusCommand(const string& sCommand); + virtual void OnModCommand(const string& sCommand); + virtual void OnModNotice(const string& sMessage); + virtual void OnModCTCP(const string& sMessage); + + virtual void OnQuit(const CNick& Nick, const string& sMessage); + virtual void OnNick(const CNick& Nick, const string& sNewNick); + virtual void OnKick(const CNick& Nick, const string& sOpNick, const CChan& Channel, const string& sMessage); + virtual void OnJoin(const CNick& Nick, const CChan& Channel); + virtual void OnPart(const CNick& Nick, const CChan& Channel); + + virtual bool OnUserCTCPReply(const CNick& Nick, string& sMessage); + virtual bool OnCTCPReply(const CNick& Nick, string& sMessage); + virtual bool OnUserCTCP(const string& sTarget, string& sMessage); + virtual bool OnPrivCTCP(const CNick& Nick, string& sMessage); + virtual bool OnChanCTCP(const CNick& Nick, const CChan& Channel, string& sMessage); + virtual bool OnUserMsg(const string& sTarget, string& sMessage); + virtual bool OnPrivMsg(const CNick& Nick, string& sMessage); + virtual bool OnChanMsg(const CNick& Nick, const CChan& Channel, string& sMessage); + virtual bool OnUserNotice(const string& sTarget, string& sMessage); + virtual bool OnPrivNotice(const CNick& Nick, string& sMessage); + virtual bool OnChanNotice(const CNick& Nick, const CChan& Channel, string& sMessage); + + void * GetDLL(); + static double GetVersion() { return VERSION; } + + virtual bool PutIRC(const string& sLine); + virtual bool PutUser(const string& sLine); + virtual bool PutStatus(const string& sLine); + virtual bool PutModule(const string& sLine, const string& sIdent = "znc", const string& sHost = "znc.com"); + virtual bool PutModNotice(const string& sLine, const string& sIdent = "znc", const string& sHost = "znc.com"); + + const string& GetModName(); + string GetModNick(); + + // Timer stuff + bool AddTimer(CTimer* pTimer); + bool RemTimer(const string& sLabel); + bool UnlinkTimer(CTimer* pTimer); + CTimer* FindTimer(const string& sLabel); + virtual void ListTimers(); + // !Timer stuff + +protected: + vector m_vTimers; + void* m_pDLL; + TSocketManager* m_pManager; + CUser* m_pUser; + string m_sModName; +}; + +class CModules : public vector { +public: + CModules(); + virtual ~CModules(); + + void UnloadAll(); + + virtual bool OnLoad(const string& sArgs); + virtual bool OnBoot(); + virtual void OnUserAttached(); + virtual void OnUserDetached(); + virtual void OnIRCDisconnected(); + virtual void OnIRCConnected(); + + virtual bool OnDCCUserSend(const CNick& RemoteNick, unsigned long uLongIP, unsigned short uPort, const string& sFile, unsigned long uFileSize); + + virtual void OnOp(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnDeop(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnVoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnDevoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange); + virtual void OnRawMode(const CNick& OpNick, const CChan& Channel, const string& sModes, const string& sArgs); + + virtual bool OnUserRaw(string& sLine); + virtual bool OnRaw(string& sLine); + + virtual bool OnStatusCommand(const string& sCommand); + virtual void OnModCommand(const string& sCommand); + virtual void OnModNotice(const string& sMessage); + virtual void OnModCTCP(const string& sMessage); + + virtual void OnQuit(const CNick& Nick, const string& sMessage); + virtual void OnNick(const CNick& Nick, const string& sNewNick); + virtual void OnKick(const CNick& Nick, const string& sOpNick, const CChan& Channel, const string& sMessage); + virtual void OnJoin(const CNick& Nick, const CChan& Channel); + virtual void OnPart(const CNick& Nick, const CChan& Channel); + + virtual bool OnUserCTCPReply(const CNick& Nick, string& sMessage); + virtual bool OnCTCPReply(const CNick& Nick, string& sMessage); + virtual bool OnUserCTCP(const string& sTarget, string& sMessage); + virtual bool OnPrivCTCP(const CNick& Nick, string& sMessage); + virtual bool OnChanCTCP(const CNick& Nick, const CChan& Channel, string& sMessage); + virtual bool OnUserMsg(const string& sTarget, string& sMessage); + virtual bool OnPrivMsg(const CNick& Nick, string& sMessage); + virtual bool OnChanMsg(const CNick& Nick, const CChan& Channel, string& sMessage); + virtual bool OnUserNotice(const string& sTarget, string& sMessage); + virtual bool OnPrivNotice(const CNick& Nick, string& sMessage); + virtual bool OnChanNotice(const CNick& Nick, const CChan& Channel, string& sMessage); + + CModule* FindModule(const string& sModule); + bool LoadModule(const string& sModule, const string& sArgs, CUser* pUser, const string& sPath, string& sRetMsg); + bool UnloadModule(const string& sModule, string& sRetMsg); + bool ReloadModule(const string& sModule, const string& sArgs, CUser* pUser, const string& sPath, string& sRetMsg); +}; + +#endif // !_MODULES_H diff --git a/Nick.cpp b/Nick.cpp new file mode 100644 index 00000000..d29cae7d --- /dev/null +++ b/Nick.cpp @@ -0,0 +1,62 @@ +#include "Nick.h" + +CNick::CNick() { + m_bIsOp = false; + m_bIsVoice = false; +} + +CNick::CNick(const string& sNick) { + Parse(sNick); + m_bIsOp = false; + m_bIsVoice = false; +} + +CNick::~CNick() {} + +void CNick::Parse(const string& sNickMask) { + if (sNickMask.empty()) { + return; + } + + unsigned int uPos = sNickMask.find('!'); + + if (uPos == string::npos) { + m_sNick = sNickMask.substr((sNickMask[0] == ':')); + return; + } + + m_sNick = sNickMask.substr((sNickMask[0] == ':'), uPos); + m_sHost = sNickMask.substr(uPos +1); + + if ((uPos = m_sHost.find('@')) != string::npos) { + m_sIdent = m_sHost.substr(0, uPos); + m_sHost = m_sHost.substr(uPos +1); + } +} + +void CNick::SetNick(const string& s) { m_sNick = s; } +void CNick::SetIdent(const string& s) { m_sIdent = s; } +void CNick::SetHost(const string& s) { m_sHost = s; } +void CNick::SetOp(bool b) { m_bIsOp = b; } +void CNick::SetVoice(bool b) { m_bIsVoice = b; } + +bool CNick::IsOp() const { return m_bIsOp; } +bool CNick::IsVoice() const { return m_bIsVoice; } +const string& CNick::GetNick() const { return m_sNick; } +const string& CNick::GetIdent() const { return m_sIdent; } +const string& CNick::GetHost() const { return m_sHost; } +string CNick::GetNickMask() const { return m_sNick + "!" + m_sIdent + "@" + m_sHost; } + +string CNick::GetHostMask() const { + string sRet = m_sNick; + + if (!m_sIdent.empty()) { + sRet += "!" + m_sIdent; + } + + if (!m_sHost.empty()) { + sRet += "@" + m_sHost; + } + + return (sRet); +} diff --git a/Nick.h b/Nick.h new file mode 100644 index 00000000..b97227bf --- /dev/null +++ b/Nick.h @@ -0,0 +1,44 @@ +#ifndef _NICK_H +#define _NICK_H + +#include +using std::string; + +class CNick +{ +public: + CNick(); + CNick(const string& sNick); + virtual ~CNick(); + + void Parse(const string& sNickMask); + string GetHostMask() const; + + // Setters + void SetNick(const string& s); + void SetIdent(const string& s); + void SetHost(const string& s); + void SetOp(bool b); + void SetVoice(bool b); + // !Setters + + // Getters + bool IsOp() const; + bool IsVoice() const; + const string& GetNick() const; + const string& GetIdent() const; + const string& GetHost() const; + string GetNickMask() const; + // !Getters +private: +protected: + string m_sNick; + string m_sIdent; + string m_sHost; + + bool m_bIsOp; + bool m_bIsVoice; +}; + +#endif // !_NICK_H + diff --git a/Server.cpp b/Server.cpp new file mode 100644 index 00000000..f12b0c19 --- /dev/null +++ b/Server.cpp @@ -0,0 +1,17 @@ +#include "main.h" +#include "Server.h" + +CServer::CServer(const string& sName, unsigned short uPort, const string& sPass, bool bSSL) { + m_sName = sName; + m_uPort = (uPort) ? uPort : 6667; + m_sPass = sPass; + m_bSSL = bSSL; +} + +CServer::~CServer() {} + +const string& CServer::GetName() const { return m_sName; } +unsigned short CServer::GetPort() const { return m_uPort; } +const string& CServer::GetPass() const { return m_sPass; } +bool CServer::IsSSL() const { return m_bSSL; } + diff --git a/Server.h b/Server.h new file mode 100644 index 00000000..802abdb2 --- /dev/null +++ b/Server.h @@ -0,0 +1,23 @@ +#ifndef _SERVER_H +#define _SERVER_H + +#include + +class CServer { +public: + CServer(const string& sName, unsigned short uPort = 6667, const string& sPass = "", bool bSSL = false); + virtual ~CServer(); + + const string& GetName() const; + unsigned short GetPort() const; + const string& GetPass() const; + bool IsSSL() const; +private: +protected: + string m_sName; + unsigned short m_uPort; + string m_sPass; + bool m_bSSL; +}; + +#endif // !_SERVER_H diff --git a/Timers.h b/Timers.h new file mode 100644 index 00000000..22751f3a --- /dev/null +++ b/Timers.h @@ -0,0 +1,49 @@ +#include "main.h" + +class CKeepNickTimer : public CCron { +public: + CKeepNickTimer(CUser* pUser) : CCron() { + m_pUser = pUser; + Start(5); + } + virtual ~CKeepNickTimer() {} + +private: +protected: + virtual void RunJob() { + CIRCSock* pSock = m_pUser->GetIRCSock(); + if (pSock) { + pSock->KeepNick(); + } + } + + CUser* m_pUser; +}; + +class CJoinTimer : public CCron { +public: + CJoinTimer(CUser* pUser) : CCron() { + m_pUser = pUser; + Start(20); + } + virtual ~CJoinTimer() {} + +private: +protected: + virtual void RunJob() { + CIRCSock* pSock = m_pUser->GetIRCSock(); + + if (pSock) { + const vector& vChans = m_pUser->GetChans(); + + for (unsigned int a = 0; a < vChans.size(); a++) { + if (!vChans[a]->IsOn()) { + pSock->PutServ("JOIN " + vChans[a]->GetName() + " " + vChans[a]->GetKey()); + } + } + } + } + + CUser* m_pUser; +}; + diff --git a/User.cpp b/User.cpp new file mode 100644 index 00000000..7de493f1 --- /dev/null +++ b/User.cpp @@ -0,0 +1,397 @@ +#include "main.h" +#include "znc.h" +#include "User.h" +#include "Server.h" +#include "IRCSock.h" +#include "UserSock.h" +#include "DCCBounce.h" +#include "DCCSock.h" +#include "md5.h" +#include "Timers.h" + +CUser::CUser(const string& sUserName, CZNC* pZNC) { + m_sUserName = sUserName; + m_sNick = sUserName; + m_sIdent = sUserName; + m_sRealName = sUserName; + m_uServerIdx = 0; + m_pZNC = pZNC; + m_bPassHashed = false; + m_bUseClientIP = false; + m_bKeepNick = false; + m_bDenyLoadMod = false; + m_sStatusPrefix = "*"; + m_pZNC->GetManager().AddCron(new CKeepNickTimer(this)); + m_pZNC->GetManager().AddCron(new CJoinTimer(this)); +} + +CUser::~CUser() { +#ifdef _MODULES + m_Modules.UnloadAll(); +#endif + for (unsigned int a = 0; a < m_vServers.size(); a++) { + delete m_vServers[a]; + } + + for (unsigned int b = 0; b < m_vChans.size(); b++) { + delete m_vChans[b]; + } +} + +bool CUser::OnBoot() { +#ifdef _MODULES + return m_Modules.OnBoot(); +#endif + return true; +} + +bool CUser::AddAllowedHost(const string& sHostMask) { + if (m_ssAllowedHosts.find(sHostMask) != m_ssAllowedHosts.end()) { + return false; + } + + m_ssAllowedHosts.insert(sHostMask); + return true; +} + +bool CUser::IsHostAllowed(const string& sHostMask) { + for (set::iterator a = m_ssAllowedHosts.begin(); a != m_ssAllowedHosts.end(); a++) { + if (CUtils::WildCmp(*a, sHostMask)) { + return true; + } + } + + return false; +} + +bool CUser::AddChan(CChan* pChan) { + if (!pChan) { + return false; + } + + for (unsigned int a = 0; a < m_vChans.size(); a++) { + if (strcasecmp(m_vChans[a]->GetName().c_str(), pChan->GetName().c_str()) == 0) { + delete pChan; + return false; + } + } + + m_vChans.push_back(pChan); + return true; +} + +bool CUser::AddChan(const string& sName, unsigned int uBufferCount) { + if (sName.empty()) { + return false; + } + + for (unsigned int a = 0; a < m_vChans.size(); a++) { + if (strcasecmp(sName.c_str(), m_vChans[a]->GetName().c_str()) == 0) { + return false; + } + } + + CChan* pChan = new CChan(sName, this, uBufferCount); + m_vChans.push_back(pChan); + return true; +} + +bool CUser::DelChan(const string& sName) { + for (vector::iterator a = m_vChans.begin(); a != m_vChans.end(); a++) { + if (strcasecmp(sName.c_str(), (*a)->GetName().c_str()) == 0) { + m_vChans.erase(a); + return true; + } + } + + return false; +} + +CChan* CUser::FindChan(const string& sName) { + for (unsigned int a = 0; a < m_vChans.size(); a++) { + if (strcasecmp(sName.c_str(), m_vChans[a]->GetName().c_str()) == 0) { + return m_vChans[a]; + } + } + + return NULL; +} + +bool CUser::AddServer(const string& sName) { + if (sName.empty()) { + return false; + } + + bool bSSL = false; + string sLine = sName; + CUtils::Trim(sLine); + + string sHost = CUtils::Token(sLine, 0); + string sPort = CUtils::Token(sLine, 1); + + if (CUtils::Left(sPort, 1) == "+") { + bSSL = true; + CUtils::LeftChomp(sPort); + } + + unsigned short uPort = strtoul(sPort.c_str(), NULL, 10); + string sPass = CUtils::Token(sLine, 2, true); + + return AddServer(sHost, uPort, sPass, bSSL); +} + +bool CUser::AddServer(const string& sName, unsigned short uPort, const string& sPass, bool bSSL) { +#ifndef HAVE_LIBSSL + if (bSSL) { + return false; + } +#endif + if (sName.empty()) { + return false; + } + + if (!uPort) { + uPort = 6667; + } + + CServer* pServer = new CServer(sName, uPort, sPass, bSSL); + m_vServers.push_back(pServer); + + return true; +} + +CServer* CUser::GetNextServer() { + if (m_vServers.empty()) { + return NULL; + } + + if (m_uServerIdx >= m_vServers.size()) { + m_uServerIdx = 0; + } + + return m_vServers[m_uServerIdx++]; // Todo: cycle through these +} + +bool CUser::CheckPass(const string& sPass) { + if (!m_bPassHashed) { + return (sPass == m_sPass); + } + + return (strcasecmp(m_sPass.c_str(), CMD5(sPass)) == 0); +} + +TSocketManager* CUser::GetManager() { + return &m_pZNC->GetManager(); +} + +CZNC* CUser::GetZNC() { + return m_pZNC; +} + +CUserSock* CUser::GetUserSock() { + // Todo: optimize this by saving a pointer to the sock + return (CUserSock*) m_pZNC->GetManager().FindSockByName("USR::" + m_sUserName); +} + +bool CUser::IsUserAttached() { + CUserSock* pUserSock = GetUserSock(); + + if (!pUserSock) { + return false; + } + + return pUserSock->IsAttached(); +} + +CIRCSock* CUser::GetIRCSock() { + // Todo: optimize this by saving a pointer to the sock + return (CIRCSock*) m_pZNC->GetManager().FindSockByName("IRC::" + m_sUserName); +} + +string CUser::GetLocalIP() { + CIRCSock* pIRCSock = GetIRCSock(); + + if (pIRCSock) { + return pIRCSock->GetLocalIP(); + } + + CUserSock* pUserSock = GetUserSock(); + + if (pUserSock) { + return pUserSock->GetLocalIP(); + } + + return ""; +} + +bool CUser::PutIRC(const string& sLine) { + CIRCSock* pIRCSock = GetIRCSock(); + + if (!pIRCSock) { + return false; + } + + pIRCSock->PutServ(sLine); + return true; +} + +bool CUser::PutUser(const string& sLine) { + CUserSock* pUserSock = GetUserSock(); + + if (!pUserSock) { + return false; + } + + pUserSock->PutServ(sLine); + return true; +} + +bool CUser::PutStatus(const string& sLine) { + CUserSock* pUserSock = GetUserSock(); + + if (!pUserSock) { + return false; + } + + pUserSock->PutStatus(sLine); + return true; +} + +bool CUser::PutModule(const string& sModule, const string& sLine) { + CUserSock* pUserSock = GetUserSock(); + + if (!pUserSock) { + return false; + } + + pUserSock->PutModule(sModule, sLine); + return true; +} + +bool CUser::ResumeFile(const string& sRemoteNick, unsigned short uPort, unsigned long uFileSize) { + TSocketManager& Manager = m_pZNC->GetManager(); + + for (unsigned int a = 0; a < Manager.size(); a++) { + if (strncasecmp(Manager[a]->GetSockName().c_str(), "DCC::LISTEN::", 13) == 0) { + CDCCSock* pSock = (CDCCSock*) Manager[a]; + + if (pSock->GetLocalPort() == uPort) { + if (pSock->Seek(uFileSize)) { + PutModule(pSock->GetModuleName(), "DCC -> [" + pSock->GetRemoteNick() + "][" + pSock->GetFileName() + "] - Attempting to resume from file position [" + CUtils::ToString(uFileSize) + "]"); + return true; + } else { + return false; + } + } + } + } + + return false; +} + +bool CUser::SendFile(const string& sRemoteNick, const string& sFileName, const string& sModuleName) { + string sFullPath = CUtils::ChangeDir(GetDLPath(), sFileName, GetHomePath()); + CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sFullPath, sModuleName); + + CFile* pFile = pSock->OpenFile(false); + + if (!pFile) { + delete pSock; + return false; + } + + int iPort = GetManager()->ListenAllRand("DCC::LISTEN::" + sRemoteNick, false, SOMAXCONN, pSock, 120); + + if (strcasecmp(GetNick().c_str(), sRemoteNick.c_str()) == 0) { + PutUser(":" + GetStatusPrefix() + "status!znc@znc.com PRIVMSG " + sRemoteNick + " :\001DCC SEND " + pFile->GetShortName() + " " + CUtils::ToString(CUtils::GetLongIP(GetLocalIP())) + " " + + CUtils::ToString(iPort) + " " + CUtils::ToString(pFile->GetSize()) + "\001"); + } else { + PutIRC("PRIVMSG " + sRemoteNick + " :\001DCC SEND " + pFile->GetShortName() + " " + CUtils::ToString(CUtils::GetLongIP(GetLocalIP())) + " " + + CUtils::ToString(iPort) + " " + CUtils::ToString(pFile->GetSize()) + "\001"); + } + + PutModule(sModuleName, "DCC -> [" + sRemoteNick + "][" + pFile->GetShortName() + "] - Attempting Send."); + return true; +} + +bool CUser::GetFile(const string& sRemoteNick, const string& sRemoteIP, unsigned short uRemotePort, const string& sFileName, unsigned long uFileSize, const string& sModuleName) { + if (CFile::Exists(sFileName)) { + PutModule(sModuleName, "DCC <- [" + sRemoteNick + "][" + sFileName + "] - File already exists."); + return false; + } + + CDCCSock* pSock = new CDCCSock(this, sRemoteNick, sRemoteIP, uRemotePort, sFileName, uFileSize, sModuleName); + + if (!pSock->OpenFile()) { + delete pSock; + return false; + } + + if (!GetManager()->Connect(sRemoteIP, uRemotePort, "DCC::GET::" + sRemoteNick, 60, false, GetLocalIP(), pSock)) { + PutModule(sModuleName, "DCC <- [" + sRemoteNick + "][" + sFileName + "] - Unable to connect."); + return false; + } + + PutModule(sModuleName, "DCC <- [" + sRemoteNick + "][" + sFileName + "] - Attempting to connect to [" + sRemoteIP + "]"); + return true; +} + +string CUser::GetCurNick() { + CIRCSock* pIRCSock = GetIRCSock(); + + if (pIRCSock) { + return pIRCSock->GetNick(); + } + + CUserSock* pUserSock = GetUserSock(); + + if (pUserSock) { + return pUserSock->GetNick(); + } + + return ""; +} + +// Setters +void CUser::SetNick(const string& s) { m_sNick = s; } +void CUser::SetAltNick(const string& s) { m_sAltNick = s; } +void CUser::SetIdent(const string& s) { m_sIdent = s; } +void CUser::SetRealName(const string& s) { m_sRealName = s; } +void CUser::SetVHost(const string& s) { m_sVHost = s; } +void CUser::SetPass(const string& s, bool bHashed) { m_sPass = s; m_bPassHashed = bHashed; } +void CUser::SetUseClientIP(bool b) { m_bUseClientIP = b; } +void CUser::SetKeepNick(bool b) { m_bKeepNick = b; } +void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; } +void CUser::SetDefaultChanModes(const string& s) { m_sDefaultChanModes = s; } + +bool CUser::SetStatusPrefix(const string& s) { + if ((!s.empty()) && (s.length() < 6) && (s.find(' ') == string::npos)) { + m_sStatusPrefix = s; + return true; + } + + return false; +} +// !Setters + +// Getters +const string& CUser::GetUserName() const { return m_sUserName; } +const string& CUser::GetNick() const { return m_sNick; } +const string& CUser::GetAltNick() const { return m_sAltNick; } +const string& CUser::GetIdent() const { return m_sIdent; } +const string& CUser::GetRealName() const { return m_sRealName; } +const string& CUser::GetVHost() const { return m_sVHost; } +const string& CUser::GetPass() const { return m_sPass; } + +const string& CUser::GetBinPath() const { return m_pZNC->GetBinPath(); } +const string& CUser::GetDLPath() const { return m_pZNC->GetDLPath(); } +const string& CUser::GetModPath() const { return m_pZNC->GetModPath(); } +const string& CUser::GetHomePath() const { return m_pZNC->GetHomePath(); } + +bool CUser::UseClientIP() const { return m_bUseClientIP; } +bool CUser::KeepNick() const { return m_bKeepNick; } +bool CUser::DenyLoadMod() const { return m_bDenyLoadMod; } +const string& CUser::GetStatusPrefix() const { return m_sStatusPrefix; } +const string& CUser::GetDefaultChanModes() const { return m_sDefaultChanModes; } +const vector& CUser::GetChans() const { return m_vChans; } +// !Getters diff --git a/User.h b/User.h new file mode 100644 index 00000000..b62a402c --- /dev/null +++ b/User.h @@ -0,0 +1,124 @@ +#ifndef _USER_H +#define _USER_H + +#ifdef _MODULES +#include "Modules.h" +#endif + +#include +#include + +using std::vector; +using std::set; + +class CZNC; +class CChan; +class CServer; +class CIRCSock; +class CUserSock; + +class CUser { +public: + CUser(const string& sUserName, CZNC* pZNC); + virtual ~CUser(); + + CChan* FindChan(const string& sName); + bool AddChan(CChan* pChan); + bool AddChan(const string& sName, unsigned int uBufferCount = 50); + bool DelChan(const string& sName); + bool AddServer(const string& sName); + bool AddServer(const string& sName, unsigned short uPort, const string& sPass = "", bool bSSL = false); + CServer* GetNextServer(); + bool CheckPass(const string& sPass); + bool AddAllowedHost(const string& sHostMask); + bool IsHostAllowed(const string& sHostMask); + +#ifdef _MODULES + // Modules + CModules& GetModules() { return m_Modules; } + // !Modules +#endif + + bool OnBoot(); + bool IsUserAttached(); + + bool PutIRC(const string& sLine); + bool PutUser(const string& sLine); + bool PutStatus(const string& sLine); + bool PutModule(const string& sModule, const string& sLine); + + string GetLocalIP(); + + bool SendFile(const string& sRemoteNick, const string& sFileName, const string& sModuleName = ""); + bool GetFile(const string& sRemoteNick, const string& sRemoteIP, unsigned short uRemotePort, const string& sFileName, unsigned long uFileSize, const string& sModuleName = ""); + bool ResumeFile(const string& sRemoteNick, unsigned short uPort, unsigned long uFileSize); + string GetCurNick(); + + // Setters + void SetNick(const string& s); + void SetAltNick(const string& s); + void SetIdent(const string& s); + void SetRealName(const string& s); + void SetVHost(const string& s); + void SetPass(const string& s, bool bHashed); + void SetUseClientIP(bool b); + void SetKeepNick(bool b); + void SetDenyLoadMod(bool b); + bool SetStatusPrefix(const string& s); + void SetDefaultChanModes(const string& s); + // !Setters + + // Getters + CUserSock* GetUserSock(); + CIRCSock* GetIRCSock(); + TSocketManager* GetManager(); + CZNC* GetZNC(); + const string& GetUserName() const; + const string& GetNick() const; + const string& GetAltNick() const; + const string& GetIdent() const; + const string& GetRealName() const; + const string& GetVHost() const; + const string& GetPass() const; + + const string& GetBinPath() const; + const string& GetDLPath() const; + const string& GetModPath() const; + const string& GetHomePath() const; + + bool UseClientIP() const; + bool KeepNick() const; + bool DenyLoadMod() const; + const string& GetStatusPrefix() const; + const string& GetDefaultChanModes() const; + const vector& GetChans() const; + // !Getters +private: +protected: + CZNC* m_pZNC; + + string m_sUserName; + string m_sNick; + string m_sAltNick; + string m_sIdent; + string m_sRealName; + string m_sVHost; + string m_sPass; + string m_sStatusPrefix; + string m_sDefaultChanModes; + + bool m_bPassHashed; + bool m_bUseClientIP; + bool m_bKeepNick; + bool m_bDenyLoadMod; + vector m_vServers; + vector m_vChans; + set m_ssAllowedHosts; + unsigned int m_uServerIdx; + +#ifdef _MODULES + CModules m_Modules; +#endif +}; + +#endif // !_USER_H diff --git a/UserSock.cpp b/UserSock.cpp new file mode 100644 index 00000000..a56b528b --- /dev/null +++ b/UserSock.cpp @@ -0,0 +1,846 @@ +#include "main.h" +#include "UserSock.h" +#include "User.h" +#include "znc.h" +#include "IRCSock.h" +#include "DCCBounce.h" +#include "DCCSock.h" + +void CUserSock::ReadLine(const string& sData) { + string sLine = sData; + + while ((CUtils::Right(sLine, 1) == "\r") || (CUtils::Right(sLine, 1) == "\n")) { + CUtils::RightChomp(sLine); + } + + DEBUG_ONLY(cout << GetSockName() << " <- [" << sLine << "]" << endl); + +#ifdef _MODULES + if (m_bAuthed) { + if ((m_pUser) && (m_pUser->GetModules().OnUserRaw(sLine))) { + return; + } + } +#endif + + string sCommand = CUtils::Token(sLine, 0); + + if (strcasecmp(sCommand.c_str(), "ZNC") == 0) { + PutStatus("Hello. How may I help you?"); + return; + } else if (strcasecmp(sCommand.c_str(), "PONG") == 0) { + return; // Block pong replies, we already responded to the pings + } else if (strcasecmp(sCommand.c_str(), "PASS") == 0) { + m_sPass = CUtils::Token(sLine, 1); + + if (m_sPass.find(":") != string::npos) { + m_sUser = CUtils::Token(m_sPass, 0, false, ':'); + m_sPass = CUtils::Token(m_sPass, 1, true, ':'); + } + + m_bGotUser = (!m_sUser.empty()); + + if ((m_bGotNick) && (m_bGotUser)) { + AuthUser(); + } + + return; // Don't forward this msg. ZNC has already registered us. + } else if (strcasecmp(sCommand.c_str(), "NICK") == 0) { + string sNick = CUtils::Token(sLine, 1); + if (CUtils::Left(sNick, 1) == ":") { + CUtils::LeftChomp(sNick); + } + + if (!m_bAuthed) { + m_sNick = sNick; + m_bGotNick = true; + + if (m_bGotUser) { + AuthUser(); + } + return; // Don't forward this msg. The bnc will handle nick changes until auth is complete + } + + if ((m_pUser) && (strcasecmp(sNick.c_str(), m_pUser->GetNick().c_str()) == 0)) { + m_uKeepNickCounter++; + } + } else if (strcasecmp(sCommand.c_str(), "USER") == 0) { + if ((!m_bGotUser) && (!m_bAuthed)) { + m_sUser = CUtils::Token(sLine, 1); + m_bGotUser = true; + + if (m_bGotNick) { + AuthUser(); + } + } + + return; // Don't forward this msg. ZNC has already registered us. + } else if (strcasecmp(sCommand.c_str(), "JOIN") == 0) { + string sChan = CUtils::Token(sLine, 1); + if (CUtils::Left(sChan, 1) == ":") { + CUtils::LeftChomp(sChan); + } + + if (m_pUser) { + CChan* pChan = m_pUser->FindChan(sChan); + + if ((pChan) && (!pChan->IsOn())) { + pChan->IncClientRequests(); + } + } + } else if (strcasecmp(sCommand.c_str(), "QUIT") == 0) { + if (m_pIRCSock) { + m_pIRCSock->UserDisconnected(); + } + + Close(); // Treat a client quit as a detach + return; // Don't forward this msg. We don't want the client getting us disconnected. + } else if (strcasecmp(sCommand.c_str(), "NOTICE") == 0) { + string sTarget = CUtils::Token(sLine, 1); + string sMsg = CUtils::Token(sLine, 2, true); + + if (CUtils::Left(sMsg, 1) == ":") { + CUtils::LeftChomp(sMsg); + } + + if ((!m_pUser) || (strcasecmp(sTarget.c_str(), string(m_pUser->GetStatusPrefix() + "status").c_str())) == 0) { + return; + } + + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { +#ifdef _MODULES + if (m_pUser) { + string sModule = sTarget; + CUtils::LeftChomp(sModule, m_pUser->GetStatusPrefix().length()); + + CModule* pModule = m_pUser->GetModules().FindModule(sModule); + if (pModule) { + pModule->OnModNotice(sMsg); + } else { + PutStatus("No such module [" + sModule + "]"); + } + } +#endif + return; + } + + if (CUtils::WildCmp("DCC * (*)", sMsg.c_str())) { + sMsg = "DCC " + CUtils::Token(sLine, 3) + " (" + ((m_pIRCSock) ? m_pIRCSock->GetLocalIP() : GetLocalIP()) + ")"; + } + + CChan* pChan = m_pUser->FindChan(sTarget); + + if ((pChan) && (pChan->KeepBuffer())) { + pChan->AddBuffer(":" + GetNickMask() + " NOTICE " + sTarget + " :" + sMsg); + } + +#ifdef _MODULES + if (CUtils::WildCmp("\001*\001", sMsg.c_str())) { + string sCTCP = sMsg; + CUtils::LeftChomp(sCTCP); + CUtils::RightChomp(sCTCP); + + if ((m_pUser) && (m_pUser->GetModules().OnUserCTCPReply(sTarget, sCTCP))) { + return; + } + + sMsg = "\001" + sCTCP + "\001"; + } else { + if ((m_pUser) && (m_pUser->GetModules().OnUserNotice(sTarget, sMsg))) { + return; + } + } +#endif + + PutIRC("NOTICE " + sTarget + " :" + sMsg); + return; + } else if (strcasecmp(sCommand.c_str(), "PRIVMSG") == 0) { + string sTarget = CUtils::Token(sLine, 1); + string sMsg = CUtils::Token(sLine, 2, true); + + if (CUtils::Left(sMsg, 1) == ":") { + CUtils::LeftChomp(sMsg); + } + + if (CUtils::WildCmp("\001*\001", sMsg.c_str())) { + string sCTCP = sMsg; + CUtils::LeftChomp(sCTCP); + CUtils::RightChomp(sCTCP); + + if (strncasecmp(sCTCP.c_str(), "DCC ", 4) == 0) { + string sType = CUtils::Token(sCTCP, 1); + string sFile = CUtils::Token(sCTCP, 2); + unsigned long uLongIP = strtoul(CUtils::Token(sCTCP, 3).c_str(), NULL, 10); + unsigned short uPort = strtoul(CUtils::Token(sCTCP, 4).c_str(), NULL, 10); + unsigned long uFileSize = strtoul(CUtils::Token(sCTCP, 5).c_str(), NULL, 10); + string sIP = (m_pIRCSock) ? m_pIRCSock->GetLocalIP() : GetLocalIP(); + + if (!m_pUser->UseClientIP()) { + uLongIP = CUtils::GetLongIP(GetRemoteIP()); + } + + if (strcasecmp(sType.c_str(), "CHAT") == 0) { + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { + } else { + unsigned short uBNCPort = CDCCBounce::DCCRequest(sTarget, uLongIP, uPort, "", true, m_pUser, (m_pIRCSock) ? m_pIRCSock->GetLocalIP() : GetLocalIP(), ""); + if (uBNCPort) { + PutIRC("PRIVMSG " + sTarget + " :\001DCC CHAT chat " + CUtils::ToString(CUtils::GetLongIP(sIP)) + " " + CUtils::ToString(uBNCPort) + "\001"); + } + } + } else if (strcasecmp(sType.c_str(), "SEND") == 0) { + // DCC SEND readme.txt 403120438 5550 1104 + + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { + if ((!m_pUser) || (strcasecmp(sTarget.c_str(), string(m_pUser->GetStatusPrefix() + "status").c_str()) == 0)) { + if (!m_pUser) { + return; + } + + string sPath = m_pUser->GetDLPath(); + if (!CFile::Exists(sPath)) { + PutStatus("Directory [" + sPath + "] not found, creating."); + if (mkdir(sPath.c_str(), 0700)) { + PutStatus("Could not create [" + sPath + "] directory."); + return; + } + } else if (!CFile::IsDir(sPath)) { + PutStatus("Error: [" + sPath + "] is not a directory."); + return; + } + + string sLocalFile = sPath + "/" + sFile; + + if (m_pUser) { + m_pUser->GetFile(GetNick(), CUtils::GetIP(uLongIP), uPort, sLocalFile, uFileSize); + } + } else { +#ifdef _MODULES + if ((m_pUser) && (m_pUser->GetModules().OnDCCUserSend(sTarget, uLongIP, uPort, sFile, uFileSize))) { + return; + } +#endif + } + } else { + unsigned short uBNCPort = CDCCBounce::DCCRequest(sTarget, uLongIP, uPort, sFile, false, m_pUser, (m_pIRCSock) ? m_pIRCSock->GetLocalIP() : GetLocalIP(), ""); + if (uBNCPort) { + PutIRC("PRIVMSG " + sTarget + " :\001DCC SEND " + sFile + " " + CUtils::ToString(CUtils::GetLongIP(sIP)) + " " + CUtils::ToString(uBNCPort) + " " + CUtils::ToString(uFileSize) + "\001"); + } + } + } else if (strcasecmp(sType.c_str(), "RESUME") == 0) { + // PRIVMSG user :DCC RESUME "znc.o" 58810 151552 + unsigned short uResumePort = atoi(CUtils::Token(sCTCP, 3).c_str()); + unsigned long uResumeSize = strtoul(CUtils::Token(sCTCP, 4).c_str(), NULL, 10); + + // Need to lookup the connection by port, filter the port, and forward to the user + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { + if ((m_pUser) && (m_pUser->ResumeFile(sTarget, uResumePort, uResumeSize))) { + PutServ(":" + sTarget + "!znc@znc.com PRIVMSG " + GetNick() + " :\001DCC ACCEPT " + sFile + " " + CUtils::ToString(uResumePort) + " " + CUtils::ToString(uResumeSize) + "\001"); + } else { + PutStatus("DCC -> [" + GetNick() + "][" + sFile + "] Unable to find send to initiate resume."); + } + } else { + CDCCBounce* pSock = (CDCCBounce*) m_pZNC->GetManager().FindSockByLocalPort(uResumePort); + if ((pSock) && (strncasecmp(pSock->GetSockName().c_str(), "DCC::", 5) == 0)) { + PutIRC("PRIVMSG " + sTarget + " :\001DCC " + sType + " " + sFile + " " + CUtils::ToString(pSock->GetUserPort()) + " " + CUtils::Token(sCTCP, 4) + "\001"); + } + } + } else if (strcasecmp(sType.c_str(), "ACCEPT") == 0) { + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { + } else { + // Need to lookup the connection by port, filter the port, and forward to the user + TSocketManager& Manager = m_pZNC->GetManager(); + + for (unsigned int a = 0; a < Manager.size(); a++) { + CDCCBounce* pSock = (CDCCBounce*) Manager[a]; + + if ((pSock) && (strncasecmp(pSock->GetSockName().c_str(), "DCC::", 5) == 0)) { + if (pSock->GetUserPort() == atoi(CUtils::Token(sCTCP, 3).c_str())) { + PutIRC("PRIVMSG " + sTarget + " :\001DCC " + sType + " " + sFile + " " + CUtils::ToString(pSock->GetLocalPort()) + " " + CUtils::Token(sCTCP, 4) + "\001"); + } + } + } + } + } + + return; + } + + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { +#ifdef _MODULES + string sModule = sTarget; + CUtils::LeftChomp(sModule, m_pUser->GetStatusPrefix().length()); + + CModule* pModule = m_pUser->GetModules().FindModule(sModule); + if (pModule) { + pModule->OnModCTCP(sCTCP); + } else { + PutStatus("No such module [" + sModule + "]"); + } +#endif + return; + } + +#ifdef _MODULES + if ((m_pUser) && (m_pUser->GetModules().OnUserCTCP(sTarget, sCTCP))) { + return; + } +#endif + + PutIRC("PRIVMSG " + sTarget + " :\001" + sCTCP + "\001"); + return; + } + + if ((m_pUser) && (strcasecmp(sTarget.c_str(), string(m_pUser->GetStatusPrefix() + "status").c_str()) == 0)) { +#ifdef _MODULES + if ((m_pUser) && (m_pUser->GetModules().OnStatusCommand(sMsg))) { + return; + } +#endif + UserCommand(sMsg); + return; + } + + if (strncasecmp(sTarget.c_str(), m_pUser->GetStatusPrefix().c_str(), m_pUser->GetStatusPrefix().length()) == 0) { +#ifdef _MODULES + if (m_pUser) { + string sModule = sTarget; + CUtils::LeftChomp(sModule, m_pUser->GetStatusPrefix().length()); + + CModule* pModule = m_pUser->GetModules().FindModule(sModule); + if (pModule) { + pModule->OnModCommand(sMsg); + } else { + PutStatus("No such module [" + sModule + "]"); + } + } +#endif + return; + } + + CChan* pChan = m_pUser->FindChan(sTarget); + + if ((pChan) && (pChan->KeepBuffer())) { + pChan->AddBuffer(":" + GetNickMask() + " PRIVMSG " + sTarget + " :" + sMsg); + } + +#ifdef _MODULES + if ((m_pUser) && (m_pUser->GetModules().OnUserMsg(sTarget, sMsg))) { + return; + } +#endif + PutIRC("PRIVMSG " + sTarget + " :" + sMsg); + return; + } + + PutIRC(sLine); +} + +void CUserSock::SetNick(const string& s) { + m_sNick = s; + + if (m_pUser) { + if (strcasecmp(m_sNick.c_str(), m_pUser->GetNick().c_str()) == 0) { + m_uKeepNickCounter = 0; + } + } +} + +bool CUserSock::DecKeepNickCounter() { + if (!m_uKeepNickCounter) { + return false; + } + + m_uKeepNickCounter--; + return true; +} + +void CUserSock::UserCommand(const string& sLine) { + if (!m_pUser) { + return; + } + + if (sLine.empty()) { + return; + } + + string sCommand = CUtils::Token(sLine, 0); + + if (strcasecmp(sCommand.c_str(), "LISTNICKS") == 0) { + string sChan = CUtils::Token(sLine, 1); + + if (sChan.empty()) { + PutStatus("Usage: ListNicks <#chan>"); + return; + } + + CChan* pChan = m_pUser->FindChan(sChan); + + if (!pChan) { + PutStatus("You are not on [" + sChan + "]"); + return; + } + + if (!pChan->IsOn()) { + PutStatus("You are not on [" + sChan + "] [trying]"); + return; + } + + const map& msNicks = pChan->GetNicks(); + + if (!msNicks.size()) { + PutStatus("No nicks on [" + sChan + "]"); + return; + } + + CTable Table; + Table.AddColumn("@"); + Table.AddColumn("+"); + Table.AddColumn("Nick"); + Table.AddColumn("Ident"); + Table.AddColumn("Host"); + + for (map::const_iterator a = msNicks.begin(); a != msNicks.end(); a++) { + Table.AddRow(); + if (a->second->IsOp()) { Table.SetCell("@", "@"); } + if (a->second->IsVoice()) { Table.SetCell("+", "+"); } + Table.SetCell("Nick", a->second->GetNick()); + Table.SetCell("Ident", a->second->GetIdent()); + Table.SetCell("Host", a->second->GetHost()); + } + + unsigned int uTableIdx = 0; + string sLine; + + while (Table.GetLine(uTableIdx++, sLine)) { + PutStatus(sLine); + } + } else if (strcasecmp(sCommand.c_str(), "JUMP") == 0) { + if (m_pUser) { + if (m_pIRCSock) { + m_pIRCSock->Close(); + PutStatus("Jumping to the next server in the list..."); + return; + } + } + } else if (strcasecmp(sCommand.c_str(), "LISTCHANS") == 0) { + if (m_pUser) { + const vector& vChans = m_pUser->GetChans(); + + CTable Table; + Table.AddColumn("Name"); + Table.AddColumn("Status"); + Table.AddColumn("Buf"); + Table.AddColumn("Modes"); + Table.AddColumn("Users"); + Table.AddColumn("+o"); + Table.AddColumn("+v"); + + for (unsigned int a = 0; a < vChans.size(); a++) { + CChan* pChan = vChans[a]; + Table.AddRow(); + Table.SetCell("Name", pChan->GetName()); + Table.SetCell("Status", ((vChans[a]->IsOn()) ? "Joined" : "Trying")); + Table.SetCell("Buf", CUtils::ToString(pChan->GetBufferCount())); + + string sModes = pChan->GetModeString(); + unsigned int uLimit = pChan->GetLimit(); + const string& sKey = pChan->GetKey(); + + if (uLimit) { sModes += " " + CUtils::ToString(uLimit); } + if (!sKey.empty()) { sModes += " " + sKey; } + + Table.SetCell("Modes", sModes); + Table.SetCell("Users", CUtils::ToString(pChan->GetNickCount())); + Table.SetCell("+o", CUtils::ToString(pChan->GetOpCount())); + Table.SetCell("+v", CUtils::ToString(pChan->GetVoiceCount())); + } + + if (Table.size()) { + unsigned int uTableIdx = 0; + string sLine; + + while (Table.GetLine(uTableIdx++, sLine)) { + PutStatus(sLine); + } + } + } + } else if (strcasecmp(sCommand.c_str(), "SEND") == 0) { + string sToNick = CUtils::Token(sLine, 1); + string sFile = CUtils::Token(sLine, 2); + + if ((sToNick.empty()) || (sFile.empty())) { + PutStatus("Usage: Send "); + return; + } + + if (m_pUser) { + m_pUser->SendFile(sToNick, sFile); + } + } else if (strcasecmp(sCommand.c_str(), "GET") == 0) { + string sFile = CUtils::Token(sLine, 1); + + if (sFile.empty()) { + PutStatus("Usage: Get "); + return; + } + + if (m_pUser) { + m_pUser->SendFile(GetNick(), sFile); + } + } else if (strcasecmp(sCommand.c_str(), "LISTDCCS") == 0) { + TSocketManager& Manager = m_pZNC->GetManager(); + + CTable Table; + Table.AddColumn("Type"); + Table.AddColumn("State"); + Table.AddColumn("Speed"); + Table.AddColumn("Nick"); + Table.AddColumn("IP"); + Table.AddColumn("File"); + + for (unsigned int a = 0; a < Manager.size(); a++) { + const string& sSockName = Manager[a]->GetSockName(); + + if (strncasecmp(sSockName.c_str(), "DCC::", 5) == 0) { + if (strncasecmp(sSockName.c_str() +5, "XFER::REMOTE::", 14) == 0) { + continue; + } + + if (strncasecmp(sSockName.c_str() +5, "CHAT::REMOTE::", 14) == 0) { + continue; + } + + if (strncasecmp(sSockName.c_str() +5, "SEND", 4) == 0) { + CDCCSock* pSock = (CDCCSock*) Manager[a]; + + Table.AddRow(); + Table.SetCell("Type", "Sending"); + Table.SetCell("State", CUtils::ToPercent(pSock->GetProgress())); + Table.SetCell("Speed", CUtils::ToKBytes(pSock->GetAvgWrite() / 1000.0)); + Table.SetCell("Nick", pSock->GetRemoteNick()); + Table.SetCell("IP", pSock->GetRemoteIP()); + Table.SetCell("File", pSock->GetFileName()); + } else if (strncasecmp(sSockName.c_str() +5, "GET", 3) == 0) { + CDCCSock* pSock = (CDCCSock*) Manager[a]; + + Table.AddRow(); + Table.SetCell("Type", "Getting"); + Table.SetCell("State", CUtils::ToPercent(pSock->GetProgress())); + Table.SetCell("Speed", CUtils::ToKBytes(pSock->GetAvgRead() / 1000.0)); + Table.SetCell("Nick", pSock->GetRemoteNick()); + Table.SetCell("IP", pSock->GetRemoteIP()); + Table.SetCell("File", pSock->GetFileName()); + } else if (strncasecmp(sSockName.c_str() +5, "LISTEN", 6) == 0) { + CDCCSock* pSock = (CDCCSock*) Manager[a]; + + Table.AddRow(); + Table.SetCell("Type", "Sending"); + Table.SetCell("State", "Waiting"); + Table.SetCell("Nick", pSock->GetRemoteNick()); + Table.SetCell("IP", pSock->GetRemoteIP()); + Table.SetCell("File", pSock->GetFileName()); + } else if (strncasecmp(sSockName.c_str() +5, "XFER::LOCAL", 11) == 0) { + CDCCBounce* pSock = (CDCCBounce*) Manager[a]; + + string sState = "Waiting"; + if ((pSock->IsConnected()) || (pSock->IsPeerConnected())) { + sState = "Halfway"; + if ((pSock->IsPeerConnected()) && (pSock->IsPeerConnected())) { + sState = "Connected"; + } + } + + Table.AddRow(); + Table.SetCell("Type", "Xfer"); + Table.SetCell("State", sState); + Table.SetCell("Nick", pSock->GetRemoteNick()); + Table.SetCell("IP", pSock->GetRemoteIP()); + Table.SetCell("File", pSock->GetFileName()); + } else if (strncasecmp(sSockName.c_str() +5, "CHAT::LOCAL", 11) == 0) { + CDCCBounce* pSock = (CDCCBounce*) Manager[a]; + + string sState = "Waiting"; + if ((pSock->IsConnected()) || (pSock->IsPeerConnected())) { + sState = "Halfway"; + if ((pSock->IsPeerConnected()) && (pSock->IsPeerConnected())) { + sState = "Connected"; + } + } + + Table.AddRow(); + Table.SetCell("Type", "Chat"); + Table.SetCell("State", sState); + Table.SetCell("Nick", pSock->GetRemoteNick()); + Table.SetCell("IP", pSock->GetRemoteIP()); + } + } + } + + if (Table.size()) { + unsigned int uTableIdx = 0; + string sLine; + + while (Table.GetLine(uTableIdx++, sLine)) { + PutStatus(sLine); + } + } else { + PutStatus("You have no active DCCs."); + } + } else if ((strcasecmp(sCommand.c_str(), "LISTMODS") == 0) || (strcasecmp(sCommand.c_str(), "LISTMODULES") == 0)) { +#ifdef _MODULES + if (m_pUser) { + CModules& Modules = m_pUser->GetModules(); + + if (!Modules.size()) { + PutStatus("You have no modules loaded."); + return; + } + + CTable Table; + Table.AddColumn("Name"); + Table.AddColumn("Description"); + + for (unsigned int b = 0; b < Modules.size(); b++) { + Table.AddRow(); + Table.SetCell("Name", Modules[b]->GetModName()); + Table.SetCell("Description", CUtils::Ellipsize(Modules[b]->GetDescription(), 128)); + } + + unsigned int uTableIdx = 0; string sLine; + while (Table.GetLine(uTableIdx++, sLine)) { + PutStatus(sLine); + } + } +#else + PutStatus("Modules are not enabled."); +#endif + return; + } else if ((strcasecmp(sCommand.c_str(), "LOADMOD") == 0) || (strcasecmp(sCommand.c_str(), "LOADMODULE") == 0)) { + string sMod = CUtils::Token(sLine, 1); + string sArgs = CUtils::Token(sLine, 2, true); + + if (m_pUser->DenyLoadMod()) { + PutStatus("Unable to load [" + sMod + "] Access Denied."); + return; + } +#ifdef _MODULES + if (sMod.empty()) { + PutStatus("Usage: LoadMod [args]"); + return; + } + + string sModRet; + m_pUser->GetModules().LoadModule(sMod, sArgs, m_pUser, m_pZNC->GetBinPath(), sModRet); + PutStatus(sModRet); +#else + PutStatus("Unable to load [" + sMod + "] Modules are not enabled."); +#endif + return; + } else if ((strcasecmp(sCommand.c_str(), "UNLOADMOD") == 0) || (strcasecmp(sCommand.c_str(), "UNLOADMODULE") == 0)) { + string sMod = CUtils::Token(sLine, 1); + + if (m_pUser->DenyLoadMod()) { + PutStatus("Unable to unload [" + sMod + "] Access Denied."); + return; + } +#ifdef _MODULES + if (sMod.empty()) { + PutStatus("Usage: UnloadMod "); + return; + } + + string sModRet; + m_pUser->GetModules().UnloadModule(sMod, sModRet); + PutStatus(sModRet); +#else + PutStatus("Unable to unload [" + sMod + "] Modules are not enabled."); +#endif + return; + } else if ((strcasecmp(sCommand.c_str(), "RELOADMOD") == 0) || (strcasecmp(sCommand.c_str(), "RELOADMODULE") == 0)) { + string sMod = CUtils::Token(sLine, 1); + string sArgs = CUtils::Token(sLine, 2, true); + + if (m_pUser->DenyLoadMod()) { + PutStatus("Unable to reload [" + sMod + "] Access Denied."); + return; + } +#ifdef _MODULES + if (sMod.empty()) { + PutStatus("Usage: ReloadMod [args]"); + return; + } + + string sModRet; + m_pUser->GetModules().ReloadModule(sMod, sArgs, m_pUser, m_pZNC->GetBinPath(), sModRet); + PutStatus(sModRet); +#else + PutStatus("Unable to unload [" + sMod + "] Modules are not enabled."); +#endif + return; + } else if (strcasecmp(sCommand.c_str(), "SETBUFFER") == 0) { + string sChan = CUtils::Token(sLine, 1); + + if (sChan.empty()) { + PutStatus("Usage: SetBuffer <#chan> [linecount]"); + return; + } + + CChan* pChan = m_pUser->FindChan(sChan); + + if (!pChan) { + PutStatus("You are not on [" + sChan + "]"); + return; + } + + if (!pChan->IsOn()) { + PutStatus("You are not on [" + sChan + "] [trying]"); + return; + } + + unsigned int uLineCount = strtoul(CUtils::Token(sLine, 2).c_str(), NULL, 10); + + if (uLineCount > 500) { + PutStatus("Max linecount is 500."); + return; + } + + pChan->SetBufferCount(uLineCount); + + PutStatus("BufferCount for [" + sChan + "] set to [" + CUtils::ToString(pChan->GetBufferCount()) + "]"); + } else { + PutStatus("I'm too stupid to help you with [" + sCommand + "]"); + } +} + +bool CUserSock::ConnectionFrom(const string& sHost, int iPort) { + DEBUG_ONLY(cout << GetSockName() << " == ConnectionFrom(" << sHost << ", " << iPort << ")" << endl); + return m_pZNC->IsHostAllowed(sHost); +} + +void CUserSock::AuthUser() { + if (!m_pZNC) { + DEBUG_ONLY(cout << "znc not set!" << endl); + Close(); + return; + } + + CUser* pUser = m_pZNC->GetUser(m_sUser); + + if ((!pUser) || (!pUser->CheckPass(m_sPass))) { + if (pUser) { + pUser->PutStatus("Another user attempted to login as you, with a bad password."); + } + + PutStatus("Bad username and/or password."); + PutServ(":irc.znc.com 464 " + GetNick() + " :Password Incorrect"); + Close(); + } else { + m_sPass = ""; + m_pUser = pUser; + + if (!m_pUser->IsHostAllowed(GetRemoteIP())) { + Close(); + return; + } + + m_bAuthed = true; + SetSockName("USR::" + pUser->GetUserName()); + + CIRCSock* pIRCSock = (CIRCSock*) m_pZNC->FindSockByName("IRC::" + pUser->GetUserName()); + + if (pIRCSock) { + m_pIRCSock = pIRCSock; + pIRCSock->UserConnected(this); + } + +#ifdef _MODULES + m_pUser->GetModules().OnUserAttached(); +#endif + } +} + +void CUserSock::Connected() { + DEBUG_ONLY(cout << GetSockName() << " == Connected();" << endl); + SetTimeout(900); // Now that we are connected, let nature take its course +} + +void CUserSock::ConnectionRefused() { + DEBUG_ONLY(cout << GetSockName() << " == ConnectionRefused()" << endl); +} + +void CUserSock::Disconnected() { + if (m_pIRCSock) { + m_pIRCSock->UserDisconnected(); + m_pIRCSock = NULL; + } + +#ifdef _MODULES + if (m_pUser) { + m_pUser->GetModules().OnUserDetached(); + } +#endif +} + +void CUserSock::IRCConnected(CIRCSock* pIRCSock) { + m_pIRCSock = pIRCSock; +} + +void CUserSock::BouncedOff() { + PutStatus("You are being disconnected because another user just authenticated as you."); + m_pIRCSock = NULL; + Close(); +} + +void CUserSock::IRCDisconnected() { + m_pIRCSock = NULL; +} + +Csock* CUserSock::GetSockObj(const string& sHost, int iPort) { + CUserSock* pSock = new CUserSock(sHost, iPort); + pSock->SetZNC(m_pZNC); + + return pSock; +} + +void CUserSock::PutIRC(const string& sLine) { + if (m_pIRCSock) { + m_pIRCSock->PutServ(sLine); + } +} + +void CUserSock::PutServ(const string& sLine) { + DEBUG_ONLY(cout << GetSockName() << " -> [" << sLine << "]" << endl); + Write(sLine + "\r\n"); +} + +void CUserSock::PutStatus(const string& sLine) { + PutModule("status", sLine); +} + +void CUserSock::PutModule(const string& sModule, const string& sLine) { + if (!m_pUser) { + return; + } + + DEBUG_ONLY(cout << GetSockName() << " -> [:" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.com PRIVMSG " << GetNick() << " :" << sLine << "]" << endl); + Write(":" + m_pUser->GetStatusPrefix() + ((sModule.empty()) ? "status" : sModule) + "!znc@znc.com PRIVMSG " + GetNick() + " :" + sLine + "\r\n"); +} + +string CUserSock::GetNick() const { + string sRet; + + if ((m_bAuthed) && (m_pIRCSock)) { + sRet = m_pIRCSock->GetNick(); + } + + return (sRet.empty()) ? m_sNick : sRet; +} + +string CUserSock::GetNickMask() const { + if (m_pIRCSock) { + return m_pIRCSock->GetNickMask(); + } + + return GetNick() + "!" + m_pUser->GetIdent() + "@" + m_pUser->GetVHost(); +} + diff --git a/UserSock.h b/UserSock.h new file mode 100644 index 00000000..5889398f --- /dev/null +++ b/UserSock.h @@ -0,0 +1,73 @@ +#ifndef _USERSOCK_H +#define _USERSOCK_H + +#include "main.h" + +// Forward Declarations +class CZNC; +class CUser; +class CIRCSock; +// !Forward Declarations + + +class CUserSock : public Csock { +public: + CUserSock() : Csock() { + Init(); + } + CUserSock(const string& sHostname, int iport, int itimeout = 60) : Csock(sHostname, iport, itimeout) { + Init(); + } + virtual ~CUserSock() {} + + void Init() { + m_pZNC = NULL; + m_pUser = NULL; + m_pIRCSock = NULL; + m_bAuthed = false; + m_bGotNick = false; + m_bGotUser = false; + m_uKeepNickCounter = 0; + EnableReadLine(); + } + + string GetNick() const; + string GetNickMask() const; + + bool DecKeepNickCounter(); + void UserCommand(const string& sCommand); + void IRCConnected(CIRCSock* pIRCSock); + void IRCDisconnected(); + void BouncedOff(); + bool IsAttached() const { return m_bAuthed; } + + void PutIRC(const string& sLine); + void PutServ(const string& sLine); + void PutStatus(const string& sLine); + void PutModule(const string& sModule, const string& sLine); + + virtual void ReadLine(const string& sData); + void AuthUser(); + virtual void Connected(); + virtual void Disconnected(); + virtual void ConnectionRefused(); + virtual bool ConnectionFrom(const string& sHost, int iPort); + virtual Csock* GetSockObj(const string& sHost, int iPort); + + void SetZNC(CZNC* pZNC) { m_pZNC = pZNC; } + void SetNick(const string& s); +private: +protected: + bool m_bAuthed; + bool m_bGotNick; + bool m_bGotUser; + CZNC* m_pZNC; + CUser* m_pUser; + string m_sNick; + string m_sPass; + string m_sUser; + CIRCSock* m_pIRCSock; + unsigned int m_uKeepNickCounter; +}; + +#endif // !_USERSOCK_H diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 00000000..8e60c431 --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,687 @@ +#include "Utils.h" +#include +#include +#include +#include +#include +#include +#include +using std::stringstream; + +CUtils::CUtils() {} +CUtils::~CUtils() {} + +string CUtils::GetIP(unsigned long addr) { + char szBuf[16]; + memset((char*) szBuf, 0, 16); + + if (addr >= (1 << 24)) { + unsigned long ip[4]; + ip[0] = addr >> 24 & 255; + ip[1] = addr >> 16 & 255; + ip[2] = addr >> 8 & 255; + ip[3] = addr & 255; + sprintf(szBuf, "%lu.%lu.%lu.%lu", ip[0], ip[1], ip[2], ip[3]); + } + + return szBuf; +} + +unsigned long CUtils::GetLongIP(const string& sIP) { + register int i; + char *addr = (char *) malloc(sIP.length() +1); + char ip[4][4], n; + + strcpy(addr, sIP.c_str()); + + for (i=0; i<4; ip[0][i]=ip[1][i]=ip[2][i]=ip[3][i]='\0', i++); + + if (sscanf(addr, "%3[0-9].%3[0-9].%3[0-9].%3[0-9]%[^\n]", ip[0], ip[1], ip[2], ip[3], &n) != 4) { + free(addr); + return 0; + } + + free(addr); + return (unsigned long) ((atoi(ip[0]) << 24) + (atoi(ip[1]) << 16) + (atoi(ip[2]) << 8) + atoi(ip[3])); +} + +string CUtils::ChangeDir(const string& sPath, const string& sAdd, const string& sHomeDir) { + if (sAdd == "~") { + return sHomeDir; + } + + string sAddDir = sAdd; + + if (CUtils::Left(sAddDir, 2) == "~/") { + CUtils::LeftChomp(sAddDir); + sAddDir = sHomeDir + sAddDir; + } + + string sRet = ((sAddDir.size()) && (sAddDir[0] == '/')) ? "" : sPath; + sAddDir += "/"; + string sCurDir; + + if (CUtils::Right(sRet, 1) == "/") { + CUtils::RightChomp(sRet); + } + + for (unsigned int a = 0; a < sAddDir.size(); a++) { + switch (sAddDir[a]) { + case '/': + if (sCurDir == "..") { + sRet = sRet.substr(0, sRet.rfind('/')); + } else if ((sCurDir != "") && (sCurDir != ".")) { + sRet += "/" + sCurDir; + } + + sCurDir = ""; + break; + default: + sCurDir += sAddDir[a]; + break; + } + } + + return (sRet.empty()) ? "/" : sRet; +} + +string CUtils::ToString(short i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(unsigned short i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(int i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(unsigned int i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(long i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(unsigned long i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(unsigned long long i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(double i) { stringstream s; s << i; return s.str(); } +string CUtils::ToString(float i) { stringstream s; s << i; return s.str(); } + +string CUtils::ToPercent(double d) { + char szRet[32]; + snprintf(szRet, 32, "%.02f%%", d); + return szRet; +} + +string CUtils::ToKBytes(double d) { + char szRet[32]; + snprintf(szRet, 32, "%.0f K/s", d); + return szRet; +} + +string CUtils::Left(const string& s, unsigned int u) { + u = (u > s.length()) ? s.length() : u; + return s.substr(0, u); +} + +string CUtils::Right(const string& s, unsigned int u) { + u = (u > s.length()) ? s.length() : u; + return s.substr(s.length() - u, u); +} + +string& CUtils::Trim(string& s) { + while ((Right(s, 1) == " ") || (Right(s, 1) == "\t") || (Right(s, 1) == "\r") || (Right(s, 1) == "\n")) { + RightChomp(s); + } + + while ((Left(s, 1) == " ") || (Left(s, 1) == "\t") || (Left(s, 1) == "\r") || (Left(s, 1) == "\n")) { + LeftChomp(s); + } + + return s; +} + +string& CUtils::LeftChomp(string& s, unsigned int uLen) { + while ((uLen--) && (s.length())) { + s.erase(0, 1); + } + + return s; +} + +string& CUtils::RightChomp(string& s, unsigned int uLen) { + while ((uLen--) && (s.length())) { + s.erase(s.length() -1); + } + + return s; +} + +string CUtils::Token(const string& s, unsigned int uPos, bool bRest, char cSep) { + string sRet; + const char* p = s.c_str(); + + while (*p) { + if (uPos) { + if (*p == cSep) { + uPos--; + } + } else { + if ((*p == cSep) && (!bRest)) { + return sRet; + } + + sRet += *p; + } + + p++; + } + + return sRet; +} + +string CUtils::Ellipsize(const string& s, unsigned int uLen) { + if (uLen >= s.size()) { + return s; + } + + string sRet; + + if (uLen < 4) { + for (unsigned int a = 0; a < uLen; a++) { + sRet += "."; + } + + return sRet; + } + + sRet = s.substr(0, uLen -3) + "..."; + + return sRet; +} + +bool CUtils::WildCmp(const string& sWild, const string& sString) { + char *wild = (char*) sWild.c_str(); + char *string = (char*) sString.c_str(); + + register int i, len; + char *copy, *cp, *p, *lastmatch = string; + + if ((p = strchr (wild, '*')) == NULL) { + return (strcasecmp(wild, string) ? false : true); + } + + if (wild[0] != '*' && strncasecmp(string, wild, p-wild)) { + return false; + } + + len = strlen(wild) + 1; + + if (wild[len -2] != '*') { + p = strrchr(wild, '*') + 1; + if (strcasecmp(string + strlen(string) - strlen(p), p) != 0) { + return false; + } + } + + cp = copy = (char*) malloc(len); + memset((char*) copy, 0, len); + + for (i=0; i); + return size() -1; +} + +bool CTable::SetCell(const string& sColumn, const string& sValue, unsigned int uRowIdx) { + if (uRowIdx == (unsigned int) ~0) { + if (!size()) { + return false; + } + + uRowIdx = size() -1; + } + + (*(*this)[uRowIdx])[sColumn] = sValue; + return true; +} + +bool CTable::GetLine(unsigned int uIdx, string& sLine) { + stringstream ssRet; + + if (!size()) { + return false; + } + + if (uIdx == 1) { + m_msuWidths.clear(); // Clear out the width cache + ssRet.fill(' '); + ssRet << "| "; + + for (unsigned int a = 0; a < m_vsHeaders.size(); a++) { + ssRet.width(GetColumnWidth(a)); + ssRet << std::left << m_vsHeaders[a]; + ssRet << ((a == m_vsHeaders.size() -1) ? " |" : " | "); + } + + sLine = ssRet.str(); + return true; + } else if ((uIdx == 0) || (uIdx == 2) || (uIdx == (size() +3))) { + ssRet.fill('-'); + ssRet << "+-"; + + for (unsigned int a = 0; a < m_vsHeaders.size(); a++) { + ssRet.width(GetColumnWidth(a)); + ssRet << std::left << "-"; + ssRet << ((a == m_vsHeaders.size() -1) ? "-+" : "-+-"); + } + + sLine = ssRet.str(); + return true; + } else { + uIdx -= 3; + + if (uIdx < size()) { + map* pRow = (*this)[uIdx]; + ssRet.fill(' '); + ssRet << "| "; + + for (unsigned int c = 0; c < m_vsHeaders.size(); c++) { + ssRet.width(GetColumnWidth(c)); + ssRet << std::left << (*pRow)[m_vsHeaders[c]]; + ssRet << ((c == m_vsHeaders.size() -1) ? " |" : " | "); + } + + sLine = ssRet.str(); + return true; + } + } + + return false; +} + +/* +bool CTable::Output(std::ostream oOut) { + stringstream ssSep; + + ssSep << "-+-"; + + oOut << endl << ssSep.str() << endl; + + for (unsigned int b = 0; b < size(); b++) { + map* pRow = (*this)[b]; + + oOut << " | "; + + for (unsigned int c = 0; c < m_vsHeaders.size(); c++) { + oOut.width(GetColumnWidth(c)); + oOut << (*pRow)[m_vsHeaders[c]]; + oOut << " | "; + } + + oOut << endl; + } + + oOut << ssSep.str() << endl; + return true; +} +*/ + +unsigned int CTable::GetColumnWidth(unsigned int uIdx) { + if (uIdx >= m_vsHeaders.size()) { + return 0; + } + + const string& sColName = m_vsHeaders[uIdx]; + unsigned int uRet = sColName.size(); + map::iterator it = m_msuWidths.find(sColName); + + if (it != m_msuWidths.end()) { + return it->second; + } + + for (unsigned int a = 0; a < size(); a++) { + map* pRow = (*this)[a]; + uRet = uRet >? (*pRow)[m_vsHeaders[uIdx]].size(); + } + + return uRet; +} + + +CFile::CFile(const string& sLongName) { + m_sLongName = sLongName; + m_iFD = -1; + + m_sShortName = sLongName; + + while (CUtils::Left(m_sShortName, 1) == "/") { + CUtils::LeftChomp(m_sShortName); + } + + unsigned int uPos = m_sShortName.rfind('/'); + if (uPos != string::npos) { + m_sShortName = m_sShortName.substr(uPos +1); + } +} +CFile::~CFile() { + if (m_iFD != -1) { + Close(); + } +} + +bool CFile::IsReg(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_REGULAR, bUseLstat); } +bool CFile::IsDir(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_DIRECTORY, bUseLstat); } +bool CFile::IsChr(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_CHARACTER, bUseLstat); } +bool CFile::IsBlk(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_BLOCK, bUseLstat); } +bool CFile::IsFifo(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_FIFO, bUseLstat); } +bool CFile::IsLnk(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_LINK, bUseLstat); } +bool CFile::IsSock(const string& sLongName, bool bUseLstat) { return CFile::FType(sLongName, FT_SOCK, bUseLstat); } + +bool CFile::IsReg(bool bUseLstat) { return CFile::IsReg(m_sLongName, bUseLstat); } +bool CFile::IsDir(bool bUseLstat) { return CFile::IsDir(m_sLongName, bUseLstat); } +bool CFile::IsChr(bool bUseLstat) { return CFile::IsChr(m_sLongName, bUseLstat); } +bool CFile::IsBlk(bool bUseLstat) { return CFile::IsBlk(m_sLongName, bUseLstat); } +bool CFile::IsFifo(bool bUseLstat) { return CFile::IsFifo(m_sLongName, bUseLstat); } +bool CFile::IsLnk(bool bUseLstat) { return CFile::IsLnk(m_sLongName, bUseLstat); } +bool CFile::IsSock(bool bUseLstat) { return CFile::IsSock(m_sLongName, bUseLstat); } + +bool CFile::access(int mode) { return (::access(m_sLongName.c_str(), mode) == 0); } + +// for gettin file types, using fstat instead +bool CFile::FType(const string sFileName, EFileTypes eType, bool bUseLstat) { + struct stat st; + + if (!bUseLstat) { + if (stat(sFileName.c_str(), &st) != 0) { + return false; + } + } else { + if (lstat(sFileName.c_str(), &st) != 0) { + return false; + } + } + + switch (eType) { + case FT_REGULAR: + return S_ISREG(st.st_mode); + case FT_DIRECTORY: + return S_ISDIR(st.st_mode); + case FT_CHARACTER: + return S_ISCHR(st.st_mode); + case FT_BLOCK: + return S_ISBLK(st.st_mode); + case FT_FIFO: + return S_ISFIFO(st.st_mode); + case FT_LINK: + return S_ISLNK(st.st_mode); + case FT_SOCK: + return S_ISSOCK(st.st_mode); + default: + return false; + } + return false; +} + +// +// Functions to retrieve file information +// +bool CFile::Exists() const { return CFile::Exists(m_sLongName); } +unsigned long long CFile::GetSize() const { return CFile::GetSize(m_sLongName); } +unsigned int CFile::GetATime() const { return CFile::GetATime(m_sLongName); } +unsigned int CFile::GetMTime() const { return CFile::GetMTime(m_sLongName); } +unsigned int CFile::GetCTime() const { return CFile::GetCTime(m_sLongName); } +int CFile::GetUID() const { return CFile::GetUID(m_sLongName); } +int CFile::GetGID() const { return CFile::GetGID(m_sLongName); } +bool CFile::Exists(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) == 0); +} + +unsigned long long CFile::GetSize(const string& sFile) { + struct stat st; + if(stat(sFile.c_str(), &st) != 0) { + return 0; + } + + return (S_ISREG(st.st_mode)) ? st.st_size : 0; +} + +unsigned int CFile::GetATime(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_atime; +} + +unsigned int CFile::GetMTime(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_mtime; +} + +unsigned int CFile::GetCTime(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) != 0) ? 0 : st.st_ctime; +} + +int CFile::GetUID(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) != 0) ? -1 : (int) st.st_uid; +} + +int CFile::GetGID(const string& sFile) { + struct stat st; + return (stat(sFile.c_str(), &st) != 0) ? -1 : (int) st.st_gid; +} +int CFile::GetInfo(const string& sFile, struct stat& st) { + return stat(sFile.c_str(), &st); +} + +// +// Functions to manipulate the file on the filesystem +// +int CFile::Delete() { return CFile::Delete(m_sLongName); } +int CFile::Move(const string& sNewFileName, bool bOverwrite) { + return CFile::Move(m_sLongName, sNewFileName, bOverwrite); +} + +bool CFile::Delete(const string& sFileName) { + if(!CFile::Exists(sFileName)) { + return false; + } + + return (unlink(sFileName.c_str()) == 0) ? true : false; +} + +bool CFile::Move(const string& sOldFileName, const string& sNewFileName, bool bOverwrite) { + if((!bOverwrite) && (CFile::Exists(sNewFileName))) { + return false; + } + + //string sNewLongName = (sNewFileName[0] == '/') ? sNewFileName : m_sPath + "/" + sNewFileName; + return (rename(sOldFileName.c_str(), sNewFileName.c_str()) == 0) ? true : false; +} + +bool CFile::Chmod(mode_t mode) { + return CFile::Chmod(m_sLongName, mode); +} + +bool CFile::Chmod(const string& sFile, mode_t mode) { + return (chmod(sFile.c_str(), mode) == 0); +} + +bool CFile::Seek(unsigned long uPos) { + return (m_iFD == -1) ? false : ((unsigned int) lseek(m_iFD, uPos, SEEK_SET) == uPos); +} + +bool CFile::Open(int iFlags, mode_t iMode) { + if (m_iFD != -1) { + return false; + } + + m_iFD = open(m_sLongName.c_str(), iFlags, iMode); + return (m_iFD > -1); +} + +int CFile::Read(char *pszBuffer, int iBytes) { + if (m_iFD == -1) { + return -1; + } + + return read(m_iFD, pszBuffer, iBytes); +} + +bool CFile::ReadLine(string & sData) { + char buff[64]; + sData.clear(); + if (m_iFD == -1) { + return false; + } + + bool bEOF = false; + + while(true) { + u_int iFind = m_sBuffer.find("\n"); + if (iFind != string::npos) { + sData = m_sBuffer.substr(0, (iFind + 1)); + m_sBuffer.erase(0, (iFind + 1)); + break; + } + + memset((char *)buff, '\0', 64); + int iBytes = read(m_iFD, buff, 64); + switch(iBytes) { + case -1: { + bEOF = true; + break; + } + case 0: { + bEOF = true; + break; + } + default: { + m_sBuffer.append(buff, iBytes); + break; + } + } + + if (bEOF) { + break; + } + } + + u_int iFind = m_sBuffer.find("\n"); + if (iFind != string::npos) { + return true; + } + + return !bEOF; +} + +int CFile::Write(const char *pszBuffer, u_int iBytes) { + if (m_iFD == -1) { + return -1; + } + + return write(m_iFD, pszBuffer, iBytes); +} + +int CFile::Write(const string & sData) { + return Write(sData.data(), sData.size()); +} +void CFile::Close() { close(m_iFD); m_iFD = -1; } + +string CFile::GetLongName() const { return m_sLongName; } +string CFile::GetShortName() const { return m_sShortName; } +void CFile::SetFD(int iFD) { m_iFD = iFD; } + + +#ifdef HAVE_LIBSSL +CBlowfish::CBlowfish(const string & sPassword, int iEncrypt, const string & sIvec) { + m_iEncrypt = iEncrypt; + m_ivec = (unsigned char *)calloc(sizeof(unsigned char), 8); + m_num = 0; + + if (sIvec.length() >= 8) { + memcpy(m_ivec, sIvec.data(), 8); + } + + BF_set_key(&m_bkey, sPassword.length(), (unsigned char *)sPassword.data()); +} + +CBlowfish::~CBlowfish() { + free(m_ivec); +} + +//! output must be freed +unsigned char *CBlowfish::MD5(const unsigned char *input, u_int ilen) { + unsigned char *output = (unsigned char *)malloc(MD5_DIGEST_LENGTH); + ::MD5(input, ilen, output); + return output; +} + +//! returns an md5 of the string (not hex encoded) +string CBlowfish::MD5(const string & sInput, bool bHexEncode) { + string sRet; + unsigned char *data = MD5((const unsigned char *)sInput.data(), sInput.length()); + + if (!bHexEncode) { + sRet.append((const char *)data, MD5_DIGEST_LENGTH); + } else { + for(int a = 0; a < MD5_DIGEST_LENGTH; a++) { + sRet += g_HexDigits[data[a] >> 4]; + sRet += g_HexDigits[data[a] & 0xf]; + } + } + + free(data); + return sRet; +} + +//! output must be the same size as input +void CBlowfish::Crypt(unsigned char *input, unsigned char *output, u_int ibytes) { + BF_cfb64_encrypt(input, output, ibytes, &m_bkey, m_ivec, &m_num, m_iEncrypt); +} + +//! must free result +unsigned char * CBlowfish::Crypt(unsigned char *input, u_int ibytes) { + unsigned char *buff = (unsigned char *)malloc(ibytes); + Crypt(input, buff, ibytes); + return buff; +} + +string CBlowfish::Crypt(const string & sData) { + unsigned char *buff = Crypt((unsigned char *)sData.data(), sData.length()); + string sOutput; + sOutput.append((const char *)buff, sData.length()); + free(buff); + return sOutput; +} + +#endif // HAVE_LIBSSL + diff --git a/Utils.h b/Utils.h new file mode 100644 index 00000000..cafd93cb --- /dev/null +++ b/Utils.h @@ -0,0 +1,291 @@ +#ifndef _UTILS_H +#define _UTILS_H + +#include +#include + +#include +#include +#include +using std::string; +using std::vector; +using std::map; + +#ifdef _DEBUG +#define DEBUG_ONLY(f) f +#else +#define DEBUG_ONLY(f) ((void)0) +#endif + +static const char g_HexDigits[] = "0123456789abcdef"; + +class CUtils +{ +public: + CUtils(); + virtual ~CUtils(); + + static string GetIP(unsigned long addr); + static unsigned long GetLongIP(const string& sIP); + static string ChangeDir(const string& sPath, const string& sAdd, const string& sHomeDir); + + static string ToString(short i); + static string ToString(unsigned short i); + static string ToString(int i); + static string ToString(unsigned int i); + static string ToString(long i); + static string ToString(unsigned long i); + static string ToString(unsigned long long i); + static string ToString(double i); + static string ToString(float i); + static string ToPercent(double d); + static string ToKBytes(double d); + + static string Left(const string& s, unsigned int u); + static string Right(const string& s, unsigned int u); + static string& Trim(string& s); + static string& LeftChomp(string& s, unsigned int uLen = 1); + static string& RightChomp(string& s, unsigned int uLen = 1); + static string Token(const string& s, unsigned int uPos, bool bRest = false, char cSep = ' '); + static string Ellipsize(const string& s, unsigned int uLen); + static bool WildCmp(const string& sWild, const string& sString); +private: +protected: +}; + + +class CTable : public vector* > { +public: + CTable(); + virtual ~CTable(); + + bool AddColumn(const string& sName); + unsigned int AddRow(); + bool SetCell(const string& sColumn, const string& sValue, unsigned int uRowIdx = ~0); + bool GetLine(unsigned int uIdx, string& sLine); + + unsigned int GetColumnWidth(unsigned int uIdx); +private: +protected: + vector m_vsHeaders; + map m_msuWidths; // Used to cache the width of a column +}; + + +class CFile { +public: + CFile(const string& sLongName); + virtual ~CFile(); + + enum EFileTypes + { + FT_REGULAR, + FT_DIRECTORY, + FT_CHARACTER, + FT_BLOCK, + FT_FIFO, + FT_LINK, + FT_SOCK + }; + + static bool IsReg( const string& sLongName, bool bUseLstat = false ); + static bool IsDir( const string& sLongName, bool bUseLstat = false ); + static bool IsChr( const string& sLongName, bool bUseLstat = false ); + static bool IsBlk( const string& sLongName, bool bUseLstat = false ); + static bool IsFifo( const string& sLongName, bool bUseLstat = false ); + static bool IsLnk( const string& sLongName, bool bUseLstat = true ); + static bool IsSock( const string& sLongName, bool bUseLstat = false ); + + bool IsReg( bool bUseLstat = false ); + bool IsDir( bool bUseLstat = false ); + bool IsChr( bool bUseLstat = false ); + bool IsBlk( bool bUseLstat = false ); + bool IsFifo( bool bUseLstat = false ); + bool IsLnk( bool bUseLstat = true ); + bool IsSock( bool bUseLstat = false ); + + bool access( int mode ); + + // for gettin file types, using fstat instead + static bool FType( const string sFileName, EFileTypes eType, bool bUseLstat = false ); + + enum EFileAttr { + FA_Name, + FA_Size, + FA_ATime, + FA_MTime, + FA_CTime, + FA_UID + }; + + // + // Functions to retrieve file information + // + bool Exists() const; + unsigned long long GetSize() const; + unsigned int GetATime() const; + unsigned int GetMTime() const; + unsigned int GetCTime() const; + int GetUID() const; + int GetGID() const; + static bool Exists(const string& sFile); + + static unsigned long long GetSize(const string& sFile); + static unsigned int GetATime(const string& sFile); + static unsigned int GetMTime(const string& sFile); + static unsigned int GetCTime(const string& sFile); + static int GetUID(const string& sFile); + static int GetGID(const string& sFile); + static int GetInfo(const string& sFile, struct stat& st); + + // + // Functions to manipulate the file on the filesystem + // + int Delete(); + int Move(const string& sNewFileName, bool bOverwrite = false); + + static bool Delete(const string& sFileName); + static bool Move(const string& sOldFileName, const string& sNewFileName, bool bOverwrite = false); + bool Chmod(mode_t mode); + static bool Chmod(const string& sFile, mode_t mode); + bool Seek(unsigned long uPos); + bool Open( int iFlags, mode_t iMode = 0644 ); + int Read( char *pszBuffer, int iBytes ); + bool ReadLine( string & sData ); + int Write( const char *pszBuffer, u_int iBytes ); + int Write( const string & sData ); + void Close(); + + string GetLongName() const; + string GetShortName() const; + void SetFD( int iFD ); + +private: + string m_sBuffer; + int m_iFD; + +protected: + string m_sLongName; //!< Absolute filename (m_sPath + "/" + m_sShortName) + string m_sShortName; //!< Filename alone, without path +}; + + +#ifdef HAVE_LIBSSL +#include +#include +//! does Blowfish w/64 bit feedback, no padding +class CBlowfish +{ +public: + /** + * @sPassword key to encrypt with + * @iEncrypt encrypt method (BF_DECRYPT or BF_ENCRYPT) + * @sIvec what to set the ivector to start with, default sets it all 0's + */ + CBlowfish(const string & sPassword, int iEncrypt, const string & sIvec = ""); + ~CBlowfish(); + + //! output must be freed + static unsigned char *MD5(const unsigned char *input, u_int ilen); + + //! returns an md5 of the string ( not hex encoded ) + static string MD5(const string & sInput, bool bHexEncode = false); + + //! output must be the same size as input + void Crypt(unsigned char *input, unsigned char *output, u_int ibytes); + + //! must free result + unsigned char * Crypt(unsigned char *input, u_int ibytes); + string Crypt(const string & sData); + +private: + unsigned char *m_ivec; + BF_KEY m_bkey; + int m_iEncrypt, m_num; +}; + +#endif /* HAVE_LIBSSL */ + +#define RF_BUFF 4096 +inline bool ReadFile(const string & sFilename, string & sLine) { + char inbuff[RF_BUFF]; + int bytes; + // clear ourselves out + sLine.clear(); + + FILE *f = fopen(sFilename.c_str(), "r"); + + if (!f) { + return false; + } + + while((bytes = fread(inbuff, sizeof(char), RF_BUFF, f)) > 0) { + sLine.append(inbuff, bytes); + } + + fclose(f); + if (bytes < 0) { + return false; + } + + return true; +} + +inline bool WriteFile(const string & sFilename, const string & sData) { + FILE *f = fopen(sFilename.c_str(), "w"); + if (!f) { + return false; + } + + int iRet = fwrite(sData.data(), sizeof(char), sData.length(), f); + + fclose(f); + + if (iRet <= 0) { + return false; + } + + return true; +} + +inline bool ReadLine(const string & sData, string & sLine, u_int & iPos) { + sLine.clear(); + + if (iPos >= sData.length()) { + return false; + } + + u_int iFind = sData.find("\n", iPos); + + if (iFind == string::npos) { + sLine = sData.substr(iPos, (sData.length() - iPos)); + iPos = string::npos; + return true; + } + + sLine = sData.substr(iPos, (iFind - iPos)) + "\n"; + iPos = iFind + 1; + + return true; +} + +inline string Lower(const string & sLine) { + string sRet; + for(u_int a = 0; a < sLine.length(); a++) { + sRet += tolower(sLine[a]); + } + + return sRet; +} + +inline string Upper(const string & sLine) { + string sRet; + for(u_int a = 0; a < sLine.length(); a++) { + sRet += toupper(sLine[a]); + } + + return sRet; +} + +#endif // !_UTILS_H + diff --git a/main.cpp b/main.cpp new file mode 100644 index 00000000..af19d5b2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include "znc.h" +#include "md5.h" + +void die(int sig) { + signal( SIGSEGV, SIG_DFL ); + signal( SIGABRT, SIG_DFL ); + signal( SIGPIPE, SIG_DFL ); + + delete CZNC::New(); + exit(sig); +} + +int main(int argc, char** argv) { + string sConfig = "znc.conf"; + + if (argc > 1) { + if ((argc > 2) || (strcasecmp(argv[1], "--help") == 0) || (strcasecmp(argv[1], "-h") == 0)) { + cerr << "Usage: " << argv[0] << " [--makepass|--help|znc.conf]" << endl; + return 1; + } + + if (strcasecmp(argv[1], "--makepass") == 0) { + char* pass = getpass( "Enter Password: " ); + int iLen = strlen(pass); + cout << "Use this in the section of your config:" << endl << endl << "Pass = " << CMD5(pass, iLen) << " -" << endl << endl; + memset((char*) pass, 0, iLen); // Null out our pass so it doesn't sit in memory + return 0; + } else { + sConfig = argv[1]; + } + } + + CZNC* pZNC = CZNC::New(); + + pZNC->InitDirs(((argc) ? argv[0] : "")); + + if (!pZNC->ParseConfig(sConfig)) { + cerr << endl << "*** Unrecoverable error while parsing [" << sConfig << "]" << endl; + delete pZNC; + return 1; + } + + if (!pZNC->GetListenPort()) { + cerr << "You must supply a ListenPort in your config." << endl; + delete pZNC; + return 1; + } + + if (!pZNC->OnBoot()) { + cerr << "Exiting due to module boot errors." << endl; + delete pZNC; + return 1; + } + +#ifndef _DEBUG + int iPid = fork(); + if (iPid == -1) { + cerr << "Failed to fork into background: [" << strerror(errno) << "]" << endl; + delete pZNC; + exit(1); + } + + if (iPid > 0) { + cout << "ZNC - by prozac [port: " << ((pZNC->IsSSL()) ? "+" : "") << pZNC->GetListenPort() << "] [pid: " << iPid << "]" << endl; + pZNC->WritePidFile(iPid); + exit(0); + } +#endif + + struct sigaction sa; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, (struct sigaction *)NULL); + + sa.sa_handler = die; + sigaction(SIGINT, &sa, (struct sigaction *)NULL); + sigaction(SIGILL, &sa, (struct sigaction *)NULL); + sigaction(SIGQUIT, &sa, (struct sigaction *)NULL); + sigaction(SIGBUS, &sa, (struct sigaction *)NULL); + sigaction(SIGSEGV, &sa, (struct sigaction *)NULL); + sigaction(SIGTERM, &sa, (struct sigaction *)NULL); + + int iRet = pZNC->Loop(); + delete pZNC; + + return iRet; +} diff --git a/main.h b/main.h new file mode 100644 index 00000000..a8ae971e --- /dev/null +++ b/main.h @@ -0,0 +1,17 @@ +#ifndef _MAIN_H +#define _MAIN_H + + +#ifndef CS_STRING +#define CS_STRING string +#endif + +#ifndef _NO_CSOCKET_NS +#define _NO_CSOCKET_NS +#endif + +#include +using std::string; +#include "Csocket.h" + +#endif // !_MAIN_H diff --git a/md5.cpp b/md5.cpp new file mode 100644 index 00000000..cee72c84 --- /dev/null +++ b/md5.cpp @@ -0,0 +1,283 @@ +/* + * RFC 1321 compliant MD5 implementation + * + * Copyright (C) 2001-2003 Christophe Devine + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include + +#include "md5.h" + +CMD5::CMD5() +{ + *m_szMD5 = '\0'; +} + +CMD5::CMD5(const string& sText) +{ + MakeHash(sText.c_str(), sText.length()); +} + +CMD5::CMD5(const char* szText, uint32 nTextLen) +{ + MakeHash(szText, nTextLen); +} + +CMD5::~CMD5() +{ +} + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32) (b)[(i) ] ) \ + | ( (uint32) (b)[(i) + 1] << 8 ) \ + | ( (uint32) (b)[(i) + 2] << 16 ) \ + | ( (uint32) (b)[(i) + 3] << 24 ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8) ( (n) ); \ + (b)[(i) + 1] = (uint8) ( (n) >> 8 ); \ + (b)[(i) + 2] = (uint8) ( (n) >> 16 ); \ + (b)[(i) + 3] = (uint8) ( (n) >> 24 ); \ +} + +void CMD5::md5_starts( md5_context *ctx ) const +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +void CMD5::md5_process( md5_context *ctx, uint8 data[64] ) const +{ + uint32 X[16], A, B, C, D; + + GET_UINT32( X[0], data, 0 ); + GET_UINT32( X[1], data, 4 ); + GET_UINT32( X[2], data, 8 ); + GET_UINT32( X[3], data, 12 ); + GET_UINT32( X[4], data, 16 ); + GET_UINT32( X[5], data, 20 ); + GET_UINT32( X[6], data, 24 ); + GET_UINT32( X[7], data, 28 ); + GET_UINT32( X[8], data, 32 ); + GET_UINT32( X[9], data, 36 ); + GET_UINT32( X[10], data, 40 ); + GET_UINT32( X[11], data, 44 ); + GET_UINT32( X[12], data, 48 ); + GET_UINT32( X[13], data, 52 ); + GET_UINT32( X[14], data, 56 ); + GET_UINT32( X[15], data, 60 ); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define P(a,b,c,d,k,s,t) \ +{ \ + a += F(b,c,d) + X[k] + t; a = S(a,s) + b; \ +} + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + +#define F(x,y,z) (z ^ (x & (y ^ z))) + + P( A, B, C, D, 0, 7, 0xD76AA478 ); + P( D, A, B, C, 1, 12, 0xE8C7B756 ); + P( C, D, A, B, 2, 17, 0x242070DB ); + P( B, C, D, A, 3, 22, 0xC1BDCEEE ); + P( A, B, C, D, 4, 7, 0xF57C0FAF ); + P( D, A, B, C, 5, 12, 0x4787C62A ); + P( C, D, A, B, 6, 17, 0xA8304613 ); + P( B, C, D, A, 7, 22, 0xFD469501 ); + P( A, B, C, D, 8, 7, 0x698098D8 ); + P( D, A, B, C, 9, 12, 0x8B44F7AF ); + P( C, D, A, B, 10, 17, 0xFFFF5BB1 ); + P( B, C, D, A, 11, 22, 0x895CD7BE ); + P( A, B, C, D, 12, 7, 0x6B901122 ); + P( D, A, B, C, 13, 12, 0xFD987193 ); + P( C, D, A, B, 14, 17, 0xA679438E ); + P( B, C, D, A, 15, 22, 0x49B40821 ); + +#undef F + +#define F(x,y,z) (y ^ (z & (x ^ y))) + + P( A, B, C, D, 1, 5, 0xF61E2562 ); + P( D, A, B, C, 6, 9, 0xC040B340 ); + P( C, D, A, B, 11, 14, 0x265E5A51 ); + P( B, C, D, A, 0, 20, 0xE9B6C7AA ); + P( A, B, C, D, 5, 5, 0xD62F105D ); + P( D, A, B, C, 10, 9, 0x02441453 ); + P( C, D, A, B, 15, 14, 0xD8A1E681 ); + P( B, C, D, A, 4, 20, 0xE7D3FBC8 ); + P( A, B, C, D, 9, 5, 0x21E1CDE6 ); + P( D, A, B, C, 14, 9, 0xC33707D6 ); + P( C, D, A, B, 3, 14, 0xF4D50D87 ); + P( B, C, D, A, 8, 20, 0x455A14ED ); + P( A, B, C, D, 13, 5, 0xA9E3E905 ); + P( D, A, B, C, 2, 9, 0xFCEFA3F8 ); + P( C, D, A, B, 7, 14, 0x676F02D9 ); + P( B, C, D, A, 12, 20, 0x8D2A4C8A ); + +#undef F + +#define F(x,y,z) (x ^ y ^ z) + + P( A, B, C, D, 5, 4, 0xFFFA3942 ); + P( D, A, B, C, 8, 11, 0x8771F681 ); + P( C, D, A, B, 11, 16, 0x6D9D6122 ); + P( B, C, D, A, 14, 23, 0xFDE5380C ); + P( A, B, C, D, 1, 4, 0xA4BEEA44 ); + P( D, A, B, C, 4, 11, 0x4BDECFA9 ); + P( C, D, A, B, 7, 16, 0xF6BB4B60 ); + P( B, C, D, A, 10, 23, 0xBEBFBC70 ); + P( A, B, C, D, 13, 4, 0x289B7EC6 ); + P( D, A, B, C, 0, 11, 0xEAA127FA ); + P( C, D, A, B, 3, 16, 0xD4EF3085 ); + P( B, C, D, A, 6, 23, 0x04881D05 ); + P( A, B, C, D, 9, 4, 0xD9D4D039 ); + P( D, A, B, C, 12, 11, 0xE6DB99E5 ); + P( C, D, A, B, 15, 16, 0x1FA27CF8 ); + P( B, C, D, A, 2, 23, 0xC4AC5665 ); + +#undef F + +#define F(x,y,z) (y ^ (x | ~z)) + + P( A, B, C, D, 0, 6, 0xF4292244 ); + P( D, A, B, C, 7, 10, 0x432AFF97 ); + P( C, D, A, B, 14, 15, 0xAB9423A7 ); + P( B, C, D, A, 5, 21, 0xFC93A039 ); + P( A, B, C, D, 12, 6, 0x655B59C3 ); + P( D, A, B, C, 3, 10, 0x8F0CCC92 ); + P( C, D, A, B, 10, 15, 0xFFEFF47D ); + P( B, C, D, A, 1, 21, 0x85845DD1 ); + P( A, B, C, D, 8, 6, 0x6FA87E4F ); + P( D, A, B, C, 15, 10, 0xFE2CE6E0 ); + P( C, D, A, B, 6, 15, 0xA3014314 ); + P( B, C, D, A, 13, 21, 0x4E0811A1 ); + P( A, B, C, D, 4, 6, 0xF7537E82 ); + P( D, A, B, C, 11, 10, 0xBD3AF235 ); + P( C, D, A, B, 2, 15, 0x2AD7D2BB ); + P( B, C, D, A, 9, 21, 0xEB86D391 ); + +#undef F + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; +} + +void CMD5::md5_update( md5_context *ctx, uint8 *input, uint32 length ) const +{ + uint32 left, fill; + + if( ! length ) return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += length; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < length ) + ctx->total[1]++; + + if( left && length >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + md5_process( ctx, ctx->buffer ); + length -= fill; + input += fill; + left = 0; + } + + while( length >= 64 ) + { + md5_process( ctx, input ); + length -= 64; + input += 64; + } + + if( length ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, length ); + } +} + +static uint8 md5_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void CMD5::md5_finish( md5_context *ctx, uint8 digest[16] ) const +{ + uint32 last, padn; + uint32 high, low; + uint8 msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32( low, msglen, 0 ); + PUT_UINT32( high, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + md5_update( ctx, md5_padding, padn ); + md5_update( ctx, msglen, 8 ); + + PUT_UINT32( ctx->state[0], digest, 0 ); + PUT_UINT32( ctx->state[1], digest, 4 ); + PUT_UINT32( ctx->state[2], digest, 8 ); + PUT_UINT32( ctx->state[3], digest, 12 ); +} + +char* CMD5::MakeHash(const char* szText, uint32 nTextLen) +{ + md5_context ctx; + unsigned char md5sum[16]; + unsigned short i; + + md5_starts(&ctx); + md5_update(&ctx, (uint8*)szText, nTextLen); + md5_finish(&ctx, md5sum); + + for (i = 0; i < 16; i++) + { + sprintf(m_szMD5 + i * 2, "%02x", md5sum[i]); + } + + return(m_szMD5); +} diff --git a/md5.h b/md5.h new file mode 100644 index 00000000..8e32896f --- /dev/null +++ b/md5.h @@ -0,0 +1,56 @@ +/* C implementation by Christophe Devine, C++ "class-ified" by [T3] */ + +#include +using std::string; + +#ifndef _MD5_H +#define _MD5_H + +#ifndef uint8 +#define uint8 unsigned char +#endif + +#ifndef uint32 +#define uint32 unsigned long int +#endif + +typedef struct +{ + uint32 total[2]; + uint32 state[4]; + uint8 buffer[64]; +} +md5_context; + +class CMD5 { +protected: + char m_szMD5[33]; + +public: + CMD5(); + CMD5(const string& sText); + CMD5(const char* szText, uint32 nTextLen); + virtual ~CMD5(); + + operator string() const + { + return (string) m_szMD5; + } + + operator char*() const + { + return (char*)m_szMD5; + } + + char* MakeHash(const char* szText, uint32 nTextLen); + +protected: + void md5_starts( md5_context *ctx ) const; + void md5_update( md5_context *ctx, uint8 *input, uint32 length ) const; + void md5_finish( md5_context *ctx, uint8 digest[16] ) const; + +private: + void md5_process( md5_context *ctx, uint8 data[64] ) const; +}; + +#endif /* _MD5_H */ diff --git a/modules/buildmod b/modules/buildmod new file mode 100755 index 00000000..5a5c813d --- /dev/null +++ b/modules/buildmod @@ -0,0 +1,45 @@ +#!/bin/sh +CXXFLAGS="-Wall -D_GNU_SOURCE -DHAVE_LIBSSL -I.." +LIBS="-lssl" + +if test -z "$1"; then + echo "Usage: $0 [...]" + exit 1 +fi + +for arg in "$@" +do + if test -d $arg; then + (cd $arg && make || exit 1) + elif test -f "$arg"; then + FILE="$arg" + MOD="${FILE%.cpp}" + MOD="${MOD%.cc}" + elif test -f "$arg.cpp"; then + FILE="$arg.cpp" + MOD="$arg" + elif test -f "$arg.cc"; then + FILE="$arg.cc" + MOD="$arg" + fi + + rm -f "$MOD.so" + + if test -n "$FILE"; then + COMMAND="g++ $CXXFLAGS -c $FILE" + echo $COMMAND + $COMMAND || exit 1 + COMMAND="g++ $CXXFLAGS -shared -o $MOD.so $MOD.o $LIBS" + echo $COMMAND + $COMMAND || exit 1 + fi + + if ! test -f "$MOD.so"; then + echo "Failed to build $MOD.so!" + exit 1 + else + echo "Built $MOD.so" + fi +done + +exit 0 diff --git a/modules/email.cc b/modules/email.cc new file mode 100644 index 00000000..14731034 --- /dev/null +++ b/modules/email.cc @@ -0,0 +1,269 @@ +#include "main.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" +#include "Utils.h" +#include "md5.h" +#include +#include + + +/* + * Email Monitor / Retrieval + * Author: imaginos + * + * $Log$ + * Revision 1.1 2004/08/24 00:08:52 prozacx + * Initial revision + * + * + */ + +struct EmailST +{ + string sFrom; + string sSubject; + string sUidl; + u_int iSize; +}; + +class CEmailJob : public CTimer +{ +public: + CEmailJob( CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription ) + : CTimer( pModule, uInterval, uCycles, sLabel, sDescription) {} + + virtual ~CEmailJob() {} + +protected: + virtual void RunJob(); +}; + +class CEmail : public CModule +{ +public: + MODCONSTRUCTOR(CEmail) + { + m_iLastCheck = 0; + m_bInitialized = false; + } + virtual ~CEmail() + { + vector vSocks = m_pManager->FindSocksByName( "EMAIL::" + m_pUser->GetUserName() ); + for( u_int a = 0; a < vSocks.size(); a++ ) + m_pManager->DelSockByAddr( vSocks[a] ); + } + + virtual bool OnLoad(const string & sArgs) { + m_sMailPath = sArgs; + + StartParser(); + if ( m_pUser->IsUserAttached() ) + StartTimer(); + + return true; + } + + virtual void OnUserAttached() + { + stringstream s; + s << "You have " << m_ssUidls.size() << " emails."; + PutModule( s.str() ); + StartTimer(); + } + virtual void OnUserDetached() + { + RemTimer( "EMAIL::" + m_pUser->GetUserName() ); + } + + void StartTimer() + { + if ( !FindTimer( "EMAIL::" + m_pUser->GetUserName() ) ) + { + CEmailJob *p = new CEmailJob( this, 60, 0, "EmailMonitor", "Monitors email activity" ); + AddTimer( p ); + } + } + + virtual string GetDescription() + { + return ( "Monitors Email activity on local disk /var/mail/user" ); + } + + virtual void OnModCommand( const string& sCommand ); + void StartParser(); + + void ParseEmails( const vector & vEmails ) + { + if ( !m_bInitialized ) + { + m_bInitialized = true; + for( u_int a = 0; a < vEmails.size(); a++ ) + m_ssUidls.insert( vEmails[a].sUidl ); + + stringstream s; + s << "You have " << vEmails.size() << " emails."; + PutModule( s.str() ); + } else + { + set ssUidls; + + CTable Table; + Table.AddColumn("From"); + Table.AddColumn("Size"); + Table.AddColumn("Subject"); + + for( u_int a = 0; a < vEmails.size(); a++ ) + { + if ( m_ssUidls.find( vEmails[a].sUidl ) == m_ssUidls.end() ) + { + //PutModule( "------------------- New Email -------------------" ); + Table.AddRow(); + Table.SetCell( "From", CUtils::Ellipsize( vEmails[a].sFrom, 32 ) ); + Table.SetCell( "Size", CUtils::ToString( vEmails[a].iSize ) ); + Table.SetCell( "Subject", CUtils::Ellipsize( vEmails[a].sSubject, 64 ) ); + } + ssUidls.insert( vEmails[a].sUidl ); + } + + m_ssUidls = ssUidls; // keep the list in synch + + if (Table.size()) { + unsigned int uTableIdx = 0; + string sLine; + while ( Table.GetLine( uTableIdx++, sLine ) ) { + PutModule( sLine ); + } + + stringstream s; + s << "You have " << vEmails.size() << " emails."; + PutModule( s.str() ); + } + } + } + + +private: + string m_sMailPath; + u_int m_iLastCheck; + set m_ssUidls; + bool m_bInitialized; +}; + +class CEmailFolder : public Csock +{ +public: + CEmailFolder( CEmail *pModule, const string & sMailbox ) : Csock() + { + m_pModule = pModule; + m_sMailbox = sMailbox; + EnableReadLine(); + } + + virtual ~CEmailFolder() + { + if ( !m_sMailBuffer.empty() ) + ProcessMail(); // get the last one + + if ( !m_vEmails.empty() ) + m_pModule->ParseEmails( m_vEmails ); + } + + virtual void ReadLine( const CS_STRING & sLine ) + { + if ( sLine.substr( 0, 5 ) == "From " ) + { + if ( !m_sMailBuffer.empty() ) + { + ProcessMail(); + m_sMailBuffer.clear(); + } + } + m_sMailBuffer += sLine; + } + + void ProcessMail() + { + EmailST tmp; + tmp.sUidl = (char *)CMD5( m_sMailBuffer.substr( 0, 255 ) ); + string sLine; + u_int iPos = 0; + while( ::ReadLine( m_sMailBuffer, sLine, iPos ) ) + { + CUtils::Trim( sLine ); + if ( sLine.empty() ) + break; // out of the headers + + if ( strncasecmp( sLine.c_str(), "From: ", 6 ) == 0 ) + tmp.sFrom = sLine.substr( 6, string::npos ); + else if ( strncasecmp( sLine.c_str(), "Subject: ", 9 ) == 0 ) + tmp.sSubject = sLine.substr( 9, string::npos ); + + if ( ( !tmp.sFrom.empty() ) && ( !tmp.sSubject.empty() ) ) + break; + } + tmp.iSize = m_sMailBuffer.length(); + m_vEmails.push_back( tmp ); + } +private: + CEmail *m_pModule; + string m_sMailbox; + string m_sMailBuffer; + vector m_vEmails; +}; + +void CEmail::OnModCommand( const string& sCommand ) +{ + u_int iPos = sCommand.find( " " ); + string sCom, sArgs; + if ( iPos == string::npos ) + sCom = sCommand; + else + { + sCom = sCommand.substr( 0, iPos ); + sArgs = sCommand.substr( iPos + 1, string::npos ); + } + + if ( sCom == "timers" ) + { + ListTimers(); + } else + PutModule( "Error, no such command [" + sCom + "]" ); +} + +void CEmail::StartParser() +{ + string sParserName = "EMAIL::" + m_pUser->GetUserName(); + + if ( m_pManager->FindSockByName( sParserName ) ) + return; // one at a time sucker + + CFile cFile( m_sMailPath ); + if ( ( !cFile.Exists() ) || ( cFile.GetSize() == 0 ) ) + { + m_bInitialized = true; + return; // der + } + + if ( cFile.GetMTime() <= m_iLastCheck ) + return; // only check if modified + + int iFD = open( m_sMailPath.c_str(), O_RDONLY ); + if ( iFD >= 0 ) + { + m_iLastCheck = time( NULL ); + CEmailFolder *p = new CEmailFolder( this, m_sMailPath ); + p->SetRSock( iFD ); + p->SetWSock( iFD ); + m_pManager->AddSock( (Csock *)p, "EMAIL::" + m_pUser->GetUserName() ); + } +} + +void CEmailJob::RunJob() +{ + CEmail *p = (CEmail *)m_pModule; + p->StartParser(); +} +MODULEDEFS(CEmail) + diff --git a/modules/raw.cpp b/modules/raw.cpp new file mode 100644 index 00000000..9eb1cda6 --- /dev/null +++ b/modules/raw.cpp @@ -0,0 +1,28 @@ +#include "main.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" + +class CRawMod : public CModule { +public: + MODCONSTRUCTOR(CRawMod) {} + virtual ~CRawMod() {} + + virtual string GetDescription() { + return "View all of the raw traffic."; + } + + virtual bool OnRaw(string& sLine) { + PutModule("IRC -> [" + sLine + "]"); + return false; + } + + virtual bool OnUserRaw(string& sLine) { + PutModule("YOU -> [" + sLine + "]"); + return false; + } +}; + +MODULEDEFS(CRawMod) + diff --git a/modules/sample.cpp b/modules/sample.cpp new file mode 100644 index 00000000..8a822a81 --- /dev/null +++ b/modules/sample.cpp @@ -0,0 +1,198 @@ +#include "main.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" + +class CSampleTimer : public CTimer { +public: + + CSampleTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {} + virtual ~CSampleTimer() {} + +private: +protected: + virtual void RunJob() { + m_pModule->PutModule("TEST!!!!"); + } +}; + +class CSampleMod : public CModule { +public: + MODCONSTRUCTOR(CSampleMod) {} + + virtual bool OnLoad(const string& sArgs) { + PutModule("I'm being loaded with the arguments: [" + sArgs + "]"); + //AddTimer(new CSampleTimer(this, 300, 0, "Sample", "Sample timer for sample things.")); + //AddTimer(new CSampleTimer(this, 5, 20, "Another", "Another sample timer.")); + //AddTimer(new CSampleTimer(this, 25000, 5, "Third", "A third sample timer.")); + return true; + } + + virtual ~CSampleMod() { + PutModule("I'm being unloaded!"); + } + + virtual bool OnBoot() { + // This is called when the app starts up (only modules that are loaded in the config will get this event) + return true; + } + + virtual string GetDescription() { + return "To be used as a sample for writing modules."; + } + + virtual void OnIRCConnected() { + PutModule("You got connected BoyOh."); + } + + virtual void OnIRCDisconnected() { + PutModule("You got disconnected BoyOh."); + } + + virtual void OnOp(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + PutModule(((bNoChange) ? "[0] " : "[1] [") + OpNick.GetNick() + "] opped [" + Nick.GetNick() + "] on [" + Channel.GetName() + "]"); + } + + virtual void OnDeop(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + PutModule(((bNoChange) ? "[0] " : "[1] [") + OpNick.GetNick() + "] deopped [" + Nick.GetNick() + "] on [" + Channel.GetName() + "]"); + } + + virtual void OnVoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + PutModule(((bNoChange) ? "[0] " : "[1] [") + OpNick.GetNick() + "] voiced [" + Nick.GetNick() + "] on [" + Channel.GetName() + "]"); + } + + virtual void OnDevoice(const CNick& OpNick, const CNick& Nick, const CChan& Channel, bool bNoChange) { + PutModule(((bNoChange) ? "[0] " : "[1] [") + OpNick.GetNick() + "] devoiced [" + Nick.GetNick() + "] on [" + Channel.GetName() + "]"); + } + + virtual void OnRawMode(const CNick& OpNick, const CChan& Channel, const string& sModes, const string& sArgs) { + PutModule("* " + OpNick.GetNick() + " sets mode: " + sModes + " " + sArgs + " (" + Channel.GetName() + ")"); + } + + virtual bool OnRaw(string& sLine) { + // PutModule("OnRaw() [" + sLine + "]"); + return false; + } + + virtual bool OnUserRaw(string& sLine) { + // PutModule("UserRaw() [" + sLine + "]"); + return false; + } + + virtual void OnKick(const CNick& OpNick, const string& sKickedNick, const CChan& Channel, const string& sMessage) { + PutModule("[" + OpNick.GetNick() + "] kicked [" + sKickedNick + "] from [" + Channel.GetName() + "] with the msg [" + sMessage + "]"); + } + + virtual void OnQuit(const CNick& Nick, const string& sMessage) { + PutModule("* Quits: " + Nick.GetNick() + " (" + Nick.GetIdent() + "!" + Nick.GetHost() + ") (" + sMessage + ")"); + } + + virtual void OnJoin(const CNick& Nick, const CChan& Channel) { + PutModule("* Joins: " + Nick.GetNick() + " (" + Nick.GetIdent() + "!" + Nick.GetHost() + ")"); + } + + virtual void OnPart(const CNick& Nick, const CChan& Channel) { + PutModule("* Parts: " + Nick.GetNick() + " (" + Nick.GetIdent() + "!" + Nick.GetHost() + ")"); + } + + virtual void OnNick(const CNick& OldNick, const string& sNewNick) { + PutModule("* " + OldNick.GetNick() + " is now known as " + sNewNick); + } + + virtual bool OnUserCTCPReply(const CNick& Nick, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] userctcpreply [" + sMessage + "]"); + sMessage = "\037" + sMessage + "\037"; + + return false; + } + + virtual bool OnCTCPReply(const CNick& Nick, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] ctcpreply [" + sMessage + "]"); + + return false; + } + + virtual bool OnUserCTCP(const string& sTarget, string& sMessage) { + PutModule("[" + sTarget + "] userctcp [" + sMessage + "]"); + + return false; + } + + virtual bool OnPrivCTCP(const CNick& Nick, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] privctcp [" + sMessage + "]"); + sMessage = "\002" + sMessage + "\002"; + + return false; + } + + virtual bool OnChanCTCP(const CNick& Nick, const CChan& Channel, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] chanctcp [" + sMessage + "] to [" + Channel.GetName() + "]"); + sMessage = "\00311,5 " + sMessage + " \003"; + + return false; + } + + virtual bool OnUserNotice(const string& sTarget, string& sMessage) { + PutModule("[" + sTarget + "] usernotice [" + sMessage + "]"); + sMessage = "\037" + sMessage + "\037"; + + return false; + } + + virtual bool OnPrivNotice(const CNick& Nick, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] privnotice [" + sMessage + "]"); + sMessage = "\002" + sMessage + "\002"; + + return false; + } + + virtual bool OnChanNotice(const CNick& Nick, const CChan& Channel, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] channotice [" + sMessage + "] to [" + Channel.GetName() + "]"); + sMessage = "\00311,5 " + sMessage + " \003"; + + return false; + } + + virtual bool OnUserMsg(const string& sTarget, string& sMessage) { + PutModule("[" + sTarget + "] usermsg [" + sMessage + "]"); + sMessage = "\0034" + sMessage + "\003"; + + return false; + } + + virtual bool OnPrivMsg(const CNick& Nick, string& sMessage) { + PutModule("[" + Nick.GetNick() + "] privmsg [" + sMessage + "]"); + sMessage = "\002" + sMessage + "\002"; + + return false; + } + + virtual bool OnChanMsg(const CNick& Nick, const CChan& Channel, string& sMessage) { + if (sMessage == "!ping") { + PutIRC("PRIVMSG " + Channel.GetName() + " :PONG?"); + } + + sMessage = "x " + sMessage + " x"; + + return false; + } + + virtual void OnModCommand(const string& sCommand) { + if (strcasecmp(sCommand.c_str(), "TIMERS") == 0) { + ListTimers(); + } + } + + virtual bool OnStatusCommand(const string& sCommand) { + if (strcasecmp(sCommand.c_str(), "SAMPLE") == 0) { + PutModule("Hi, I'm your friendly sample module."); + return true; + } + + return false; + } +}; + +MODULEDEFS(CSampleMod) + diff --git a/modules/savebuff.cc b/modules/savebuff.cc new file mode 100644 index 00000000..38824225 --- /dev/null +++ b/modules/savebuff.cc @@ -0,0 +1,211 @@ +#include "main.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" +#include "Utils.h" +#include + +#ifndef HAVE_LIBSSL +#error This plugin only works with OpenSSL +#endif /* HAVE_LIBSSL */ + +#define CRYPT_VERIFICATION_TOKEN "::__:SAVEBUFF:__::" + +/* + * Buffer Saving thing, incase your shit goes out while your out + * Author: imaginos + * + * Its only as secure as your shell, the encryption only offers a slightly + * better solution then plain text. + * + * $Log$ + * Revision 1.1 2004/08/24 00:08:52 prozacx + * Initial revision + * + * + */ + +class CSaveBuff; + +class CSaveBuffJob : public CTimer +{ +public: + CSaveBuffJob( CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription ) + : CTimer( pModule, uInterval, uCycles, sLabel, sDescription) {} + + virtual ~CSaveBuffJob() {} + +protected: + virtual void RunJob(); +}; + +class CSaveBuff : public CModule +{ +public: + MODCONSTRUCTOR(CSaveBuff) + { + // m_sPassword = CBlowfish::MD5( "" ); + AddTimer( new CSaveBuffJob( this, 60, 0, "SaveBuff", "Saves the current buffer to disk every 1 minute" ) ); + } + virtual ~CSaveBuff() + { + SaveBufferToDisk(); + } + + virtual bool OnBoot() + { + if ( m_sPassword.empty() ) + { + char *pTmp = getpass( "Enter Encryption Key for savebuff.so: " ); + + if ( pTmp ) + m_sPassword = CBlowfish::MD5( pTmp ); + + *pTmp = 0; + } + + const vector& vChans = m_pUser->GetChans(); + for( u_int a = 0; a < vChans.size(); a++ ) + { + string sFile; + string sPath = GetPath( vChans[a]->GetName() ); + + if ( ( sPath.empty() ) || ( !ReadFile( sPath, sFile ) ) ) + continue; + if ( !sFile.empty() ) + { + CBlowfish c( m_sPassword, BF_DECRYPT ); + sFile = c.Crypt( sFile ); + + if ( sFile.substr( 0, strlen( CRYPT_VERIFICATION_TOKEN ) ) != CRYPT_VERIFICATION_TOKEN ) + { + // failed to decode :( + cerr << "Unable to decode Encrypted file [" << sPath << "]" << endl; + continue; + } + sFile.erase( 0, strlen( CRYPT_VERIFICATION_TOKEN ) ); + + string sLine; + u_int iPos = 0; + while( ReadLine( sFile, sLine, iPos ) ) + { + CUtils::Trim( sLine ); + vChans[a]->AddBuffer( sLine ); + } + } + } + + return true; + } + void SaveBufferToDisk() + { + if ( !m_sPassword.empty() ) + { + const vector& vChans = m_pUser->GetChans(); + for( u_int a = 0; a < vChans.size(); a++ ) + { + const vector & vBuffer = vChans[a]->GetBuffer(); + if ( vBuffer.empty() ) + continue; + + string sFile = CRYPT_VERIFICATION_TOKEN; + + for( u_int b = 0; b < vBuffer.size(); b++ ) + sFile += vBuffer[b] + "\n"; + + CBlowfish c( m_sPassword, BF_ENCRYPT ); + sFile = c.Crypt( sFile ); + string sPath = GetPath( vChans[a]->GetName() ); + if ( !sPath.empty() ) + { + WriteFile( sPath, sFile ); + chmod( sPath.c_str(), 0600 ); + } + } + } + } + + virtual string GetDescription() + { + return ( "Stores channel buffers to disk, encrypted." ); + } + + virtual void OnModCommand( const string& sCommand ) + { + u_int iPos = sCommand.find( " " ); + string sCom, sArgs; + if ( iPos == string::npos ) + sCom = sCommand; + else + { + sCom = sCommand.substr( 0, iPos ); + sArgs = sCommand.substr( iPos + 1, string::npos ); + } + + if ( strcasecmp( sCom.c_str(), "setpass" ) == 0 ) + { + PutModule( "Password set to [" + sArgs + "]" ); + m_sPassword = CBlowfish::MD5( sArgs ); + + } else if ( strcasecmp( sCom.c_str(), "dumpbuff" ) == 0 ) + { + string sChannel = GetPath( sArgs ); + string sFile; + + if ( ( sChannel.empty() ) || ( !ReadFile( sChannel, sFile ) ) ) + { + PutModule( "Unable to find buffer for that channel" ); + return; + } + + if ( !sFile.empty() ) + { + CBlowfish c( m_sPassword, BF_DECRYPT ); + sFile = c.Crypt( sFile ); + + if ( sFile.substr( 0, strlen( CRYPT_VERIFICATION_TOKEN ) ) != CRYPT_VERIFICATION_TOKEN ) + { + // failed to decode :( + PutModule( "Unable to decode Encrypted file [" + sChannel + "]" ); + return; + } + sFile.erase( 0, strlen( CRYPT_VERIFICATION_TOKEN ) ); + + string sLine; + u_int iPos = 0; + while( ReadLine( sFile, sLine, iPos ) ) + { + CUtils::Trim( sLine ); + PutModule( "[" + sLine + "]" ); + } + } + } else if ( strcasecmp( sCom.c_str(), "save" ) == 0 ) + { + SaveBufferToDisk(); + PutModule( "Done." ); + } else + PutModule( "Unknown command [" + sCommand + "]" ); + } + + string GetPath( const string & sChannel ) + { + string sBuffer = m_pUser->GetUserName() + Lower( sChannel ); + string sRet = m_pUser->GetHomePath(); + sRet += "/.znc-savebuff-" + CBlowfish::MD5( sBuffer, true ); + return( sRet ); + } + +private: + string m_sPassword; +}; + + +void CSaveBuffJob::RunJob() +{ + CSaveBuff *p = (CSaveBuff *)m_pModule; + p->SaveBufferToDisk(); +} + +MODULEDEFS(CSaveBuff) + diff --git a/modules/schat.cc b/modules/schat.cc new file mode 100644 index 00000000..4b2595d4 --- /dev/null +++ b/modules/schat.cc @@ -0,0 +1,462 @@ +#include "main.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" +#include "Utils.h" +#include "Csocket.h" +#include "md5.h" +#include +#include + +/* + * Secure chat system + * Author: imaginos + * + * $Log$ + * Revision 1.1 2004/08/24 00:08:52 prozacx + * Initial revision + * + * + */ +class CSChat; + +class CRemMarkerJob : public CTimer +{ +public: + CRemMarkerJob( CModule* pModule, unsigned int uInterval, unsigned int uCycles, const string& sLabel, const string& sDescription ) + : CTimer( pModule, uInterval, uCycles, sLabel, sDescription) {} + + virtual ~CRemMarkerJob() {} + void SetNick( const string & sNick ) + { + m_sNick = sNick; + } + +protected: + virtual void RunJob(); + string m_sNick; +}; + +class CSChatSock : public Csock +{ +public: + CSChatSock( CSChat *pMod ) : Csock() + { + m_pModule = pMod; + } + CSChatSock( int itimeout = 60 ) : Csock( itimeout ) + { + m_pModule = NULL; + EnableReadLine(); + } + CSChatSock( const CS_STRING & sHost, int iPort, int iTimeout = 60 ) + : Csock( sHost, iPort, iTimeout ) + { + m_pModule = NULL; + EnableReadLine(); + } + + virtual Csock *GetSockObj( const CS_STRING & sHostname, int iPort ) + { + CSChatSock *p = new CSChatSock( sHostname, iPort ); + return( p ); + } + + virtual bool ConnectionFrom( const CS_STRING & sHost, int iPort ) + { + Close(); // close the listener after the first connection + return( true ); + } + + virtual void Connected(); + virtual bool CreatedChild( Csock *pSock ) + { + CSChatSock *p = (CSChatSock *)pSock; + p->SetModule( m_pModule ); + p->SetChatNick( m_sChatNick ); + p->SetSockName( GetSockName() + "::" + m_sChatNick ); + return( true ); + } + + virtual void Timeout(); + + void SetModule( CSChat *p ) + { + m_pModule = p; + } + void SetChatNick( const string & sNick ) + { + m_sChatNick = sNick; + } + + const string & GetChatNick() const { return( m_sChatNick ); } + + virtual void ReadLine( const CS_STRING & sLine ); + virtual void Disconnected(); + + virtual void AddLine( const string & sLine ) + { + m_vBuffer.insert( m_vBuffer.begin(), sLine ); + if ( m_vBuffer.size() > 200 ) + m_vBuffer.pop_back(); + } + + virtual void DumpBuffer() + { + for( vector::reverse_iterator it = m_vBuffer.rbegin(); it != m_vBuffer.rend(); it++ ) + ReadLine( *it ); + + m_vBuffer.clear(); + } + +private: + CSChat *m_pModule; + string m_sChatNick; + vector m_vBuffer; +}; + + + +class CSChat : public CModule +{ +public: + MODCONSTRUCTOR(CSChat) {} + virtual ~CSChat() { CleanSocks(); } + + virtual bool OnLoad( const string & sArgs ) + { + m_sPemFile = sArgs; + + if ( m_sPemFile.empty() ) + { + m_sPemFile = m_pUser->GetBinPath() + "/znc.pem"; + } + + if (!CFile::Exists(m_sPemFile)) { + PutModule("Unable to load pem file [" + m_sPemFile + "]"); + return false; + } + + return true; + } + + virtual void OnUserAttached() + { + string sName = "SCHAT::" + m_pUser->GetUserName(); + for( u_int a = 0; a < m_pManager->size(); a++ ) + { + if ( ( strncmp( (*m_pManager)[a]->GetSockName().c_str(), sName.c_str(), sName.length() ) != 0 ) || ( (*m_pManager)[a]->GetType() == CSChatSock::LISTENER ) ) + continue; + + CSChatSock *p = (CSChatSock *)(*m_pManager)[a]; + p->DumpBuffer(); + } + } + virtual void OnUserDetached() {} + + void CleanSocks() + { + string sName = "SCHAT::" + m_pUser->GetUserName(); + for( u_int a= 0; a < m_pManager->size(); a++ ) + { + if ( strncmp( (*m_pManager)[a]->GetSockName().c_str(), sName.c_str(), sName.length() ) == 0 ) + m_pManager->DelSock( a-- ); + } + } + + virtual string GetDescription() + { + return ( "Secure cross platform (:P) chat system" ); + } + + virtual bool OnUserRaw( string & sLine ) + { + if ( strncasecmp( sLine.c_str(), "schat ", 6 ) == 0 ) + { + OnModCommand( "chat " + sLine.substr( 6, string::npos ) ); + return( true ); + + } else if ( strcasecmp( sLine.c_str(), "schat" ) == 0 ) + { + PutModule( "SChat User Area ..." ); + OnModCommand( "help" ); + return( true ); + + } + + return( false ); + } + virtual void OnModCommand( const string& sCommand ) + { + u_int iPos = sCommand.find( " " ); + string sCom, sArgs; + if ( iPos == string::npos ) + sCom = sCommand; + else + { + sCom = sCommand.substr( 0, iPos ); + sArgs = sCommand.substr( iPos + 1, string::npos ); + } + + if ( ( strcasecmp( sCom.c_str(), "chat" ) == 0 ) && ( !sArgs.empty() ) ) + { + string sSockName = "SCHAT::" + m_pUser->GetUserName(); + string sNick = "(s)" + sArgs; + for( u_int a= 0; a < m_pManager->size(); a++ ) + { + if ( strncmp( (*m_pManager)[a]->GetSockName().c_str(), sSockName.c_str(), sSockName.length() ) != 0 ) + continue; + + CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a]; + if ( strcasecmp( pSock->GetChatNick().c_str(), sNick.c_str() ) == 0 ) + { + PutModule( "Already Connected to [" + sArgs + "]" ); + return; + } + } + + CSChatSock *pSock = new CSChatSock; + pSock->SetCipher( "HIGH" ); + pSock->SetPemLocation( m_sPemFile ); + pSock->SetModule( this ); + pSock->SetChatNick( sNick ); + + u_short iPort = m_pManager->ListenRand( sSockName, m_pUser->GetLocalIP(), true, SOMAXCONN, pSock, 60 ); + + if ( iPort == 0 ) + { + PutModule( "Failed to start chat!" ); + return; + } + + stringstream s; + s << "PRIVMSG " << sArgs << " :\001"; + s << "DCC SCHAT chat "; + s << CUtils::GetLongIP( m_pUser->GetLocalIP() ); + s << " " << iPort << "\001"; + + PutIRC( s.str() ); + + } else if ( strcasecmp( sCom.c_str(), "list" ) == 0 ) + { + string sName = "SCHAT::" + m_pUser->GetUserName(); + CTable Table; + Table.AddColumn( "Nick" ); + Table.AddColumn( "Created" ); + Table.AddColumn( "Host" ); + Table.AddColumn( "Port" ); + Table.AddColumn( "Status" ); + Table.AddColumn( "Cipher" ); + for( u_int a= 0; a < m_pManager->size(); a++ ) + { + if ( strncmp( (*m_pManager)[a]->GetSockName().c_str(), sName.c_str(), sName.length() ) != 0 ) + continue; + + Table.AddRow(); + + CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a]; + Table.SetCell( "Nick", pSock->GetChatNick() ); + unsigned long long iStartTime = pSock->GetStartTime(); + time_t iTime = iStartTime / 1000; + char *pTime = ctime( &iTime ); + if ( pTime ) + { + string sTime = pTime; + CUtils::Trim( sTime ); + Table.SetCell( "Created", sTime ); + } + + if ( pSock->GetType() != CSChatSock::LISTENER ) + { + Table.SetCell( "Status", "Established" ); + Table.SetCell( "Host", pSock->GetRemoteIP() ); + Table.SetCell( "Port", CUtils::ToString( pSock->GetRemotePort() ) ); + SSL_SESSION *pSession = pSock->GetSSLSession(); + if ( ( pSession ) && ( pSession->cipher ) && ( pSession->cipher->name ) ) + Table.SetCell( "Cipher", pSession->cipher->name ); + + } else + { + Table.SetCell( "Status", "Waiting" ); + Table.SetCell( "Port", CUtils::ToString( pSock->GetLocalPort() ) ); + } + } + if ( Table.size() ) + { + unsigned int uTableIdx = 0; + string sLine; + while ( Table.GetLine( uTableIdx++, sLine ) ) + PutModule( sLine ); + } else + PutModule( "No SDCCs currently in session" ); + + } else if ( strcasecmp( sCom.c_str(), "close" ) == 0 ) + { + string sName = "SCHAT::" + m_pUser->GetUserName(); + for( u_int a= 0; a < m_pManager->size(); a++ ) + { + if ( strncmp( (*m_pManager)[a]->GetSockName().c_str(), sName.c_str(), sName.length() ) != 0 ) + continue; + + CSChatSock *pSock = (CSChatSock *)(*m_pManager)[a]; + if ( strncasecmp( sArgs.c_str(), "(s)", 3 ) != 0 ) + sArgs = "(s)" + sArgs; + + if ( strcasecmp( sArgs.c_str(), pSock->GetChatNick().c_str() ) == 0 ) + { + pSock->Close(); + return; + } + PutModule( "No Such Chat [" + sArgs + "]" ); + } + + } else if ( strcasecmp( sCom.c_str(), "help" ) == 0 ) + { + PutModule( "Commands are: " ); + PutModule( " help - This text." ); + PutModule( " chat - Chat a nick." ); + PutModule( " list - List current chats." ); + PutModule( " close - Close a chat to a nick." ); + PutModule( " timers - Shows related timers." ); + } else if ( strcasecmp( sCom.c_str(), "timers" ) == 0 ) + ListTimers(); + else + PutModule( "Unknown command [" + sCom + "] [" + sArgs + "]" ); + } + + virtual bool OnPrivCTCP( const CNick& Nick, string& sMessage ) + { + if ( strncasecmp( sMessage.c_str(), "DCC SCHAT ", 10 ) == 0 ) + { + // chat ip port + unsigned long iIP = strtoul( CUtils::Token( sMessage, 3 ).c_str(), NULL, 10 ); + unsigned short iPort = strtoul( CUtils::Token( sMessage, 4 ).c_str(), NULL, 10 ); + + if ( ( iIP > 0 ) && ( iPort > 0 ) ) + { + pair pTmp; + pTmp.first = iIP; + pTmp.second = iPort; + m_siiWaitingChats["(s)" + Nick.GetNick()] = pTmp; + SendToUser( "(s)" + Nick.GetNick() + "!" + "(s)" + Nick.GetNick() + "@" + CUtils::GetIP( iIP ), "*** Incoming DCC SCHAT, Accept ? (yes/no)" ); + CRemMarkerJob *p = new CRemMarkerJob( this, 60, 1, "Remove (s)" + Nick.GetNick(), "Removes this nicks entry for waiting DCC." ); + p->SetNick( "(s)" + Nick.GetNick() ); + AddTimer( p ); + return( true ); + } + } + + return( false ); + } + + void AcceptSDCC( const string & sNick, u_long iIP, u_short iPort ) + { + CSChatSock *p = new CSChatSock( CUtils::GetIP( iIP ), iPort, 60 ); + p->SetModule( this ); + p->SetChatNick( sNick ); + string sSockName = "SCHAT::" + m_pUser->GetUserName() + "::" + sNick; + m_pManager->Connect( CUtils::GetIP( iIP ), iPort, sSockName, 60, true, m_pUser->GetLocalIP(), p ); + RemTimer( "Remove " + sNick ); // delete any associated timer to this nick + } + virtual bool OnUserMsg( const string& sTarget, string& sMessage ) + { + if ( strncmp( sTarget.c_str(), "(s)", 3 ) == 0 ) + { + string sSockName = "SCHAT::" + m_pUser->GetUserName() + "::" + sTarget; + CSChatSock *p = (CSChatSock *)m_pManager->FindSockByName( sSockName ); + if ( !p ) + { + map< string,pair< u_long,u_short > >::iterator it = m_siiWaitingChats.find( sTarget ); + if ( it != m_siiWaitingChats.end() ) + { + if ( strcasecmp( sMessage.c_str(), "yes" ) != 0 ) + SendToUser( sTarget + "!" + sTarget + "@" + CUtils::GetIP( it->second.first ), "Refusing to accept DCC SCHAT!" ); + else + AcceptSDCC( sTarget, it->second.first, it->second.second ); + + m_siiWaitingChats.erase( it ); + return( true ); + } + PutModule( "No such SCHAT to [" + sTarget + "]" ); + } else + p->Write( sMessage + "\n" ); + + return( true ); + } + return( false ); + } + + virtual void RemoveMarker( const string & sNick ) + { + map< string,pair< u_long,u_short > >::iterator it = m_siiWaitingChats.find( sNick ); + if ( it != m_siiWaitingChats.end() ) + m_siiWaitingChats.erase( it ); + } + + void SendToUser( const string & sFrom, const string & sText ) + { + //:*schat!znc@znc.com PRIVMSG Jim : + string sSend = ":" + sFrom + " PRIVMSG " + m_pUser->GetCurNick() + " :" + sText; + PutUser( sSend ); + } + + bool IsAttached() + { + return( m_pUser->IsUserAttached() ); + } + +private: + map< string,pair< u_long,u_short > > m_siiWaitingChats; + string m_sPemFile; +}; + + +//////////////////// methods //////////////// + +void CSChatSock::ReadLine( const CS_STRING & sLine ) +{ + if ( m_pModule ) + { + string sText = sLine; + CUtils::Trim( sText ); + if ( m_pModule->IsAttached() ) + m_pModule->SendToUser( m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), sText ); + else + AddLine( sText ); + } +} + +void CSChatSock::Disconnected() +{ + if ( m_pModule ) + m_pModule->SendToUser( m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), "*** Disconnected." ); +} + +void CSChatSock::Connected() +{ + SetTimeout( 0 ); + if ( m_pModule ) + m_pModule->SendToUser( m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), "*** Connected." ); +} + +void CSChatSock::Timeout() +{ + if ( m_pModule ) + { + if ( GetType() == LISTENER ) + m_pModule->PutModule( "Timeout while waiting for [" + m_sChatNick + "]" ); + else + m_pModule->SendToUser( m_sChatNick + "!" + m_sChatNick + "@" + GetRemoteIP(), "*** Connection Timed out." ); + } +} + +void CRemMarkerJob::RunJob() +{ + CSChat *p = (CSChat *)m_pModule; + p->RemoveMarker( m_sNick ); + + // store buffer +} +MODULEDEFS(CSChat) + diff --git a/modules/shell.cpp b/modules/shell.cpp new file mode 100644 index 00000000..5efa1bc5 --- /dev/null +++ b/modules/shell.cpp @@ -0,0 +1,220 @@ +#ifndef _NO_CSOCKET_NS +#define _NO_CSOCKET_NS +#endif + +#include "Csocket.h" +#include "User.h" +#include "Nick.h" +#include "Modules.h" +#include "Chan.h" +#include "Utils.h" +#include + +// Forward Declaration +class CShellMod; + +class CExecSock : public Csock { +public: + CExecSock(CShellMod* pShellMod, const string& sExec) : Csock() { + EnableReadLine(); + m_pParent = pShellMod; + int iReadFD, iWriteFD; + m_iPid = popen2(iReadFD, iWriteFD, sExec); + ConnectFD(iReadFD, iWriteFD, "0.0.0.0:0"); + } + + virtual ~CExecSock() { + close2(m_iPid, GetRSock(), GetWSock()); + SetRSock( -1 ); + SetWSock( -1 ); + } + + // These next two function's bodies are at the bottom of the file since they reference CShellMod + virtual void ReadLine(const string& sData); + virtual void Disconnected(); + + CShellMod* m_pParent; + int m_iPid; + + int popen2(int & iReadFD, int & iWriteFD, const string & sCommand) { + int rpipes[2] = { -1, -1 }; + int wpipes[2] = { -1, -1 }; + iReadFD = -1; + iWriteFD = -1; + + pipe(rpipes); + pipe(wpipes); + + int iPid = fork(); + + if (iPid == -1) { + return -1; + } + + if (iPid == 0) { + close(wpipes[1]); + close(rpipes[0]); + dup2(wpipes[0], 0); + dup2(rpipes[1], 1); + dup2(rpipes[1], 2); + close(wpipes[0]); + close(rpipes[1]); + system( sCommand.c_str() ); + exit(0); + } + + close(wpipes[0]); + close(rpipes[1]); + + iWriteFD = wpipes[1]; + iReadFD = rpipes[0]; + + return iPid; + } + + void close2(int iPid, int iReadFD, int iWriteFD) { + close( iReadFD ); + close( iWriteFD ); + u_int iNow = time( NULL ); + while( waitpid( iPid, NULL, WNOHANG ) == 0 ) + { + if ( ( time( NULL ) - iNow ) > 5 ) + break; // giveup + usleep( 100 ); + } + return; + } +}; + +class CShellMod : public CModule { +public: + MODCONSTRUCTOR(CShellMod) { + m_sPath = pUser->GetHomePath(); + } + + virtual ~CShellMod() { + vector vSocks = m_pManager->FindSocksByName("SHELL"); + + for (unsigned int a = 0; a < vSocks.size(); a++) { + m_pManager->DelSockByAddr(vSocks[a]); + } + } + + virtual string GetDescription() { + return "Gives shell access."; + } + + virtual void OnModCommand(const string& sCommand) { + if ((strcasecmp(sCommand.c_str(), "cd") == 0) || (strncasecmp(sCommand.c_str(), "cd ", 3) == 0)) { + string sPath = CUtils::ChangeDir(m_sPath, ((sCommand.length() == 2) ? m_pUser->GetHomePath() : sCommand.substr(3)), m_pUser->GetHomePath()); + CFile Dir(sPath); + + if (Dir.IsDir()) { + m_sPath = sPath; + } else if (Dir.Exists()) { + PutShell("cd: not a directory [" + sPath + "]"); + } else { + PutShell("cd: no such directory [" + sPath + "]"); + } + + PutShell("znc$"); + } else if (strcasecmp(CUtils::Token(sCommand, 0).c_str(), "SEND") == 0) { + string sToNick = CUtils::Token(sCommand, 1); + string sFile = CUtils::Token(sCommand, 2); + + if ((sToNick.empty()) || (sFile.empty())) { + PutShell("usage: Send "); + } else { + sFile = CUtils::ChangeDir(m_sPath, sFile, m_pUser->GetHomePath()); + + if (!CFile::Exists(sFile)) { + PutShell("get: no such file [" + sFile + "]"); + } else if (!CFile::IsReg(sFile)) { + PutShell("get: not a file [" + sFile + "]"); + } else { + m_pUser->SendFile(sToNick, sFile, GetModName()); + } + } + } else if (strcasecmp(CUtils::Token(sCommand, 0).c_str(), "GET") == 0) { + string sFile = CUtils::Token(sCommand, 1); + + if (sFile.empty()) { + PutShell("usage: Get "); + } else { + sFile = CUtils::ChangeDir(m_sPath, sFile, m_pUser->GetHomePath()); + + if (!CFile::Exists(sFile)) { + PutShell("get: no such file [" + sFile + "]"); + } else if (!CFile::IsReg(sFile)) { + PutShell("get: not a file [" + sFile + "]"); + } else { + m_pUser->SendFile(m_pUser->GetCurNick(), sFile, GetModName()); + } + } + } else { + RunCommand(sCommand); + } + } + + virtual bool OnStatusCommand(const string& sCommand) { + if (strcasecmp(sCommand.c_str(), "SHELL") == 0) { + PutShell("-- ZNC Shell Service --"); + return true; + } + + return false; + } + + virtual bool OnDCCUserSend(const CNick& RemoteNick, unsigned long uLongIP, unsigned short uPort, const string& sFile, unsigned long uFileSize) { + if (strcasecmp(RemoteNick.GetNick().c_str(), string(GetModNick()).c_str()) == 0) { + string sLocalFile = CUtils::ChangeDir(m_sPath, sFile, m_pUser->GetHomePath()); + + m_pUser->GetFile(m_pUser->GetCurNick(), CUtils::GetIP(uLongIP), uPort, sLocalFile, uFileSize, GetModName()); + + return true; + } + + return false; + } + + void PutShell(const string& sLine) { + string sPath = m_sPath; + + unsigned int a = sPath.find(' '); + while (a != string::npos) { + sPath.replace(a, 1, "_"); + a = sPath.find(' '); + } + + PutModule(sLine, m_pUser->GetCurNick(), sPath); + } + + void RunCommand(const string& sCommand) { + m_pManager->AddSock((Csock*) new CExecSock(this, "cd " + m_sPath + " && " + sCommand), "SHELL"); + } +private: + string m_sPath; +}; + +void CExecSock::ReadLine(const string& sData) { + string sLine = sData; + + while ((sLine.length()) && (sLine[sLine.length() -1] == '\r') || (sLine[sLine.length() -1] == '\n')) { + sLine = sLine.substr(0, sLine.length() -1); + } + + unsigned int a = sLine.find('\t'); + while (a != string::npos) { + sLine.replace(a, 1, " "); + a = sLine.find('\t'); + } + + m_pParent->PutShell(sLine); +} + +void CExecSock::Disconnected() { + m_pParent->PutShell("znc$"); +} + +MODULEDEFS(CShellMod) + diff --git a/znc.conf b/znc.conf new file mode 100644 index 00000000..d20cc387 --- /dev/null +++ b/znc.conf @@ -0,0 +1,95 @@ +/* This is znc's config file */ +/* C style comments are supported, but may only be preceded by whitespace. */ +/* perl style #comments are also supported, and also must be preceded only by whitespace. */ +/* the ending of multiline comments (star slash) must be at the end of a line. */ + + +// This is the port that znc will listen on +// If the port is prepended with a '+' then znc listens using ssl. Note: znc must be compiled with ssl enabled. +ListenPort = 12345 + +// If you define the ISpoofFile, znc will write the ident of the user trying to connect to the file. Very useful if your box supports oidentd. +#ISpoofFile = /tmp/ispoof + +// This is the pid file that is needed if you want to crontab the zncchk shell script +PidFile = znc.pid + +// This is the prefix for the status window. Some clients or scripts may require you to change it, default is *status +StatusPrefix = * + +// User definition + + + // You can override the global StatusPrefix on a per user basis. Using - is a good alternative in mIRC but it breaks BitchX + #StatusPrefix = - + + // Password used to connect. + Pass = pass + + // You can use an md5 hash by supplying a - after the hash (with a space between) + // To generate a hashed password, use ./znc --makepass + // This next line would be just like doing "Pass = pass" like above, except your pass is not exposed simply by reading the config + #Pass = 1a1dc91c907325c69271ddf0c944bc72 - + + Nick = zncuser + AltNick = zncus3r + Ident = znc + RealName = Got ZNC? + #VHost = uncomment.and.put.your.vhost.here.com + ChanModes = +stn + KeepNick = true + + // You may use multiple Allow lines to restrict access to this user. All connections are denied by default unless they match an ip below. + // Note: IPs only! + Allow = * + #Allow = 24.24.24.* + #Allow = 192.168.1.* + + // Uncomment this next line if you want znc to use the longip that your client sends it when establishing a dcc connection + // By default znc will use the ip that you connect to it to contact your end of a bounced dcc + #DCCLookupMethod = Client + + // You can write modules and dynamically load them at runtime + + // Uncomment the next line to deny this user access to (un)loading modules. Only the modules you load for them in this config will be available to them + #DenyLoadMod = true + + // The sample module does random things, it is meant to be a sample for module authors to learn from + #LoadModule = sample + + // The shell module gives you shell access to the system in a query window + #LoadModule = shell + + // The savebuff module is a module that saves your (encrypted) channel buffers to disk. It is best used when KeepBuffer = true + #LoadModule = savebuff + + // Add the servers that you wish znc to use when connecting to IRC. [[+]port [pass]] + // If the port is prepended with a '+' then znc connects to this server with ssl. Note: znc must be compiled with ssl enabled. + Server = irc.nac.net 6667 + Server = irc.efnet.pl 6667 + #Server = irc.someserver.com +8000 serverpass + + // Add channels that you want znc to join on start. + + Key = znc + Modes = +sn-t + + // This is the playback buffer size (in LINES) - WARNING: setting this too high could cause you to get a lot of text when you attach + Buffer = 50 + + // This will play the whole buffer back to you each time you connect. It will also buffer channels while you are attached + #KeepBuffer = true + + + +// Multiple users are supported, this one is commented out +/* + Pass = somepass + Nick = user2 + Ident = user2 + RealName = Second User + Allow = * + + Server = irc.efnet.pl 6667 +*/ + diff --git a/znc.cpp b/znc.cpp new file mode 100644 index 00000000..333d4a40 --- /dev/null +++ b/znc.cpp @@ -0,0 +1,471 @@ +#include "znc.h" +#include "User.h" +#include "Server.h" +#include "UserSock.h" +#include "IRCSock.h" +#include "Utils.h" + +#include +#include + +#ifdef _MODULES +#include "Modules.h" +#endif + +CZNC::CZNC() { + m_uListenPort = 0; + m_bISpoofLocked = false; +} + +CZNC::~CZNC() { + m_Manager.Cleanup(); + DeleteUsers(); +} + +bool CZNC::OnBoot() { + for (map::iterator it = m_msUsers.begin(); it != m_msUsers.end(); it++) { + if (!it->second->OnBoot()) { + return false; + } + } + + return true; +} + +int CZNC::Loop() { + m_Manager.SetSelectTimeout(10000); + m_itUserIter = m_msUsers.begin(); + + while (true) { + m_Manager.Loop(); + + if (m_bISpoofLocked) { + continue; + } + + if (m_itUserIter == m_msUsers.end()) { + m_itUserIter = m_msUsers.begin(); + } + + string sSockName = "IRC::" + m_itUserIter->first; + CUser* pUser = m_itUserIter->second; + + m_itUserIter++; + + CIRCSock* pIRCSock = (CIRCSock*) m_Manager.FindSockByName(sSockName); + + if (!pIRCSock) { + CServer* pServer = pUser->GetNextServer(); + + if (!pServer) { + continue; + } + + if (!m_sISpoofFile.empty()) { + CFile File(m_sISpoofFile); + + if (File.Open(O_RDONLY)) { + char buf[1024]; + memset((char*) buf, 0, 1024); + File.Read(buf, 1023); + File.Close(); + m_sOrigISpoof = buf; + } + + if (File.Open(O_WRONLY | O_TRUNC | O_CREAT)) { + File.Write(pUser->GetIdent() + "\r\n"); + File.Close(); + } + + m_bISpoofLocked = true; + } + + DEBUG_ONLY( cout << "User [" << pUser->GetUserName() << "] is connecting to [" << pServer->GetName() << ":" << pServer->GetPort() << "] ..." << endl); + CUserSock* pUserSock = pUser->GetUserSock(); + + if (pUserSock) { + pUserSock->PutStatus("Attempting to connect to [" + pServer->GetName() + ":" + CUtils::ToString(pServer->GetPort()) + "] ..."); + } + + pIRCSock = new CIRCSock(this, pUser); + pIRCSock->SetPass(pServer->GetPass()); + + bool bSSL = false; +#ifdef HAVE_LIBSSL + if (pServer->IsSSL()) { + bSSL = true; + pIRCSock->SetPemLocation(GetBinPath() + "/znc.pem"); + } +#endif + if (!m_Manager.Connect(pServer->GetName(), pServer->GetPort(), sSockName, 20, bSSL, pUser->GetVHost(), pIRCSock)) { + ReleaseISpoof(); + + if (pUserSock) { + pUserSock->PutStatus("Unable to connect. (Bad host?)"); + } + } + } + } + + return 0; +} + +void CZNC::ReleaseISpoof() { + if (!m_sISpoofFile.empty()) { + CFile File(m_sISpoofFile); + + if (File.Open(O_WRONLY | O_TRUNC | O_CREAT)) { + File.Write(m_sOrigISpoof); + File.Close(); + } + + m_sOrigISpoof = ""; + } + + m_bISpoofLocked = false; +} + +bool CZNC::WritePidFile(int iPid) { + if (!m_sPidFile.empty()) { + CFile File(m_sPidFile); + + if (File.Open(O_WRONLY | O_TRUNC | O_CREAT)) { + File.Write(CUtils::ToString(iPid) + "\n"); + File.Close(); + return true; + } + } + + return false; +} + +void CZNC::DeleteUsers() { + for (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); a++) { + delete a->second; + } + + m_msUsers.clear(); + m_itUserIter = m_msUsers.end(); +} + +CUser* CZNC::GetUser(const string& sUser) { + // Todo: make this case insensitive + map::iterator it = m_msUsers.find(sUser); + return (it == m_msUsers.end()) ? NULL : it->second; +} + +Csock* CZNC::FindSockByName(const string& sSockName) { + return m_Manager.FindSockByName(sSockName); +} + +bool CZNC::Listen() { + if (!m_uListenPort) { + return false; + } + + CUserSock* pUserSock = new CUserSock; + pUserSock->SetZNC(this); + + bool bSSL = false; +#ifdef HAVE_LIBSSL + if (IsSSL()) { + bSSL = true; + pUserSock->SetPemLocation(GetBinPath() + "/znc.pem"); + } +#endif + return m_Manager.ListenAll(m_uListenPort, "_LISTENER", bSSL, SOMAXCONN, pUserSock); +} + +bool CZNC::IsHostAllowed(const string& sHostMask) { + for (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); a++) { + if (a->second->IsHostAllowed(sHostMask)) { + return true; + } + } + + return false; +} + +void CZNC::InitDirs(const string& sArgvPath) { + char buf[PATH_MAX]; + getcwd(buf, PATH_MAX); + + // If the bin was not ran from the current directory, we need to add that dir onto our cwd + unsigned int uPos = sArgvPath.rfind('/'); + m_sBinPath = (uPos == string::npos) ? string(buf) : CUtils::ChangeDir(buf, sArgvPath.substr(0, uPos), ""); + + // Try to set the user's home dir, default to binpath on failure + struct passwd* pUserInfo = getpwuid(getuid()); + + if (pUserInfo) { + m_sHomePath = pUserInfo->pw_dir; + } + + if (m_sHomePath.empty()) { + m_sHomePath = m_sBinPath; + } + + // Other dirs that we use + m_sDLPath = m_sBinPath + "/downloads"; + m_sModPath = m_sBinPath + "/modules"; +} + +bool CZNC::ParseConfig(const string& sConfigFile) { + string sStatusPrefix; + + string sFilePath = sConfigFile; + if (CUtils::Left(sConfigFile, 1) != "/") { + sFilePath = m_sBinPath + "/" + sFilePath; + } + + CFile File(sFilePath); + + if (!File.Open(O_RDONLY)) { + cerr << "Could not open config [" << sFilePath << "]" << endl; + return false; + } + + string sLine; + bool bCommented = false; // support for /**/ style comments + CUser* pUser = NULL; // Used to keep track of which user block we are in + CChan* pChan = NULL; // Used to keep track of which chan block we are in + + while (File.ReadLine(sLine)) { + while ((CUtils::Right(sLine, 1) == "\r") || (CUtils::Right(sLine, 1) == "\n")) { + CUtils::Trim(sLine); + } + + if ((sLine.empty()) || (sLine[0] == '#') || (CUtils::Left(sLine, 2) == "//")) { + continue; + } + + if (CUtils::Left(sLine, 2) == "/*") { + if (CUtils::Right(sLine, 2) != "*/") { + bCommented = true; + } + + continue; + } + + if (bCommented) { + if (CUtils::Right(sLine, 2) == "*/") { + bCommented = false; + } + + continue; + } + + if ((CUtils::Left(sLine, 1) == "<") && (CUtils::Right(sLine, 1) == ">")) { + CUtils::LeftChomp(sLine); + CUtils::RightChomp(sLine); + CUtils::Trim(sLine); + + string sTag = sLine.substr(0, sLine.find_first_of(" \t\r\n")); + string sValue = (sTag.size() < sLine.size()) ? sLine.substr(sTag.size() +1) : ""; + + CUtils::Trim(sTag); + CUtils::Trim(sValue); + + if (CUtils::Left(sLine, 1) == "/") { + string sTag = sLine.substr(1); + + if (pUser) { + if (pChan) { + if (strcasecmp(sTag.c_str(), "Chan") == 0) { + pUser->AddChan(pChan); + pChan = NULL; + continue; + } + } else if (strcasecmp(sTag.c_str(), "User") == 0) { + pUser = NULL; + continue; + } + } + } else if (strcasecmp(sTag.c_str(), "User") == 0) { + if (pUser) { + cerr << "You may not nest tags inside of other tags." << endl; + return false; + } + + if (sValue.empty()) { + cerr << "You must supply a username in the tag." << endl; + return false; + } + + if (m_msUsers.find(sValue) != m_msUsers.end()) { + cerr << "User [" << sValue << "] defined more than once." << endl; + return false; + } + + pUser = new CUser(sValue, this); + if (!sStatusPrefix.empty()) { + if (!pUser->SetStatusPrefix(sStatusPrefix)) { + cerr << "Invalid StatusPrefix [" + sStatusPrefix + "] Must be 1-5 chars, no spaces." << endl; + } + } + m_msUsers[sValue] = pUser; + continue; + } else if (strcasecmp(sTag.c_str(), "Chan") == 0) { + if (!pUser) { + cerr << " tags must be nested inside of a tag." << endl; + return false; + } + + if (pChan) { + cerr << "You may not nest tags inside of other tags." << endl; + return false; + } + + pChan = new CChan(sValue, pUser); + continue; + } + } + + // If we have a regular line, figure out where it goes + string sName = CUtils::Token(sLine, 0, false, '='); + string sValue = CUtils::Token(sLine, 1, true, '='); + CUtils::Trim(sName); + CUtils::Trim(sValue); + + if ((!sName.empty()) && (!sValue.empty())) { + if (pUser) { + if (pChan) { + if (strcasecmp(sName.c_str(), "Buffer") == 0) { + pChan->SetBufferCount(strtoul(sValue.c_str(), NULL, 10)); + continue; + } else if (strcasecmp(sName.c_str(), "KeepBuffer") == 0) { + pChan->SetKeepBuffer((strcasecmp(sValue.c_str(), "true") == 0)); + continue; + } else if (strcasecmp(sName.c_str(), "Key") == 0) { + pChan->SetKey(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "Modes") == 0) { + pChan->SetDefaultModes(sValue); + continue; + } + } else { + if (strcasecmp(sName.c_str(), "Nick") == 0) { + pUser->SetNick(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "AltNick") == 0) { + pUser->SetAltNick(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "Pass") == 0) { + if (CUtils::Right(sValue, 1) == "-") { + CUtils::RightChomp(sValue); + CUtils::Trim(sValue); + pUser->SetPass(sValue, true); + } else { + pUser->SetPass(sValue, false); + } + + continue; + } else if (strcasecmp(sName.c_str(), "Ident") == 0) { + pUser->SetIdent(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "DenyLoadMod") == 0) { + pUser->SetDenyLoadMod((strcasecmp(sValue.c_str(), "TRUE") == 0)); + continue; + } else if (strcasecmp(sName.c_str(), "StatusPrefix") == 0) { + if (!pUser->SetStatusPrefix(sValue)) { + cerr << "Invalid StatusPrefix [" + sValue + "] Must be 1-5 chars, no spaces." << endl; + } + continue; + } else if (strcasecmp(sName.c_str(), "DCCLookupMethod") == 0) { + pUser->SetUseClientIP((strcasecmp(sValue.c_str(), "Client") == 0)); + continue; + } else if (strcasecmp(sName.c_str(), "RealName") == 0) { + pUser->SetRealName(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "KeepNick") == 0) { + pUser->SetKeepNick((strcasecmp(sValue.c_str(), "true") == 0)); + continue; + } else if (strcasecmp(sName.c_str(), "ChanModes") == 0) { + pUser->SetDefaultChanModes(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "VHost") == 0) { + pUser->SetVHost(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "Allow") == 0) { + pUser->AddAllowedHost(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "Server") == 0) { + if (!pUser->AddServer(sValue)) { + cerr << "Unable to add server [" << sValue << "]" << endl; + } + continue; + } else if (strcasecmp(sName.c_str(), "Chan") == 0) { + pUser->AddChan(sValue); + continue; + } else if (strcasecmp(sName.c_str(), "LoadModule") == 0) { +#ifdef _MODULES + string sModRet; + string sModName = CUtils::Token(sValue, 0); + string sArgs = CUtils::Token(sValue, 1, true); + + pUser->GetModules().LoadModule(sModName, sArgs, pUser, m_sBinPath, sModRet); + cout << sModRet << endl; +#else + cerr << "Unable to load [" << sValue << "] Modules are not enabled." << endl; +#endif + continue; + } + } + } else { + if (strcasecmp(sName.c_str(), "ListenPort") == 0) { + m_bSSL = false; + string sPort = sValue; + if (CUtils::Left(sPort, 1) == "+") { + CUtils::LeftChomp(sPort); + m_bSSL = true; + } + + m_uListenPort = strtol(sPort.c_str(), NULL, 10); + +#ifndef HAVE_LIBSSL + if (m_bSSL) { + cerr << "SSL is not enabled, could not bind to ssl port [" << m_uListenPort << "]" << endl; + return false; + } +#endif + + if ((m_bSSL) && (!CFile::Exists(GetBinPath() + "/znc.pem"))) { + cerr << "Unable to locate pem file: [" << GetBinPath() << "/znc.pem]" << endl; + return false; + } + + if (!Listen()) { + cerr << "Could not bind to port [" << m_uListenPort << "]" << endl; + return false; + } + + continue; + } else if (strcasecmp(sName.c_str(), "ISpoofFile") == 0) { + m_sISpoofFile = sValue; + continue; + } else if (strcasecmp(sName.c_str(), "PidFile") == 0) { + m_sPidFile = sValue; + continue; + } else if (strcasecmp(sName.c_str(), "StatusPrefix") == 0) { + sStatusPrefix = sValue; + continue; + } + } + } + + cerr << "Unhandled line in config: [" << sLine << "]" << endl; + } + + if (pChan) { + delete pChan; + } + + File.Close(); + + if (!m_msUsers.size()) { + cerr << "You must define at least one user in your config." << endl; + return false; + } + + return true; +} diff --git a/znc.h b/znc.h new file mode 100644 index 00000000..71d9c367 --- /dev/null +++ b/znc.h @@ -0,0 +1,68 @@ +#ifndef _ZNC_H +#define _ZNC_H + +#include "main.h" +#include +using std::map; + +class CUser; +class CUserSock; + +class CZNC { +public: + CZNC(); + virtual ~CZNC(); + + void DeleteUsers(); + int Loop(); + void ReleaseISpoof(); + bool WritePidFile(int iPid); + CUser* GetUser(const string& sUser); + Csock* FindSockByName(const string& sSockName); + bool Listen(); + bool ParseConfig(const string& sConfigFile); + bool IsHostAllowed(const string& sHostMask); + void InitDirs(const string& sArgvPath); + bool OnBoot(); + + // Getters + TSocketManager& GetManager() { return m_Manager; } + unsigned int GetListenPort() const { return m_uListenPort; } + const string& GetBinPath() const { return m_sBinPath; } + const string& GetDLPath() const { return m_sDLPath; } + const string& GetModPath() const { return m_sModPath; } + const string& GetHomePath() const { return m_sHomePath; } + bool IsSSL() const { +#ifdef HAVE_LIBSSL + return m_bSSL; +#endif + return false; + } + // !Getters + + // Static allocator + static CZNC* New() { + static CZNC* pZNC = new CZNC; + return pZNC; + } + +private: +protected: + unsigned short m_uListenPort; + map m_msUsers; + TSocketManager m_Manager; + + string m_sBinPath; + string m_sDLPath; + string m_sModPath; + string m_sHomePath; + + string m_sISpoofFile; + string m_sOrigISpoof; + string m_sPidFile; + bool m_bISpoofLocked; + bool m_bSSL; + map::iterator m_itUserIter; // This needs to be reset to m_msUsers.begin() if anything is added or removed to the map +}; + +#endif // !_ZNC_H diff --git a/zncchk b/zncchk new file mode 100755 index 00000000..93ccc4be --- /dev/null +++ b/zncchk @@ -0,0 +1,25 @@ +#!/bin/sh +dir="." +cmd="znc" +pidfile="znc.pid" + +PATH=.:$PATH +export PATH + +cd $dir + +if test -s $pidfile; then + pid=`cat $pidfile` + + if `kill -0 $pid >/dev/null 2>&1`; then + exit 0 + fi + + rm -f $pidfile +fi + +echo "" +echo "Reloading..." +echo "" +$cmd +exit 0