]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_spanningtree/treesocket2.cpp
4ea4a929ce0df5d79623aaa3cfc37ac970ba7329
[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", RAWIO, "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][0], "From " + who->nick + ": "+ params[1]);
346                         params[1] = ":" + params[1];
347                         Utils->DoOneToAllButSender(prefix, command, params, prefix);
348                 }
349         }
350         else if (command == "BURST")
351         {
352                 // Set prefix server as bursting
353                 TreeServer* ServerSource = Utils->FindServer(prefix);
354                 if (!ServerSource)
355                 {
356                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str());
357                         return;
358                 }
359
360                 ServerSource->bursting = true;
361                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
362         }
363         else if (command == "ENDBURST")
364         {
365                 TreeServer* ServerSource = Utils->FindServer(prefix);
366                 if (!ServerSource)
367                 {
368                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str());
369                         return;
370                 }
371
372                 ServerSource->FinishBurst();
373                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
374         }
375         else if (command == "ENCAP")
376         {
377                 this->Encap(who, params);
378         }
379         else if (command == "NICK")
380         {
381                 if (params.size() != 2)
382                 {
383                         SendError("Protocol violation: NICK message without TS - :"+std::string(who->uuid)+" NICK "+params[0]);
384                         return;
385                 }
386
387                 if (IS_SERVER(who))
388                 {
389                         SendError("Protocol violation: Server changing nick");
390                         return;
391                 }
392
393                 /* Update timestamp on user when they change nicks */
394                 who->age = atoi(params[1].c_str());
395
396                 /*
397                  * On nick messages, check that the nick doesnt already exist here.
398                  * If it does, perform collision logic.
399                  */
400                 User* x = ServerInstance->FindNickOnly(params[0]);
401                 if ((x) && (x != who))
402                 {
403                         int collideret = 0;
404                         /* x is local, who is remote */
405                         collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid);
406                         if (collideret != 1)
407                         {
408                                 /*
409                                  * Remote client lost, or both lost, parsing or passing on this
410                                  * nickchange would be pointless, as the incoming client's server will
411                                  * soon recieve SVSNICK to change its nick to its UID. :) -- w00t
412                                  */
413                                 return;
414                         }
415                 }
416                 who->ForceNickChange(params[0].c_str());
417                 Utils->RouteCommand(route_back_again, command, params, who);
418         }
419         else
420         {
421                 Command* cmd = ServerInstance->Parser->GetHandler(command);
422                 CmdResult res = CMD_INVALID;
423                 if (cmd && params.size() >= cmd->min_params)
424                 {
425                         res = cmd->Handle(params, who);
426                 }
427
428                 if (res == CMD_INVALID)
429                 {
430                         irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
431                         ServerInstance->Logs->Log("spanningtree", SPARSE, "Invalid S2S command :%s %s %s",
432                                 who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
433                         SendError("Unrecognised or malformed command '" + command + "' -- possibly loaded mismatched modules");
434                 }
435                 if (res == CMD_SUCCESS)
436                         Utils->RouteCommand(route_back_again, command, params, who);
437         }
438 }
439
440 void TreeSocket::OnTimeout()
441 {
442         ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
443 }
444
445 void TreeSocket::Close()
446 {
447         if (fd != -1)
448                 ServerInstance->GlobalCulls.AddItem(this);
449         this->BufferedSocket::Close();
450         SetError("Remote host closed connection");
451
452         // Connection closed.
453         // If the connection is fully up (state CONNECTED)
454         // then propogate a netsplit to all peers.
455         if (MyRoot)
456                 Squit(MyRoot,getError());
457
458         if (!linkID.empty())
459         {
460                 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str());
461
462                 time_t server_uptime = ServerInstance->Time() - this->age;
463                 if (server_uptime)
464                         ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), Utils->Creator->TimeToStr(server_uptime).c_str());
465                 linkID.clear();
466         }
467 }