]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_spanningtree/treesocket2.cpp
Reset the already_sent IDs during slow garbage collection
[user/henk/code/inspircd.git] / src / modules / m_spanningtree / treesocket2.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2010 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "socket.h"
16 #include "xline.h"
17 #include "socketengine.h"
18
19 #include "main.h"
20 #include "utils.h"
21 #include "treeserver.h"
22 #include "link.h"
23 #include "treesocket.h"
24 #include "resolvers.h"
25
26 /* Handle ERROR command */
27 void TreeSocket::Error(parameterlist &params)
28 {
29         std::string msg = params.size() ? params[0] : "";
30         SetError("received ERROR " + msg);
31 }
32
33 void TreeSocket::Split(const std::string& line, std::string& prefix, std::string& command, parameterlist& params)
34 {
35         irc::tokenstream tokens(line);
36
37         if (!tokens.GetToken(prefix))
38                 return;
39         
40         if (prefix[0] == ':')
41         {
42                 prefix = prefix.substr(1);
43
44                 if (prefix.empty())
45                 {
46                         this->SendError("BUG (?) Empty prefix received: " + line);
47                         return;
48                 }
49                 if (!tokens.GetToken(command))
50                 {
51                         this->SendError("BUG (?) Empty command received: " + line);
52                         return;
53                 }
54         }
55         else
56         {
57                 command = prefix;
58                 prefix.clear();
59         }
60         if (command.empty())
61                 this->SendError("BUG (?) Empty command received: " + line);
62
63         std::string param;
64         while (tokens.GetToken(param))
65         {
66                 params.push_back(param);
67         }
68 }
69
70 void TreeSocket::ProcessLine(std::string &line)
71 {
72         std::string prefix;
73         std::string command;
74         parameterlist params;
75
76         ServerInstance->Logs->Log("m_spanningtree",DEBUG, "S[%d] I %s", this->GetFd(), line.c_str());
77
78         Split(line, prefix, command, params);
79
80         if (command.empty())
81                 return;
82
83         switch (this->LinkState)
84         {
85                 case WAIT_AUTH_1:
86                         /*
87                          * State WAIT_AUTH_1:
88                          *  Waiting for SERVER command from remote server. Server initiating
89                          *  the connection sends the first SERVER command, listening server
90                          *  replies with theirs if its happy, then if the initiator is happy,
91                          *  it starts to send its net sync, which starts the merge, otherwise
92                          *  it sends an ERROR.
93                          */
94                         if (command == "PASS")
95                         {
96                                 /*
97                                  * Ignore this silently. Some services packages insist on sending PASS, even
98                                  * when it is not required (i.e. by us). We have to ignore this here, otherwise
99                                  * as it's an unknown command (effectively), it will cause the connection to be
100                                  * closed, which probably isn't what people want. -- w00t
101                                  */
102                         }
103                         else if (command == "SERVER")
104                         {
105                                 this->Inbound_Server(params);
106                         }
107                         else if (command == "ERROR")
108                         {
109                                 this->Error(params);
110                         }
111                         else if (command == "USER")
112                         {
113                                 this->SendError("Client connections to this port are prohibited.");
114                         }
115                         else if (command == "CAPAB")
116                         {
117                                 this->Capab(params);
118                         }
119                         else
120                         {
121                                 this->SendError(std::string("Invalid command in negotiation phase: ") + command.c_str());
122                         }
123                 break;
124                 case WAIT_AUTH_2:
125                         /*
126                          * State WAIT_AUTH_2:
127                          *  We have sent SERVER to the other side of the connection. Now we're waiting for them to start BURST.
128                          *  The other option at this stage of things, of course, is for them to close our connection thanks
129                          *  to invalid credentials.. -- w
130                          */
131                         if (command == "SERVER")
132                         {
133                                 /*
134                                  * Connection is either attempting to re-auth itself (stupid) or sending netburst without sending BURST.
135                                  * Both of these aren't allowable, so block them here. -- w
136                                  */
137                                 this->SendError("You may not re-authenticate or commence netburst without sending BURST.");
138                         }
139                         else if (command == "BURST")
140                         {
141                                 if (params.size())
142                                 {
143                                         time_t them = atoi(params[0].c_str());
144                                         time_t delta = them - ServerInstance->Time();
145                                         if ((delta < -600) || (delta > 600))
146                                         {
147                                                 ServerInstance->SNO->WriteGlobalSno('l',"\2ERROR\2: Your clocks are out by %d seconds (this is more than five minutes). Link aborted, \2PLEASE SYNC YOUR CLOCKS!\2",abs((long)delta));
148                                                 SendError("Your clocks are out by "+ConvToStr(abs((long)delta))+" seconds (this is more than five minutes). Link aborted, PLEASE SYNC YOUR CLOCKS!");
149                                                 return;
150                                         }
151                                         else if ((delta < -30) || (delta > 30))
152                                         {
153                                                 ServerInstance->SNO->WriteGlobalSno('l',"\2WARNING\2: Your clocks are out by %d seconds. Please consider synching your clocks.", abs((long)delta));
154                                         }
155                                 }
156                                 this->LinkState = CONNECTED;
157
158                                 Utils->timeoutlist.erase(this);
159                                 parameterlist sparams;
160                                 Utils->DoOneToAllButSender(prefix, "BURST", params, MyRoot->GetName());
161                                 MyRoot->bursting = true;
162                                 this->DoBurst(MyRoot);
163                         }
164                         else if (command == "ERROR")
165                         {
166                                 this->Error(params);
167                         }
168                         else if (command == "CAPAB")
169                         {
170                                 this->Capab(params);
171                         }
172
173                 break;
174                 case CONNECTING:
175                         /*
176                          * State CONNECTING:
177                          *  We're connecting (OUTGOING) to another server. They are in state WAIT_AUTH_1 until they verify
178                          *  our credentials, when they proceed into WAIT_AUTH_2 and send SERVER to us. We then send BURST
179                          *  + our netburst, which will put them into CONNECTED state. -- w
180                          */
181                         if (command == "SERVER")
182                         {
183                                 // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state)
184                                 this->Outbound_Reply_Server(params);
185                         }
186                         else if (command == "ERROR")
187                         {
188                                 this->Error(params);
189                         }
190                         else if (command == "CAPAB")
191                         {
192                                 this->Capab(params);
193                         }
194                 break;
195                 case CONNECTED:
196                         /*
197                          * State CONNECTED:
198                          *  Credentials have been exchanged, we've gotten their 'BURST' (or sent ours).
199                          *  Anything from here on should be accepted a little more reasonably.
200                          */
201                         this->ProcessConnectedLine(prefix, command, params);
202                 break;
203                 case DYING:
204                 break;
205         }
206 }
207
208 void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params)
209 {
210         User* who = ServerInstance->FindUUID(prefix);
211         std::string direction;
212
213         if (who)
214         {
215                 direction = who->server;
216         }
217         else
218         {
219                 TreeServer* ServerSource = Utils->FindServer(prefix);
220                 if (prefix.empty())
221                         ServerSource = MyRoot;
222
223                 if (ServerSource)
224                 {
225                         who = ServerSource->ServerUser;
226                         direction = prefix;
227                 }
228                 else
229                 {
230                         /* It is important that we don't close the link here, unknown prefix can occur
231                          * due to various race conditions such as the KILL message for a user somehow
232                          * crossing the users QUIT further upstream from the server. Thanks jilles!
233                          */
234                         ServerInstance->Logs->Log("m_spanningtree", DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.",
235                                 command.c_str(), prefix.c_str());
236                         return;
237                 }
238         }
239
240         // Make sure prefix is still good
241         prefix = who->uuid;
242
243         /*
244          * Check for fake direction here, and drop any instances that are found.
245          * What is fake direction? Imagine the following server setup:
246          *    0AA <-> 0AB <-> 0AC
247          * Fake direction would be 0AC sending a message to 0AB claiming to be from
248          * 0AA, or something similar. Basically, a message taking a path that *cannot*
249          * be correct.
250          *
251          * When would this be seen?
252          * Well, hopefully never. It could be caused by race conditions, bugs, or
253          * "miscreant" servers, though, so let's check anyway. -- w
254          *
255          * We also check here for totally invalid prefixes (prefixes that are neither
256          * a valid SID or a valid UUID, so that invalid UUID or SID never makes it
257          * to the higher level functions. -- B
258          */
259         TreeServer* route_back_again = Utils->BestRouteTo(direction);
260         if ((!route_back_again) || (route_back_again->GetSocket() != this))
261         {
262                 if (route_back_again)
263                         ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Protocol violation: Fake direction '%s' from connection '%s'",
264                                 prefix.c_str(),linkID.c_str());
265                 return;
266         }
267
268         /*
269          * First up, check for any malformed commands (e.g. MODE without a timestamp)
270          * and rewrite commands where necessary (SVSMODE -> MODE for services). -- w
271          */
272         if (command == "SVSMODE") // This isn't in an "else if" so we still force FMODE for changes on channels.
273                 command = "MODE";
274
275         // TODO move all this into Commands
276         if (command == "MAP")
277         {
278                 Utils->Creator->HandleMap(params, who);
279         }
280         else if (command == "SERVER")
281         {
282                 this->RemoteServer(prefix,params);
283         }
284         else if (command == "ERROR")
285         {
286                 this->Error(params);
287         }
288         else if (command == "AWAY")
289         {
290                 this->Away(prefix,params);
291         }
292         else if (command == "PING")
293         {
294                 this->LocalPing(prefix,params);
295         }
296         else if (command == "PONG")
297         {
298                 TreeServer *s = Utils->FindServer(prefix);
299                 if (s && s->bursting)
300                 {
301                         ServerInstance->SNO->WriteGlobalSno('l',"Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", prefix.c_str());
302                         s->FinishBurst();
303                 }
304                 this->LocalPong(prefix,params);
305         }
306         else if (command == "VERSION")
307         {
308                 this->ServerVersion(prefix,params);
309         }
310         else if (command == "ADDLINE")
311         {
312                 this->AddLine(prefix,params);
313         }
314         else if (command == "DELLINE")
315         {
316                 this->DelLine(prefix,params);
317         }
318         else if (command == "SAVE")
319         {
320                 this->ForceNick(prefix,params);
321         }
322         else if (command == "OPERQUIT")
323         {
324                 this->OperQuit(prefix,params);
325         }
326         else if (command == "IDLE")
327         {
328                 this->Whois(prefix,params);
329         }
330         else if (command == "PUSH")
331         {
332                 this->Push(prefix,params);
333         }
334         else if (command == "SQUIT")
335         {
336                 if (params.size() == 2)
337                 {
338                         this->Squit(Utils->FindServer(params[0]),params[1]);
339                 }
340         }
341         else if (command == "SNONOTICE")
342         {
343                 if (params.size() >= 2)
344                 {
345                         ServerInstance->SNO->WriteToSnoMask(*(params[0].c_str()), "From " + who->nick + ": "+ params[1]);
346                         Utils->DoOneToAllButSender(prefix, command, params, prefix);
347                 }
348         }
349         else if (command == "BURST")
350         {
351                 // Set prefix server as bursting
352                 TreeServer* ServerSource = Utils->FindServer(prefix);
353                 if (!ServerSource)
354                 {
355                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str());
356                         return;
357                 }
358
359                 ServerSource->bursting = true;
360                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
361         }
362         else if (command == "ENDBURST")
363         {
364                 TreeServer* ServerSource = Utils->FindServer(prefix);
365                 if (!ServerSource)
366                 {
367                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str());
368                         return;
369                 }
370
371                 ServerSource->FinishBurst();
372                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
373         }
374         else if (command == "ENCAP")
375         {
376                 this->Encap(who, params);
377         }
378         else if (command == "NICK")
379         {
380                 if (params.size() != 2)
381                 {
382                         SendError("Protocol violation: NICK message without TS - :"+std::string(who->uuid)+" NICK "+params[0]);
383                         return;
384                 }
385
386                 if (IS_SERVER(who))
387                 {
388                         SendError("Protocol violation: Server changing nick");
389                         return;
390                 }
391
392                 /* Update timestamp on user when they change nicks */
393                 who->age = atoi(params[1].c_str());
394
395                 /*
396                  * On nick messages, check that the nick doesnt already exist here.
397                  * If it does, perform collision logic.
398                  */
399                 User* x = ServerInstance->FindNickOnly(params[0]);
400                 if ((x) && (x != who))
401                 {
402                         int collideret = 0;
403                         /* x is local, who is remote */
404                         collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid);
405                         if (collideret != 1)
406                         {
407                                 /*
408                                  * Remote client lost, or both lost, parsing or passing on this
409                                  * nickchange would be pointless, as the incoming client's server will
410                                  * soon recieve SVSNICK to change its nick to its UID. :) -- w00t
411                                  */
412                                 return;
413                         }
414                 }
415                 who->ForceNickChange(params[0].c_str());
416                 Utils->RouteCommand(route_back_again, command, params, who);
417         }
418         else
419         {
420                 Command* cmd = ServerInstance->Parser->GetHandler(command);
421                 CmdResult res = CMD_INVALID;
422                 if (cmd && params.size() >= cmd->min_params)
423                 {
424                         res = cmd->Handle(params, who);
425                 }
426
427                 if (res == CMD_INVALID)
428                         SendError("Unrecognised or malformed command '" + command + "' -- possibly loaded mismatched modules");
429                 if (res == CMD_SUCCESS)
430                         Utils->RouteCommand(route_back_again, command, params, who);
431         }
432 }
433
434 void TreeSocket::OnTimeout()
435 {
436         ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
437 }
438
439 void TreeSocket::Close()
440 {
441         if (fd != -1)
442                 ServerInstance->GlobalCulls.AddItem(this);
443         this->BufferedSocket::Close();
444         SetError("Remote host closed connection");
445
446         // Connection closed.
447         // If the connection is fully up (state CONNECTED)
448         // then propogate a netsplit to all peers.
449         if (MyRoot)
450                 Squit(MyRoot,getError());
451
452         if (!linkID.empty())
453         {
454                 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str());
455
456                 time_t server_uptime = ServerInstance->Time() - this->age;
457                 if (server_uptime)
458                         ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), Utils->Creator->TimeToStr(server_uptime).c_str());
459                 linkID.clear();
460         }
461 }