diff --git a/ChangeLog.md b/ChangeLog.md index f858646b..a8246ab0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,31 @@ +# ZNC 1.10.3 (2026-09-13) + +This is a security release to fix the following issues: + +## CVE-2026-82373 + +Use-after-free when unloading modules, triggered by unprivileged users. + +Note that the fix required a slight API change for `OnEmbeddedWebRequest`: +now the callers (webadmin is the only known caller of it) call it via +`DoEmbeddedWebRequest()` instead. The modules which implement this hook +need no changes. + +To mitigate, set `DenyLoadMod` to true. + +Found by Claude from Anthropic, reported by Ada Logics. + +## CVE-2026-82374 + +Null pointer dereference in crypt module, triggered by IRC server. + +Found and fixed by KindOne. + +## CVE-2020-11022, CVE-2020-11023 + +Updated the old bundled vulnerable version of jQuery to 3.5.1. + + # ZNC 1.10.2 (2026-05-07) ## Fixes diff --git a/include/znc/Modules.h b/include/znc/Modules.h index 631a33ad..85039edb 100644 --- a/include/znc/Modules.h +++ b/include/znc/Modules.h @@ -465,6 +465,9 @@ class CModule { void SetNetwork(CIRCNetwork* pNetwork); void SetClient(CClient* pClient); + /** True if the module can be safely unloaded. */ + bool IsCallStackEmpty() const; + /** This function throws CModule::UNLOAD which causes this module to be unloaded. */ void Unload() { throw UNLOAD; } @@ -535,9 +538,11 @@ class CModule { * @return The List. */ virtual VWebSubPages& GetSubPages() { return m_vSubPages; } + private: /** Using this hook, module can embed web stuff directly to different places. - * This method is called whenever embededded modules I/O happens. + * This method is called whenever embedded modules I/O happens. * Name of used .tmpl file (if any) is up to caller. + * Callers call it via `DoEmbeddedWebRequest()`. * @param WebSock Socket for web connection, don't do bad things with it. * @param sPageName Describes the place where web stuff is embedded to. * @param Tmpl Template. Depending on context, you can do various stuff with it. @@ -547,6 +552,9 @@ class CModule { virtual bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl); + public: + bool DoEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, + CTemplate& Tmpl); /** Called just before znc.conf is rehashed */ virtual void OnPreRehash(); @@ -1528,6 +1536,9 @@ class CModule { m_mssRegistry; //!< way to save name/value pairs. Note there is no encryption involved in this VWebSubPages m_vSubPages; std::map m_mCommands; + int m_iCallStackDepth = 0; + + friend struct CModCallProtector; }; class CModules : public std::vector, private CCoreTranslationMixin { @@ -1766,4 +1777,62 @@ class CModules : public std::vector, private CCoreTranslationMixin { CClient* m_pClient; }; +struct CModCallProtector { + explicit CModCallProtector(CModule& pMod) : m_pMod(&pMod) { + pMod.m_iCallStackDepth++; + } + ~CModCallProtector() { m_pMod->m_iCallStackDepth--; } + + CModule* m_pMod; +}; + +#ifndef SWIG +template +struct CTemporaryModField { + CTemporaryModField(CModule& pMod, T* pValue) { + m_pMod = &pMod; + m_pOldT = (pMod.*Getter)(); + if (pValue) { + (pMod.*Setter)(pValue); + } + } + + ~CTemporaryModField() { (m_pMod->*Setter)(m_pOldT); } + + CModule* m_pMod; + T* m_pOldT; +}; + +using CTemporaryModClient = + CTemporaryModField; +using CTemporaryModNetwork = + CTemporaryModField; +using CTemporaryModUser = + CTemporaryModField; + +// Same as above, but for plural CModules +template +struct CTemporaryModsField { + CTemporaryModsField(CModules& pMods, T* pValue) { + m_pMods = &pMods; + m_pOldT = (pMods.*Getter)(); + if (pValue) { + (pMods.*Setter)(pValue); + } + } + + ~CTemporaryModsField() { (m_pMods->*Setter)(m_pOldT); } + + CModules* m_pMods; + T* m_pOldT; +}; + +using CTemporaryModsClient = + CTemporaryModsField; +using CTemporaryModsNetwork = + CTemporaryModsField; +using CTemporaryModsUser = + CTemporaryModsField; +#endif + #endif // !ZNC_MODULES_H diff --git a/include/znc/main.h b/include/znc/main.h index 77403dfd..4c7e89ac 100644 --- a/include/znc/main.h +++ b/include/znc/main.h @@ -56,21 +56,12 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; #define _GLOBALMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ do { \ CModules& GMods = CZNC::Get().GetModules(); \ - CUser* pOldGUser = GMods.GetUser(); \ - CIRCNetwork* pOldGNetwork = GMods.GetNetwork(); \ - CClient* pOldGClient = GMods.GetClient(); \ - GMods.SetUser(macUSER); \ - GMods.SetNetwork(macNETWORK); \ - GMods.SetClient(macCLIENT); \ + CTemporaryModsUser TempUser(GMods, macUSER); \ + CTemporaryModsNetwork TempNetwork(GMods, macNETWORK); \ + CTemporaryModsClient TempClient(GMods, macCLIENT); \ if (GMods.macFUNC) { \ - GMods.SetUser(pOldGUser); \ - GMods.SetNetwork(pOldGNetwork); \ - GMods.SetClient(pOldGClient); \ *macEXITER = true; \ } \ - GMods.SetUser(pOldGUser); \ - GMods.SetNetwork(pOldGNetwork); \ - GMods.SetClient(pOldGClient); \ } while (false) #define _USERMODULECALL(macFUNC, macUSER, macNETWORK, macCLIENT, macEXITER) \ @@ -84,17 +75,11 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; } \ if (macUSER != nullptr) { \ CModules& UMods = macUSER->GetModules(); \ - CIRCNetwork* pOldUNetwork = UMods.GetNetwork(); \ - CClient* pOldUClient = UMods.GetClient(); \ - UMods.SetNetwork(macNETWORK); \ - UMods.SetClient(macCLIENT); \ + CTemporaryModsNetwork TempNetwork(UMods, macNETWORK); \ + CTemporaryModsClient TempClient(UMods, macCLIENT); \ if (UMods.macFUNC) { \ - UMods.SetNetwork(pOldUNetwork); \ - UMods.SetClient(pOldUClient); \ *macEXITER = true; \ } \ - UMods.SetNetwork(pOldUNetwork); \ - UMods.SetClient(pOldUClient); \ } \ } while (false) @@ -109,13 +94,10 @@ extern bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; } \ if (macNETWORK != nullptr) { \ CModules& NMods = macNETWORK->GetModules(); \ - CClient* pOldNClient = NMods.GetClient(); \ - NMods.SetClient(macCLIENT); \ + CTemporaryModsClient TempClient(NMods, macCLIENT); \ if (NMods.macFUNC) { \ - NMods.SetClient(pOldNClient); \ *macEXITER = true; \ } \ - NMods.SetClient(pOldNClient); \ } \ } while (false) diff --git a/modules/crypt.cpp b/modules/crypt.cpp index 2797c423..d545482e 100644 --- a/modules/crypt.cpp +++ b/modules/crypt.cpp @@ -211,13 +211,19 @@ class CCryptMod : public CModule { FilterOutgoing(Message); return CONTINUE; } + EModRet OnPrivTextMessage(CTextMessage& Message) override { + CNick& Nick = Message.GetNick(); + CString sMessage = Message.GetText(); + CString sHostmask = Nick.GetHostMask(); - EModRet OnPrivMsg(CNick& Nick, CString& sMessage) override { FilterIncoming(Nick.GetNick(), Nick, sMessage); + Message.SetText(sMessage); return CONTINUE; } - EModRet OnPrivNotice(CNick& Nick, CString& sMessage) override { + EModRet OnPrivNoticeMessage(CNoticeMessage& Message) override { + CNick& Nick = Message.GetNick(); + CString sMessage = Message.GetText(); CString sCommand = sMessage.Token(0); CString sOtherPubKey = sMessage.Token(1); @@ -268,33 +274,42 @@ class CCryptMod : public CModule { } FilterIncoming(Nick.GetNick(), Nick, sMessage); + Message.SetText(sMessage); return CONTINUE; } - EModRet OnPrivAction(CNick& Nick, CString& sMessage) override { - FilterIncoming(Nick.GetNick(), Nick, sMessage); + EModRet OnPrivActionMessage(CActionMessage& Message) override { + CString sText = Message.GetText(); + FilterIncoming(Message.GetNick().GetNick(), Message.GetNick(), sText); + Message.SetText(sText); return CONTINUE; } - EModRet OnChanMsg(CNick& Nick, CChan& Channel, CString& sMessage) override { - FilterIncoming(Channel.GetName(), Nick, sMessage); + EModRet OnChanTextMessage(CTextMessage& Message) override { + CString sText = Message.GetText(); + FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); + Message.SetText(sText); return CONTINUE; } - EModRet OnChanNotice(CNick& Nick, CChan& Channel, - CString& sMessage) override { - FilterIncoming(Channel.GetName(), Nick, sMessage); + EModRet OnChanNoticeMessage(CNoticeMessage& Message) override { + CString sText = Message.GetText(); + FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); + Message.SetText(sText); return CONTINUE; } - EModRet OnChanAction(CNick& Nick, CChan& Channel, - CString& sMessage) override { - FilterIncoming(Channel.GetName(), Nick, sMessage); + EModRet OnChanActionMessage(CActionMessage& Message) override { + CString sText = Message.GetText(); + FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); + Message.SetText(sText); return CONTINUE; } - EModRet OnTopic(CNick& Nick, CChan& Channel, CString& sMessage) override { - FilterIncoming(Channel.GetName(), Nick, sMessage); + EModRet OnTopicMessage(CTopicMessage& Message) override { + CString sText = Message.GetText(); + FilterIncoming(Message.GetTarget(), Message.GetNick(), sText); + Message.SetText(sText); return CONTINUE; } @@ -305,10 +320,16 @@ class CCryptMod : public CModule { CChan* pChan = GetNetwork()->FindChan(Message.GetParam(1)); if (pChan) { - CNick* Nick = pChan->FindNick(Message.GetParam(0)); + CNick* pNick = pChan->FindNick(Message.GetParam(0)); CString sTopic = Message.GetParam(2); - FilterIncoming(pChan->GetName(), *Nick, sTopic); + if (pNick) { + FilterIncoming(pChan->GetName(), *pNick, sTopic); + } else { + // 332 can arrive before ZNC has this nick in the nicklist. + CNick TempNick(Message.GetParam(0)); + FilterIncoming(pChan->GetName(), TempNick, sTopic); + } Message.SetParam(2, sTopic); } diff --git a/modules/modperl/functions.in b/modules/modperl/functions.in index a44c818a..bfa67119 100644 --- a/modules/modperl/functions.in +++ b/modules/modperl/functions.in @@ -66,7 +66,7 @@ void OnServerCapResult(const CString& sCap, bool bSuccess) void OnClientAttached() void OnClientDetached() EModRet OnTimerAutoJoin(CChan& Channel) -bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) +bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)=false EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) EModRet OnDeleteNetwork(CIRCNetwork& Network) EModRet OnSendToClient(CString& sLine, CClient& Client) diff --git a/modules/modpython/functions.in b/modules/modpython/functions.in index 76ed437a..d2914f9d 100644 --- a/modules/modpython/functions.in +++ b/modules/modpython/functions.in @@ -66,7 +66,7 @@ void OnServerCapResult(const CString& sCap, bool bSuccess) void OnClientAttached() void OnClientDetached() EModRet OnTimerAutoJoin(CChan& Channel) -bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) +bool OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl)=false EModRet OnAddNetwork(CIRCNetwork& Network, CString& sErrorRet) EModRet OnDeleteNetwork(CIRCNetwork& Network) EModRet OnSendToClient(CString& sLine, CClient& Client) diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index fceaae73..666db814 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -742,7 +742,7 @@ class CWebAdminMod : public CModule { CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); mod.insert(Tmpl.begin(), Tmpl.end()); mod["WebadminAction"] = "display"; - if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/channel", + if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/channel", mod)) { mod["Embed"] = WebSock.FindTmpl(*i, "WebadminChan.tmpl"); mod["ModName"] = (*i)->GetModName(); @@ -816,7 +816,7 @@ class CWebAdminMod : public CModule { TmplMod["ChanName"] = pChan->GetName(); TmplMod["WebadminAction"] = "change"; FOR_EACH_MODULE(it, pNetwork) { - (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/channel", TmplMod); + (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/channel", TmplMod); } if (!CZNC::Get().WriteConfig()) { @@ -1027,7 +1027,7 @@ class CWebAdminMod : public CModule { CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); mod.insert(Tmpl.begin(), Tmpl.end()); mod["WebadminAction"] = "display"; - if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/network", + if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/network", mod)) { mod["Embed"] = WebSock.FindTmpl(*i, "WebadminNetwork.tmpl"); mod["ModName"] = (*i)->GetModName(); @@ -1246,7 +1246,7 @@ class CWebAdminMod : public CModule { TmplMod["Name"] = pNetwork->GetName(); TmplMod["WebadminAction"] = "change"; FOR_EACH_MODULE(it, make_pair(pUser, pNetwork)) { - (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod); + (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod); } if (!CZNC::Get().WriteConfig()) { @@ -1670,7 +1670,7 @@ class CWebAdminMod : public CModule { CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop"); mod.insert(Tmpl.begin(), Tmpl.end()); mod["WebadminAction"] = "display"; - if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/user", mod)) { + if ((*i)->DoEmbeddedWebRequest(WebSock, "webadmin/user", mod)) { mod["Embed"] = WebSock.FindTmpl(*i, "WebadminUser.tmpl"); mod["ModName"] = (*i)->GetModName(); } @@ -1731,7 +1731,7 @@ class CWebAdminMod : public CModule { TmplMod["Username"] = sUsername; TmplMod["WebadminAction"] = "change"; FOR_EACH_MODULE(it, pUser) { - (*it)->OnEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod); + (*it)->DoEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod); } if (!CZNC::Get().WriteConfig()) { diff --git a/src/Client.cpp b/src/Client.cpp index a2f65fa0..c5d7c5a7 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -21,8 +21,8 @@ #include #include -using std::set; using std::map; +using std::set; using std::vector; #define CALLMOD(MOD, CLIENT, USER, NETWORK, FUNC) \ @@ -30,10 +30,9 @@ using std::vector; CModule* pModule = nullptr; \ if (NETWORK && (pModule = (NETWORK)->GetModules().FindModule(MOD))) { \ try { \ - CClient* pOldClient = pModule->GetClient(); \ - pModule->SetClient(CLIENT); \ + CModCallProtector Inside(*pModule); \ + CTemporaryModClient TempClient(*pModule, CLIENT); \ pModule->FUNC; \ - pModule->SetClient(pOldClient); \ } catch (const CModule::EModException& e) { \ if (e == CModule::UNLOAD) { \ (NETWORK)->GetModules().UnloadModule(MOD); \ @@ -41,13 +40,10 @@ using std::vector; } \ } else if ((pModule = (USER)->GetModules().FindModule(MOD))) { \ try { \ - CClient* pOldClient = pModule->GetClient(); \ - CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ - pModule->SetClient(CLIENT); \ - pModule->SetNetwork(NETWORK); \ + CModCallProtector Inside(*pModule); \ + CTemporaryModClient TempClient(*pModule, CLIENT); \ + CTemporaryModNetwork TempNetwork(*pModule, NETWORK); \ pModule->FUNC; \ - pModule->SetClient(pOldClient); \ - pModule->SetNetwork(pOldNetwork); \ } catch (const CModule::EModException& e) { \ if (e == CModule::UNLOAD) { \ (USER)->GetModules().UnloadModule(MOD); \ @@ -55,16 +51,11 @@ using std::vector; } \ } else if ((pModule = CZNC::Get().GetModules().FindModule(MOD))) { \ try { \ - CClient* pOldClient = pModule->GetClient(); \ - CIRCNetwork* pOldNetwork = pModule->GetNetwork(); \ - CUser* pOldUser = pModule->GetUser(); \ - pModule->SetClient(CLIENT); \ - pModule->SetNetwork(NETWORK); \ - pModule->SetUser(USER); \ + CModCallProtector Inside(*pModule); \ + CTemporaryModClient TempClient(*pModule, CLIENT); \ + CTemporaryModNetwork TempNetwork(*pModule, NETWORK); \ + CTemporaryModUser TempUser(*pModule, USER); \ pModule->FUNC; \ - pModule->SetClient(pOldClient); \ - pModule->SetNetwork(pOldNetwork); \ - pModule->SetUser(pOldUser); \ } catch (const CModule::EModException& e) { \ if (e == CModule::UNLOAD) { \ CZNC::Get().GetModules().UnloadModule(MOD); \ diff --git a/src/Modules.cpp b/src/Modules.cpp index 13580775..98f4eae2 100644 --- a/src/Modules.cpp +++ b/src/Modules.cpp @@ -36,67 +36,45 @@ bool ZNC_NO_NEED_TO_DO_ANYTHING_ON_MODULE_CALL_EXITER; #warning "your crap box doesn't define RTLD_LOCAL !?" #endif -#define MODUNLOADCHK(func) \ - for (CModule * pMod : *this) { \ - try { \ - CClient* pOldClient = pMod->GetClient(); \ - pMod->SetClient(m_pClient); \ - CUser* pOldUser = nullptr; \ - if (m_pUser) { \ - pOldUser = pMod->GetUser(); \ - pMod->SetUser(m_pUser); \ - } \ - CIRCNetwork* pNetwork = nullptr; \ - if (m_pNetwork) { \ - pNetwork = pMod->GetNetwork(); \ - pMod->SetNetwork(m_pNetwork); \ - } \ - pMod->func; \ - if (m_pUser) pMod->SetUser(pOldUser); \ - if (m_pNetwork) pMod->SetNetwork(pNetwork); \ - pMod->SetClient(pOldClient); \ - } catch (const CModule::EModException& e) { \ - if (e == CModule::UNLOAD) { \ - UnloadModule(pMod->GetModName()); \ - } \ - } \ +#define MODUNLOADCHK(func) \ + for (CModule* pMod : *this) { \ + try { \ + CModCallProtector inside(*pMod); \ + CTemporaryModClient TempClient(*pMod, m_pClient); \ + CTemporaryModUser TempUser(*pMod, m_pUser); \ + CTemporaryModNetwork TempNetwork(*pMod, m_pNetwork); \ + pMod->func; \ + } catch (const CModule::EModException& e) { \ + if (e == CModule::UNLOAD) { \ + UnloadModule(pMod->GetModName()); \ + } \ + } \ } -#define MODHALTCHK(func) \ - bool bHaltCore = false; \ - for (CModule * pMod : *this) { \ - try { \ - CModule::EModRet e = CModule::CONTINUE; \ - CClient* pOldClient = pMod->GetClient(); \ - pMod->SetClient(m_pClient); \ - CUser* pOldUser = nullptr; \ - if (m_pUser) { \ - pOldUser = pMod->GetUser(); \ - pMod->SetUser(m_pUser); \ - } \ - CIRCNetwork* pNetwork = nullptr; \ - if (m_pNetwork) { \ - pNetwork = pMod->GetNetwork(); \ - pMod->SetNetwork(m_pNetwork); \ - } \ - e = pMod->func; \ - if (m_pUser) pMod->SetUser(pOldUser); \ - if (m_pNetwork) pMod->SetNetwork(pNetwork); \ - pMod->SetClient(pOldClient); \ - if (e == CModule::HALTMODS) { \ - break; \ - } else if (e == CModule::HALTCORE) { \ - bHaltCore = true; \ - } else if (e == CModule::HALT) { \ - bHaltCore = true; \ - break; \ - } \ - } catch (const CModule::EModException& e) { \ - if (e == CModule::UNLOAD) { \ - UnloadModule(pMod->GetModName()); \ - } \ - } \ - } \ +#define MODHALTCHK(func) \ + bool bHaltCore = false; \ + for (CModule* pMod : *this) { \ + try { \ + CModCallProtector inside(*pMod); \ + CModule::EModRet e = CModule::CONTINUE; \ + CTemporaryModClient TempClient(*pMod, m_pClient); \ + CTemporaryModUser TempUser(*pMod, m_pUser); \ + CTemporaryModNetwork TempNetwork(*pMod, m_pNetwork); \ + e = pMod->func; \ + if (e == CModule::HALTMODS) { \ + break; \ + } else if (e == CModule::HALTCORE) { \ + bHaltCore = true; \ + } else if (e == CModule::HALT) { \ + bHaltCore = true; \ + break; \ + } \ + } catch (const CModule::EModException& e) { \ + if (e == CModule::UNLOAD) { \ + UnloadModule(pMod->GetModName()); \ + } \ + } \ + } \ return bHaltCore; /////////////////// Timer /////////////////// @@ -205,6 +183,8 @@ void CModule::SetUser(CUser* pUser) { m_pUser = pUser; } void CModule::SetNetwork(CIRCNetwork* pNetwork) { m_pNetwork = pNetwork; } void CModule::SetClient(CClient* pClient) { m_pClient = pClient; } +bool CModule::IsCallStackEmpty() const { return m_iCallStackDepth == 0; } + CString CModule::ExpandString(const CString& sStr) const { CString sRet; return ExpandString(sStr, sRet); @@ -622,6 +602,11 @@ bool CModule::OnEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) { return false; } +bool CModule::DoEmbeddedWebRequest(CWebSock& WebSock, const CString& sPageName, + CTemplate& Tmpl) { + CModCallProtector inside(*this); + return OnEmbeddedWebRequest(WebSock, sPageName, Tmpl); +} // !Webmods bool CModule::OnLoad(const CString& sArgs, CString& sMessage) { @@ -1953,6 +1938,11 @@ bool CModules::UnloadModule(const CString& sModule, CString& sRetMsg) { return false; } + if (!pModule->IsCallStackEmpty()) { + sRetMsg = t_f("Module [{1}] is being called, cannot unload.")(sMod); + return false; + } + bool bSuccess; bool bHandled = false; _GLOBALMODULECALL(OnModuleUnloading(pModule, bSuccess, sRetMsg), diff --git a/test/integration/tests/modules.cpp b/test/integration/tests/modules.cpp index 628e8f5c..8da63d6e 100644 --- a/test/integration/tests/modules.cpp +++ b/test/integration/tests/modules.cpp @@ -138,7 +138,7 @@ TEST_F(ZNCTest, WatchModule) { ":*spaces!watch@znc.in PRIVMSG nick : SOMETHING word1 word2 SOMETHING"); } -TEST_F(ZNCTest, ModuleCrypt) { +TEST_F(ZNCTest, CryptModule) { #ifndef HAVE_LIBSSL GTEST_SKIP() << "SSL is disabled"; #endif @@ -188,6 +188,8 @@ TEST_F(ZNCTest, ModuleCrypt) { QByteArray key2(""); client2.ReadUntilAndGet("\002user\017: ", key2); ASSERT_EQ(key1.mid(9), key2.mid(8)); + + // OnPrivTextMessage client1.Write("CAP REQ :echo-message"); client1.Write("PRIVMSG .nick2 :Hello"); QByteArray secretmsg; @@ -195,6 +197,72 @@ TEST_F(ZNCTest, ModuleCrypt) { ircd2.Write(":user!user@user/test " + secretmsg); client2.ReadUntil("Hello"); client1.ReadUntil(secretmsg); // by echo-message + + client1.Write("PRIVMSG *crypt :SetNickPrefix ."); + client1.ReadUntil("Setting Nick Prefix to ."); + client2.Write("PRIVMSG *crypt :SetNickPrefix ."); + client2.ReadUntil("Setting Nick Prefix to ."); + + // OnPrivNoticeMessage + client1.Write("NOTICE .nick2 :secret notice"); + QByteArray noticeMsg; + ircd1.ReadUntilAndGet("NOTICE nick2 :+OK ", noticeMsg); + ircd2.Write(":user!user@user/test " + noticeMsg); + QByteArray noticeLine; + client2.ReadUntilAndGet("NOTICE nick2 :", noticeLine); + QByteArray noticemessage = noticeLine.mid(noticeLine.lastIndexOf(':') + 1); + EXPECT_EQ(noticemessage, "secret notice"); + + // OnUserActionMessage + client1.Write("PRIVMSG .nick2 :\001ACTION waves\001"); + QByteArray actionMsg; + ircd1.ReadUntilAndGet("PRIVMSG nick2 :\001ACTION +OK ", actionMsg); + ircd2.Write(":user!user@user/test " + actionMsg); + QByteArray actionLine; + client2.ReadUntilAndGet("PRIVMSG nick2 :", actionLine); + QByteArray actionMessage = actionLine.mid(actionLine.lastIndexOf(':') + 1); + EXPECT_EQ(actionMessage, "\001ACTION waves\001"); + + client1.Write("JOIN #test"); + client2.Write("JOIN #test"); + + QByteArray chanKey = "channelKey123"; + client1.Write(QByteArray("PRIVMSG *crypt :SetKey #test ") + chanKey); + client1.ReadUntil(QByteArray("Set encryption key for [#test] to [") + + chanKey + "]"); + client2.Write(QByteArray("PRIVMSG *crypt :SetKey #test ") + chanKey); + client2.ReadUntil(QByteArray("Set encryption key for [#test] to [") + + chanKey + "]"); + + // OnChanTextMessage + client1.Write("PRIVMSG #test :channel secret"); + QByteArray chanMsg; + ircd1.ReadUntilAndGet("PRIVMSG #test :+OK ", chanMsg); + ircd2.Write(":user!user@user/test " + chanMsg); + client2.ReadUntil("channel secret"); + + // OnChanNoticeMessage + client1.Write("NOTICE #test :chan notice"); + QByteArray chanNoticeMsg; + ircd1.ReadUntilAndGet("NOTICE #test :+OK ", chanNoticeMsg); + ircd2.Write(":user!user@user/test " + chanNoticeMsg); + client2.ReadUntil("chan notice"); + + // OnChanActionMessage + client1.Write("PRIVMSG #test :\001ACTION dances\001"); + QByteArray chanActionMsg; + ircd1.ReadUntilAndGet("PRIVMSG #test :\001ACTION +OK ", chanActionMsg); + ircd2.Write(":user!user@user/test " + chanActionMsg); + client2.ReadUntil("dances"); + + // OnTopicMessage / OnNumericMessage + client1.Write("TOPIC #test :new chan topic"); + QByteArray chanTopicEncrypted; + ircd1.ReadUntilAndGet("TOPIC #test :+OK ", chanTopicEncrypted); + chanTopicEncrypted = "+OK " + chanTopicEncrypted.mid( + chanTopicEncrypted.lastIndexOf(' ') + 1); + ircd2.Write(":ircd2 332 nick2 #test :" + chanTopicEncrypted); + client2.ReadUntil("new chan topic"); } TEST_F(ZNCTest, AutoAttachModule) {