/* * Copyright (C) 2004-2019 ZNC, see the NOTICE file for details. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include using std::vector; using std::set; class CIRCNetworkPingTimer : public CCron { public: CIRCNetworkPingTimer(CIRCNetwork* pNetwork) : CCron(), m_pNetwork(pNetwork) { SetName("CIRCNetworkPingTimer::" + m_pNetwork->GetUser()->GetUsername() + "::" + m_pNetwork->GetName()); Start(m_pNetwork->GetUser()->GetPingSlack()); } ~CIRCNetworkPingTimer() override {} CIRCNetworkPingTimer(const CIRCNetworkPingTimer&) = delete; CIRCNetworkPingTimer& operator=(const CIRCNetworkPingTimer&) = delete; protected: void RunJob() override { CIRCSock* pIRCSock = m_pNetwork->GetIRCSock(); auto uFrequency = m_pNetwork->GetUser()->GetPingFrequency(); if (pIRCSock && pIRCSock->GetTimeSinceLastDataTransaction() >= uFrequency) { pIRCSock->PutIRC("PING :ZNC"); } const vector& vClients = m_pNetwork->GetClients(); for (CClient* pClient : vClients) { if (pClient->GetTimeSinceLastDataTransaction() >= uFrequency) { pClient->PutClient("PING :ZNC"); } } // Restart timer for the case if the period had changed. Usually this is // noop Start(m_pNetwork->GetUser()->GetPingSlack()); } private: CIRCNetwork* m_pNetwork; }; class CIRCNetworkJoinTimer : public CCron { constexpr static int JOIN_FREQUENCY = 30 /* seconds */; public: CIRCNetworkJoinTimer(CIRCNetwork* pNetwork) : CCron(), m_bDelayed(false), m_pNetwork(pNetwork) { SetName("CIRCNetworkJoinTimer::" + m_pNetwork->GetUser()->GetUsername() + "::" + m_pNetwork->GetName()); Start(JOIN_FREQUENCY); } ~CIRCNetworkJoinTimer() override {} CIRCNetworkJoinTimer(const CIRCNetworkJoinTimer&) = delete; CIRCNetworkJoinTimer& operator=(const CIRCNetworkJoinTimer&) = delete; void Delay(unsigned short int uDelay) { m_bDelayed = true; Start(uDelay); } protected: void RunJob() override { if (m_bDelayed) { m_bDelayed = false; Start(JOIN_FREQUENCY); } if (m_pNetwork->IsIRCConnected()) { m_pNetwork->JoinChans(); } } private: bool m_bDelayed; CIRCNetwork* m_pNetwork; }; bool CIRCNetwork::IsValidNetwork(const CString& sNetwork) { // ^[-\w]+$ if (sNetwork.empty()) { return false; } const char* p = sNetwork.c_str(); while (*p) { if (*p != '_' && *p != '-' && !isalnum(*p)) { return false; } p++; } return true; } CIRCNetwork::CIRCNetwork(CUser* pUser, const CString& sName) : m_sName(sName), m_pUser(nullptr), m_sNick(""), m_sAltNick(""), m_sIdent(""), m_sRealName(""), m_sBindHost(""), m_sEncoding(""), m_sQuitMsg(""), m_ssTrustedFingerprints(), m_pModules(new CModules), m_vClients(), m_pIRCSock(nullptr), m_vChans(), m_vQueries(), m_sChanPrefixes(""), m_bIRCConnectEnabled(true), m_bTrustAllCerts(false), m_bTrustPKI(true), m_sIRCServer(""), m_vServers(), m_uServerIdx(0), m_IRCNick(), m_bIRCAway(false), m_fFloodRate(2), m_uFloodBurst(9), m_RawBuffer(), m_MotdBuffer(), m_NoticeBuffer(), m_pPingTimer(nullptr), m_pJoinTimer(nullptr), m_uJoinDelay(0), m_uBytesRead(0), m_uBytesWritten(0) { SetUser(pUser); // This should be more than enough raws, especially since we are buffering // the MOTD separately m_RawBuffer.SetLineCount(100, true); // This should be more than enough motd lines m_MotdBuffer.SetLineCount(200, true); m_NoticeBuffer.SetLineCount(250, true); m_pPingTimer = new CIRCNetworkPingTimer(this); CZNC::Get().GetManager().AddCron(m_pPingTimer); m_pJoinTimer = new CIRCNetworkJoinTimer(this); CZNC::Get().GetManager().AddCron(m_pJoinTimer); SetIRCConnectEnabled(true); } CIRCNetwork::CIRCNetwork(CUser* pUser, const CIRCNetwork& Network) : CIRCNetwork(pUser, "") { Clone(Network); } void CIRCNetwork::Clone(const CIRCNetwork& Network, bool bCloneName) { if (bCloneName) { m_sName = Network.GetName(); } m_fFloodRate = Network.GetFloodRate(); m_uFloodBurst = Network.GetFloodBurst(); m_uJoinDelay = Network.GetJoinDelay(); SetNick(Network.GetNick()); SetAltNick(Network.GetAltNick()); SetIdent(Network.GetIdent()); SetRealName(Network.GetRealName()); SetBindHost(Network.GetBindHost()); SetEncoding(Network.GetEncoding()); SetQuitMsg(Network.GetQuitMsg()); m_ssTrustedFingerprints = Network.m_ssTrustedFingerprints; // Servers const vector& vServers = Network.GetServers(); CString sServer; CServer* pCurServ = GetCurrentServer(); if (pCurServ) { sServer = pCurServ->GetName(); } DelServers(); for (CServer* pServer : vServers) { AddServer(pServer->GetName(), pServer->GetPort(), pServer->GetPass(), pServer->IsSSL()); } m_uServerIdx = 0; for (size_t a = 0; a < m_vServers.size(); a++) { if (sServer.Equals(m_vServers[a]->GetName())) { m_uServerIdx = a + 1; break; } } if (m_uServerIdx == 0) { m_uServerIdx = m_vServers.size(); CIRCSock* pSock = GetIRCSock(); if (pSock) { PutStatus( t_s("Jumping servers because this server is no longer in the " "list")); pSock->Quit(); } } // !Servers // Chans const vector& vChans = Network.GetChans(); for (CChan* pNewChan : vChans) { CChan* pChan = FindChan(pNewChan->GetName()); if (pChan) { pChan->SetInConfig(pNewChan->InConfig()); } else { AddChan(pNewChan->GetName(), pNewChan->InConfig()); } } for (CChan* pChan : m_vChans) { CChan* pNewChan = Network.FindChan(pChan->GetName()); if (!pNewChan) { pChan->SetInConfig(false); } else { pChan->Clone(*pNewChan); } } // !Chans // Modules set ssUnloadMods; CModules& vCurMods = GetModules(); const CModules& vNewMods = Network.GetModules(); for (CModule* pNewMod : vNewMods) { CString sModRet; CModule* pCurMod = vCurMods.FindModule(pNewMod->GetModName()); if (!pCurMod) { vCurMods.LoadModule(pNewMod->GetModName(), pNewMod->GetArgs(), CModInfo::NetworkModule, m_pUser, this, sModRet); } else if (pNewMod->GetArgs() != pCurMod->GetArgs()) { vCurMods.ReloadModule(pNewMod->GetModName(), pNewMod->GetArgs(), m_pUser, this, sModRet); } } for (CModule* pCurMod : vCurMods) { CModule* pNewMod = vNewMods.FindModule(pCurMod->GetModName()); if (!pNewMod) { ssUnloadMods.insert(pCurMod->GetModName()); } } for (const CString& sMod : ssUnloadMods) { vCurMods.UnloadModule(sMod); } // !Modules SetIRCConnectEnabled(Network.GetIRCConnectEnabled()); } CIRCNetwork::~CIRCNetwork() { if (m_pIRCSock) { CZNC::Get().GetManager().DelSockByAddr(m_pIRCSock); m_pIRCSock = nullptr; } // Delete clients while (!m_vClients.empty()) { CZNC::Get().GetManager().DelSockByAddr(m_vClients[0]); } m_vClients.clear(); // Delete servers DelServers(); // Delete modules (this unloads all modules) delete m_pModules; m_pModules = nullptr; // Delete Channels for (CChan* pChan : m_vChans) { delete pChan; } m_vChans.clear(); // Delete Queries for (CQuery* pQuery : m_vQueries) { delete pQuery; } m_vQueries.clear(); CUser* pUser = GetUser(); SetUser(nullptr); // Make sure we are not in the connection queue CZNC::Get().GetConnectionQueue().remove(this); CZNC::Get().GetManager().DelCronByAddr(m_pPingTimer); CZNC::Get().GetManager().DelCronByAddr(m_pJoinTimer); if (pUser) { pUser->AddBytesRead(m_uBytesRead); pUser->AddBytesWritten(m_uBytesWritten); } else { CZNC::Get().AddBytesRead(m_uBytesRead); CZNC::Get().AddBytesWritten(m_uBytesWritten); } } void CIRCNetwork::DelServers() { for (CServer* pServer : m_vServers) { delete pServer; } m_vServers.clear(); } CString CIRCNetwork::GetNetworkPath() const { CString sNetworkPath = m_pUser->GetUserPath() + "/networks/" + m_sName; if (!CFile::Exists(sNetworkPath)) { CDir::MakeDir(sNetworkPath); } return sNetworkPath; } template struct TOption { const char* name; void (CIRCNetwork::*pSetter)(T); }; bool CIRCNetwork::ParseConfig(CConfig* pConfig, CString& sError, bool bUpgrade) { VCString vsList; if (!bUpgrade) { TOption StringOptions[] = { {"nick", &CIRCNetwork::SetNick}, {"altnick", &CIRCNetwork::SetAltNick}, {"ident", &CIRCNetwork::SetIdent}, {"realname", &CIRCNetwork::SetRealName}, {"bindhost", &CIRCNetwork::SetBindHost}, {"encoding", &CIRCNetwork::SetEncoding}, {"quitmsg", &CIRCNetwork::SetQuitMsg}, }; TOption BoolOptions[] = { {"ircconnectenabled", &CIRCNetwork::SetIRCConnectEnabled}, {"trustallcerts", &CIRCNetwork::SetTrustAllCerts}, {"trustpki", &CIRCNetwork::SetTrustPKI}, }; TOption DoubleOptions[] = { {"floodrate", &CIRCNetwork::SetFloodRate}, }; TOption SUIntOptions[] = { {"floodburst", &CIRCNetwork::SetFloodBurst}, {"joindelay", &CIRCNetwork::SetJoinDelay}, }; for (const auto& Option : StringOptions) { CString sValue; if (pConfig->FindStringEntry(Option.name, sValue)) (this->*Option.pSetter)(sValue); } for (const auto& Option : BoolOptions) { CString sValue; if (pConfig->FindStringEntry(Option.name, sValue)) (this->*Option.pSetter)(sValue.ToBool()); } for (const auto& Option : DoubleOptions) { double fValue; if (pConfig->FindDoubleEntry(Option.name, fValue)) (this->*Option.pSetter)(fValue); } for (const auto& Option : SUIntOptions) { unsigned short value; if (pConfig->FindUShortEntry(Option.name, value)) (this->*Option.pSetter)(value); } pConfig->FindStringVector("loadmodule", vsList); for (const CString& sValue : vsList) { CString sModName = sValue.Token(0); CString sNotice = "Loading network module [" + sModName + "]"; // XXX Legacy crap, added in ZNC 0.203, modified in 0.207 // Note that 0.203 == 0.207 if (sModName == "away") { sNotice = "NOTICE: [away] was renamed, loading [awaystore] instead"; sModName = "awaystore"; } // XXX Legacy crap, added in ZNC 0.207 if (sModName == "autoaway") { sNotice = "NOTICE: [autoaway] was renamed, loading [awaystore] " "instead"; sModName = "awaystore"; } // XXX Legacy crap, added in 1.1; fakeonline module was dropped in // 1.0 and returned in 1.1 if (sModName == "fakeonline") { sNotice = "NOTICE: [fakeonline] was renamed, loading " "[modules_online] instead"; sModName = "modules_online"; } CString sModRet; CString sArgs = sValue.Token(1, true); bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet); if (!bModRet) { // XXX The awaynick module was retired in 1.6 (still available // as external module) if (sModName == "awaynick") { // load simple_away instead, unless it's already on the list bool bFound = false; for (const CString& sLoadMod : vsList) { if (sLoadMod.Token(0).Equals("simple_away")) { bFound = true; } } if (!bFound) { sNotice = "NOTICE: awaynick was retired, loading network " "module [simple_away] instead; if you still need " "awaynick, install it as an external module"; sModName = "simple_away"; // not a fatal error if simple_away is not available LoadModule(sModName, sArgs, sNotice, sModRet); } } else { sError = sModRet; return false; } } } } pConfig->FindStringVector("server", vsList); CUtils::PrintAction("Adding " + CString(vsList.size()) + " servers"); for (const CString& sServer : vsList) { CUtils::PrintStatus(AddServer(sServer)); } pConfig->FindStringVector("trustedserverfingerprint", vsList); for (const CString& sFP : vsList) { AddTrustedFingerprint(sFP); } pConfig->FindStringVector("chan", vsList); for (const CString& sChan : vsList) { AddChan(sChan, true); } CConfig::SubConfig subConf; CConfig::SubConfig::const_iterator subIt; pConfig->FindSubConfig("chan", subConf); for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) { const CString& sChanName = subIt->first; CConfig* pSubConf = subIt->second.m_pSubConfig; CChan* pChan = new CChan(sChanName, this, true, pSubConf); if (!pSubConf->empty()) { sError = "Unhandled lines in config for User [" + m_pUser->GetUsername() + "], Network [" + GetName() + "], Channel [" + sChanName + "]!"; CUtils::PrintError(sError); CZNC::DumpConfig(pSubConf); delete pChan; return false; } // Save the channel name, because AddChan // deletes the CChannel*, if adding fails sError = pChan->GetName(); if (!AddChan(pChan)) { sError = "Channel [" + sError + "] defined more than once"; CUtils::PrintError(sError); return false; } sError.clear(); } return true; } CConfig CIRCNetwork::ToConfig() const { CConfig config; if (!m_sNick.empty()) { config.AddKeyValuePair("Nick", m_sNick); } if (!m_sAltNick.empty()) { config.AddKeyValuePair("AltNick", m_sAltNick); } if (!m_sIdent.empty()) { config.AddKeyValuePair("Ident", m_sIdent); } if (!m_sRealName.empty()) { config.AddKeyValuePair("RealName", m_sRealName); } if (!m_sBindHost.empty()) { config.AddKeyValuePair("BindHost", m_sBindHost); } config.AddKeyValuePair("IRCConnectEnabled", CString(GetIRCConnectEnabled())); config.AddKeyValuePair("TrustAllCerts", CString(GetTrustAllCerts())); config.AddKeyValuePair("TrustPKI", CString(GetTrustPKI())); config.AddKeyValuePair("FloodRate", CString(GetFloodRate())); config.AddKeyValuePair("FloodBurst", CString(GetFloodBurst())); config.AddKeyValuePair("JoinDelay", CString(GetJoinDelay())); config.AddKeyValuePair("Encoding", m_sEncoding); if (!m_sQuitMsg.empty()) { config.AddKeyValuePair("QuitMsg", m_sQuitMsg); } // Modules const CModules& Mods = GetModules(); if (!Mods.empty()) { for (CModule* pMod : Mods) { CString sArgs = pMod->GetArgs(); if (!sArgs.empty()) { sArgs = " " + sArgs; } config.AddKeyValuePair("LoadModule", pMod->GetModName() + sArgs); } } // Servers for (CServer* pServer : m_vServers) { config.AddKeyValuePair("Server", pServer->GetString()); } for (const CString& sFP : m_ssTrustedFingerprints) { config.AddKeyValuePair("TrustedServerFingerprint", sFP); } // Chans for (CChan* pChan : m_vChans) { if (pChan->InConfig()) { config.AddSubConfig("Chan", pChan->GetName(), pChan->ToConfig()); } } return config; } void CIRCNetwork::BounceAllClients() { for (CClient* pClient : m_vClients) { pClient->BouncedOff(); } m_vClients.clear(); } bool CIRCNetwork::IsUserOnline() const { for (CClient* pClient : m_vClients) { if (!pClient->IsAway()) { return true; } } return false; } void CIRCNetwork::ClientConnected(CClient* pClient) { if (!m_pUser->MultiClients()) { BounceAllClients(); } m_vClients.push_back(pClient); size_t uIdx, uSize; if (m_pIRCSock) { pClient->NotifyServerDependentCaps(m_pIRCSock->GetAcceptedCaps()); } pClient->SetPlaybackActive(true); if (m_RawBuffer.IsEmpty()) { pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() + " :" + t_s("Welcome to ZNC")); } else { const CString& sClientNick = pClient->GetNick(false); MCString msParams; msParams["target"] = sClientNick; uSize = m_RawBuffer.Size(); for (uIdx = 0; uIdx < uSize; uIdx++) { pClient->PutClient(m_RawBuffer.GetLine(uIdx, *pClient, msParams)); } const CNick& Nick = GetIRCNick(); if (sClientNick != Nick.GetNick()) { // case-sensitive match pClient->PutClient(":" + sClientNick + "!" + Nick.GetIdent() + "@" + Nick.GetHost() + " NICK :" + Nick.GetNick()); pClient->SetNick(Nick.GetNick()); } } MCString msParams; msParams["target"] = GetIRCNick().GetNick(); // Send the cached MOTD uSize = m_MotdBuffer.Size(); if (uSize > 0) { for (uIdx = 0; uIdx < uSize; uIdx++) { pClient->PutClient(m_MotdBuffer.GetLine(uIdx, *pClient, msParams)); } } if (GetIRCSock() != nullptr) { CString sUserMode(""); const set& scUserModes = GetIRCSock()->GetUserModes(); for (char cMode : scUserModes) { sUserMode += cMode; } if (!sUserMode.empty()) { pClient->PutClient(":" + GetIRCNick().GetNickMask() + " MODE " + GetIRCNick().GetNick() + " :+" + sUserMode); } } if (m_bIRCAway) { // If they want to know their away reason they'll have to whois // themselves. At least we can tell them their away status... pClient->PutClient(":irc.znc.in 306 " + GetIRCNick().GetNick() + " :You have been marked as being away"); } const vector& vChans = GetChans(); for (CChan* pChan : vChans) { if ((pChan->IsOn()) && (!pChan->IsDetached())) { pChan->AttachUser(pClient); } } bool bClearQuery = m_pUser->AutoClearQueryBuffer(); for (CQuery* pQuery : m_vQueries) { pQuery->SendBuffer(pClient); if (bClearQuery) { delete pQuery; } } if (bClearQuery) { m_vQueries.clear(); } uSize = m_NoticeBuffer.Size(); for (uIdx = 0; uIdx < uSize; uIdx++) { const CBufLine& BufLine = m_NoticeBuffer.GetBufLine(uIdx); CMessage Message(BufLine.GetLine(*pClient, msParams)); Message.SetNetwork(this); Message.SetClient(pClient); Message.SetTime(BufLine.GetTime()); Message.SetTags(BufLine.GetTags()); bool bContinue = false; NETWORKMODULECALL(OnPrivBufferPlayMessage(Message), m_pUser, this, nullptr, &bContinue); if (bContinue) continue; pClient->PutClient(Message); } m_NoticeBuffer.Clear(); pClient->SetPlaybackActive(false); // Tell them why they won't connect if (!GetIRCConnectEnabled()) pClient->PutStatus( t_s("You are currently disconnected from IRC. Use 'connect' to " "reconnect.")); } void CIRCNetwork::ClientDisconnected(CClient* pClient) { auto it = std::find(m_vClients.begin(), m_vClients.end(), pClient); if (it != m_vClients.end()) { m_vClients.erase(it); } } CUser* CIRCNetwork::GetUser() const { return m_pUser; } const CString& CIRCNetwork::GetName() const { return m_sName; } std::vector CIRCNetwork::FindClients( const CString& sIdentifier) const { std::vector vClients; for (CClient* pClient : m_vClients) { if (pClient->GetIdentifier().Equals(sIdentifier)) { vClients.push_back(pClient); } } return vClients; } void CIRCNetwork::SetUser(CUser* pUser) { for (CClient* pClient : m_vClients) { pClient->PutStatus( t_s("This network is being deleted or moved to another user.")); pClient->SetNetwork(nullptr); } m_vClients.clear(); if (m_pUser) { m_pUser->RemoveNetwork(this); } m_pUser = pUser; if (m_pUser) { m_pUser->AddNetwork(this); } } bool CIRCNetwork::SetName(const CString& sName) { if (IsValidNetwork(sName)) { m_sName = sName; return true; } return false; } bool CIRCNetwork::PutUser(const CString& sLine, CClient* pClient, CClient* pSkipClient) { for (CClient* pEachClient : m_vClients) { if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { pEachClient->PutClient(sLine); if (pClient) { return true; } } } return (pClient == nullptr); } bool CIRCNetwork::PutUser(const CMessage& Message, CClient* pClient, CClient* pSkipClient) { for (CClient* pEachClient : m_vClients) { if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { pEachClient->PutClient(Message); if (pClient) { return true; } } } return (pClient == nullptr); } bool CIRCNetwork::PutStatus(const CString& sLine, CClient* pClient, CClient* pSkipClient) { for (CClient* pEachClient : m_vClients) { if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { pEachClient->PutStatus(sLine); if (pClient) { return true; } } } return (pClient == nullptr); } bool CIRCNetwork::PutModule(const CString& sModule, const CString& sLine, CClient* pClient, CClient* pSkipClient) { for (CClient* pEachClient : m_vClients) { if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) { pEachClient->PutModule(sModule, sLine); if (pClient) { return true; } } } return (pClient == nullptr); } // Channels const vector& CIRCNetwork::GetChans() const { return m_vChans; } CChan* CIRCNetwork::FindChan(CString sName) const { if (GetIRCSock()) { // See // https://tools.ietf.org/html/draft-brocklesby-irc-isupport-03#section-3.16 sName.TrimLeft(GetIRCSock()->GetISupport("STATUSMSG", "")); } for (CChan* pChan : m_vChans) { if (sName.Equals(pChan->GetName())) { return pChan; } } return nullptr; } std::vector CIRCNetwork::FindChans(const CString& sWild) const { std::vector vChans; vChans.reserve(m_vChans.size()); const CString sLower = sWild.AsLower(); for (CChan* pChan : m_vChans) { if (pChan->GetName().AsLower().WildCmp(sLower)) vChans.push_back(pChan); } return vChans; } bool CIRCNetwork::AddChan(CChan* pChan) { if (!pChan) { return false; } for (CChan* pEachChan : m_vChans) { if (pEachChan->GetName().Equals(pChan->GetName())) { delete pChan; return false; } } m_vChans.push_back(pChan); return true; } bool CIRCNetwork::AddChan(const CString& sName, bool bInConfig) { if (sName.empty() || FindChan(sName)) { return false; } CChan* pChan = new CChan(sName, this, bInConfig); m_vChans.push_back(pChan); return true; } bool CIRCNetwork::DelChan(const CString& sName) { for (vector::iterator a = m_vChans.begin(); a != m_vChans.end(); ++a) { if (sName.Equals((*a)->GetName())) { delete *a; m_vChans.erase(a); return true; } } return false; } void CIRCNetwork::JoinChans() { // Avoid divsion by zero, it's bad! if (m_vChans.empty()) return; // We start at a random offset into the channel list so that if your // first 3 channels are invite-only and you got MaxJoins == 3, ZNC will // still be able to join the rest of your channels. unsigned int start = rand() % m_vChans.size(); unsigned int uJoins = m_pUser->MaxJoins(); set sChans; for (unsigned int a = 0; a < m_vChans.size(); a++) { unsigned int idx = (start + a) % m_vChans.size(); CChan* pChan = m_vChans[idx]; if (!pChan->IsOn() && !pChan->IsDisabled()) { if (!JoinChan(pChan)) continue; sChans.insert(pChan); // Limit the number of joins if (uJoins != 0 && --uJoins == 0) { // Reset the timer. m_pJoinTimer->Reset(); break; } } } while (!sChans.empty()) JoinChans(sChans); } void CIRCNetwork::JoinChans(set& sChans) { CString sKeys, sJoin; bool bHaveKey = false; size_t uiJoinLength = strlen("JOIN "); while (!sChans.empty()) { set::iterator it = sChans.begin(); const CString& sName = (*it)->GetName(); const CString& sKey = (*it)->GetKey(); size_t len = sName.length() + sKey.length(); len += 2; // two comma if (!sKeys.empty() && uiJoinLength + len >= 512) break; if (!sJoin.empty()) { sJoin += ","; sKeys += ","; } uiJoinLength += len; sJoin += sName; if (!sKey.empty()) { sKeys += sKey; bHaveKey = true; } sChans.erase(it); } if (bHaveKey) PutIRC("JOIN " + sJoin + " " + sKeys); else PutIRC("JOIN " + sJoin); } bool CIRCNetwork::JoinChan(CChan* pChan) { bool bReturn = false; NETWORKMODULECALL(OnJoining(*pChan), m_pUser, this, nullptr, &bReturn); if (bReturn) return false; if (m_pUser->JoinTries() != 0 && pChan->GetJoinTries() >= m_pUser->JoinTries()) { PutStatus(t_f("The channel {1} could not be joined, disabling it.")( pChan->GetName())); pChan->Disable(); } else { pChan->IncJoinTries(); bool bFailed = false; NETWORKMODULECALL(OnTimerAutoJoin(*pChan), m_pUser, this, nullptr, &bFailed); if (bFailed) return false; return true; } return false; } bool CIRCNetwork::IsChan(const CString& sChan) const { if (sChan.empty()) return false; // There is no way this is a chan if (GetChanPrefixes().empty()) return true; // We can't know, so we allow everything // Thanks to the above if (empty), we can do sChan[0] return GetChanPrefixes().find(sChan[0]) != CString::npos; } // Queries const vector& CIRCNetwork::GetQueries() const { return m_vQueries; } CQuery* CIRCNetwork::FindQuery(const CString& sName) const { for (CQuery* pQuery : m_vQueries) { if (sName.Equals(pQuery->GetName())) { return pQuery; } } return nullptr; } std::vector CIRCNetwork::FindQueries(const CString& sWild) const { std::vector vQueries; vQueries.reserve(m_vQueries.size()); const CString sLower = sWild.AsLower(); for (CQuery* pQuery : m_vQueries) { if (pQuery->GetName().AsLower().WildCmp(sLower)) vQueries.push_back(pQuery); } return vQueries; } CQuery* CIRCNetwork::AddQuery(const CString& sName) { if (sName.empty()) { return nullptr; } CQuery* pQuery = FindQuery(sName); if (!pQuery) { pQuery = new CQuery(sName, this); m_vQueries.push_back(pQuery); if (m_pUser->MaxQueryBuffers() > 0) { while (m_vQueries.size() > m_pUser->MaxQueryBuffers()) { delete *m_vQueries.begin(); m_vQueries.erase(m_vQueries.begin()); } } } return pQuery; } bool CIRCNetwork::DelQuery(const CString& sName) { for (vector::iterator a = m_vQueries.begin(); a != m_vQueries.end(); ++a) { if (sName.Equals((*a)->GetName())) { delete *a; m_vQueries.erase(a); return true; } } return false; } // Server list const vector& CIRCNetwork::GetServers() const { return m_vServers; } CServer* CIRCNetwork::FindServer(const CString& sName) const { for (CServer* pServer : m_vServers) { if (sName.Equals(pServer->GetName())) { return pServer; } } return nullptr; } bool CIRCNetwork::DelServer(const CString& sName, unsigned short uPort, const CString& sPass) { if (sName.empty()) { return false; } unsigned int a = 0; bool bSawCurrentServer = false; CServer* pCurServer = GetCurrentServer(); for (vector::iterator it = m_vServers.begin(); it != m_vServers.end(); ++it, a++) { CServer* pServer = *it; if (pServer == pCurServer) bSawCurrentServer = true; if (!pServer->GetName().Equals(sName)) continue; if (uPort != 0 && pServer->GetPort() != uPort) continue; if (!sPass.empty() && pServer->GetPass() != sPass) continue; m_vServers.erase(it); if (pServer == pCurServer) { CIRCSock* pIRCSock = GetIRCSock(); // Make sure we don't skip the next server in the list! if (m_uServerIdx) { m_uServerIdx--; } if (pIRCSock) { pIRCSock->Quit(); PutStatus(t_s("Your current server was removed, jumping...")); } } else if (!bSawCurrentServer) { // Our current server comes after the server which we // are removing. This means that it now got a different // index in m_vServers! m_uServerIdx--; } delete pServer; return true; } return false; } bool CIRCNetwork::AddServer(const CString& sName) { if (sName.empty()) { return false; } bool bSSL = false; CString sLine = sName; sLine.Trim(); CString sHost = sLine.Token(0); CString sPort = sLine.Token(1); if (sPort.TrimPrefix("+")) { bSSL = true; } unsigned short uPort = sPort.ToUShort(); CString sPass = sLine.Token(2, true); return AddServer(sHost, uPort, sPass, bSSL); } bool CIRCNetwork::AddServer(const CString& sName, unsigned short uPort, const CString& sPass, bool bSSL) { #ifndef HAVE_LIBSSL if (bSSL) { return false; } #endif if (sName.empty()) { return false; } if (!uPort) { uPort = 6667; } // Check if server is already added for (CServer* pServer : m_vServers) { if (!sName.Equals(pServer->GetName())) continue; if (uPort != pServer->GetPort()) continue; if (sPass != pServer->GetPass()) continue; if (bSSL != pServer->IsSSL()) continue; // Server is already added return false; } CServer* pServer = new CServer(sName, uPort, sPass, bSSL); m_vServers.push_back(pServer); CheckIRCConnect(); return true; } CServer* CIRCNetwork::GetNextServer(bool bAdvance) { if (m_vServers.empty()) { return nullptr; } if (m_uServerIdx >= m_vServers.size()) { m_uServerIdx = 0; } if (bAdvance) { return m_vServers[m_uServerIdx++]; } else { return m_vServers[m_uServerIdx]; } } CServer* CIRCNetwork::GetCurrentServer() const { size_t uIdx = (m_uServerIdx) ? m_uServerIdx - 1 : 0; if (uIdx >= m_vServers.size()) { return nullptr; } return m_vServers[uIdx]; } void CIRCNetwork::SetIRCServer(const CString& s) { m_sIRCServer = s; } bool CIRCNetwork::SetNextServer(const CServer* pServer) { for (unsigned int a = 0; a < m_vServers.size(); a++) { if (m_vServers[a] == pServer) { m_uServerIdx = a; return true; } } return false; } bool CIRCNetwork::IsLastServer() const { return (m_uServerIdx >= m_vServers.size()); } const CString& CIRCNetwork::GetIRCServer() const { return m_sIRCServer; } const CNick& CIRCNetwork::GetIRCNick() const { return m_IRCNick; } void CIRCNetwork::SetIRCNick(const CNick& n) { m_IRCNick = n; for (CClient* pClient : m_vClients) { pClient->SetNick(n.GetNick()); } } CString CIRCNetwork::GetCurNick() const { const CIRCSock* pIRCSock = GetIRCSock(); if (pIRCSock) { return pIRCSock->GetNick(); } if (!m_vClients.empty()) { return m_vClients[0]->GetNick(); } return ""; } bool CIRCNetwork::Connect() { if (!GetIRCConnectEnabled() || m_pIRCSock || !HasServers()) return false; CServer* pServer = GetNextServer(); if (!pServer) return false; if (CZNC::Get().GetServerThrottle(pServer->GetName())) { // Can't connect right now, schedule retry later CZNC::Get().AddNetworkToQueue(this); return false; } CZNC::Get().AddServerThrottle(pServer->GetName()); bool bSSL = pServer->IsSSL(); #ifndef HAVE_LIBSSL if (bSSL) { PutStatus( t_f("Cannot connect to {1}, because ZNC is not compiled with SSL " "support.")(pServer->GetString(false))); CZNC::Get().AddNetworkToQueue(this); return false; } #endif CIRCSock* pIRCSock = new CIRCSock(this); pIRCSock->SetPass(pServer->GetPass()); pIRCSock->SetSSLTrustedPeerFingerprints(m_ssTrustedFingerprints); pIRCSock->SetTrustAllCerts(GetTrustAllCerts()); pIRCSock->SetTrustPKI(GetTrustPKI()); DEBUG("Connecting user/network [" << m_pUser->GetUsername() << "/" << m_sName << "]"); bool bAbort = false; NETWORKMODULECALL(OnIRCConnecting(pIRCSock), m_pUser, this, nullptr, &bAbort); if (bAbort) { DEBUG("Some module aborted the connection attempt"); PutStatus(t_s("Some module aborted the connection attempt")); delete pIRCSock; CZNC::Get().AddNetworkToQueue(this); return false; } CString sSockName = "IRC::" + m_pUser->GetUsername() + "::" + m_sName; CZNC::Get().GetManager().Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, GetBindHost(), pIRCSock); return true; } bool CIRCNetwork::IsIRCConnected() const { const CIRCSock* pSock = GetIRCSock(); return (pSock && pSock->IsAuthed()); } void CIRCNetwork::SetIRCSocket(CIRCSock* pIRCSock) { m_pIRCSock = pIRCSock; } void CIRCNetwork::IRCConnected() { const SCString& ssCaps = m_pIRCSock->GetAcceptedCaps(); for (CClient* pClient : m_vClients) { pClient->NotifyServerDependentCaps(ssCaps); } if (m_uJoinDelay > 0) { m_pJoinTimer->Delay(m_uJoinDelay); } else { JoinChans(); } } void CIRCNetwork::IRCDisconnected() { for (CClient* pClient : m_vClients) { pClient->ClearServerDependentCaps(); } m_pIRCSock = nullptr; SetIRCServer(""); m_bIRCAway = false; // Get the reconnect going CheckIRCConnect(); } void CIRCNetwork::SetIRCConnectEnabled(bool b) { m_bIRCConnectEnabled = b; if (m_bIRCConnectEnabled) { CheckIRCConnect(); } else if (GetIRCSock()) { if (GetIRCSock()->IsConnected()) { GetIRCSock()->Quit(); } else { GetIRCSock()->Close(); } } } void CIRCNetwork::CheckIRCConnect() { // Do we want to connect? if (GetIRCConnectEnabled() && GetIRCSock() == nullptr) CZNC::Get().AddNetworkToQueue(this); } bool CIRCNetwork::PutIRC(const CString& sLine) { CIRCSock* pIRCSock = GetIRCSock(); if (!pIRCSock) { return false; } pIRCSock->PutIRC(sLine); return true; } bool CIRCNetwork::PutIRC(const CMessage& Message) { CIRCSock* pIRCSock = GetIRCSock(); if (!pIRCSock) { return false; } pIRCSock->PutIRC(Message); return true; } void CIRCNetwork::ClearQueryBuffer() { std::for_each(m_vQueries.begin(), m_vQueries.end(), std::default_delete()); m_vQueries.clear(); } const CString& CIRCNetwork::GetNick(const bool bAllowDefault) const { if (m_sNick.empty()) { return m_pUser->GetNick(bAllowDefault); } return m_sNick; } const CString& CIRCNetwork::GetAltNick(const bool bAllowDefault) const { if (m_sAltNick.empty()) { return m_pUser->GetAltNick(bAllowDefault); } return m_sAltNick; } const CString& CIRCNetwork::GetIdent(const bool bAllowDefault) const { if (m_sIdent.empty()) { return m_pUser->GetIdent(bAllowDefault); } return m_sIdent; } CString CIRCNetwork::GetRealName() const { if (m_sRealName.empty()) { return m_pUser->GetRealName(); } return m_sRealName; } const CString& CIRCNetwork::GetBindHost() const { if (m_sBindHost.empty()) { return m_pUser->GetBindHost(); } return m_sBindHost; } const CString& CIRCNetwork::GetEncoding() const { return m_sEncoding; } CString CIRCNetwork::GetQuitMsg() const { if (m_sQuitMsg.empty()) { return m_pUser->GetQuitMsg(); } return m_sQuitMsg; } void CIRCNetwork::SetNick(const CString& s) { if (m_pUser->GetNick().Equals(s)) { m_sNick = ""; } else { m_sNick = s; } } void CIRCNetwork::SetAltNick(const CString& s) { if (m_pUser->GetAltNick().Equals(s)) { m_sAltNick = ""; } else { m_sAltNick = s; } } void CIRCNetwork::SetIdent(const CString& s) { if (m_pUser->GetIdent().Equals(s)) { m_sIdent = ""; } else { m_sIdent = s; } } void CIRCNetwork::SetRealName(const CString& s) { if (m_pUser->GetRealName().Equals(s)) { m_sRealName = ""; } else { m_sRealName = s; } } void CIRCNetwork::SetBindHost(const CString& s) { if (m_pUser->GetBindHost().Equals(s)) { m_sBindHost = ""; } else { m_sBindHost = s; } } void CIRCNetwork::SetEncoding(const CString& s) { m_sEncoding = CZNC::Get().FixupEncoding(s); if (GetIRCSock()) { GetIRCSock()->SetEncoding(m_sEncoding); } } void CIRCNetwork::SetQuitMsg(const CString& s) { if (m_pUser->GetQuitMsg().Equals(s)) { m_sQuitMsg = ""; } else { m_sQuitMsg = s; } } CString CIRCNetwork::ExpandString(const CString& sStr) const { CString sRet; return ExpandString(sStr, sRet); } CString& CIRCNetwork::ExpandString(const CString& sStr, CString& sRet) const { sRet = sStr; sRet.Replace("%altnick%", GetAltNick()); sRet.Replace("%bindhost%", GetBindHost()); sRet.Replace("%defnick%", GetNick()); sRet.Replace("%ident%", GetIdent()); sRet.Replace("%network%", GetName()); sRet.Replace("%nick%", GetCurNick()); sRet.Replace("%realname%", GetRealName()); return m_pUser->ExpandString(sRet, sRet); } bool CIRCNetwork::LoadModule(const CString& sModName, const CString& sArgs, const CString& sNotice, CString& sError) { CUtils::PrintAction(sNotice); CString sModRet; bool bModRet = GetModules().LoadModule( sModName, sArgs, CModInfo::NetworkModule, GetUser(), this, sModRet); CUtils::PrintStatus(bModRet, sModRet); if (!bModRet) { sError = sModRet; } return bModRet; }