2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2007-2008, 2012 Robin Burchell <robin+git@viroteck.net>
5 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
6 * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc>
7 * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
8 * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org>
9 * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
11 * This file is part of InspIRCd. InspIRCd is free software: you can
12 * redistribute it and/or modify it under the terms of the GNU General Public
13 * License as published by the Free Software Foundation, version 2.
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 #include "treeserver.h"
30 #include "treesocket.h"
31 #include "resolvers.h"
34 /* Handle ERROR command */
35 void TreeSocket::Error(CommandBase::Params& params)
37 std::string msg = params.size() ? params[0] : "";
38 SetError("received ERROR " + msg);
41 void TreeSocket::Split(const std::string& line, std::string& tags, std::string& prefix, std::string& command, CommandBase::Params& params)
44 irc::tokenstream tokens(line);
46 if (!tokens.GetMiddle(token))
51 if (token.length() <= 1)
53 this->SendError("BUG: Received a message with empty tags: " + line);
57 tags.assign(token, 1, std::string::npos);
58 if (!tokens.GetMiddle(token))
60 this->SendError("BUG: Received a message with no command: " + line);
67 if (token.length() <= 1)
69 this->SendError("BUG: Received a message with an empty prefix: " + line);
73 prefix.assign(token, 1, std::string::npos);
74 if (!tokens.GetMiddle(token))
76 this->SendError("BUG: Received a message with no command: " + line);
81 command.assign(token);
82 while (tokens.GetTrailing(token))
83 params.push_back(token);
86 void TreeSocket::ProcessLine(std::string &line)
91 CommandBase::Params params;
93 ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str());
95 Split(line, tags, prefix, command, params);
100 switch (this->LinkState)
105 * Waiting for SERVER command from remote server. Server initiating
106 * the connection sends the first SERVER command, listening server
107 * replies with theirs if its happy, then if the initiator is happy,
108 * it starts to send its net sync, which starts the merge, otherwise
111 if (command == "PASS")
114 * Ignore this silently. Some services packages insist on sending PASS, even
115 * when it is not required (i.e. by us). We have to ignore this here, otherwise
116 * as it's an unknown command (effectively), it will cause the connection to be
117 * closed, which probably isn't what people want. -- w00t
120 else if (command == "SERVER")
122 this->Inbound_Server(params);
124 else if (command == "ERROR")
128 else if (command == "USER")
130 this->SendError("Client connections to this port are prohibited.");
132 else if (command == "CAPAB")
138 this->SendError("Invalid command in negotiation phase: " + command);
144 * We have sent SERVER to the other side of the connection. Now we're waiting for them to start BURST.
145 * The other option at this stage of things, of course, is for them to close our connection thanks
146 * to invalid credentials.. -- w
148 if (command == "SERVER")
151 * Connection is either attempting to re-auth itself (stupid) or sending netburst without sending BURST.
152 * Both of these aren't allowable, so block them here. -- w
154 this->SendError("You may not re-authenticate or commence netburst without sending BURST.");
156 else if (command == "BURST")
160 time_t them = ConvToNum<time_t>(params[0]);
161 time_t delta = them - ServerInstance->Time();
162 if ((delta < -600) || (delta > 600))
164 ServerInstance->SNO->WriteGlobalSno('l',"\2ERROR\2: Your clocks are out by %ld seconds (this is more than five minutes). Link aborted, \2PLEASE SYNC YOUR CLOCKS!\2",labs((long)delta));
165 SendError("Your clocks are out by "+ConvToStr(labs((long)delta))+" seconds (this is more than five minutes). Link aborted, PLEASE SYNC YOUR CLOCKS!");
168 else if ((delta < -30) || (delta > 30))
170 ServerInstance->SNO->WriteGlobalSno('l',"\2WARNING\2: Your clocks are out by %ld seconds. Please consider synching your clocks.", labs((long)delta));
174 // Check for duplicate server name/sid again, it's possible that a new
175 // server was introduced while we were waiting for them to send BURST.
176 // (we do not reserve their server name/sid when they send SERVER, we do it now)
177 if (!CheckDuplicate(capab->name, capab->sid))
180 FinishAuth(capab->name, capab->sid, capab->description, capab->hidden);
182 else if (command == "ERROR")
186 else if (command == "CAPAB")
195 * We're connecting (OUTGOING) to another server. They are in state WAIT_AUTH_1 until they verify
196 * our credentials, when they proceed into WAIT_AUTH_2 and send SERVER to us. We then send BURST
197 * + our netburst, which will put them into CONNECTED state. -- w
199 if (command == "SERVER")
201 // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state)
202 this->Outbound_Reply_Server(params);
204 else if (command == "ERROR")
208 else if (command == "CAPAB")
216 * Credentials have been exchanged, we've gotten their 'BURST' (or sent ours).
217 * Anything from here on should be accepted a little more reasonably.
219 this->ProcessConnectedLine(tags, prefix, command, params);
226 User* TreeSocket::FindSource(const std::string& prefix, const std::string& command)
228 // Empty prefix means the source is the directly connected server that sent this command
230 return MyRoot->ServerUser;
232 if (prefix.size() == 3)
234 // Prefix looks like a sid
235 TreeServer* server = Utils->FindServerID(prefix);
237 return server->ServerUser;
241 // If the prefix string is a uuid FindUUID() returns the appropriate User object
242 User* user = ServerInstance->FindUUID(prefix);
247 // Some implementations wrongly send a server name as prefix occasionally, handle that too for now
248 TreeServer* const server = Utils->FindServer(prefix);
250 return server->ServerUser;
252 /* It is important that we don't close the link here, unknown prefix can occur
253 * due to various race conditions such as the KILL message for a user somehow
254 * crossing the users QUIT further upstream from the server. Thanks jilles!
257 if ((prefix.length() == UIDGenerator::UUID_LENGTH) && (isdigit(prefix[0])) &&
258 ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE")))
260 /* Special case, we cannot drop these commands as they've been committed already on a
261 * part of the network by the time we receive them, so in this scenario pretend the
262 * command came from a server to avoid desync.
265 TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3));
267 return usersserver->ServerUser;
268 return this->MyRoot->ServerUser;
275 void TreeSocket::ProcessTag(User* source, const std::string& tag, ClientProtocol::TagMap& tags)
279 const std::string::size_type p = tag.find('=');
280 if (p != std::string::npos)
283 tagkey.assign(tag, 0, p);
284 tagval.assign(tag, p + 1, std::string::npos);
291 const Events::ModuleEventProvider::SubscriberList& list = Utils->Creator->tagevprov.GetSubscribers();
292 for (Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i)
294 ClientProtocol::MessageTagProvider* const tagprov = static_cast<ClientProtocol::MessageTagProvider*>(*i);
295 const ModResult res = tagprov->OnClientProtocolProcessTag(source, tagkey, tagval);
296 if (res == MOD_RES_ALLOW)
297 tags.insert(std::make_pair(tagkey, ClientProtocol::MessageTagData(tagprov, tagval)));
298 else if (res == MOD_RES_DENY)
303 void TreeSocket::ProcessConnectedLine(std::string& taglist, std::string& prefix, std::string& command, CommandBase::Params& params)
305 User* who = FindSource(prefix, command);
308 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str());
313 * Check for fake direction here, and drop any instances that are found.
314 * What is fake direction? Imagine the following server setup:
315 * 0AA <-> 0AB <-> 0AC
316 * Fake direction would be 0AC sending a message to 0AB claiming to be from
317 * 0AA, or something similar. Basically, a message taking a path that *cannot*
320 * When would this be seen?
321 * Well, hopefully never. It could be caused by race conditions, bugs, or
322 * "miscreant" servers, though, so let's check anyway. -- w
324 * We also check here for totally invalid prefixes (prefixes that are neither
325 * a valid SID or a valid UUID, so that invalid UUID or SID never makes it
326 * to the higher level functions. -- B
328 TreeServer* const server = TreeServer::Get(who);
329 if (server->GetSocket() != this)
331 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str());
335 // Translate commands coming from servers using an older protocol
336 if (proto_version < ProtocolVersion)
338 if (!PreProcessOldProtocolMessage(who, command, params))
342 ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command);
343 CommandBase* cmdbase = scmd;
347 // Not a special server-to-server command
348 cmd = ServerInstance->Parser.GetHandler(command);
351 if (command == "ERROR")
356 else if (command == "BURST")
358 // This is sent even when there is no need for it, drop it here for now
362 throw ProtocolException("Unknown command: " + command);
367 if (params.size() < cmdbase->min_params)
368 throw ProtocolException("Insufficient parameters");
370 if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param))
372 // 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
373 if (params.size()-1 < cmdbase->min_params)
380 res = scmd->Handle(who, params);
383 ClientProtocol::TagMap tags;
385 irc::sepstream tagstream(taglist, ';');
386 while (tagstream.GetToken(tag))
387 ProcessTag(who, tag, tags);
388 res = cmd->Handle(who, CommandBase::Params(params, tags));
389 if (res == CMD_INVALID)
390 throw ProtocolException("Error in command handler");
393 if (res == CMD_SUCCESS)
394 Utils->RouteCommand(server->GetRoute(), cmdbase, params, who);
397 void TreeSocket::OnTimeout()
399 ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
402 void TreeSocket::Close()
407 ServerInstance->GlobalCulls.AddItem(this);
408 this->BufferedSocket::Close();
409 SetError("Remote host closed connection");
411 // Connection closed.
412 // If the connection is fully up (state CONNECTED)
413 // then propogate a netsplit to all peers.
415 MyRoot->SQuit(getError());
417 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str());
419 time_t server_uptime = ServerInstance->Time() - this->age;
422 std::string timestr = ModuleSpanningTree::TimeToStr(server_uptime);
423 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), timestr.c_str());
427 void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden)
429 this->LinkState = CONNECTED;
430 Utils->timeoutlist.erase(this);
434 MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden);
436 // Mark the server as bursting
437 MyRoot->BeginBurst();
438 this->DoBurst(MyRoot);
440 CommandServer::Builder(MyRoot).Forward(MyRoot);