2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5 * Copyright (C) 2013, 2018-2020 Sadie Powell <sadie@witchery.services>
6 * Copyright (C) 2013 Adam <Adam@anope.org>
7 * Copyright (C) 2012-2016, 2018 Attila Molnar <attilamolnar@hush.com>
8 * Copyright (C) 2012, 2019 Robby <robby@chatbelgie.be>
9 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
10 * Copyright (C) 2008, 2012 Robin Burchell <robin+git@viroteck.net>
11 * Copyright (C) 2007-2008, 2010 Craig Edwards <brain@inspircd.org>
12 * Copyright (C) 2007-2008 Dennis Friis <peavey@inspircd.org>
14 * This file is part of InspIRCd. InspIRCd is free software: you can
15 * redistribute it and/or modify it under the terms of the GNU General Public
16 * License as published by the Free Software Foundation, version 2.
18 * This program is distributed in the hope that it will be useful, but WITHOUT
19 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
32 #include "treeserver.h"
33 #include "treesocket.h"
34 #include "resolvers.h"
37 /* Handle ERROR command */
38 void TreeSocket::Error(CommandBase::Params& params)
40 std::string msg = params.size() ? params[0] : "";
41 SetError("received ERROR " + msg);
44 void TreeSocket::Split(const std::string& line, std::string& tags, std::string& prefix, std::string& command, CommandBase::Params& params)
47 irc::tokenstream tokens(line);
49 if (!tokens.GetMiddle(token))
54 if (token.length() <= 1)
56 this->SendError("BUG: Received a message with empty tags: " + line);
60 tags.assign(token, 1, std::string::npos);
61 if (!tokens.GetMiddle(token))
63 this->SendError("BUG: Received a message with no command: " + line);
70 if (token.length() <= 1)
72 this->SendError("BUG: Received a message with an empty prefix: " + line);
76 prefix.assign(token, 1, std::string::npos);
77 if (!tokens.GetMiddle(token))
79 this->SendError("BUG: Received a message with no command: " + line);
84 command.assign(token);
85 while (tokens.GetTrailing(token))
86 params.push_back(token);
89 void TreeSocket::ProcessLine(std::string &line)
94 CommandBase::Params params;
96 ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str());
98 Split(line, tags, prefix, command, params);
103 switch (this->LinkState)
108 * Waiting for SERVER command from remote server. Server initiating
109 * the connection sends the first SERVER command, listening server
110 * replies with theirs if its happy, then if the initiator is happy,
111 * it starts to send its net sync, which starts the merge, otherwise
114 if (command == "PASS")
117 * Ignore this silently. Some services packages insist on sending PASS, even
118 * when it is not required (i.e. by us). We have to ignore this here, otherwise
119 * as it's an unknown command (effectively), it will cause the connection to be
120 * closed, which probably isn't what people want. -- w00t
123 else if (command == "SERVER")
125 this->Inbound_Server(params);
127 else if (command == "ERROR")
131 else if (command == "USER")
133 this->SendError("Client connections to this port are prohibited.");
135 else if (command == "CAPAB")
141 this->SendError("Invalid command in negotiation phase: " + command);
147 * We have sent SERVER to the other side of the connection. Now we're waiting for them to start BURST.
148 * The other option at this stage of things, of course, is for them to close our connection thanks
149 * to invalid credentials.. -- w
151 if (command == "SERVER")
154 * Connection is either attempting to re-auth itself (stupid) or sending netburst without sending BURST.
155 * Both of these aren't allowable, so block them here. -- w
157 this->SendError("You may not re-authenticate or commence netburst without sending BURST.");
159 else if (command == "BURST")
163 time_t them = ConvToNum<time_t>(params[0]);
164 time_t delta = them - ServerInstance->Time();
165 if ((delta < -60) || (delta > 60))
167 ServerInstance->SNO->WriteGlobalSno('l', "\002ERROR\002: Your clocks are off by %ld seconds (this is more than one minute). Link aborted, \002PLEASE SYNC YOUR CLOCKS!\002", labs((long)delta));
168 SendError("Your clocks are out by "+ConvToStr(labs((long)delta))+" seconds (this is more than one minute). Link aborted, PLEASE SYNC YOUR CLOCKS!");
171 else if ((delta < -15) || (delta > 15))
173 ServerInstance->SNO->WriteGlobalSno('l', "\002WARNING\002: Your clocks are off by %ld seconds. Please consider syncing your clocks.", labs((long)delta));
177 // Check for duplicate server name/sid again, it's possible that a new
178 // server was introduced while we were waiting for them to send BURST.
179 // (we do not reserve their server name/sid when they send SERVER, we do it now)
180 if (!CheckDuplicate(capab->name, capab->sid))
183 FinishAuth(capab->name, capab->sid, capab->description, capab->hidden);
185 else if (command == "ERROR")
189 else if (command == "CAPAB")
198 * We're connecting (OUTGOING) to another server. They are in state WAIT_AUTH_1 until they verify
199 * our credentials, when they proceed into WAIT_AUTH_2 and send SERVER to us. We then send BURST
200 * + our netburst, which will put them into CONNECTED state. -- w
202 if (command == "SERVER")
204 // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state)
205 this->Outbound_Reply_Server(params);
207 else if (command == "ERROR")
211 else if (command == "CAPAB")
219 * Credentials have been exchanged, we've gotten their 'BURST' (or sent ours).
220 * Anything from here on should be accepted a little more reasonably.
222 this->ProcessConnectedLine(tags, prefix, command, params);
229 User* TreeSocket::FindSource(const std::string& prefix, const std::string& command)
231 // Empty prefix means the source is the directly connected server that sent this command
233 return MyRoot->ServerUser;
235 if (prefix.size() == 3)
237 // Prefix looks like a sid
238 TreeServer* server = Utils->FindServerID(prefix);
240 return server->ServerUser;
244 // If the prefix string is a uuid FindUUID() returns the appropriate User object
245 User* user = ServerInstance->FindUUID(prefix);
250 // Some implementations wrongly send a server name as prefix occasionally, handle that too for now
251 TreeServer* const server = Utils->FindServer(prefix);
253 return server->ServerUser;
255 /* It is important that we don't close the link here, unknown prefix can occur
256 * due to various race conditions such as the KILL message for a user somehow
257 * crossing the users QUIT further upstream from the server. Thanks jilles!
260 if ((prefix.length() == UIDGenerator::UUID_LENGTH) && (isdigit(prefix[0])) &&
261 ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE")))
263 /* Special case, we cannot drop these commands as they've been committed already on a
264 * part of the network by the time we receive them, so in this scenario pretend the
265 * command came from a server to avoid desync.
268 TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3));
270 return usersserver->ServerUser;
271 return this->MyRoot->ServerUser;
278 void TreeSocket::ProcessTag(User* source, const std::string& tag, ClientProtocol::TagMap& tags)
282 const std::string::size_type p = tag.find('=');
283 if (p != std::string::npos)
286 tagkey.assign(tag, 0, p);
287 tagval.assign(tag, p + 1, std::string::npos);
294 const Events::ModuleEventProvider::SubscriberList& list = Utils->Creator->tagevprov.GetSubscribers();
295 for (Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i)
297 ClientProtocol::MessageTagProvider* const tagprov = static_cast<ClientProtocol::MessageTagProvider*>(*i);
298 const ModResult res = tagprov->OnProcessTag(source, tagkey, tagval);
299 if (res == MOD_RES_ALLOW)
300 tags.insert(std::make_pair(tagkey, ClientProtocol::MessageTagData(tagprov, tagval)));
301 else if (res == MOD_RES_DENY)
306 void TreeSocket::ProcessConnectedLine(std::string& taglist, std::string& prefix, std::string& command, CommandBase::Params& params)
308 User* who = FindSource(prefix, command);
311 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str());
316 * Check for fake direction here, and drop any instances that are found.
317 * What is fake direction? Imagine the following server setup:
318 * 0AA <-> 0AB <-> 0AC
319 * Fake direction would be 0AC sending a message to 0AB claiming to be from
320 * 0AA, or something similar. Basically, a message taking a path that *cannot*
323 * When would this be seen?
324 * Well, hopefully never. It could be caused by race conditions, bugs, or
325 * "miscreant" servers, though, so let's check anyway. -- w
327 * We also check here for totally invalid prefixes (prefixes that are neither
328 * a valid SID or a valid UUID, so that invalid UUID or SID never makes it
329 * to the higher level functions. -- B
331 TreeServer* const server = TreeServer::Get(who);
332 if (server->GetSocket() != this)
334 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str());
338 // Translate commands coming from servers using an older protocol
339 if (proto_version < PROTO_NEWEST)
341 if (!PreProcessOldProtocolMessage(who, command, params))
345 ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command);
346 CommandBase* cmdbase = scmd;
350 // Not a special server-to-server command
351 cmd = ServerInstance->Parser.GetHandler(command);
354 if (command == "ERROR")
359 else if (command == "BURST")
361 // This is sent even when there is no need for it, drop it here for now
365 throw ProtocolException("Unknown command: " + command);
370 if (params.size() < cmdbase->min_params)
371 throw ProtocolException("Insufficient parameters");
373 if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param))
375 // 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
376 if (params.size()-1 < cmdbase->min_params)
382 ClientProtocol::TagMap tags;
384 irc::sepstream tagstream(taglist, ';');
385 while (tagstream.GetToken(tag))
386 ProcessTag(who, tag, tags);
388 CommandBase::Params newparams(params, tags);
391 res = scmd->Handle(who, newparams);
394 res = cmd->Handle(who, newparams);
395 if (res == CMD_INVALID)
396 throw ProtocolException("Error in command handler");
399 if (res == CMD_SUCCESS)
400 Utils->RouteCommand(server->GetRoute(), cmdbase, newparams, who);
403 void TreeSocket::OnTimeout()
405 ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
408 void TreeSocket::Close()
413 ServerInstance->GlobalCulls.AddItem(this);
414 this->BufferedSocket::Close();
415 SetError("Remote host closed connection");
417 // Connection closed.
418 // If the connection is fully up (state CONNECTED)
419 // then propagate a netsplit to all peers.
421 MyRoot->SQuit(getError(), true);
423 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' failed.", linkID.c_str());
425 time_t server_uptime = ServerInstance->Time() - this->age;
428 std::string timestr = InspIRCd::DurationString(server_uptime);
429 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' was established for %s", linkID.c_str(), timestr.c_str());
433 void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden)
435 this->LinkState = CONNECTED;
436 Utils->timeoutlist.erase(this);
440 MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden);
442 // Mark the server as bursting
443 MyRoot->BeginBurst();
444 this->DoBurst(MyRoot);
446 CommandServer::Builder(MyRoot).Forward(MyRoot);