diff --git a/include/znc/znc.h b/include/znc/znc.h index e6da2a2e..1365d518 100644 --- a/include/znc/znc.h +++ b/include/znc/znc.h @@ -83,6 +83,8 @@ class CZNC : private CCoreTranslationMixin { void ClearTrustedProxies(); bool AddTrustedProxy(const CString& sHost); bool RemTrustedProxy(const CString& sHost); + const SCString& GetClientCapBlacklist() const { return m_ssClientCapBlacklist; } + const SCString& GetServerCapBlacklist() const { return m_ssServerCapBlacklist; } void Broadcast(const CString& sMessage, bool bAdminOnly = false, CUser* pSkipUser = nullptr, CClient* pSkipClient = nullptr); void AddBytesRead(unsigned long long u) { m_uBytesRead += u; } @@ -307,6 +309,8 @@ class CZNC : private CCoreTranslationMixin { VCString m_vsBindHosts; // TODO: remove (deprecated in 1.7.0) VCString m_vsTrustedProxies; VCString m_vsMotd; + SCString m_ssClientCapBlacklist; + SCString m_ssServerCapBlacklist; CFile* m_pLockFile; unsigned int m_uiConnectDelay; unsigned int m_uiAnonIPLimit; diff --git a/src/Client.cpp b/src/Client.cpp index 31cb7ac5..762f2bb3 100644 --- a/src/Client.cpp +++ b/src/Client.cpp @@ -815,7 +815,8 @@ void CClient::RespondCap(const CString& sResponse) { PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse); } -static VCString MultiLine(const SCString& ssCaps) { +template +static VCString MultiLine(const ManyStrings& ssCaps) { VCString vsRes = {""}; for (const CString& sCap : ssCaps) { if (vsRes.back().length() + sCap.length() > 400) { @@ -917,7 +918,15 @@ void CClient::HandleCap(const CMessage& Message) { } } NETWORKMODULECALL(OnClientCapLs(this, ssOfferCaps), GetUser(), GetNetwork(), this, NOTHING); - VCString vsCaps = MultiLine(ssOfferCaps); + VCString vsFilteredCaps; + vsFilteredCaps.reserve(ssOfferCaps.size()); + for (CString sCap : std::move(ssOfferCaps)) { + if (!CZNC::Get().GetClientCapBlacklist().count( + sCap.Token(0, false, "="))) { + vsFilteredCaps.push_back(std::move(sCap)); + } + } + VCString vsCaps = MultiLine(vsFilteredCaps); m_bInCap = true; if (HasCap302()) { m_bCapNotify = true; @@ -961,7 +970,7 @@ void CClient::HandleCap(const CMessage& Message) { NETWORKMODULECALL(IsClientCapSupported(this, sCap, bVal), GetUser(), GetNetwork(), this, &bAccepted); - if (!bAccepted) { + if (!bAccepted || CZNC::Get().GetClientCapBlacklist().count(sCap)) { // Some unsupported capability is requested RespondCap("NAK :" + Message.GetParam(1)); return; diff --git a/src/IRCSock.cpp b/src/IRCSock.cpp index eec2faf2..d0f738e4 100644 --- a/src/IRCSock.cpp +++ b/src/IRCSock.cpp @@ -416,6 +416,9 @@ bool CIRCSock::OnCapabilityMessage(CMessage& Message) { sCap = sToken.substr(0, eq); sValue = sToken.substr(eq + 1); } + if (CZNC::Get().GetServerCapBlacklist().count(sCap)) { + continue; + } m_msCapLsValues[sCap] = sValue; if (OnServerCapAvailable(sCap, sValue) || mSupportedCaps.count(sCap)) { m_ssPendingCaps.insert(sCap); diff --git a/src/znc.cpp b/src/znc.cpp index 9dc9daea..0aff8e83 100644 --- a/src/znc.cpp +++ b/src/znc.cpp @@ -55,11 +55,14 @@ CZNC::CZNC() m_vsBindHosts(), m_vsTrustedProxies(), m_vsMotd(), + m_ssClientCapBlacklist(), + m_ssServerCapBlacklist(), m_pLockFile(nullptr), m_uiConnectDelay(5), m_uiAnonIPLimit(10), m_uiMaxBufferSize(500), - m_uDisabledSSLProtocols(Csock::EDP_SSL | Csock::EDP_TLSv1 | Csock::EDP_TLSv1_1), + m_uDisabledSSLProtocols(Csock::EDP_SSL | Csock::EDP_TLSv1 | + Csock::EDP_TLSv1_1), m_pModules(new CModules), m_uBytesRead(0), m_uBytesWritten(0), @@ -1185,6 +1188,17 @@ bool CZNC::LoadGlobal(CConfig& config, CString& sError) { AddTrustedProxy(sProxy); } + m_ssClientCapBlacklist.clear(); + config.FindStringVector("disableclientcap", vsList); + for (const CString& sCap : vsList) { + m_ssClientCapBlacklist.insert(sCap); + } + m_ssServerCapBlacklist.clear(); + config.FindStringVector("disableservercap", vsList); + for (const CString& sCap : vsList) { + m_ssServerCapBlacklist.insert(sCap); + } + CString sVal; if (config.FindStringEntry("pidfile", sVal)) m_sPidFile = sVal; if (config.FindStringEntry("statusprefix", sVal)) m_sStatusPrefix = sVal; diff --git a/test/integration/tests/core.cpp b/test/integration/tests/core.cpp index 4d57a1f5..67e21eb7 100644 --- a/test/integration/tests/core.cpp +++ b/test/integration/tests/core.cpp @@ -1104,5 +1104,29 @@ TEST_F(ZNCTest, InviteNotify) { ASSERT_THAT(ircd.ReadRemainder().toStdString(), Not(HasSubstr("__INV__"))); } +TEST_F(ZNCTest, DisableCap) { + { + QFile conf(m_dir.path() + "/configs/znc.conf"); + ASSERT_TRUE(conf.open(QIODevice::Append | QIODevice::Text)); + QTextStream out(&conf); + out << R"( + DisableClientCap = sasl + DisableServerCap = chghost + )"; + } + auto znc = Run(); + auto ircd = ConnectIRCd(); + + ircd.Write("CAP user LS :chghost"); + ASSERT_THAT(ircd.ReadRemainder().toStdString(), Not(HasSubstr("chghost"))); + + auto client = ConnectClient(); + client.Write("NICK foo"); + client.Write("CAP LS 302"); + ASSERT_THAT(client.ReadRemainder().toStdString(), Not(HasSubstr("sasl"))); + client.Write("CAP REQ sasl"); + client.ReadUntil("CAP foo NAK :sasl"); +} + } // namespace } // namespace znc_inttest