]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_spanningtree/treesocket2.cpp
Get rid of CommandBuilder::push_back.
[user/henk/code/inspircd.git] / src / modules / m_spanningtree / treesocket2.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
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>
10  *
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.
14  *
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
18  * details.
19  *
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/>.
22  */
23
24
25 #include "inspircd.h"
26
27 #include "main.h"
28 #include "utils.h"
29 #include "treeserver.h"
30 #include "treesocket.h"
31 #include "resolvers.h"
32 #include "commands.h"
33
34 /* Handle ERROR command */
35 void TreeSocket::Error(CommandBase::Params& params)
36 {
37         std::string msg = params.size() ? params[0] : "";
38         SetError("received ERROR " + msg);
39 }
40
41 void TreeSocket::Split(const std::string& line, std::string& tags, std::string& prefix, std::string& command, CommandBase::Params& params)
42 {
43         std::string token;
44         irc::tokenstream tokens(line);
45
46         if (!tokens.GetMiddle(token))
47                 return;
48
49         if (token[0] == '@')
50         {
51                 if (token.length() <= 1)
52                 {
53                         this->SendError("BUG: Received a message with empty tags: " + line);
54                         return;
55                 }
56
57                 tags.assign(token, 1, std::string::npos);
58                 if (!tokens.GetMiddle(token))
59                 {
60                         this->SendError("BUG: Received a message with no command: " + line);
61                         return;
62                 }
63         }
64
65         if (token[0] == ':')
66         {
67                 if (token.length() <= 1)
68                 {
69                         this->SendError("BUG: Received a message with an empty prefix: " + line);
70                         return;
71                 }
72
73                 prefix.assign(token, 1, std::string::npos);
74                 if (!tokens.GetMiddle(token))
75                 {
76                         this->SendError("BUG: Received a message with no command: " + line);
77                         return;
78                 }
79         }
80
81         command.assign(token);
82         while (tokens.GetTrailing(token))
83                 params.push_back(token);
84 }
85
86 void TreeSocket::ProcessLine(std::string &line)
87 {
88         std::string tags;
89         std::string prefix;
90         std::string command;
91         CommandBase::Params params;
92
93         ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str());
94
95         Split(line, tags, prefix, command, params);
96
97         if (command.empty())
98                 return;
99
100         switch (this->LinkState)
101         {
102                 case WAIT_AUTH_1:
103                         /*
104                          * State WAIT_AUTH_1:
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
109                          *  it sends an ERROR.
110                          */
111                         if (command == "PASS")
112                         {
113                                 /*
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
118                                  */
119                         }
120                         else if (command == "SERVER")
121                         {
122                                 this->Inbound_Server(params);
123                         }
124                         else if (command == "ERROR")
125                         {
126                                 this->Error(params);
127                         }
128                         else if (command == "USER")
129                         {
130                                 this->SendError("Client connections to this port are prohibited.");
131                         }
132                         else if (command == "CAPAB")
133                         {
134                                 this->Capab(params);
135                         }
136                         else
137                         {
138                                 this->SendError("Invalid command in negotiation phase: " + command);
139                         }
140                 break;
141                 case WAIT_AUTH_2:
142                         /*
143                          * State WAIT_AUTH_2:
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
147                          */
148                         if (command == "SERVER")
149                         {
150                                 /*
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
153                                  */
154                                 this->SendError("You may not re-authenticate or commence netburst without sending BURST.");
155                         }
156                         else if (command == "BURST")
157                         {
158                                 if (params.size())
159                                 {
160                                         time_t them = ConvToNum<time_t>(params[0]);
161                                         time_t delta = them - ServerInstance->Time();
162                                         if ((delta < -600) || (delta > 600))
163                                         {
164                                                 ServerInstance->SNO->WriteGlobalSno('l', "\002ERROR\002: Your clocks are off by %ld seconds (this is more than five minutes). Link aborted, \002PLEASE SYNC YOUR CLOCKS!\002", 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!");
166                                                 return;
167                                         }
168                                         else if ((delta < -30) || (delta > 30))
169                                         {
170                                                 ServerInstance->SNO->WriteGlobalSno('l', "\002WARNING\002: Your clocks are off by %ld seconds. Please consider syncing your clocks.", labs((long)delta));
171                                         }
172                                 }
173
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))
178                                         return;
179
180                                 FinishAuth(capab->name, capab->sid, capab->description, capab->hidden);
181                         }
182                         else if (command == "ERROR")
183                         {
184                                 this->Error(params);
185                         }
186                         else if (command == "CAPAB")
187                         {
188                                 this->Capab(params);
189                         }
190
191                 break;
192                 case CONNECTING:
193                         /*
194                          * State CONNECTING:
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
198                          */
199                         if (command == "SERVER")
200                         {
201                                 // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state)
202                                 this->Outbound_Reply_Server(params);
203                         }
204                         else if (command == "ERROR")
205                         {
206                                 this->Error(params);
207                         }
208                         else if (command == "CAPAB")
209                         {
210                                 this->Capab(params);
211                         }
212                 break;
213                 case CONNECTED:
214                         /*
215                          * State CONNECTED:
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.
218                          */
219                         this->ProcessConnectedLine(tags, prefix, command, params);
220                 break;
221                 case DYING:
222                 break;
223         }
224 }
225
226 User* TreeSocket::FindSource(const std::string& prefix, const std::string& command)
227 {
228         // Empty prefix means the source is the directly connected server that sent this command
229         if (prefix.empty())
230                 return MyRoot->ServerUser;
231
232         if (prefix.size() == 3)
233         {
234                 // Prefix looks like a sid
235                 TreeServer* server = Utils->FindServerID(prefix);
236                 if (server)
237                         return server->ServerUser;
238         }
239         else
240         {
241                 // If the prefix string is a uuid FindUUID() returns the appropriate User object
242                 User* user = ServerInstance->FindUUID(prefix);
243                 if (user)
244                         return user;
245         }
246
247         // Some implementations wrongly send a server name as prefix occasionally, handle that too for now
248         TreeServer* const server = Utils->FindServer(prefix);
249         if (server)
250                 return server->ServerUser;
251
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!
255          */
256
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")))
259         {
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.
263                  */
264
265                 TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3));
266                 if (usersserver)
267                         return usersserver->ServerUser;
268                 return this->MyRoot->ServerUser;
269         }
270
271         // Unknown prefix
272         return NULL;
273 }
274
275 void TreeSocket::ProcessTag(User* source, const std::string& tag, ClientProtocol::TagMap& tags)
276 {
277         std::string tagkey;
278         std::string tagval;
279         const std::string::size_type p = tag.find('=');
280         if (p != std::string::npos)
281         {
282                 // Tag has a value
283                 tagkey.assign(tag, 0, p);
284                 tagval.assign(tag, p + 1, std::string::npos);
285         }
286         else
287         {
288                 tagkey.assign(tag);
289         }
290
291         const Events::ModuleEventProvider::SubscriberList& list = Utils->Creator->tagevprov.GetSubscribers();
292         for (Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i)
293         {
294                 ClientProtocol::MessageTagProvider* const tagprov = static_cast<ClientProtocol::MessageTagProvider*>(*i);
295                 const ModResult res = tagprov->OnProcessTag(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)
299                         break;
300         }
301 }
302
303 void TreeSocket::ProcessConnectedLine(std::string& taglist, std::string& prefix, std::string& command, CommandBase::Params& params)
304 {
305         User* who = FindSource(prefix, command);
306         if (!who)
307         {
308                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str());
309                 return;
310         }
311
312         /*
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*
318          * be correct.
319          *
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
323          *
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
327          */
328         TreeServer* const server = TreeServer::Get(who);
329         if (server->GetSocket() != this)
330         {
331                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str());
332                 return;
333         }
334
335         // Translate commands coming from servers using an older protocol
336         if (proto_version < PROTO_NEWEST)
337         {
338                 if (!PreProcessOldProtocolMessage(who, command, params))
339                         return;
340         }
341
342         ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command);
343         CommandBase* cmdbase = scmd;
344         Command* cmd = NULL;
345         if (!scmd)
346         {
347                 // Not a special server-to-server command
348                 cmd = ServerInstance->Parser.GetHandler(command);
349                 if (!cmd)
350                 {
351                         if (command == "ERROR")
352                         {
353                                 this->Error(params);
354                                 return;
355                         }
356                         else if (command == "BURST")
357                         {
358                                 // This is sent even when there is no need for it, drop it here for now
359                                 return;
360                         }
361
362                         throw ProtocolException("Unknown command: " + command);
363                 }
364                 cmdbase = cmd;
365         }
366
367         if (params.size() < cmdbase->min_params)
368                 throw ProtocolException("Insufficient parameters");
369
370         if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param))
371         {
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)
374                         return;
375                 params.pop_back();
376         }
377
378         CmdResult res;
379         ClientProtocol::TagMap tags;
380         std::string tag;
381         irc::sepstream tagstream(taglist, ';');
382         while (tagstream.GetToken(tag))
383                 ProcessTag(who, tag, tags);
384
385         CommandBase::Params newparams(params, tags);
386
387         if (scmd)
388                 res = scmd->Handle(who, newparams);
389         else
390         {
391                 res = cmd->Handle(who, newparams);
392                 if (res == CMD_INVALID)
393                         throw ProtocolException("Error in command handler");
394         }
395
396         if (res == CMD_SUCCESS)
397                 Utils->RouteCommand(server->GetRoute(), cmdbase, newparams, who);
398 }
399
400 void TreeSocket::OnTimeout()
401 {
402         ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
403 }
404
405 void TreeSocket::Close()
406 {
407         if (fd < 0)
408                 return;
409
410         ServerInstance->GlobalCulls.AddItem(this);
411         this->BufferedSocket::Close();
412         SetError("Remote host closed connection");
413
414         // Connection closed.
415         // If the connection is fully up (state CONNECTED)
416         // then propogate a netsplit to all peers.
417         if (MyRoot)
418                 MyRoot->SQuit(getError());
419
420         ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' failed.", linkID.c_str());
421
422         time_t server_uptime = ServerInstance->Time() - this->age;
423         if (server_uptime)
424         {
425                 std::string timestr = InspIRCd::DurationString(server_uptime);
426                 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' was established for %s", linkID.c_str(), timestr.c_str());
427         }
428 }
429
430 void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden)
431 {
432         this->LinkState = CONNECTED;
433         Utils->timeoutlist.erase(this);
434
435         linkID = remotename;
436
437         MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden);
438
439         // Mark the server as bursting
440         MyRoot->BeginBurst();
441         this->DoBurst(MyRoot);
442
443         CommandServer::Builder(MyRoot).Forward(MyRoot);
444 }