]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_spanningtree/treesocket2.cpp
835170da20dd0f6d83fea72501efa8e983aec627
[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(MyRoot->GetID(), "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                 TreeServer* ServerSource = Utils->FindServer(prefix);
216                 if (prefix.empty())
217                         ServerSource = MyRoot;
218
219                 if (ServerSource)
220                 {
221                         who = ServerSource->ServerUser;
222                 }
223                 else
224                 {
225                         /* It is important that we don't close the link here, unknown prefix can occur
226                          * due to various race conditions such as the KILL message for a user somehow
227                          * crossing the users QUIT further upstream from the server. Thanks jilles!
228                          */
229                         ServerInstance->Logs->Log("m_spanningtree", DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.",
230                                 command.c_str(), prefix.c_str());
231                         return;
232                 }
233         }
234
235         // Make sure prefix is still good
236         direction = who->server;
237         prefix = who->uuid;
238
239         /*
240          * Check for fake direction here, and drop any instances that are found.
241          * What is fake direction? Imagine the following server setup:
242          *    0AA <-> 0AB <-> 0AC
243          * Fake direction would be 0AC sending a message to 0AB claiming to be from
244          * 0AA, or something similar. Basically, a message taking a path that *cannot*
245          * be correct.
246          *
247          * When would this be seen?
248          * Well, hopefully never. It could be caused by race conditions, bugs, or
249          * "miscreant" servers, though, so let's check anyway. -- w
250          *
251          * We also check here for totally invalid prefixes (prefixes that are neither
252          * a valid SID or a valid UUID, so that invalid UUID or SID never makes it
253          * to the higher level functions. -- B
254          */
255         TreeServer* route_back_again = Utils->BestRouteTo(direction);
256         if ((!route_back_again) || (route_back_again->GetSocket() != this))
257         {
258                 if (route_back_again)
259                         ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Protocol violation: Fake direction '%s' from connection '%s'",
260                                 prefix.c_str(),linkID.c_str());
261                 return;
262         }
263
264         /*
265          * First up, check for any malformed commands (e.g. MODE without a timestamp)
266          * and rewrite commands where necessary (SVSMODE -> MODE for services). -- w
267          */
268         if (command == "SVSMODE") // This isn't in an "else if" so we still force FMODE for changes on channels.
269                 command = "MODE";
270
271         // TODO move all this into Commands
272         if (command == "MAP")
273         {
274                 Utils->Creator->HandleMap(params, who);
275         }
276         else if (command == "SERVER")
277         {
278                 this->RemoteServer(prefix,params);
279         }
280         else if (command == "ERROR")
281         {
282                 this->Error(params);
283         }
284         else if (command == "AWAY")
285         {
286                 this->Away(prefix,params);
287         }
288         else if (command == "PING")
289         {
290                 this->LocalPing(prefix,params);
291         }
292         else if (command == "PONG")
293         {
294                 TreeServer *s = Utils->FindServer(prefix);
295                 if (s && s->bursting)
296                 {
297                         ServerInstance->SNO->WriteGlobalSno('l',"Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", prefix.c_str());
298                         s->FinishBurst();
299                 }
300                 this->LocalPong(prefix,params);
301         }
302         else if (command == "VERSION")
303         {
304                 this->ServerVersion(prefix,params);
305         }
306         else if (command == "ADDLINE")
307         {
308                 this->AddLine(prefix,params);
309         }
310         else if (command == "DELLINE")
311         {
312                 this->DelLine(prefix,params);
313         }
314         else if (command == "SAVE")
315         {
316                 this->ForceNick(prefix,params);
317         }
318         else if (command == "OPERQUIT")
319         {
320                 this->OperQuit(prefix,params);
321         }
322         else if (command == "IDLE")
323         {
324                 this->Whois(prefix,params);
325         }
326         else if (command == "PUSH")
327         {
328                 this->Push(prefix,params);
329         }
330         else if (command == "SQUIT")
331         {
332                 if (params.size() == 2)
333                 {
334                         this->Squit(Utils->FindServer(params[0]),params[1]);
335                 }
336         }
337         else if (command == "SNONOTICE")
338         {
339                 if (params.size() >= 2)
340                 {
341                         ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + who->nick + ": "+ params[1]);
342                         params[1] = ":" + params[1];
343                         Utils->DoOneToAllButSender(prefix, command, params, prefix);
344                 }
345         }
346         else if (command == "BURST")
347         {
348                 // Set prefix server as bursting
349                 TreeServer* ServerSource = Utils->FindServer(prefix);
350                 if (!ServerSource)
351                 {
352                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str());
353                         return;
354                 }
355
356                 ServerSource->bursting = true;
357                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
358         }
359         else if (command == "ENDBURST")
360         {
361                 TreeServer* ServerSource = Utils->FindServer(prefix);
362                 if (!ServerSource)
363                 {
364                         ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str());
365                         return;
366                 }
367
368                 ServerSource->FinishBurst();
369                 Utils->DoOneToAllButSender(prefix, command, params, prefix);
370         }
371         else if (command == "ENCAP")
372         {
373                 this->Encap(who, params);
374         }
375         else if (command == "NICK")
376         {
377                 if (params.size() != 2)
378                 {
379                         SendError("Protocol violation: NICK message without TS - :"+std::string(who->uuid)+" NICK "+params[0]);
380                         return;
381                 }
382
383                 if (IS_SERVER(who))
384                 {
385                         SendError("Protocol violation: Server changing nick");
386                         return;
387                 }
388
389                 /* Update timestamp on user when they change nicks */
390                 who->age = atoi(params[1].c_str());
391
392                 /*
393                  * On nick messages, check that the nick doesnt already exist here.
394                  * If it does, perform collision logic.
395                  */
396                 User* x = ServerInstance->FindNickOnly(params[0]);
397                 if ((x) && (x != who))
398                 {
399                         int collideret = 0;
400                         /* x is local, who is remote */
401                         collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid);
402                         if (collideret != 1)
403                         {
404                                 /*
405                                  * Remote client lost, or both lost, parsing or passing on this
406                                  * nickchange would be pointless, as the incoming client's server will
407                                  * soon recieve SVSNICK to change its nick to its UID. :) -- w00t
408                                  */
409                                 return;
410                         }
411                 }
412                 who->ForceNickChange(params[0].c_str());
413                 Utils->RouteCommand(route_back_again, command, params, who);
414         }
415         else
416         {
417                 Command* cmd = ServerInstance->Parser->GetHandler(command);
418                 
419                 if (!cmd)
420                 {
421                         irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
422                         ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised S2S command :%s %s %s",
423                                 who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
424                         SendError("Unrecognised command '" + command + "' -- possibly loaded mismatched modules");
425                 }
426
427                 if (params.size() < cmd->min_params)
428                 {
429                         irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
430                         ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Insufficient parameters for S2S command :%s %s %s",
431                                 who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
432                         SendError("Insufficient parameters for command '" + command + "'");
433                 }
434
435                 CmdResult res = cmd->Handle(params, who);
436
437                 if (res == CMD_INVALID)
438                 {
439                         irc::stringjoiner pmlist(" ", params, 0, params.size() - 1);
440                         ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Error handing S2S command :%s %s %s",
441                                 who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str());
442                         SendError("Error handling '" + command + "' -- possibly loaded mismatched modules");
443                 }
444                 if (res == CMD_SUCCESS)
445                         Utils->RouteCommand(route_back_again, command, params, who);
446         }
447 }
448
449 void TreeSocket::OnTimeout()
450 {
451         ServerInstance->SNO->WriteGlobalSno('l', "CONNECT: Connection to \002%s\002 timed out.", linkID.c_str());
452 }
453
454 void TreeSocket::Close()
455 {
456         if (fd != -1)
457                 ServerInstance->GlobalCulls.AddItem(this);
458         this->BufferedSocket::Close();
459         SetError("Remote host closed connection");
460
461         // Connection closed.
462         // If the connection is fully up (state CONNECTED)
463         // then propogate a netsplit to all peers.
464         if (MyRoot)
465                 Squit(MyRoot,getError());
466
467         if (!linkID.empty())
468         {
469                 ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str());
470
471                 time_t server_uptime = ServerInstance->Time() - this->age;
472                 if (server_uptime)
473                         ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), Utils->Creator->TimeToStr(server_uptime).c_str());
474                 linkID.clear();
475         }
476 }