void ModuleSpanningTree::OnChangeName(User* user, const std::string &gecos) { if (user->registered != REG_ALL || !IS_LOCAL(user)) return; CmdBuilder(user, "FNAME").push_last(gecos).Broadcast(); }
void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) { if ((user->registered != REG_ALL) || (!IS_LOCAL(user))) return; CmdBuilder(user, "FIDENT").push(ident).Broadcast(); }
void TreeServer::SQuitChild(TreeServer* server, const std::string& reason) { stdalgo::erase(Children, server); if (IsRoot()) { // Server split from us, generate a SQUIT message and broadcast it ServerInstance->SNO->WriteGlobalSno('l', "Server \002" + server->GetName() + "\002 split: " + reason); CmdBuilder("SQUIT").push(server->GetID()).push_last(reason).Broadcast(); } else { ServerInstance->SNO->WriteToSnoMask('L', "Server \002" + server->GetName() + "\002 split from server \002" + GetName() + "\002 with reason: " + reason); } unsigned int num_lost_servers = 0; server->SQuitInternal(num_lost_servers); const std::string quitreason = GetName() + " " + server->GetName(); unsigned int num_lost_users = QuitUsers(quitreason); ServerInstance->SNO->WriteToSnoMask(IsRoot() ? 'l' : 'L', "Netsplit complete, lost \002%u\002 user%s on \002%u\002 server%s.", num_lost_users, num_lost_users != 1 ? "s" : "", num_lost_servers, num_lost_servers != 1 ? "s" : ""); // No-op if the socket is already closed (i.e. it called us) if (server->IsLocal()) server->GetSocket()->Close(); // Add the server to the cull list, the servers behind it are handled by cull() and the destructor ServerInstance->GlobalCulls.AddItem(server); }
void ModuleSpanningTree::OnChangeHost(User* user, const std::string &newhost) { if (user->registered != REG_ALL || !IS_LOCAL(user)) return; CmdBuilder(user, "FHOST").push(newhost).Broadcast(); }
void ModuleSpanningTree::OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) { if (IS_LOCAL(user)) { if (oper_message != reason) ServerInstance->PI->SendMetaData(user, "operquit", oper_message); CmdBuilder(user, "QUIT").push_last(reason).Broadcast(); } else { // Hide the message if one of the following is true: // - User is being quit due to a netsplit and quietbursts is on // - Server is a silent uline TreeServer* server = TreeServer::Get(user); bool hide = (((server->IsDead()) && (Utils->quiet_bursts)) || (server->IsSilentULine())); if (!hide) { ServerInstance->SNO->WriteToSnoMask('Q', "Client exiting on server %s: %s (%s) [%s]", user->server->GetName().c_str(), user->GetFullRealHost().c_str(), user->GetIPString().c_str(), oper_message.c_str()); } } // Regardless, We need to modify the user Counts.. TreeServer::Get(user)->UserCount--; }
void ModuleSpanningTree::DoPingChecks(time_t curtime) { /* * Cancel remote burst mode on any servers which still have it enabled due to latency/lack of data. * This prevents lost REMOTECONNECT notices */ long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); restart: for (server_hash::iterator i = Utils->serverlist.begin(); i != Utils->serverlist.end(); i++) { TreeServer *s = i->second; // Skip myself if (s->IsRoot()) continue; // Do not ping servers that are not fully connected yet! // Servers which are connected to us have IsLocal() == true and if they're fully connected // then Socket->LinkState == CONNECTED. Servers that are linked to another server are always fully connected. if (s->IsLocal() && s->GetSocket()->GetLinkState() != CONNECTED) continue; // Now do PING checks on all servers // Only ping if this server needs one if (curtime >= s->NextPingTime()) { // And if they answered the last if (s->AnsweredLastPing()) { // They did, send a ping to them s->SetNextPingTime(curtime + Utils->PingFreq); s->GetSocket()->WriteLine(CmdBuilder("PING").push(s->GetID())); s->LastPingMsec = ts; } else { // They didn't answer the last ping, if they are locally connected, get rid of them. if (s->IsLocal()) { TreeSocket* sock = s->GetSocket(); sock->SendError("Ping timeout"); sock->Close(); goto restart; } } } // If warn on ping enabled and not warned and the difference is sufficient and they didn't answer the last ping... if ((Utils->PingWarnTime) && (!s->Warned) && (curtime >= s->NextPingTime() - (Utils->PingFreq - Utils->PingWarnTime)) && (!s->AnsweredLastPing())) { /* The server hasnt responded, send a warning to opers */ ServerInstance->SNO->WriteToSnoMask('l',"Server \002%s\002 has not responded to PING for %d seconds, high latency.", s->GetName().c_str(), Utils->PingWarnTime); s->Warned = true; } } }
void SpanningTreeProtocolInterface::BroadcastEncap(const std::string& cmd, const parameterlist& params, User* source, User* omit) { if (!source) source = ServerInstance->FakeClient; // If omit is non-NULL we pass the route belonging to the user to Forward(), // otherwise we pass NULL, which is equivalent to Broadcast() TreeServer* server = (omit ? TreeServer::Get(omit)->GetRoute() : NULL); CmdBuilder(source, "ENCAP * ").push_raw(cmd).insert(params).Forward(server); }
void ModuleSpanningTree::OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) { if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) { if (oper_message != reason) ServerInstance->PI->SendMetaData(user, "operquit", oper_message); CmdBuilder(user, "QUIT").push_last(reason).Broadcast(); } // Regardless, We need to modify the user Counts.. TreeServer* SourceServer = Utils->FindServer(user->server); if (SourceServer) { SourceServer->UserCount--; } }
ModResult ModuleSpanningTree::HandleRemoteWhois(const std::vector<std::string>& parameters, User* user) { if ((IS_LOCAL(user)) && (parameters.size() > 1)) { User* remote = ServerInstance->FindNickOnly(parameters[1]); if (remote && !IS_LOCAL(remote)) { CmdBuilder(user, "IDLE").push(remote->uuid).Unicast(remote); return MOD_RES_DENY; } else if (!remote) { user->WriteNumeric(401, "%s %s :No such nick/channel",user->nick.c_str(), parameters[1].c_str()); user->WriteNumeric(318, "%s %s :End of /WHOIS list.",user->nick.c_str(), parameters[1].c_str()); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; }
CmdResult CommandIJoin::HandleRemote(RemoteUser* user, std::vector<std::string>& params) { Channel* chan = ServerInstance->FindChan(params[0]); if (!chan) { // Desync detected, recover // Ignore the join and send RESYNC, this will result in the remote server sending all channel data to us ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Received IJOIN for non-existant channel: " + params[0]); CmdBuilder("RESYNC").push(params[0]).Unicast(user); return CMD_FAILURE; } bool apply_modes; if (params.size() > 1) { time_t RemoteTS = ConvToInt(params[1]); if (!RemoteTS) { ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Invalid TS in IJOIN: " + params[1]); return CMD_INVALID; } if (RemoteTS < chan->age) { ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Attempted to lower TS via IJOIN. Channel=" + params[0] + " RemoteTS=" + params[1] + " LocalTS=" + ConvToStr(chan->age)); return CMD_INVALID; } apply_modes = ((params.size() > 2) && (RemoteTS == chan->age)); } else apply_modes = false; chan->ForceJoin(user, apply_modes ? ¶ms[2] : NULL); return CMD_SUCCESS; }
void SpanningTreeProtocolInterface::SendSNONotice(char snomask, const std::string &text) { CmdBuilder("SNONOTICE").push(snomask).push_last(text).Broadcast(); }
void TreeSocket::WriteLine(const std::string& original_line) { if (LinkState == CONNECTED) { if (original_line.c_str()[0] != ':') { ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Sending line without server prefix!"); WriteLine(":" + ServerInstance->Config->GetSID() + " " + original_line); return; } if (proto_version != ProtocolVersion) { std::string line = original_line; std::string::size_type a = line.find(' '); std::string::size_type b = line.find(' ', a + 1); std::string command = line.substr(a + 1, b-a-1); // now try to find a translation entry // TODO a more efficient lookup method will be needed later if (proto_version < 1205) { if (command == "IJOIN") { // Convert // :<uid> IJOIN <chan> <membid> [<ts> [<flags>]] // to // :<sid> FJOIN <chan> <ts> + [<flags>],<uuid> std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = line.find(' ', c + 1); // Erase membership id first line.erase(c, d-c); if (d == std::string::npos) { // No TS or modes in the command // :22DAAAAAB IJOIN #chan const std::string channame = line.substr(b+1, c-b-1); Channel* chan = ServerInstance->FindChan(channame); if (!chan) return; line.push_back(' '); line.append(ConvToStr(chan->age)); line.append(" + ,"); } else { d = line.find(' ', c + 1); if (d == std::string::npos) { // TS present, no modes // :22DAAAAAC IJOIN #chan 12345 line.append(" + ,"); } else { // Both TS and modes are present // :22DAAAAAC IJOIN #chan 12345 ov std::string::size_type e = line.find(' ', d + 1); if (e != std::string::npos) line.erase(e); line.insert(d, " +"); line.push_back(','); } } // Move the uuid to the end and replace the I with an F line.append(line.substr(1, 9)); line.erase(4, 6); line[5] = 'F'; } else if (command == "RESYNC") return; else if (command == "METADATA") { // Drop TS for channel METADATA, translate METADATA operquit into an OPERQUIT command // :sid METADATA #target TS extname ... // A B C D if (b == std::string::npos) return; std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = line.find(' ', c + 1); if (d == std::string::npos) return; if (line[b + 1] == '#') { // We're sending channel metadata line.erase(c, d-c); } else if (!line.compare(c, d-c, " operquit", 9)) { // ":22D METADATA 22DAAAAAX operquit :message" -> ":22DAAAAAX OPERQUIT :message" line = ":" + line.substr(b+1, c-b) + "OPERQUIT" + line.substr(d); } } else if (command == "FTOPIC") { // Drop channel TS for FTOPIC // :sid FTOPIC #target TS TopicTS setter :newtopic // A B C D E F // :uid FTOPIC #target TS TopicTS :newtopic // A B C D E if (b == std::string::npos) return; std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = line.find(' ', c + 1); if (d == std::string::npos) return; std::string::size_type e = line.find(' ', d + 1); if (line[e+1] == ':') { line.erase(c, e-c); line.erase(a+1, 1); } else line.erase(c, d-c); } else if ((command == "PING") || (command == "PONG")) { // :22D PING 20D if (line.length() < 13) return; // Insert the source SID (and a space) between the command and the first parameter line.insert(10, line.substr(1, 4)); } else if (command == "OPERTYPE") { std::string::size_type colon = line.find(':', b); if (colon != std::string::npos) { for (std::string::iterator i = line.begin()+colon; i != line.end(); ++i) { if (*i == ' ') *i = '_'; } line.erase(colon, 1); } } else if (command == "INVITE") { // :22D INVITE 22DAAAAAN #chan TS ExpirationTime // A B C D E if (b == std::string::npos) return; std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = line.find(' ', c + 1); if (d == std::string::npos) return; std::string::size_type e = line.find(' ', d + 1); // If there is no expiration time then everything will be erased from 'd' line.erase(d, e-d); } else if (command == "FJOIN") { // Strip membership ids // :22D FJOIN #chan 1234 +f 4:3 :o,22DAAAAAB:15 o,22DAAAAAA:15 // :22D FJOIN #chan 1234 +f 4:3 o,22DAAAAAB:15 // :22D FJOIN #chan 1234 +Pf 4:3 : // If the last parameter is prefixed by a colon then it's a userlist which may have 0 or more users; // if it isn't, then it is a single member std::string::size_type spcolon = line.find(" :"); if (spcolon != std::string::npos) { spcolon++; // Loop while there is a ':' in the userlist, this is never true if the channel is empty std::string::size_type pos = std::string::npos; while ((pos = line.rfind(':', pos-1)) > spcolon) { // Find the next space after the ':' std::string::size_type sp = line.find(' ', pos); // Erase characters between the ':' and the next space after it, including the ':' but not the space; // if there is no next space, everything will be erased between pos and the end of the line line.erase(pos, sp-pos); } } else { // Last parameter is a single member std::string::size_type sp = line.rfind(' '); std::string::size_type colon = line.find(':', sp); line.erase(colon); } } else if (command == "KICK") { // Strip membership id if the KICK has one if (b == std::string::npos) return; std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = line.find(' ', c + 1); if ((d < line.size()-1) && (original_line[d+1] != ':')) { // There is a third parameter which doesn't begin with a colon, erase it std::string::size_type e = line.find(' ', d + 1); line.erase(d, e-d); } } else if (command == "SINFO") { // :22D SINFO version :InspIRCd-2.2 // A B C std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; // Only translating SINFO version, discard everything else if (line.compare(b, 9, " version ", 9)) return; line = line.substr(0, 5) + "VERSION" + line.substr(c); } else if (command == "SERVER") { // :001 SERVER inspircd.test 002 [<anything> ...] :gecos // A B C std::string::size_type c = line.find(' ', b + 1); if (c == std::string::npos) return; std::string::size_type d = c + 4; std::string::size_type spcolon = line.find(" :", d); if (spcolon == std::string::npos) return; line.erase(d, spcolon-d); line.insert(c, " * 0"); if (burstsent) { WriteLineNoCompat(line); // Synthesize a :<newserver> BURST <time> message spcolon = line.find(" :"); line = CmdBuilder(line.substr(spcolon-3, 3), "BURST").push_int(ServerInstance->Time()).str(); } } } WriteLineNoCompat(line); return; } } WriteLineNoCompat(original_line); }
void TreeSocket::ProcessLine(std::string &line) { std::string prefix; std::string command; parameterlist params; ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str()); Split(line, prefix, command, params); if (command.empty()) return; switch (this->LinkState) { case WAIT_AUTH_1: /* * State WAIT_AUTH_1: * Waiting for SERVER command from remote server. Server initiating * the connection sends the first SERVER command, listening server * replies with theirs if its happy, then if the initiator is happy, * it starts to send its net sync, which starts the merge, otherwise * it sends an ERROR. */ if (command == "PASS") { /* * Ignore this silently. Some services packages insist on sending PASS, even * when it is not required (i.e. by us). We have to ignore this here, otherwise * as it's an unknown command (effectively), it will cause the connection to be * closed, which probably isn't what people want. -- w00t */ } else if (command == "SERVER") { this->Inbound_Server(params); } else if (command == "ERROR") { this->Error(params); } else if (command == "USER") { this->SendError("Client connections to this port are prohibited."); } else if (command == "CAPAB") { this->Capab(params); } else { this->SendError("Invalid command in negotiation phase: " + command); } break; case WAIT_AUTH_2: /* * State WAIT_AUTH_2: * We have sent SERVER to the other side of the connection. Now we're waiting for them to start BURST. * The other option at this stage of things, of course, is for them to close our connection thanks * to invalid credentials.. -- w */ if (command == "SERVER") { /* * Connection is either attempting to re-auth itself (stupid) or sending netburst without sending BURST. * Both of these aren't allowable, so block them here. -- w */ this->SendError("You may not re-authenticate or commence netburst without sending BURST."); } else if (command == "BURST") { if (params.size()) { time_t them = ConvToInt(params[0]); time_t delta = them - ServerInstance->Time(); if ((delta < -600) || (delta > 600)) { 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)); SendError("Your clocks are out by "+ConvToStr(abs((long)delta))+" seconds (this is more than five minutes). Link aborted, PLEASE SYNC YOUR CLOCKS!"); return; } else if ((delta < -30) || (delta > 30)) { ServerInstance->SNO->WriteGlobalSno('l',"\2WARNING\2: Your clocks are out by %d seconds. Please consider synching your clocks.", abs((long)delta)); } } // Check for duplicate server name/sid again, it's possible that a new // server was introduced while we were waiting for them to send BURST. // (we do not reserve their server name/sid when they send SERVER, we do it now) if (!CheckDuplicate(capab->name, capab->sid)) return; this->LinkState = CONNECTED; Utils->timeoutlist.erase(this); linkID = capab->name; MyRoot = new TreeServer(capab->name, capab->description, capab->sid, Utils->TreeRoot, this, capab->hidden); Utils->TreeRoot->AddChild(MyRoot); MyRoot->bursting = true; this->DoBurst(MyRoot); CommandServer::Builder(MyRoot).Forward(MyRoot); CmdBuilder(MyRoot->GetID(), "BURST").insert(params).Forward(MyRoot); } else if (command == "ERROR") { this->Error(params); } else if (command == "CAPAB") { this->Capab(params); } break; case CONNECTING: /* * State CONNECTING: * We're connecting (OUTGOING) to another server. They are in state WAIT_AUTH_1 until they verify * our credentials, when they proceed into WAIT_AUTH_2 and send SERVER to us. We then send BURST * + our netburst, which will put them into CONNECTED state. -- w */ if (command == "SERVER") { // Our credentials have been accepted, send netburst. (this puts US into the CONNECTED state) this->Outbound_Reply_Server(params); } else if (command == "ERROR") { this->Error(params); } else if (command == "CAPAB") { this->Capab(params); } break; case CONNECTED: /* * State CONNECTED: * Credentials have been exchanged, we've gotten their 'BURST' (or sent ours). * Anything from here on should be accepted a little more reasonably. */ this->ProcessConnectedLine(prefix, command, params); break; case DYING: break; } }
void SpanningTreeProtocolInterface::PushToClient(User* target, const std::string &rawline) { CmdBuilder("PUSH").push(target->uuid).push_last(rawline).Unicast(target); }