diff options
| author | 2024-07-17 00:06:50 +0100 | |
|---|---|---|
| committer | 2024-07-17 00:06:50 +0100 | |
| commit | 889d521e05f2e922683d48c74eb00290e7a2f548 (patch) | |
| tree | b4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /modules/spanningtree/treesocket2.cpp | |
| parent | Use std::endian from C++20 in the sha1 module. (diff) | |
Shuffle the modules about a bit.
Diffstat (limited to 'modules/spanningtree/treesocket2.cpp')
| -rw-r--r-- | modules/spanningtree/treesocket2.cpp | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/modules/spanningtree/treesocket2.cpp b/modules/spanningtree/treesocket2.cpp new file mode 100644 index 000000000..eae8cbe21 --- /dev/null +++ b/modules/spanningtree/treesocket2.cpp @@ -0,0 +1,458 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com> + * Copyright (C) 2018-2024 Sadie Powell <sadie@witchery.services> + * Copyright (C) 2012-2016, 2018 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2012 Robby <robby@chatbelgie.be> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2008, 2012 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2007-2008 Dennis Friis <peavey@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <brain@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "timeutils.h" + +#include "main.h" +#include "utils.h" +#include "treeserver.h" +#include "treesocket.h" +#include "resolvers.h" +#include "commands.h" + +/* Handle ERROR command */ +void TreeSocket::Error(CommandBase::Params& params) +{ + const std::string msg = params.empty() ? "" : params[0]; + SetError("received ERROR " + msg); +} + +void TreeSocket::Split(const std::string& line, std::string& tags, std::string& prefix, std::string& command, CommandBase::Params& params) +{ + std::string token; + irc::tokenstream tokens(line); + + if (!tokens.GetMiddle(token)) + return; + + if (token[0] == '@') + { + if (token.length() <= 1) + { + this->SendError("BUG: Received a message with empty tags: " + line); + return; + } + + tags.assign(token, 1, std::string::npos); + if (!tokens.GetMiddle(token)) + { + this->SendError("BUG: Received a message with no command: " + line); + return; + } + } + + if (token[0] == ':') + { + if (token.length() <= 1) + { + this->SendError("BUG: Received a message with an empty prefix: " + line); + return; + } + + prefix.assign(token, 1, std::string::npos); + if (!tokens.GetMiddle(token)) + { + this->SendError("BUG: Received a message with no command: " + line); + return; + } + } + + command.assign(token); + while (tokens.GetTrailing(token)) + params.push_back(token); +} + +void TreeSocket::ProcessLine(std::string& line) +{ + std::string tags; + std::string prefix; + std::string command; + CommandBase::Params params; + + ServerInstance->Logs.RawIO(MODNAME, "S[{}] I {}", GetFd(), line); + + Split(line, tags, prefix, command, params); + + if (command.empty()) + return; + + switch (this->LinkState) + { + case WAIT_AUTH_1: + /* + * State WAIT_AUTH_1: + * Waiting for SERVER command from remote server. Server initiating + * the connection sends the first SERVER command, listening server + * replies with theirs if its happy, then if the initiator is happy, + * it starts to send its net sync, which starts the merge, otherwise + * it sends an ERROR. + */ + if (command == "PASS") + { + /* + * Ignore this silently. Some services packages insist on sending PASS, even + * when it is not required (i.e. by us). We have to ignore this here, otherwise + * as it's an unknown command (effectively), it will cause the connection to be + * closed, which probably isn't what people want. -- w00t + */ + } + else if (command == "SERVER") + { + this->Inbound_Server(params); + } + else if (command == "ERROR") + { + this->Error(params); + } + else if (command == "USER") + { + this->SendError("Client connections to this port are prohibited."); + } + else if (command == "CAPAB") + { + this->Capab(params); + } + else + { + this->SendError("Invalid command in negotiation phase: " + command); + } + break; + case WAIT_AUTH_2: + /* + * State WAIT_AUTH_2: + * We have sent SERVER to the other side of the connection. Now we're waiting for them to start BURST. + * The other option at this stage of things, of course, is for them to close our connection thanks + * to invalid credentials.. -- w + */ + if (command == "SERVER") + { + /* + * Connection is either attempting to re-auth itself (stupid) or sending netburst without sending BURST. + * Both of these aren't allowable, so block them here. -- w + */ + this->SendError("You may not re-authenticate or commence netburst without sending BURST."); + } + else if (command == "BURST") + { + if (!params.empty()) + { + time_t them = ServerCommand::ExtractTS(params[0]); + time_t delta = them - ServerInstance->Time(); + if ((delta < -15) || (delta > 15)) + { + ServerInstance->SNO.WriteGlobalSno('l', "\002ERROR\002: Your clocks are off by {} seconds (this is more than fifteen seconds). Link aborted, \002PLEASE SYNC YOUR CLOCKS!\002", labs((long)delta)); + SendError("Your clocks are out by "+ConvToStr(labs((long)delta))+" seconds (this is more than fifteen seconds). Link aborted, PLEASE SYNC YOUR CLOCKS!"); + return; + } + else if ((delta < -5) || (delta > 5)) + { + ServerInstance->SNO.WriteGlobalSno('l', "\002WARNING\002: Your clocks are off by {} seconds. Please consider syncing your clocks.", labs((long)delta)); + } + } + + // Check for duplicate server name/sid again, it's possible that a new + // server was introduced while we were waiting for them to send BURST. + // (we do not reserve their server name/sid when they send SERVER, we do it now) + if (!CheckDuplicate(capab->name, capab->sid)) + return; + + FinishAuth(capab->name, capab->sid, capab->description, capab->hidden); + } + else if (command == "ERROR") + { + this->Error(params); + } + else if (command == "CAPAB") + { + this->Capab(params); + } + else + { + ServerInstance->Logs.Debug(MODNAME, "Unknown command from fd {} in the WAIT_AUTH_2 phase: {}", + GetFd(), command); + } + + break; + case CONNECTING: + /* + * State CONNECTING: + * We're connecting (OUTGOING) to another server. They are in state WAIT_AUTH_1 until they verify + * our credentials, when they proceed into WAIT_AUTH_2 and send SERVER to us. We then send BURST + * + our netburst, which will put them into CONNECTED state. -- w + */ + if (command == "SERVER") + { + // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state) + this->Outbound_Reply_Server(params); + } + else if (command == "ERROR") + { + this->Error(params); + } + else if (command == "CAPAB") + { + this->Capab(params); + } + else + { + ServerInstance->Logs.Debug(MODNAME, "Unknown command from fd {} in the CONNECTING phase: {}", + GetFd(), command); + } + break; + + case CONNECTED: + /* + * State CONNECTED: + * Credentials have been exchanged, we've gotten their 'BURST' (or sent ours). + * Anything from here on should be accepted a little more reasonably. + */ + this->ProcessConnectedLine(tags, prefix, command, params); + break; + + case DYING: + break; + } +} + +User* TreeSocket::FindSource(const std::string& prefix, const std::string& command) +{ + // Empty prefix means the source is the directly connected server that sent this command + if (prefix.empty()) + return MyRoot->ServerUser; + + if (prefix.size() == 3) + { + // Prefix looks like a sid + TreeServer* server = Utils->FindServerID(prefix); + if (server) + return server->ServerUser; + } + else + { + // If the prefix string is a uuid FindUUID() returns the appropriate User object + auto* user = ServerInstance->Users.FindUUID(prefix); + if (user) + return user; + } + + // Some implementations wrongly send a server name as prefix occasionally, handle that too for now + TreeServer* const server = Utils->FindServer(prefix); + if (server) + return server->ServerUser; + + /* It is important that we don't close the link here, unknown prefix can occur + * due to various race conditions such as the KILL message for a user somehow + * crossing the users QUIT further upstream from the server. Thanks jilles! + */ + + if ((prefix.length() == UIDGenerator::UUID_LENGTH) && (isdigit(prefix[0])) && + ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE"))) + { + /* Special case, we cannot drop these commands as they've been committed already on a + * part of the network by the time we receive them, so in this scenario pretend the + * command came from a server to avoid desync. + */ + + TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3)); + if (usersserver) + return usersserver->ServerUser; + return this->MyRoot->ServerUser; + } + + // Unknown prefix + return nullptr; +} + +void TreeSocket::ProcessTag(User* source, const std::string& tag, ClientProtocol::TagMap& tags) +{ + std::string tagkey; + std::string tagval; + const std::string::size_type p = tag.find('='); + if (p != std::string::npos) + { + // Tag has a value + tagkey.assign(tag, 0, p); + tagval.assign(tag, p + 1, std::string::npos); + } + else + { + tagkey.assign(tag); + } + + for (auto* subscriber : Utils->Creator->tagevprov.GetSubscribers()) + { + ClientProtocol::MessageTagProvider* const tagprov = static_cast<ClientProtocol::MessageTagProvider*>(subscriber); + const ModResult res = tagprov->OnProcessTag(source, tagkey, tagval); + if (res == MOD_RES_ALLOW) + tags.emplace(tagkey, ClientProtocol::MessageTagData(tagprov, tagval)); + else if (res == MOD_RES_DENY) + break; + } +} + +void TreeSocket::ProcessConnectedLine(std::string& taglist, std::string& prefix, std::string& command, CommandBase::Params& params) +{ + User* who = FindSource(prefix, command); + if (!who) + { + ServerInstance->Logs.Debug(MODNAME, "Command '{}' from unknown prefix '{}'! Dropping entire command.", command, prefix); + return; + } + + /* + * Check for fake direction here, and drop any instances that are found. + * What is fake direction? Imagine the following server setup: + * 0AA <-> 0AB <-> 0AC + * Fake direction would be 0AC sending a message to 0AB claiming to be from + * 0AA, or something similar. Basically, a message taking a path that *cannot* + * be correct. + * + * When would this be seen? + * Well, hopefully never. It could be caused by race conditions, bugs, or + * "miscreant" servers, though, so let's check anyway. -- w + * + * We also check here for totally invalid prefixes (prefixes that are neither + * a valid SID or a valid UUID, so that invalid UUID or SID never makes it + * to the higher level functions. -- B + */ + TreeServer* const server = TreeServer::Get(who); + if (server->GetSocket() != this) + { + ServerInstance->Logs.Debug(MODNAME, "Protocol violation: Fake direction '{}' from connection '{}'", prefix, linkID); + return; + } + + // Translate commands coming from servers using an older protocol + if (proto_version < PROTO_NEWEST) + { + if (!PreProcessOldProtocolMessage(who, command, params)) + return; + } + + ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); + CommandBase* cmdbase = scmd; + Command* cmd = nullptr; + if (!scmd) + { + // Not a special server-to-server command + cmd = ServerInstance->Parser.GetHandler(command); + if (!cmd) + { + if (command == "ERROR") + { + this->Error(params); + return; + } + else if (command == "BURST") + { + // This is sent even when there is no need for it, drop it here for now + return; + } + + throw ProtocolException("Unknown command: " + command); + } + cmdbase = cmd; + } + + if (params.size() < cmdbase->min_params) + throw ProtocolException("Insufficient parameters"); + + if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param)) + { + // the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last + if (params.size()-1 < cmdbase->min_params) + return; + params.pop_back(); + } + + CmdResult res; + ClientProtocol::TagMap tags; + std::string tag; + irc::sepstream tagstream(taglist, ';'); + while (tagstream.GetToken(tag)) + ProcessTag(who, tag, tags); + + CommandBase::Params newparams(params, tags); + + if (scmd) + res = scmd->Handle(who, newparams); + else + { + res = cmd->Handle(who, newparams); + if (res == CmdResult::INVALID) + throw ProtocolException("Error in command handler"); + } + + if (res == CmdResult::SUCCESS) + Utils->RouteCommand(server->GetRoute(), cmdbase, newparams, who); +} + +void TreeSocket::OnTimeout() +{ + ServerInstance->SNO.WriteGlobalSno('l', "CONNECT: Connection to \002{}\002 timed out.", linkID); +} + +void TreeSocket::Close() +{ + if (!HasFd()) + return; + + ServerInstance->GlobalCulls.AddItem(this); + this->BufferedSocket::Close(); + SetError("Remote host closed connection"); + + // Connection closed. + // If the connection is fully up (state CONNECTED) + // then propagate a netsplit to all peers. + if (MyRoot && !MyRoot->IsDead()) + MyRoot->SQuit(GetError(), true); + else + ServerInstance->SNO.WriteGlobalSno('l', "Connection to '\002{}\002' failed.", linkID); + + time_t server_uptime = ServerInstance->Time() - this->age; + if (server_uptime) + { + std::string timestr = Duration::ToString(server_uptime); + ServerInstance->SNO.WriteGlobalSno('l', "Connection to '\002{}\002' was established for {}", linkID, timestr); + } +} + +void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden) +{ + this->LinkState = CONNECTED; + Utils->timeoutlist.erase(this); + + linkID = remotename; + + MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden); + + // Mark the server as bursting + MyRoot->BeginBurst(); + this->DoBurst(MyRoot); + + CommandServer::Builder(MyRoot).Forward(MyRoot); +} |
