diff --git a/include/znc/IRCNetwork.h b/include/znc/IRCNetwork.h index fe654d4d..524c407a 100644 --- a/include/znc/IRCNetwork.h +++ b/include/znc/IRCNetwork.h @@ -95,6 +95,7 @@ public: bool IsIRCAway() const { return m_bIRCAway; } void SetIRCAway(bool b) { m_bIRCAway = b; } + bool Connect(); /** This method will return whether the user is connected and authenticated to an IRC server. */ bool IsIRCConnected() const; diff --git a/include/znc/znc.h b/include/znc/znc.h index 25dc85d0..cba22f94 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -15,13 +15,15 @@ #include #include #include +#include using std::map; +using std::list; class CListener; class CUser; class CIRCNetwork; -class CConnectUserTimer; +class CConnectQueueTimer; class CConfig; class CFile; @@ -139,14 +141,18 @@ public: const VCString& GetMotd() const { return m_vsMotd; } // !MOTD - // Create a CIRCSocket. Return false if user cant connect - bool ConnectNetwork(CIRCNetwork *pNetwork); - // This creates a CConnectUserTimer if we haven't got one yet - void EnableConnectUser(); - void DisableConnectUser(); + void AddServerThrottle(CString sName) { m_sConnectThrottle.AddItem(sName); } + bool GetServerThrottle(CString sName) { return m_sConnectThrottle.GetItem(sName); } - // Never call this unless you are CConnectUserTimer::~CConnectUserTimer() - void LeakConnectUser(CConnectUserTimer *pTimer); + void AddNetworkToQueue(CIRCNetwork *pNetwork); + list& GetConnectionQueue() { return m_lpConnectQueue; } + + // This creates a CConnectQueueTimer if we haven't got one yet + void EnableConnectQueue(); + void DisableConnectQueue(); + + // Never call this unless you are CConnectQueueTimer::~CConnectQueueTimer() + void LeakConnectQueueTimer(CConnectQueueTimer *pTimer); static void DumpConfig(const CConfig* Config); @@ -187,7 +193,8 @@ protected: CModules* m_pModules; unsigned long long m_uBytesRead; unsigned long long m_uBytesWritten; - CConnectUserTimer *m_pConnectUserTimer; + list m_lpConnectQueue; + CConnectQueueTimer *m_pConnectQueueTimer; TCacheMap m_sConnectThrottle; bool m_bProtectWebSessions; }; diff --git a/src/IRCNetwork.cpp b/src/IRCNetwork.cpp index 0458feff..00be3465 100644 --- a/src/IRCNetwork.cpp +++ b/src/IRCNetwork.cpp @@ -159,6 +159,9 @@ CIRCNetwork::~CIRCNetwork() { m_vChans.clear(); SetUser(NULL); + + // Make sure we are not in the connection queue + CZNC::Get().GetConnectionQueue().remove(this); } void CIRCNetwork::DelServers() { @@ -810,6 +813,51 @@ CString CIRCNetwork::GetCurNick() const { return ""; } +bool CIRCNetwork::Connect() { + if (!m_pUser->GetIRCConnectEnabled() || m_pIRCSock || !HasServers()) + return false; + + CServer *pServer = GetNextServer(); + if (!pServer) + return false; + + if (CZNC::Get().GetServerThrottle(pServer->GetName())) { + CZNC::Get().AddNetworkToQueue(this); + return false; + } + + CZNC::Get().AddServerThrottle(pServer->GetName()); + + CIRCSock *pIRCSock = new CIRCSock(this); + pIRCSock->SetPass(pServer->GetPass()); + + bool bSSL = false; +#ifdef HAVE_LIBSSL + if (pServer->IsSSL()) { + bSSL = true; + } +#endif + + DEBUG("Connecting user/network [" << m_sName << "/" << m_sName << "]"); + + NETWORKMODULECALL(OnIRCConnecting(pIRCSock), m_pUser, this, NULL, + DEBUG("Some module aborted the connection attempt"); + PutStatus("Some module aborted the connection attempt"); + delete pIRCSock; + CZNC::Get().AddNetworkToQueue(this); + return false; + ); + + CString sSockName = "IRC::" + m_pUser->GetUserName() + "::" + m_sName; + if (!CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, m_pUser->GetBindHost(), pIRCSock)) { + PutStatus("Unable to connect. (Bad host?)"); + CZNC::Get().AddNetworkToQueue(this); + return false; + } + + return true; +} + bool CIRCNetwork::IsIRCConnected() const { const CIRCSock* pSock = GetIRCSock(); return (pSock && pSock->IsAuthed()); @@ -832,7 +880,7 @@ void CIRCNetwork::IRCDisconnected() { void CIRCNetwork::CheckIRCConnect() { // Do we want to connect? if (m_pUser->GetIRCConnectEnabled() && GetIRCSock() == NULL) - CZNC::Get().EnableConnectUser(); + CZNC::Get().AddNetworkToQueue(this); } bool CIRCNetwork::PutIRC(const CString& sLine) { diff --git a/src/znc.cpp b/src/znc.cpp index 9ea43238..ae026f3e 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -15,7 +15,6 @@ #include #include #include -#include static inline CString FormatBindError() { CString sError = (errno == 0 ? CString("unknown error, check the host name") : CString(strerror(errno))); @@ -34,7 +33,7 @@ CZNC::CZNC() { m_uBytesRead = 0; m_uBytesWritten = 0; m_uiMaxBufferSize = 500; - m_pConnectUserTimer = NULL; + m_pConnectQueueTimer = NULL; m_eConfigState = ECONFIG_NOTHING; m_TimeStarted = time(NULL); m_sConnectThrottle.SetTTL(30000); @@ -57,8 +56,8 @@ CZNC::~CZNC() { a->second->SetBeingDeleted(true); } - m_pConnectUserTimer = NULL; - // This deletes m_pConnectUserTimer + m_pConnectQueueTimer = NULL; + // This deletes m_pConnectQueueTimer m_Manager.Cleanup(); DeleteUsers(); @@ -103,54 +102,6 @@ bool CZNC::OnBoot() { return true; } -bool CZNC::ConnectNetwork(CIRCNetwork *pNetwork) { - CUser *pUser = pNetwork->GetUser(); - CString sSockName = "IRC::" + pUser->GetUserName() + "::" + pNetwork->GetName(); - CIRCSock* pIRCSock = pNetwork->GetIRCSock(); - - if (!pUser->GetIRCConnectEnabled()) - return false; - - if (pIRCSock || !pNetwork->HasServers()) - return false; - - CServer* pServer = pNetwork->GetNextServer(); - - if (!pServer) - return false; - - if (m_sConnectThrottle.GetItem(pServer->GetName())) - return false; - - m_sConnectThrottle.AddItem(pServer->GetName()); - - DEBUG("User [" << pUser->GetUserName() << "] is connecting to [" << pServer->GetString(false) << "] on network [" << pNetwork->GetName() << "]"); - pNetwork->PutStatus("Attempting to connect to [" + pServer->GetString(false) + "] ..."); - - pIRCSock = new CIRCSock(pNetwork); - pIRCSock->SetPass(pServer->GetPass()); - - bool bSSL = false; -#ifdef HAVE_LIBSSL - if (pServer->IsSSL()) { - bSSL = true; - } -#endif - - NETWORKMODULECALL(OnIRCConnecting(pIRCSock), pUser, pNetwork, NULL, - DEBUG("Some module aborted the connection attempt"); - pUser->PutStatus("Some module aborted the connection attempt"); - delete pIRCSock; - return false; - ); - - if (!m_Manager.Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, pUser->GetBindHost(), pIRCSock)) { - pNetwork->PutStatus("Unable to connect. (Bad host?)"); - } - - return true; -} - bool CZNC::HandleUserDeletion() { map::iterator it; @@ -297,7 +248,7 @@ void CZNC::DeleteUsers() { } m_msUsers.clear(); - DisableConnectUser(); + DisableConnectQueue(); } bool CZNC::IsHostAllowed(const CString& sHostMask) const { @@ -1201,7 +1152,7 @@ bool CZNC::DoRehash(CString& sError) if (config.FindStringEntry("skin", sVal)) SetSkinName(sVal); if (config.FindStringEntry("connectdelay", sVal)) - m_uiConnectDelay = sVal.ToUInt(); + SetConnectDelay(sVal.ToUInt()); if (config.FindStringEntry("serverthrottle", sVal)) m_sConnectThrottle.SetTTL(sVal.ToUInt() * 1000); if (config.FindStringEntry("anoniplimit", sVal)) @@ -1351,11 +1302,6 @@ bool CZNC::DoRehash(CString& sError) return false; } - // Make sure that users that want to connect do so and also make sure a - // new ConnectDelay setting is applied. - DisableConnectUser(); - EnableConnectUser(); - return true; } @@ -1771,119 +1717,94 @@ void CZNC::AuthUser(CSmartPtr AuthClass) { AuthClass->AcceptLogin(*pUser); } -class CConnectUserTimer : public CCron { +class CConnectQueueTimer : public CCron { public: - CConnectUserTimer(int iSecs) : CCron() { + CConnectQueueTimer(int iSecs) : CCron() { SetName("Connect users"); Start(iSecs); - m_uiPosNextUser = 0; // Don't wait iSecs seconds for first timer run m_bRunOnNextCall = true; } - virtual ~CConnectUserTimer() { + virtual ~CConnectQueueTimer() { // This is only needed when ZNC shuts down: - // CZNC::~CZNC() sets its CConnectUserTimer pointer to NULL and + // CZNC::~CZNC() sets its CConnectQueueTimer pointer to NULL and // calls the manager's Cleanup() which destroys all sockets and - // timers. If something calls CZNC::EnableConnectUser() here + // timers. If something calls CZNC::EnableConnectQueue() here // (e.g. because a CIRCSock is destroyed), the socket manager // deletes that timer almost immediately, but CZNC now got a // dangling pointer to this timer which can crash later on. // // Unlikely but possible ;) - CZNC::Get().LeakConnectUser(this); + CZNC::Get().LeakConnectQueueTimer(this); } protected: virtual void RunJob() { - unsigned int uiUserCount; - bool bUsersLeft = false; - const map& mUsers = CZNC::Get().GetUserMap(); - map::const_iterator it = mUsers.begin(); + list& ConnectionQueue = CZNC::Get().GetConnectionQueue(); - uiUserCount = CZNC::Get().GetUserMap().size(); + /* We store the end of the queue, so CIRCNetwork::Connect() can add + * itself back to the queue and we wont end up in an infinite loop. */ + list::iterator end = ConnectionQueue.end(); + list::iterator it; - if (m_uiPosNextUser >= uiUserCount) { - m_uiPosNextUser = 0; - } + for (it = ConnectionQueue.begin(); it != end;) { + CIRCNetwork *pNetwork = *it; - for (unsigned int i = 0; i < m_uiPosNextUser; i++) { - it++; - } + /* We must erase the network from the queue before we try to connect + * because it may try to add the network to the queue (which would + * fail if we were already in the queue) */ + it = ConnectionQueue.erase(it); - // Try to connect each user, if this doesnt work, abort - for (unsigned int i = 0; i < uiUserCount; i++) { - if (it == mUsers.end()) - it = mUsers.begin(); - - CUser* pUser = it->second; - it++; - m_uiPosNextUser = (m_uiPosNextUser + 1) % uiUserCount; - - // Does this user want to connect? - if (!pUser->GetIRCConnectEnabled()) - continue; - - vector vNetworks = pUser->GetNetworks(); - for (vector::iterator it2 = vNetworks.begin(); it2 != vNetworks.end(); ++it2) { - CIRCNetwork* pNetwork = *it2; - - // Is this network disconnected? - if (pNetwork->GetIRCSock() != NULL) - continue; - - // Does this user have any servers? - if (!pNetwork->HasServers()) - continue; - - // The timer runs until it once didn't find any users to connect - bUsersLeft = true; - - DEBUG("Connecting user [" << pUser->GetUserName() << "/" << pNetwork->GetName() << "]"); - - if (CZNC::Get().ConnectNetwork(pNetwork)) { - // User connecting, wait until next time timer fires - return; - } + if (pNetwork->Connect()) { + break; } } - if (bUsersLeft == false) { - DEBUG("ConnectUserTimer done"); - CZNC::Get().DisableConnectUser(); + if (ConnectionQueue.empty()) { + DEBUG("ConnectQueueTimer done"); + CZNC::Get().DisableConnectQueue(); } } - -private: - size_t m_uiPosNextUser; }; void CZNC::SetConnectDelay(unsigned int i) { - if (m_uiConnectDelay != i && m_pConnectUserTimer != NULL) { - m_pConnectUserTimer->Start(i); + if (m_uiConnectDelay != i && m_pConnectQueueTimer != NULL) { + m_pConnectQueueTimer->Start(i); } m_uiConnectDelay = i; } -void CZNC::EnableConnectUser() { - if (m_pConnectUserTimer != NULL) - return; - - m_pConnectUserTimer = new CConnectUserTimer(m_uiConnectDelay); - GetManager().AddCron(m_pConnectUserTimer); +void CZNC::EnableConnectQueue() { + if (!m_pConnectQueueTimer) { + m_pConnectQueueTimer = new CConnectQueueTimer(m_uiConnectDelay); + GetManager().AddCron(m_pConnectQueueTimer); + } } -void CZNC::DisableConnectUser() { - if (m_pConnectUserTimer == NULL) - return; - - // This will kill the cron - m_pConnectUserTimer->Stop(); - m_pConnectUserTimer = NULL; +void CZNC::DisableConnectQueue() { + if (m_pConnectQueueTimer) { + // This will kill the cron + m_pConnectQueueTimer->Stop(); + m_pConnectQueueTimer = NULL; + } } -void CZNC::LeakConnectUser(CConnectUserTimer *pTimer) { - if (m_pConnectUserTimer == pTimer) - m_pConnectUserTimer = NULL; +void CZNC::AddNetworkToQueue(CIRCNetwork *pNetwork) { + // Make sure we are not already in the queue + for (list::const_iterator it = m_lpConnectQueue.begin(); it != m_lpConnectQueue.end(); ++it) { + if (*it == pNetwork) { + return; + } + } + + + m_lpConnectQueue.push_back(pNetwork); + EnableConnectQueue(); +} + +void CZNC::LeakConnectQueueTimer(CConnectQueueTimer *pTimer) { + if (m_pConnectQueueTimer == pTimer) + m_pConnectQueueTimer = NULL; } bool CZNC::WaitForChildLock() {