Add a way to disable certain capabilities

This is a way for admins to mitigate some issues caused by caps if such issues ever arise.

E.g. add this to global level in znc.conf:

DisableClientCap = sasl
DisableServerCap = chghost
DisableServerCap = message-tags

Then these caps will be NAKed to client / not requested from server.

Note that this mechanism doesn't fully prevent a cap from being activated, e.g. one could use *send_raw module to request it from server even when disabled.
This commit is contained in:
Alexey Sokolov
2025-05-08 21:55:40 +01:00
parent 1063b7f5d6
commit 1c197a5508
5 changed files with 58 additions and 4 deletions
+4
View File
@@ -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;
+12 -3
View File
@@ -815,7 +815,8 @@ void CClient::RespondCap(const CString& sResponse) {
PutClient(":irc.znc.in CAP " + GetNick() + " " + sResponse);
}
static VCString MultiLine(const SCString& ssCaps) {
template <typename ManyStrings>
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;
+3
View File
@@ -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);
+15 -1
View File
@@ -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;
+24
View File
@@ -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