aboutsummaryrefslogtreecommitdiffstats
path: root/modules/spanningtree/capab.cpp
diff options
context:
space:
mode:
authorGravatar Sadie Powell2026-06-24 04:55:50 +0100
committerGravatar Sadie Powell2026-06-24 21:22:25 +0100
commit6f2646b2fed7b0dff3e4ac22baea6322dd54aabd (patch)
treed48b75f928e3789ab6478f8ba99917c6c3fe800d /modules/spanningtree/capab.cpp
parentSync the name of a config var and its internal name. (diff)
Rework capabilities to check all tokens not just CASEMAPPING.
Diffstat (limited to 'modules/spanningtree/capab.cpp')
-rw-r--r--modules/spanningtree/capab.cpp193
1 files changed, 114 insertions, 79 deletions
diff --git a/modules/spanningtree/capab.cpp b/modules/spanningtree/capab.cpp
index fd7f05c55..6f72631d8 100644
--- a/modules/spanningtree/capab.cpp
+++ b/modules/spanningtree/capab.cpp
@@ -39,6 +39,53 @@ namespace
// A map which holds the difference between local and remote tokens.
using TokenDiff = insp::casemapped_map<std::pair<std::optional<std::string>, std::optional<std::string>>>;
+ // Builds a list of local capabilities.
+ CapabData::CapabilityMap BuildCapabilityList(TreeSocket* ts)
+ {
+ CapabData::CapabilityMap capabilities = {
+ { "CASEMAPPING", ServerInstance->Config->CaseMapping },
+ { "MAXAWAY", ConvToStr(ServerInstance->Config->Limits.MaxAway) },
+ { "MAXCHANNEL", ConvToStr(ServerInstance->Config->Limits.MaxChannel) },
+ { "MAXHOST", ConvToStr(ServerInstance->Config->Limits.MaxHost) },
+ { "MAXKEY", ConvToStr(ServerInstance->Config->Limits.MaxKey) },
+ { "MAXKICK", ConvToStr(ServerInstance->Config->Limits.MaxKick) },
+ { "MAXLINE", ConvToStr(ServerInstance->Config->Limits.MaxLine) },
+ { "MAXMODES", ConvToStr(ServerInstance->Config->Limits.MaxModes) },
+ { "MAXNICK", ConvToStr(ServerInstance->Config->Limits.MaxNick) },
+ { "MAXQUIT", ConvToStr(ServerInstance->Config->Limits.MaxQuit) },
+ { "MAXREAL", ConvToStr(ServerInstance->Config->Limits.MaxReal) },
+ { "MAXTOPIC", ConvToStr(ServerInstance->Config->Limits.MaxTopic) },
+ { "MAXUSER", ConvToStr(ServerInstance->Config->Limits.MaxUser) },
+ };
+
+ // If SHA256 hashing support is available then send a challenge token.
+ if (ts->proto_version < PROTO_INSPIRCD_5 && ServerInstance->Modules.FindService("Hash::Provider", "sha256"))
+ {
+ if (ts->GetOurChallenge().empty())
+ ts->SetOurChallenge(ServerInstance->GenRandomStr(20));
+ capabilities["CHALLENGE"] = ts->GetOurChallenge();
+ }
+
+ ExtBan::ManagerRef extbanmgr(Utils->CreatorPtr);
+ if (extbanmgr)
+ {
+ std::string& xbformat = capabilities["EXTBANFORMAT"];
+ switch (extbanmgr->GetFormat())
+ {
+ case ExtBan::Format::ANY:
+ xbformat = "any";
+ break;
+ case ExtBan::Format::NAME:
+ xbformat = "name";
+ break;
+ case ExtBan::Format::LETTER:
+ xbformat = "letter";
+ break;
+ }
+ }
+ return capabilities;
+ }
+
// Builds a list of the local modules with the specified property.
CapabData::ModuleMap BuildModuleList(ModuleFlags property, uint16_t protocol)
{
@@ -81,6 +128,25 @@ namespace
return modules;
}
+ bool CompareCapabilities(const CapabData::CapabilityMap& remote, TreeSocket* ts, std::ostringstream& out)
+ {
+ // For capabilities we only compare the common keys so we can add new
+ // tokens later without breaking compatibility.
+ auto okay = true;
+ for (const auto& [tname, tvalue] : BuildCapabilityList(ts))
+ {
+ auto it = remote.find(tname);
+ if (it != remote.end() && it->second != tvalue)
+ {
+ if (!okay)
+ out << ", ";
+ okay = false;
+ out << tname << " is set to " << tvalue << " here and " << it->second << " there";
+ }
+ }
+ return okay;
+ }
+
// Compares the module data sent by a remote server to that of the local server.
bool CompareModuleData(const ModulePtr& mod, const Module::LinkData& otherdata, std::ostringstream& diffconfig)
{
@@ -180,50 +246,9 @@ namespace
// Generates a capability list in the format "FOO=BAR BAZ=BAX".
std::string FormatCapabilities(TreeSocket* ts)
{
- std::unordered_map<std::string, std::string> capabilities = {
- { "CASEMAPPING", ServerInstance->Config->CaseMapping },
- { "MAXAWAY", ConvToStr(ServerInstance->Config->Limits.MaxAway) },
- { "MAXCHANNEL", ConvToStr(ServerInstance->Config->Limits.MaxChannel) },
- { "MAXHOST", ConvToStr(ServerInstance->Config->Limits.MaxHost) },
- { "MAXKEY", ConvToStr(ServerInstance->Config->Limits.MaxKey) },
- { "MAXKICK", ConvToStr(ServerInstance->Config->Limits.MaxKick) },
- { "MAXLINE", ConvToStr(ServerInstance->Config->Limits.MaxLine) },
- { "MAXMODES", ConvToStr(ServerInstance->Config->Limits.MaxModes) },
- { "MAXNICK", ConvToStr(ServerInstance->Config->Limits.MaxNick) },
- { "MAXQUIT", ConvToStr(ServerInstance->Config->Limits.MaxQuit) },
- { "MAXREAL", ConvToStr(ServerInstance->Config->Limits.MaxReal) },
- { "MAXTOPIC", ConvToStr(ServerInstance->Config->Limits.MaxTopic) },
- { "MAXUSER", ConvToStr(ServerInstance->Config->Limits.MaxUser) },
- };
-
- // If SHA256 hashing support is available then send a challenge token.
- if (ts->proto_version < PROTO_INSPIRCD_5 && ServerInstance->Modules.FindService("Hash::Provider", "sha256"))
- {
- ts->SetOurChallenge(ServerInstance->GenRandomStr(20));
- capabilities["CHALLENGE"] = ts->GetOurChallenge();
- }
-
- ExtBan::ManagerRef extbanmgr(Utils->CreatorPtr);
- if (extbanmgr)
- {
- std::string& xbformat = capabilities["EXTBANFORMAT"];
- switch (extbanmgr->GetFormat())
- {
- case ExtBan::Format::ANY:
- xbformat = "any";
- break;
- case ExtBan::Format::NAME:
- xbformat = "name";
- break;
- case ExtBan::Format::LETTER:
- xbformat = "letter";
- break;
- }
- }
-
auto first = true;
std::stringstream capabilitystr;
- for (const auto& [capkey, capvalue] : capabilities)
+ for (const auto& [capkey, capvalue] : BuildCapabilityList(ts))
{
if (!first)
capabilitystr << ' ';
@@ -248,6 +273,33 @@ namespace
return modules.str();
}
+ // Parses a capability list in the format "FOO BAR=baz".
+ void ParseCapabilities(const std::string& caplist, CapabData::CapabilityMap& map, TreeSocket* ts)
+ {
+ StringSplitter capstream(caplist);
+ for (std::string cap; capstream.GetToken(cap); )
+ {
+ std::string capval;
+ const auto split = cap.find('=');
+ if (split != std::string::npos)
+ {
+ capval.assign(cap, split + 1);
+ cap.erase(split);
+ }
+
+ // BEGIN COMPATIBILITY CODE
+ if (ts->proto_version < PROTO_INSPIRCD_5 && insp::casemapped_equals(cap, "CHALLENGE"))
+ {
+ ts->SetTheirChallenge(capval);
+ continue;
+ }
+ // END COMPATIBILITY CODE
+
+ ServerInstance->Logs.Debug(MODNAME, "Parsed capability: {} {}", cap, capval);
+ map.emplace(cap, capval);
+ }
+ }
+
// Parses a challenge in the format "<algo> [<algo>]+ :<challenge>".
void ParseChallenge(const CommandBase::Params& params, std::string& out)
{
@@ -434,9 +486,9 @@ bool TreeSocket::Capab(const CommandBase::Params& params)
}
if (insp::casemapped_equals(params[0], "START"))
{
+ capab->capabilities.clear();
capab->requiredmodules.reset();
capab->optionalmodules.reset();
- capab->CapKeys.clear();
if (params.size() > 1)
proto_version = ConvToNum<uint16_t>(params[1]);
@@ -475,6 +527,20 @@ bool TreeSocket::Capab(const CommandBase::Params& params)
return false;
}
}
+ else if (!CompareCapabilities(this->capab->capabilities, this, errormsg))
+ {
+ if (Utils->AllowMismatch)
+ {
+ ServerInstance->SNO.WriteToSnoMask('l', "Capabilities do not match. Some functionality may behave inconsistently. {}.",
+ errormsg.str());
+ }
+ else
+ {
+ SendError(FMT::format("CAPAB negotiation failed. Capabilities do not match and <spanningtree:allowmismatch> is not enabled. {}.",
+ errormsg.str()));
+ return false;
+ }
+ }
if (!capab->ChanModes.empty())
{
@@ -545,19 +611,6 @@ bool TreeSocket::Capab(const CommandBase::Params& params)
}
}
- if (this->capab->CapKeys.find("CASEMAPPING") != this->capab->CapKeys.end())
- {
- const std::string casemapping = this->capab->CapKeys.find("CASEMAPPING")->second;
- if (casemapping != ServerInstance->Config->CaseMapping)
- {
- std::string reason = "The casemapping of the remote server differs to that of the local server."
- " Local casemapping: " + ServerInstance->Config->CaseMapping +
- " Remote casemapping: " + casemapping;
- this->SendError("CAPAB negotiation failed: " + reason);
- return false;
- }
- }
-
if (this->LinkState == CONNECTING)
{
this->SendCapabilities(2);
@@ -584,6 +637,11 @@ bool TreeSocket::Capab(const CommandBase::Params& params)
if (params.size() >= 3)
ParseChallenge(params, capab->theirchallenge);
}
+ else if (insp::casemapped_equals(params[0], "CAPABILITIES"))
+ {
+ if (params.size() >= 2)
+ ParseCapabilities(params[1], capab->capabilities, this);
+ }
else if (insp::casemapped_equals(params[0], "CHANMODES") && (params.size() == 2))
{
capab->ChanModes = params[1];
@@ -596,28 +654,5 @@ bool TreeSocket::Capab(const CommandBase::Params& params)
{
capab->ExtBans = params[1];
}
- else if (insp::casemapped_equals(params[0], "CAPABILITIES") && (params.size() == 2))
- {
- StringSplitter capabs(params[1]);
- std::string item;
- while (capabs.GetToken(item))
- {
- /* Process each key/value pair */
- std::string::size_type equals = item.find('=');
- if (equals != std::string::npos)
- {
- std::string var(item, 0, equals);
- std::string value(item, equals+1);
-
- if (proto_version < PROTO_INSPIRCD_5 && insp::casemapped_equals(var, "CHALLENGE"))
- {
- this->SetTheirChallenge(value);
- continue;
- }
-
- capab->CapKeys[var] = value;
- }
- }
- }
return true;
}