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