From 83db7684f2c78ca7a632ddb726ad5ea672857d48 Mon Sep 17 00:00:00 2001 From: psychon Date: Wed, 4 Aug 2010 18:50:44 +0000 Subject: [PATCH] Server part of CAP stuff This introduces the code for modules to request CAPs on the IRC server. They will get a callback when the capability was accepted or rejected. Thanks to DarthGandalf for this patch. This should turn DarthGandalf and tomaw into happy znc users again. ;) git-svn-id: https://znc.svn.sourceforge.net/svnroot/znc/trunk@2099 726aef4b-f618-498e-8847-2d620e286838 --- IRCSock.cpp | 90 +++++++++++++++++++++++++++++++++-------------------- IRCSock.h | 4 +++ Modules.cpp | 34 ++++++++++++++++++++ Modules.h | 19 +++++++++++ 4 files changed, 114 insertions(+), 33 deletions(-) diff --git a/IRCSock.cpp b/IRCSock.cpp index f69057dd..fdeab932 100644 --- a/IRCSock.cpp +++ b/IRCSock.cpp @@ -647,48 +647,67 @@ void CIRCSock::ReadLine(const CString& sData) { m_pUser->AddQueryBuffer(":" + Nick.GetNickMask() + " WALLOPS ", ":" + m_pUser->AddTimestamp(sMsg), false); } } else if (sCmd.Equals("CAP")) { - // sRest.Token(0) is most likely "*". No idea why, the - // CAP spec don't mention this, but all implementations - // I've seen add this extra asterisk - CString sSubCmd = sRest.Token(1); + // CAPs are supported only before authorization. + if (!m_bAuthed) { + // sRest.Token(0) is most likely "*". No idea why, the + // CAP spec don't mention this, but all implementations + // I've seen add this extra asterisk + CString sSubCmd = sRest.Token(1); - // If the caplist of a reply is too long, it's split - // into multiple replies. A "*" is prepended to show - // that the list was split into multiple replies. - CString sArgs; - if (sRest.Token(2) == "*") { - sArgs = sRest.Token(3, true).TrimPrefix_n(":"); - } else { - sArgs = sRest.Token(2, true).TrimPrefix_n(":"); - } - - if (sSubCmd == "LS" && !m_bAuthed) { - VCString vsTokens; - VCString::iterator it; - sArgs.Split(" ", vsTokens, false); - - for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { - if (*it == "multi-prefix" || *it == "userhost-in-names") { - PutIRC("CAP REQ :" + *it); - } + // If the caplist of a reply is too long, it's split + // into multiple replies. A "*" is prepended to show + // that the list was split into multiple replies. + // This is useful mainly for LS. For ACK and NAK + // replies, there's no real need for this, because + // we request only 1 capability per line. + // If we will need to support broken servers or will + // send several requests per line, need to delay ACK + // actions until all ACK lines are received and + // to recognize past request of NAK by 100 chars + // of this reply. + CString sArgs; + if (sRest.Token(2) == "*") { + sArgs = sRest.Token(3, true).TrimPrefix_n(":"); + } else { + sArgs = sRest.Token(2, true).TrimPrefix_n(":"); } - // Tell the IRC server we are done with CAP - PutIRC("CAP END"); - } else if (sSubCmd == "ACK" && !m_bAuthed) { - VCString vsTokens; - VCString::iterator it; - sArgs.Split(" ", vsTokens, false); + if (sSubCmd == "LS") { + VCString vsTokens; + VCString::iterator it; + sArgs.Split(" ", vsTokens, false); - for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { - if (*it == "multi-prefix") { + for (it = vsTokens.begin(); it != vsTokens.end(); ++it) { + if (OnServerCapAvailable(*it) || *it == "multi-prefix" || *it == "userhost-in-names") { + // For real support of ack (~) modifier need also + // to queue these cap requests. + PutIRC("CAP REQ :" + *it); + m_ssPendingCaps.insert(*it); + } + } + } else if (sSubCmd == "ACK") { + sArgs.Trim(); + m_ssPendingCaps.erase(sArgs); + MODULECALL(OnServerCapAccepted(sArgs), m_pUser, NULL, ); + if ("multi-prefix" == sArgs) { m_bNamesx = true; - } else if (*it == "userhost-in-names") { + } else if ("userhost-in-names" == sArgs) { m_bUHNames = true; } + m_ssAcceptedCaps.insert(sArgs); + } else if (sSubCmd == "NAK") { + // This should work because there's no [known] + // capability with length of name more than 100 characters. + sArgs.Trim(); + m_ssPendingCaps.erase(sArgs); + MODULECALL(OnServerCapRejected(sArgs), m_pUser, NULL, ); + } + + if (m_ssPendingCaps.empty()) { + // We already got all needed ACK/NAK replies. + PutIRC("CAP END"); } } - // Don't forward any CAP stuff to the client return; } @@ -697,6 +716,11 @@ void CIRCSock::ReadLine(const CString& sData) { m_pUser->PutUser(sLine); } +bool CIRCSock::OnServerCapAvailable(const CString& sCap) { + MODULECALL(OnServerCapAvailable(sCap), m_pUser, NULL, return true); + return false; +} + bool CIRCSock::OnCTCPReply(CNick& Nick, CString& sMessage) { MODULECALL(OnCTCPReply(Nick, sMessage), m_pUser, NULL, return true); diff --git a/IRCSock.h b/IRCSock.h index 6a7fbde8..2301de56 100644 --- a/IRCSock.h +++ b/IRCSock.h @@ -40,6 +40,7 @@ public: bool OnChanMsg(CNick& Nick, const CString& sChan, CString& sMessage); bool OnPrivNotice(CNick& Nick, CString& sMessage); bool OnChanNotice(CNick& Nick, const CString& sChan, CString& sMessage); + bool OnServerCapAvailable(const CString& sCap); // !Message Handlers virtual void ReadLine(const CString& sData); @@ -76,6 +77,7 @@ public: const set& GetUserModes() const { return m_scUserModes; } // This is true if we are past raw 001 bool IsAuthed() const { return m_bAuthed; } + bool IsCapAccepted(const CString& sCap) { return 1 == m_ssAcceptedCaps.count(sCap); } // !Getters // This handles NAMESX and UHNAMES in a raw 353 reply @@ -100,6 +102,8 @@ protected: CString m_sPass; map m_msChans; unsigned int m_uMaxNickLen; + SCString m_ssAcceptedCaps; + SCString m_ssPendingCaps; }; #endif // !_IRCSOCK_H diff --git a/Modules.cpp b/Modules.cpp index 05fc3fcb..62af05a5 100644 --- a/Modules.cpp +++ b/Modules.cpp @@ -455,6 +455,10 @@ CModule::EModRet CModule::OnChanNotice(CNick& Nick, CChan& Channel, CString& sMe CModule::EModRet CModule::OnTopic(CNick& Nick, CChan& Channel, CString& sTopic) { return CONTINUE; } CModule::EModRet CModule::OnTimerAutoJoin(CChan& Channel) { return CONTINUE; } +bool CModule::OnServerCapAvailable(const CString& sCap) { return false; } +void CModule::OnServerCapAccepted(const CString& sCap) {} +void CModule::OnServerCapRejected(const CString& sCap) {} + bool CModule::PutIRC(const CString& sLine) { return (m_pUser) ? m_pUser->PutIRC(sLine) : false; } @@ -597,6 +601,36 @@ bool CModules::OnModCommand(const CString& sCommand) { MODUNLOADCHK(OnModCommand bool CModules::OnModNotice(const CString& sMessage) { MODUNLOADCHK(OnModNotice(sMessage)); return false; } bool CModules::OnModCTCP(const CString& sMessage) { MODUNLOADCHK(OnModCTCP(sMessage)); return false; } +// Why MODHALTCHK works only with functions returning EModRet ? :( +bool CModules::OnServerCapAvailable(const CString& sCap) { + bool bResult = false; + for (unsigned int a = 0; a < size(); ++a) { + try { + CModule* pMod = (*this)[a]; + CClient* pOldClient = pMod->GetClient(); + pMod->SetClient(m_pClient); + if (m_pUser) { + CUser* pOldUser = pMod->GetUser(); + pMod->SetUser(m_pUser); + bResult |= pMod->OnServerCapAvailable(sCap); + pMod->SetUser(pOldUser); + } else { + // WTF? Is that possible? + bResult |= pMod->OnServerCapAvailable(sCap); + } + pMod->SetClient(pOldClient); + } catch (CModule::EModException e) { + if (CModule::UNLOAD == e) { + UnloadModule((*this)[a]->GetModName()); + } + } + } + return bResult; +} + +bool CModules::OnServerCapAccepted(const CString& sCap) { MODUNLOADCHK(OnServerCapAccepted(sCap)); return false; } +bool CModules::OnServerCapRejected(const CString& sCap) { MODUNLOADCHK(OnServerCapRejected(sCap)); return false; } + //////////////////// // CGlobalModules // //////////////////// diff --git a/Modules.h b/Modules.h index bab1cbb0..bed2e5d8 100644 --- a/Modules.h +++ b/Modules.h @@ -638,6 +638,21 @@ public: */ virtual EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); + /** Called for every CAP received via CAP LS from server. + * @param sCap capability supported by server. + * @return true if your module supports this CAP and + * needs to turn it on with CAP REQ. + */ + virtual bool OnServerCapAvailable(const CString& sCap); + /** Called for every CAP accepted by server (with CAP ACK after our CAP REQ). + * @param sCap capability accepted by server. + */ + virtual void OnServerCapAccepted(const CString& sCap); + /** Called for every CAP rejected by server (with CAP NAK after our CAP REQ). + * @param sCap capability rejected by server. + */ + virtual void OnServerCapRejected(const CString& sCap); + /** This module hook is called just before ZNC tries to join a channel * by itself because it's in the config but wasn't joined yet. * @param Channel The channel which will be joined. @@ -868,6 +883,10 @@ public: bool OnTopic(CNick& Nick, CChan& Channel, CString& sTopic); bool OnTimerAutoJoin(CChan& Channel); + bool OnServerCapAvailable(const CString& sCap); + bool OnServerCapAccepted(const CString& sCap); + bool OnServerCapRejected(const CString& sCap); + CModule* FindModule(const CString& sModule) const; bool LoadModule(const CString& sModule, const CString& sArgs, CUser* pUser, CString& sRetMsg); bool UnloadModule(const CString& sModule);