diff --git a/include/znc/IRCSock.h b/include/znc/IRCSock.h index 73a1b550..4852a2e8 100644 --- a/include/znc/IRCSock.h +++ b/include/znc/IRCSock.h @@ -171,6 +171,8 @@ class CIRCSock : public CIRCSocket { // TODO move this function to CIRCNetwork and make it non-static? static bool IsFloodProtected(double fRate); + bool IsNickVisibleInAttachedChannels(const CString& sNick) const; + private: // Message Handlers bool OnAccountMessage(CMessage& Message); diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index b9ce227c..a349ca56 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -322,8 +322,25 @@ static void FixupChanNick(CNick& Nick, CChan* pChan) { } } +// #1826: CAP away-notify clients shouldn't receive notifications if all shared +// channels are detached +// This applies to account, away-notify, and chghost. +bool CIRCSock::IsNickVisibleInAttachedChannels(const CString& sNick) const { + const vector& vChans = m_pNetwork->GetChans(); + for (const CChan* pChan : vChans) { + if (!pChan->IsDetached() && pChan->FindNick(sNick)) { + return true; + } + } + return false; +} + bool CIRCSock::OnAccountMessage(CMessage& Message) { // TODO: IRCSOCKMODULECALL(OnAccountMessage(Message)) ? + // Do not send ACCOUNT if all shared channels are detached. + if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { + return true; + } return false; } @@ -377,6 +394,10 @@ bool CIRCSock::OnActionMessage(CActionMessage& Message) { bool CIRCSock::OnAwayMessage(CMessage& Message) { // TODO: IRCSOCKMODULECALL(OnAwayMessage(Message)) ? + // Do not send away-notify if all shared channels are detached. + if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { + return true; + } return false; } @@ -570,6 +591,10 @@ bool CIRCSock::OnCTCPMessage(CCTCPMessage& Message) { } bool CIRCSock::OnChgHostMessage(CChgHostMessage& Message) { + // Do not send chghost if all shared channels are detached. + if (!IsNickVisibleInAttachedChannels(Message.GetNick().GetNick())) { + return true; + } // The emulation of QUIT+JOIN would be cleaner inside CClient::PutClient() // but computation of new modes is difficult enough so that I don't want to // repeat it for every client diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index 0a56ba72..197ea6d3 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -1221,5 +1221,58 @@ TEST_F(ZNCTest, DisconnectedTagmsgCrash) { client.ReadUntil("AddServer"); } +// https://github.com/znc/znc/issues/1826 +TEST_F(ZNCTest, CAPDetached) { + auto znc = Run(); + auto ircd = ConnectIRCd(); + + ircd.Write("CAP user LS :away-notify"); + ircd.ReadUntil("CAP REQ :away-notify"); + ircd.Write("CAP user ACK :away-notify"); + + ircd.Write("CAP user LS :chghost"); + ircd.ReadUntil("CAP REQ :chghost"); + ircd.Write("CAP user ACK :chghost"); + + ircd.Write("CAP user LS :account-notify"); + ircd.ReadUntil("CAP REQ :account-notify"); + ircd.Write("CAP user ACK :account-notify"); + + auto client = LoginClient(); + + client.Write("CAP LS"); + + client.Write("CAP REQ :cap-notify"); + client.ReadUntil("ACK :cap-notify"); + client.Write("CAP REQ :away-notify"); + client.ReadUntil("ACK :away-notify"); + client.Write("CAP REQ :chghost"); + client.ReadUntil("ACK :chghost"); + client.Write("CAP REQ :account-notify"); + client.ReadUntil("ACK :account-notify"); + client.Write("CAP END"); + + client.Write(":nick!ident@host JOIN #test"); + ircd.Write(":test!test@test JOIN #test"); + client.ReadUntil(":test!test@test JOIN #test"); + ircd.Write("353 nick = #test :nick!ident@host test!test@test"); + ircd.Write("366 nick #test :End of /NAMES list."); + client.ReadUntil("#test :End of /NAMES"); + client.Write("znc detach #test"); + client.ReadUntil(":*status!status@znc.in PRIVMSG nick :Detached 1 channel"); + + ircd.Write(":test!test@test ACCOUNT test"); + EXPECT_THAT(client.ReadRemainder().toStdString(), Not(HasSubstr(":test!test@test ACCOUNT test"))) + << "Client saw account-notify even though all channels are detached"; + + ircd.Write(":test!test@test AWAY :going away"); + EXPECT_THAT(client.ReadRemainder().toStdString(), Not(HasSubstr(":test!test@test AWAY :going away"))) + << "Client saw away-notify even though all channels are detached"; + + ircd.Write(":test!test@test CHGHOST test detached.test"); + EXPECT_THAT(client.ReadRemainder().toStdString(), Not(HasSubstr(":test!test@test CHGHOST test detached.test"))) + << "Client saw chghost even though all channels are detached"; +} + } // namespace } // namespace znc_inttest