diff options
Diffstat (limited to 'src/modules/m_spanningtree')
63 files changed, 4239 insertions, 3624 deletions
diff --git a/src/modules/m_spanningtree/addline.cpp b/src/modules/m_spanningtree/addline.cpp index 16043b2aa..1bf847604 100644 --- a/src/modules/m_spanningtree/addline.cpp +++ b/src/modules/m_spanningtree/addline.cpp @@ -20,60 +20,37 @@ #include "inspircd.h" #include "xline.h" -#include "treesocket.h" #include "treeserver.h" #include "utils.h" +#include "commands.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::AddLine(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandAddLine::Handle(User* usr, std::vector<std::string>& params) { - if (params.size() < 6) - { - std::string servername = MyRoot->GetName(); - ServerInstance->SNO->WriteToSnoMask('d', "%s sent me a malformed ADDLINE", servername.c_str()); - return true; - } - XLineFactory* xlf = ServerInstance->XLines->GetFactory(params[0]); - - std::string setter = "<unknown>"; - User* usr = ServerInstance->FindNick(prefix); - if (usr) - setter = usr->nick; - else - { - TreeServer* t = Utils->FindServer(prefix); - if (t) - setter = t->GetName(); - } + const std::string& setter = usr->nick; if (!xlf) { ServerInstance->SNO->WriteToSnoMask('d',"%s sent me an unknown ADDLINE type (%s).",setter.c_str(),params[0].c_str()); - return true; + return CMD_FAILURE; } - long created = atol(params[3].c_str()), expires = atol(params[4].c_str()); - if (created < 0 || expires < 0) - return true; - XLine* xl = NULL; try { - xl = xlf->Generate(ServerInstance->Time(), expires, params[2], params[5], params[1]); + xl = xlf->Generate(ServerInstance->Time(), ConvToInt(params[4]), params[2], params[5], params[1]); } catch (ModuleException &e) { - ServerInstance->SNO->WriteToSnoMask('d',"Unable to ADDLINE type %s from %s: %s", params[0].c_str(), setter.c_str(), e.GetReason()); - return true; + ServerInstance->SNO->WriteToSnoMask('d',"Unable to ADDLINE type %s from %s: %s", params[0].c_str(), setter.c_str(), e.GetReason().c_str()); + return CMD_FAILURE; } - xl->SetCreateTime(created); + xl->SetCreateTime(ConvToInt(params[3])); if (ServerInstance->XLines->AddLine(xl, NULL)) { if (xl->duration) { - std::string timestr = ServerInstance->TimeString(xl->expiry); + std::string timestr = InspIRCd::TimeString(xl->expiry); ServerInstance->SNO->WriteToSnoMask('X',"%s added %s%s on %s to expire on %s: %s",setter.c_str(),params[0].c_str(),params[0].length() == 1 ? "-line" : "", params[1].c_str(), timestr.c_str(), params[5].c_str()); } @@ -82,20 +59,29 @@ bool TreeSocket::AddLine(const std::string &prefix, parameterlist ¶ms) ServerInstance->SNO->WriteToSnoMask('X',"%s added permanent %s%s on %s: %s",setter.c_str(),params[0].c_str(),params[0].length() == 1 ? "-line" : "", params[1].c_str(),params[5].c_str()); } - params[5] = ":" + params[5]; - User* u = ServerInstance->FindNick(prefix); - Utils->DoOneToAllButSender(prefix, "ADDLINE", params, u ? u->server : prefix); - TreeServer *remoteserver = Utils->FindServer(u ? u->server : prefix); + TreeServer* remoteserver = TreeServer::Get(usr); - if (!remoteserver->bursting) + if (!remoteserver->IsBursting()) { ServerInstance->XLines->ApplyLines(); } + return CMD_SUCCESS; } else + { delete xl; - - return true; + return CMD_FAILURE; + } } +CommandAddLine::Builder::Builder(XLine* xline, User* user) + : CmdBuilder(user, "ADDLINE") +{ + push(xline->type); + push(xline->Displayable()); + push(xline->source); + push_int(xline->set_time); + push_int(xline->duration); + push_last(xline->reason); +} diff --git a/src/modules/m_spanningtree/away.cpp b/src/modules/m_spanningtree/away.cpp index ed97c48cd..7c514c49e 100644 --- a/src/modules/m_spanningtree/away.cpp +++ b/src/modules/m_spanningtree/away.cpp @@ -21,32 +21,38 @@ #include "main.h" #include "utils.h" -#include "treeserver.h" -#include "treesocket.h" +#include "commands.h" -bool TreeSocket::Away(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandAway::HandleRemote(::RemoteUser* u, std::vector<std::string>& params) { - User* u = ServerInstance->FindNick(prefix); - if ((!u) || (IS_SERVER(u))) - return true; if (params.size()) { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, params[params.size() - 1])); + FOREACH_MOD(OnSetAway, (u, params.back())); if (params.size() > 1) - u->awaytime = atoi(params[0].c_str()); + u->awaytime = ConvToInt(params[0]); else u->awaytime = ServerInstance->Time(); - u->awaymsg = params[params.size() - 1]; - - params[params.size() - 1] = ":" + params[params.size() - 1]; + u->awaymsg = params.back(); } else { - FOREACH_MOD(I_OnSetAway, OnSetAway(u, "")); + FOREACH_MOD(OnSetAway, (u, "")); u->awaymsg.clear(); } - Utils->DoOneToAllButSender(prefix,"AWAY",params,u->server); - return true; + return CMD_SUCCESS; +} + +CommandAway::Builder::Builder(User* user) + : CmdBuilder(user, "AWAY") +{ + push_int(user->awaytime).push_last(user->awaymsg); +} + +CommandAway::Builder::Builder(User* user, const std::string& msg) + : CmdBuilder(user, "AWAY") +{ + if (!msg.empty()) + push_int(ServerInstance->Time()).push_last(msg); } diff --git a/src/modules/m_spanningtree/cachetimer.h b/src/modules/m_spanningtree/cachetimer.h index bad1b7419..cffbe3578 100644 --- a/src/modules/m_spanningtree/cachetimer.h +++ b/src/modules/m_spanningtree/cachetimer.h @@ -17,25 +17,13 @@ */ -#ifndef M_SPANNINGTREE_CACHETIMER_H -#define M_SPANNINGTREE_CACHETIMER_H +#pragma once -#include "timer.h" - -class ModuleSpanningTree; -class SpanningTreeUtilities; - -/** Create a timer which recurs every second, we inherit from Timer. - * Timer is only one-shot however, so at the end of each Tick() we simply - * insert another of ourselves into the pending queue :) +/** Timer that fires when we need to refresh the IP cache of servers */ class CacheRefreshTimer : public Timer { - private: - SpanningTreeUtilities *Utils; public: - CacheRefreshTimer(SpanningTreeUtilities* Util); - virtual void Tick(time_t TIME); + CacheRefreshTimer(); + bool Tick(time_t TIME); }; - -#endif diff --git a/src/modules/m_spanningtree/capab.cpp b/src/modules/m_spanningtree/capab.cpp index 0ab815fef..d22481518 100644 --- a/src/modules/m_spanningtree/capab.cpp +++ b/src/modules/m_spanningtree/capab.cpp @@ -20,39 +20,61 @@ #include "inspircd.h" -#include "xline.h" -#include "treesocket.h" #include "treeserver.h" #include "utils.h" #include "link.h" #include "main.h" -#include "../hash.h" -std::string TreeSocket::MyModules(int filter) +struct CompatMod +{ + const char* name; + ModuleFlags listflag; +}; + +static CompatMod compatmods[] = { - std::vector<std::string> modlist = ServerInstance->Modules->GetAllModuleNames(filter); + { "m_watch.so", VF_OPTCOMMON } +}; - if (filter == VF_COMMON && proto_version != ProtocolVersion) - CompatAddModules(modlist); +std::string TreeSocket::MyModules(int filter) +{ + const ModuleManager::ModuleMap& modlist = ServerInstance->Modules->GetModules(); std::string capabilities; - sort(modlist.begin(),modlist.end()); - for (std::vector<std::string>::const_iterator i = modlist.begin(); i != modlist.end(); ++i) + for (ModuleManager::ModuleMap::const_iterator i = modlist.begin(); i != modlist.end(); ++i) { - if (i != modlist.begin()) - capabilities.push_back(proto_version > 1201 ? ' ' : ','); - capabilities.append(*i); - Module* m = ServerInstance->Modules->Find(*i); - if (m && proto_version > 1201) + Module* const mod = i->second; + // 3.0 advertises its settings for the benefit of services + // 2.0 would bork on this + if (proto_version < 1205 && i->second->ModuleSourceFile == "m_kicknorejoin.so") + continue; + + bool do_compat_include = false; + if (proto_version < 1205) { - Version v = m->GetVersion(); - if (!v.link_data.empty()) + for (size_t j = 0; j < sizeof(compatmods)/sizeof(compatmods[0]); j++) { - capabilities.push_back('='); - capabilities.append(v.link_data); + if ((compatmods[j].listflag & filter) && (mod->ModuleSourceFile == compatmods[j].name)) + { + do_compat_include = true; + break; + } } } + + Version v = i->second->GetVersion(); + if ((!do_compat_include) && (!(v.Flags & filter))) + continue; + + if (i != modlist.begin()) + capabilities.push_back(' '); + capabilities.append(i->first); + if (!v.link_data.empty()) + { + capabilities.push_back('='); + capabilities.append(v.link_data); + } } return capabilities; } @@ -60,23 +82,23 @@ std::string TreeSocket::MyModules(int filter) static std::string BuildModeList(ModeType type) { std::vector<std::string> modes; - for(char c='A'; c <= 'z'; c++) + const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes.GetModes(type); + for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i) { - ModeHandler* mh = ServerInstance->Modes->FindMode(c, type); - if (mh) + const ModeHandler* const mh = i->second; + std::string mdesc = mh->name; + mdesc.push_back('='); + const PrefixMode* const pm = mh->IsPrefixMode(); + if (pm) { - std::string mdesc = mh->name; - mdesc.push_back('='); - if (mh->GetPrefix()) - mdesc.push_back(mh->GetPrefix()); - if (mh->GetModeChar()) - mdesc.push_back(mh->GetModeChar()); - modes.push_back(mdesc); + if (pm->GetPrefix()) + mdesc.push_back(pm->GetPrefix()); } + mdesc.push_back(mh->GetModeChar()); + modes.push_back(mdesc); } - sort(modes.begin(), modes.end()); - irc::stringjoiner line(" ", modes, 0, modes.size() - 1); - return line.GetJoined(); + std::sort(modes.begin(), modes.end()); + return irc::stringjoiner(modes); } void TreeSocket::SendCapabilities(int phase) @@ -91,7 +113,7 @@ void TreeSocket::SendCapabilities(int phase) if (phase < 2) return; - char sep = proto_version > 1201 ? ' ' : ','; + const char sep = ' '; irc::sepstream modulelist(MyModules(VF_COMMON), sep); irc::sepstream optmodulelist(MyModules(VF_OPTCOMMON), sep); /* Send module names, split at 509 length */ @@ -135,13 +157,15 @@ void TreeSocket::SendCapabilities(int phase) std::string extra; /* Do we have sha256 available? If so, we send a challenge */ - if (Utils->ChallengeResponse && (ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"))) + if (ServerInstance->Modules->FindService(SERVICE_DATA, "hash/sha256")) { SetOurChallenge(ServerInstance->GenRandomStr(20)); extra = " CHALLENGE=" + this->GetOurChallenge(); } - if (proto_version < 1202) - extra += ServerInstance->Modes->FindMode('h', MODETYPE_CHANNEL) ? " HALFOP=1" : " HALFOP=0"; + + // 2.0 needs this key + if (proto_version == 1202) + extra.append(" PROTOCOL="+ConvToStr(ProtocolVersion)); this->WriteLine("CAPAB CAPABILITIES " /* Preprocessor does this one. */ ":NICKMAX="+ConvToStr(ServerInstance->Config->Limits.NickMax)+ @@ -153,18 +177,18 @@ void TreeSocket::SendCapabilities(int phase) " MAXKICK="+ConvToStr(ServerInstance->Config->Limits.MaxKick)+ " MAXGECOS="+ConvToStr(ServerInstance->Config->Limits.MaxGecos)+ " MAXAWAY="+ConvToStr(ServerInstance->Config->Limits.MaxAway)+ - " IP6SUPPORT=1"+ - " PROTOCOL="+ConvToStr(ProtocolVersion)+extra+ + " MAXHOST="+ConvToStr(ServerInstance->Config->Limits.MaxHost)+ + extra+ " PREFIX="+ServerInstance->Modes->BuildPrefixes()+ - " CHANMODES="+ServerInstance->Modes->GiveModeList(MASK_CHANNEL)+ - " USERMODES="+ServerInstance->Modes->GiveModeList(MASK_USER)+ + " CHANMODES="+ServerInstance->Modes->GiveModeList(MODETYPE_CHANNEL)+ + " USERMODES="+ServerInstance->Modes->GiveModeList(MODETYPE_USER)+ // XXX: Advertise the presence or absence of m_globops in CAPAB CAPABILITIES. // Services want to know about it, and since m_globops was not marked as VF_(OPT)COMMON // in 2.0, we advertise it here to not break linking to previous versions. // Protocol version 1201 (1.2) does not have this issue because we advertise m_globops // to 1201 protocol servers irrespectively of its module flags. - (ServerInstance->Modules->Find("m_globops.so") != NULL ? " GLOBOPS=1" : " GLOBOPS=0")+ - " SVSPART=1"); + (ServerInstance->Modules->Find("m_globops.so") != NULL ? " GLOBOPS=1" : " GLOBOPS=0") + ); this->WriteLine("CAPAB END"); } @@ -209,7 +233,23 @@ bool TreeSocket::Capab(const parameterlist ¶ms) capab->OptModuleList.clear(); capab->CapKeys.clear(); if (params.size() > 1) - proto_version = atoi(params[1].c_str()); + proto_version = ConvToInt(params[1]); + + if (proto_version < MinCompatProtocol) + { + SendError("CAPAB negotiation failed: Server is using protocol version " + (proto_version ? ConvToStr(proto_version) : "1201 or older") + + " which is too old to link with this server (version " + ConvToStr(ProtocolVersion) + + (ProtocolVersion != MinCompatProtocol ? ", links with " + ConvToStr(MinCompatProtocol) + " and above)" : ")")); + return false; + } + + // Special case, may be removed in the future + if (proto_version == 1203 || proto_version == 1204) + { + SendError("CAPAB negotiation failed: InspIRCd 2.1 beta is not supported"); + return false; + } + SendCapabilities(2); } else if (params[0] == "END") @@ -219,7 +259,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) if ((this->capab->ModuleList != this->MyModules(VF_COMMON)) && (this->capab->ModuleList.length())) { std::string diffIneed, diffUneed; - ListDifference(this->capab->ModuleList, this->MyModules(VF_COMMON), proto_version > 1201 ? ' ' : ',', diffIneed, diffUneed); + ListDifference(this->capab->ModuleList, this->MyModules(VF_COMMON), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { reason = "Modules incorrectly matched on these servers."; @@ -257,21 +297,6 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } } - if (this->capab->CapKeys.find("PROTOCOL") == this->capab->CapKeys.end()) - { - reason = "Protocol version not specified"; - } - else - { - proto_version = atoi(capab->CapKeys.find("PROTOCOL")->second.c_str()); - if (proto_version < MinCompatProtocol) - { - reason = "Server is using protocol version " + ConvToStr(proto_version) + - " which is too old to link with this server (version " + ConvToStr(ProtocolVersion) - + (ProtocolVersion != MinCompatProtocol ? ", links with " + ConvToStr(MinCompatProtocol) + " and above)" : ")"); - } - } - if(this->capab->CapKeys.find("PREFIX") != this->capab->CapKeys.end() && this->capab->CapKeys.find("PREFIX")->second != ServerInstance->Modes->BuildPrefixes()) reason = "One or more of the prefixes on the remote server are invalid on this server."; @@ -293,7 +318,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else if (this->capab->CapKeys.find("CHANMODES") != this->capab->CapKeys.end()) { - if (this->capab->CapKeys.find("CHANMODES")->second != ServerInstance->Modes->GiveModeList(MASK_CHANNEL)) + if (this->capab->CapKeys.find("CHANMODES")->second != ServerInstance->Modes->GiveModeList(MODETYPE_CHANNEL)) reason = "One or more of the channel modes on the remote server are invalid on this server."; } @@ -315,13 +340,13 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else if (this->capab->CapKeys.find("USERMODES") != this->capab->CapKeys.end()) { - if (this->capab->CapKeys.find("USERMODES")->second != ServerInstance->Modes->GiveModeList(MASK_USER)) + if (this->capab->CapKeys.find("USERMODES")->second != ServerInstance->Modes->GiveModeList(MODETYPE_USER)) reason = "One or more of the user modes on the remote server are invalid on this server."; } /* Challenge response, store their challenge for our password */ std::map<std::string,std::string>::iterator n = this->capab->CapKeys.find("CHALLENGE"); - if (Utils->ChallengeResponse && (n != this->capab->CapKeys.end()) && (ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"))) + if ((n != this->capab->CapKeys.end()) && (ServerInstance->Modules->FindService(SERVICE_DATA, "hash/sha256"))) { /* Challenge-response is on now */ this->SetTheirChallenge(n->second); @@ -333,7 +358,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else { - /* They didnt specify a challenge or we don't have m_sha256.so, we use plaintext */ + // They didn't specify a challenge or we don't have sha256, we use plaintext if (this->LinkState == CONNECTING) { this->SendCapabilities(2); @@ -355,7 +380,7 @@ bool TreeSocket::Capab(const parameterlist ¶ms) } else { - capab->ModuleList.push_back(proto_version > 1201 ? ' ' : ','); + capab->ModuleList.push_back(' '); capab->ModuleList.append(params[1]); } } @@ -389,12 +414,11 @@ bool TreeSocket::Capab(const parameterlist ¶ms) std::string::size_type equals = item.find('='); if (equals != std::string::npos) { - std::string var = item.substr(0, equals); - std::string value = item.substr(equals+1, item.length()); + std::string var(item, 0, equals); + std::string value(item, equals+1); capab->CapKeys[var] = value; } } } return true; } - diff --git a/src/modules/m_spanningtree/commandbuilder.h b/src/modules/m_spanningtree/commandbuilder.h new file mode 100644 index 000000000..59de84052 --- /dev/null +++ b/src/modules/m_spanningtree/commandbuilder.h @@ -0,0 +1,149 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "utils.h" + +class TreeServer; + +class CmdBuilder +{ + protected: + std::string content; + + public: + CmdBuilder(const char* cmd) + : content(1, ':') + { + content.append(ServerInstance->Config->GetSID()); + push(cmd); + } + + CmdBuilder(const std::string& src, const char* cmd) + : content(1, ':') + { + content.append(src); + push(cmd); + } + + CmdBuilder(User* src, const char* cmd) + : content(1, ':') + { + content.append(src->uuid); + push(cmd); + } + + CmdBuilder& push_raw(const std::string& s) + { + content.append(s); + return *this; + } + + CmdBuilder& push_raw(const char* s) + { + content.append(s); + return *this; + } + + CmdBuilder& push_raw(char c) + { + content.push_back(c); + return *this; + } + + template <typename T> + CmdBuilder& push_raw_int(T i) + { + content.append(ConvToStr(i)); + return *this; + } + + template <typename InputIterator> + CmdBuilder& push_raw(InputIterator first, InputIterator last) + { + content.append(first, last); + return *this; + } + + CmdBuilder& push(const std::string& s) + { + content.push_back(' '); + content.append(s); + return *this; + } + + CmdBuilder& push(const char* s) + { + content.push_back(' '); + content.append(s); + return *this; + } + + CmdBuilder& push(char c) + { + content.push_back(' '); + content.push_back(c); + return *this; + } + + template <typename T> + CmdBuilder& push_int(T i) + { + content.push_back(' '); + content.append(ConvToStr(i)); + return *this; + } + + CmdBuilder& push_last(const std::string& s) + { + content.push_back(' '); + content.push_back(':'); + content.append(s); + return *this; + } + + template<typename T> + CmdBuilder& insert(const T& cont) + { + for (typename T::const_iterator i = cont.begin(); i != cont.end(); ++i) + push(*i); + return *this; + } + + void push_back(const std::string& s) { push(s); } + + const std::string& str() const { return content; } + operator const std::string&() const { return str(); } + + void Broadcast() const + { + Utils->DoOneToMany(*this); + } + + void Forward(TreeServer* omit) const + { + Utils->DoOneToAllButSender(*this, omit); + } + + void Unicast(User* target) const + { + Utils->DoOneToOne(*this, target->server); + } +}; diff --git a/src/modules/m_spanningtree/commands.h b/src/modules/m_spanningtree/commands.h index 3b5b499c1..8eea02915 100644 --- a/src/modules/m_spanningtree/commands.h +++ b/src/modules/m_spanningtree/commands.h @@ -17,126 +17,383 @@ */ -#ifndef M_SPANNINGTREE_COMMANDS_H -#define M_SPANNINGTREE_COMMANDS_H +#pragma once -#include "main.h" +#include "servercommand.h" +#include "commandbuilder.h" +#include "remoteuser.h" + +namespace SpanningTree +{ + class CommandAway; + class CommandNick; + class CommandPing; + class CommandPong; + class CommandServer; +} + +using SpanningTree::CommandAway; +using SpanningTree::CommandNick; +using SpanningTree::CommandPing; +using SpanningTree::CommandPong; +using SpanningTree::CommandServer; /** Handle /RCONNECT */ class CommandRConnect : public Command { - SpanningTreeUtilities* Utils; /* Utility class */ public: - CommandRConnect (Module* Callback, SpanningTreeUtilities* Util); - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + CommandRConnect(Module* Creator); + CmdResult Handle(const std::vector<std::string>& parameters, User* user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; class CommandRSQuit : public Command { - SpanningTreeUtilities* Utils; /* Utility class */ public: - CommandRSQuit(Module* Callback, SpanningTreeUtilities* Util); - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); - void NoticeUser(User* user, const std::string &msg); + CommandRSQuit(Module* Creator); + CmdResult Handle(const std::vector<std::string>& parameters, User* user); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -class CommandSVSJoin : public Command +class CommandMap : public Command { public: - CommandSVSJoin(Module* Creator) : Command(Creator, "SVSJOIN", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); + CommandMap(Module* Creator); + CmdResult Handle(const std::vector<std::string>& parameters, User* user); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -class CommandSVSPart : public Command + +class CommandSVSJoin : public ServerCommand { public: - CommandSVSPart(Module* Creator) : Command(Creator, "SVSPART", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); + CommandSVSJoin(Module* Creator) : ServerCommand(Creator, "SVSJOIN", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& params); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -class CommandSVSNick : public Command + +class CommandSVSPart : public ServerCommand { public: - CommandSVSNick(Module* Creator) : Command(Creator, "SVSNICK", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); + CommandSVSPart(Module* Creator) : ServerCommand(Creator, "SVSPART", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& params); RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -class CommandMetadata : public Command + +class CommandSVSNick : public ServerCommand { public: - CommandMetadata(Module* Creator) : Command(Creator, "METADATA", 2) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandSVSNick(Module* Creator) : ServerCommand(Creator, "SVSNICK", 3) { } + CmdResult Handle(User* user, std::vector<std::string>& params); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); }; -class CommandUID : public Command + +class CommandMetadata : public ServerCommand { public: - CommandUID(Module* Creator) : Command(Creator, "UID", 10) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandMetadata(Module* Creator) : ServerCommand(Creator, "METADATA", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& params); + + class Builder : public CmdBuilder + { + public: + Builder(User* user, const std::string& key, const std::string& val); + Builder(Channel* chan, const std::string& key, const std::string& val); + Builder(const std::string& key, const std::string& val); + }; }; -class CommandOpertype : public Command + +class CommandUID : public ServerOnlyServerCommand<CommandUID> { public: - CommandOpertype(Module* Creator) : Command(Creator, "OPERTYPE", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandUID(Module* Creator) : ServerOnlyServerCommand<CommandUID>(Creator, "UID", 10) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& params); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + }; }; -class CommandFJoin : public Command + +class CommandOpertype : public UserOnlyServerCommand<CommandOpertype> { public: - CommandFJoin(Module* Creator) : Command(Creator, "FJOIN", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandOpertype(Module* Creator) : UserOnlyServerCommand<CommandOpertype>(Creator, "OPERTYPE", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + }; +}; + +class TreeSocket; +class FwdFJoinBuilder; +class CommandFJoin : public ServerCommand +{ /** Remove all modes from a channel, including statusmodes (+qaovh etc), simplemodes, parameter modes. * This does not update the timestamp of the target channel, this must be done seperately. */ - void RemoveStatus(User* source, parameterlist ¶ms); + static void RemoveStatus(Channel* c); + + /** + * Lowers the TS on the given channel: removes all modes, unsets all extensions, + * clears the topic and removes all pending invites. + * @param chan The target channel whose TS to lower + * @param TS The new TS to set + * @param newname The new name of the channel; must be the same or a case change of the current name + */ + static void LowerTS(Channel* chan, time_t TS, const std::string& newname); + void ProcessModeUUIDPair(const std::string& item, TreeServer* sourceserver, Channel* chan, Modes::ChangeList* modechangelist, FwdFJoinBuilder& fwdfjoin); + public: + CommandFJoin(Module* Creator) : ServerCommand(Creator, "FJOIN", 3) { } + CmdResult Handle(User* user, std::vector<std::string>& params); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_LOCALONLY; } + + class Builder : public CmdBuilder + { + /** Maximum possible Membership::Id length in decimal digits, used for determining whether a user will fit into + * a message or not + */ + static const size_t membid_max_digits = 20; + static const size_t maxline = 510; + std::string::size_type pos; + + protected: + void add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend); + bool has_room(std::string::size_type nummodes) const; + + public: + Builder(Channel* chan, TreeServer* source = Utils->TreeRoot); + void add(Membership* memb) + { + add(memb, memb->modes.begin(), memb->modes.end()); + } + + bool has_room(Membership* memb) const + { + return has_room(memb->modes.size()); + } + + void clear(); + const std::string& finalize(); + }; +}; + +class CommandFMode : public ServerCommand +{ + public: + CommandFMode(Module* Creator) : ServerCommand(Creator, "FMODE", 3) { } + CmdResult Handle(User* user, std::vector<std::string>& params); +}; + +class CommandFTopic : public ServerCommand +{ + public: + CommandFTopic(Module* Creator) : ServerCommand(Creator, "FTOPIC", 4, 5) { } + CmdResult Handle(User* user, std::vector<std::string>& params); + + class Builder : public CmdBuilder + { + public: + Builder(Channel* chan); + Builder(User* user, Channel* chan); + }; +}; + +class CommandFHost : public UserOnlyServerCommand<CommandFHost> +{ + public: + CommandFHost(Module* Creator) : UserOnlyServerCommand<CommandFHost>(Creator, "FHOST", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); +}; + +class CommandFIdent : public UserOnlyServerCommand<CommandFIdent> +{ + public: + CommandFIdent(Module* Creator) : UserOnlyServerCommand<CommandFIdent>(Creator, "FIDENT", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); }; -class CommandFMode : public Command + +class CommandFName : public UserOnlyServerCommand<CommandFName> { public: - CommandFMode(Module* Creator) : Command(Creator, "FMODE", 3) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandFName(Module* Creator) : UserOnlyServerCommand<CommandFName>(Creator, "FNAME", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); }; -class CommandFTopic : public Command + +class CommandIJoin : public UserOnlyServerCommand<CommandIJoin> +{ + public: + CommandIJoin(Module* Creator) : UserOnlyServerCommand<CommandIJoin>(Creator, "IJOIN", 2) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& params); +}; + +class CommandResync : public ServerOnlyServerCommand<CommandResync> { public: - CommandFTopic(Module* Creator) : Command(Creator, "FTOPIC", 4) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandResync(Module* Creator) : ServerOnlyServerCommand<CommandResync>(Creator, "RESYNC", 1) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_LOCALONLY; } }; -class CommandFHost : public Command + +class SpanningTree::CommandAway : public UserOnlyServerCommand<SpanningTree::CommandAway> { public: - CommandFHost(Module* Creator) : Command(Creator, "FHOST", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandAway(Module* Creator) : UserOnlyServerCommand<SpanningTree::CommandAway>(Creator, "AWAY", 0, 2) { } + CmdResult HandleRemote(::RemoteUser* user, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(User* user); + Builder(User* user, const std::string& msg); + }; }; -class CommandFIdent : public Command + +class XLine; +class CommandAddLine : public ServerCommand { public: - CommandFIdent(Module* Creator) : Command(Creator, "FIDENT", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandAddLine(Module* Creator) : ServerCommand(Creator, "ADDLINE", 6, 6) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(XLine* xline, User* user = ServerInstance->FakeClient); + }; }; -class CommandFName : public Command + +class CommandDelLine : public ServerCommand { public: - CommandFName(Module* Creator) : Command(Creator, "FNAME", 1) { flags_needed = FLAG_SERVERONLY; } - CmdResult Handle (const std::vector<std::string>& parameters, User *user); - RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_BROADCAST; } + CommandDelLine(Module* Creator) : ServerCommand(Creator, "DELLINE", 2, 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); +}; + +class CommandEncap : public ServerCommand +{ + public: + CommandEncap(Module* Creator) : ServerCommand(Creator, "ENCAP", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); +}; + +class CommandIdle : public UserOnlyServerCommand<CommandIdle> +{ + public: + CommandIdle(Module* Creator) : UserOnlyServerCommand<CommandIdle>(Creator, "IDLE", 1) { } + CmdResult HandleRemote(RemoteUser* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_UNICAST(parameters[0]); } +}; + +class SpanningTree::CommandNick : public UserOnlyServerCommand<SpanningTree::CommandNick> +{ + public: + CommandNick(Module* Creator) : UserOnlyServerCommand<SpanningTree::CommandNick>(Creator, "NICK", 2) { } + CmdResult HandleRemote(::RemoteUser* user, std::vector<std::string>& parameters); +}; + +class SpanningTree::CommandPing : public ServerCommand +{ + public: + CommandPing(Module* Creator) : ServerCommand(Creator, "PING", 1) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_UNICAST(parameters[0]); } +}; + +class SpanningTree::CommandPong : public ServerOnlyServerCommand<SpanningTree::CommandPong> +{ + public: + CommandPong(Module* Creator) : ServerOnlyServerCommand<SpanningTree::CommandPong>(Creator, "PONG", 1) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) { return ROUTE_UNICAST(parameters[0]); } +}; + +class CommandSave : public ServerCommand +{ + public: + /** Timestamp of the uuid nick of all users who collided and got their nick changed to uuid + */ + static const time_t SavedTimestamp = 100; + + CommandSave(Module* Creator) : ServerCommand(Creator, "SAVE", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); +}; + +class SpanningTree::CommandServer : public ServerOnlyServerCommand<SpanningTree::CommandServer> +{ + static void HandleExtra(TreeServer* newserver, const std::vector<std::string>& params); + + public: + CommandServer(Module* Creator) : ServerOnlyServerCommand<SpanningTree::CommandServer>(Creator, "SERVER", 3) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + void push_property(const char* key, const std::string& val) + { + push(key).push_raw('=').push_raw(val); + } + public: + Builder(TreeServer* server); + }; +}; + +class CommandSQuit : public ServerOnlyServerCommand<CommandSQuit> +{ + public: + CommandSQuit(Module* Creator) : ServerOnlyServerCommand<CommandSQuit>(Creator, "SQUIT", 2) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); +}; + +class CommandSNONotice : public ServerCommand +{ + public: + CommandSNONotice(Module* Creator) : ServerCommand(Creator, "SNONOTICE", 2) { } + CmdResult Handle(User* user, std::vector<std::string>& parameters); +}; + +class CommandEndBurst : public ServerOnlyServerCommand<CommandEndBurst> +{ + public: + CommandEndBurst(Module* Creator) : ServerOnlyServerCommand<CommandEndBurst>(Creator, "ENDBURST") { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); +}; + +class CommandSInfo : public ServerOnlyServerCommand<CommandSInfo> +{ + public: + CommandSInfo(Module* Creator) : ServerOnlyServerCommand<CommandSInfo>(Creator, "SINFO", 2) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(TreeServer* server, const char* type, const std::string& value); + }; +}; + +class CommandNum : public ServerOnlyServerCommand<CommandNum> +{ + public: + CommandNum(Module* Creator) : ServerOnlyServerCommand<CommandNum>(Creator, "NUM", 3) { } + CmdResult HandleServer(TreeServer* server, std::vector<std::string>& parameters); + RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + + class Builder : public CmdBuilder + { + public: + Builder(SpanningTree::RemoteUser* target, const Numeric::Numeric& numeric); + }; }; class SpanningTreeCommands { public: - CommandRConnect rconnect; - CommandRSQuit rsquit; CommandSVSJoin svsjoin; CommandSVSPart svspart; CommandSVSNick svsnick; @@ -144,12 +401,27 @@ class SpanningTreeCommands CommandUID uid; CommandOpertype opertype; CommandFJoin fjoin; + CommandIJoin ijoin; + CommandResync resync; CommandFMode fmode; CommandFTopic ftopic; CommandFHost fhost; CommandFIdent fident; CommandFName fname; + SpanningTree::CommandAway away; + CommandAddLine addline; + CommandDelLine delline; + CommandEncap encap; + CommandIdle idle; + SpanningTree::CommandNick nick; + SpanningTree::CommandPing ping; + SpanningTree::CommandPong pong; + CommandSave save; + SpanningTree::CommandServer server; + CommandSQuit squit; + CommandSNONotice snonotice; + CommandEndBurst endburst; + CommandSInfo sinfo; + CommandNum num; SpanningTreeCommands(ModuleSpanningTree* module); }; - -#endif diff --git a/src/modules/m_spanningtree/compat.cpp b/src/modules/m_spanningtree/compat.cpp index ec0cdb036..2436e74f8 100644 --- a/src/modules/m_spanningtree/compat.cpp +++ b/src/modules/m_spanningtree/compat.cpp @@ -20,177 +20,561 @@ #include "inspircd.h" #include "main.h" #include "treesocket.h" +#include "treeserver.h" -static const char* const forge_common_1201[] = { - "m_allowinvite.so", - "m_alltime.so", - "m_auditorium.so", - "m_banexception.so", - "m_blockcaps.so", - "m_blockcolor.so", - "m_botmode.so", - "m_censor.so", - "m_chanfilter.so", - "m_chanhistory.so", - "m_channelban.so", - "m_chanprotect.so", - "m_chghost.so", - "m_chgname.so", - "m_commonchans.so", - "m_customtitle.so", - "m_deaf.so", - "m_delayjoin.so", - "m_delaymsg.so", - "m_exemptchanops.so", - "m_gecosban.so", - "m_globops.so", - "m_helpop.so", - "m_hidechans.so", - "m_hideoper.so", - "m_invisible.so", - "m_inviteexception.so", - "m_joinflood.so", - "m_kicknorejoin.so", - "m_knock.so", - "m_messageflood.so", - "m_muteban.so", - "m_nickflood.so", - "m_nicklock.so", - "m_noctcp.so", - "m_nokicks.so", - "m_nonicks.so", - "m_nonotice.so", - "m_nopartmsg.so", - "m_ojoin.so", - "m_operprefix.so", - "m_permchannels.so", - "m_redirect.so", - "m_regex_glob.so", - "m_regex_pcre.so", - "m_regex_posix.so", - "m_regex_tre.so", - "m_remove.so", - "m_sajoin.so", - "m_sakick.so", - "m_sanick.so", - "m_sapart.so", - "m_saquit.so", - "m_serverban.so", - "m_services_account.so", - "m_servprotect.so", - "m_setident.so", - "m_showwhois.so", - "m_silence.so", - "m_sslmodes.so", - "m_stripcolor.so", - "m_swhois.so", - "m_uninvite.so", - "m_watch.so" -}; - -static std::string wide_newline("\r\n"); static std::string newline("\n"); -void TreeSocket::CompatAddModules(std::vector<std::string>& modlist) +void TreeSocket::WriteLineNoCompat(const std::string& line) { - if (proto_version < 1202) - { - // you MUST have chgident loaded in order to be able to translate FIDENT - modlist.push_back("m_chgident.so"); - for(int i=0; i * sizeof(char*) < sizeof(forge_common_1201); i++) - { - if (ServerInstance->Modules->Find(forge_common_1201[i])) - modlist.push_back(forge_common_1201[i]); - } - // module was merged - if (ServerInstance->Modules->Find("m_operchans.so")) - { - modlist.push_back("m_operchans.so"); - modlist.push_back("m_operinvex.so"); - } - } + ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] O %s", this->GetFd(), line.c_str()); + this->WriteData(line); + this->WriteData(newline); } -void TreeSocket::WriteLine(std::string line) +void TreeSocket::WriteLine(const std::string& original_line) { if (LinkState == CONNECTED) { - if (line[0] != ':') + if (original_line.c_str()[0] != ':') { - ServerInstance->Logs->Log("m_spanningtree", DEFAULT, "Sending line without server prefix!"); - line = ":" + ServerInstance->Config->GetSID() + " " + line; + 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); + std::string command(line, 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 < 1202 && command == "FIDENT") - { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Rewriting FIDENT for 1201-protocol server"); - line = ":" + ServerInstance->Config->GetSID() + " CHGIDENT " + line.substr(1,a-1) + line.substr(b); - } - else if (proto_version < 1202 && command == "SAVE") - { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Rewriting SAVE for 1201-protocol server"); - std::string::size_type c = line.find(' ', b + 1); - std::string uid = line.substr(b, c - b); - line = ":" + ServerInstance->Config->GetSID() + " SVSNICK" + uid + line.substr(b); - } - else if (proto_version < 1202 && command == "AWAY") + if (proto_version < 1205) { - if (b != std::string::npos) + if (command == "IJOIN") { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Stripping AWAY timestamp for 1201-protocol server"); + // Convert + // :<uid> IJOIN <chan> <membid> [<ts> [<flags>]] + // to + // :<sid> FJOIN <chan> <ts> + [<flags>],<uuid> std::string::size_type c = line.find(' ', b + 1); - line.erase(b,c-b); + 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, 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 (proto_version < 1202 && command == "ENCAP") - { - // :src ENCAP target command [args...] - // A B C D - // Therefore B and C cannot be npos in a valid command - if (b == std::string::npos) - return; - std::string::size_type c = line.find(' ', b + 1); - if (c == std::string::npos) + else if (command == "RESYNC") return; - std::string::size_type d = line.find(' ', c + 1); - std::string subcmd = line.substr(c + 1, d - c - 1); + 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; - if (subcmd == "CHGIDENT" && d != std::string::npos) + 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 (e == std::string::npos) - return; // not valid - std::string target = line.substr(d + 1, e - 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 : - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Forging acceptance of CHGIDENT from 1201-protocol server"); - recvq.insert(0, ":" + target + " FIDENT " + line.substr(e) + "\n"); + // 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; - Command* thiscmd = ServerInstance->Parser->GetHandler(subcmd); - if (thiscmd && subcmd != "WHOISNOTICE") + 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") { - Version ver = thiscmd->creator->GetVersion(); - if (ver.Flags & VF_OPTCOMMON) + // :22D SINFO version :InspIRCd-3.0 + // 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) { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Removing ENCAP on '%s' for 1201-protocol server", - subcmd.c_str()); - line.erase(a, c-a); + WriteLineNoCompat(line); + + // Synthesize a :<newserver> BURST <time> message + spcolon = line.find(" :"); + line = CmdBuilder(line.substr(spcolon-3, 3), "BURST").push_int(ServerInstance->Time()).str(); } } + else if (command == "NUM") + { + // :<sid> NUM <numeric source sid> <target uuid> <3 digit number> <params> + // Translate to + // :<sid> PUSH <target uuid> :<numeric source name> <3 digit number> <target nick> <params> + + TreeServer* const numericsource = Utils->FindServerID(line.substr(9, 3)); + if (!numericsource) + return; + + // The nick of the target is necessary for building the PUSH message + User* const target = ServerInstance->FindUUID(line.substr(13, UIDGenerator::UUID_LENGTH)); + if (!target) + return; + + std::string push = InspIRCd::Format(":%.*s PUSH %s ::%s %.*s %s", 3, line.c_str()+1, target->uuid.c_str(), numericsource->GetName().c_str(), 3, line.c_str()+23, target->nick.c_str()); + push.append(line, 26, std::string::npos); + push.swap(line); + } } + WriteLineNoCompat(line); + return; } } - ServerInstance->Logs->Log("m_spanningtree", RAWIO, "S[%d] O %s", this->GetFd(), line.c_str()); - this->WriteData(line); - if (proto_version < 1202) - this->WriteData(wide_newline); - else - this->WriteData(newline); + WriteLineNoCompat(original_line); +} + +namespace +{ + bool InsertCurrentChannelTS(std::vector<std::string>& params, unsigned int chanindex = 0, unsigned int pos = 1) + { + Channel* chan = ServerInstance->FindChan(params[chanindex]); + if (!chan) + return false; + + // Insert the current TS of the channel after the pos-th parameter + params.insert(params.begin()+pos, ConvToStr(chan->age)); + return true; + } +} + +bool TreeSocket::PreProcessOldProtocolMessage(User*& who, std::string& cmd, std::vector<std::string>& params) +{ + if ((cmd == "METADATA") && (params.size() >= 3) && (params[0][0] == '#')) + { + // :20D METADATA #channel extname :extdata + return InsertCurrentChannelTS(params); + } + else if ((cmd == "FTOPIC") && (params.size() >= 4)) + { + // :20D FTOPIC #channel 100 Attila :topic text + return InsertCurrentChannelTS(params); + } + else if ((cmd == "PING") || (cmd == "PONG")) + { + if (params.size() == 1) + { + // If it's a PING with 1 parameter, reply with a PONG now, if it's a PONG with 1 parameter (weird), do nothing + if (cmd[1] == 'I') + this->WriteData(":" + ServerInstance->Config->GetSID() + " PONG " + params[0] + newline); + + // Don't process this message further + return false; + } + + // :20D PING 20D 22D + // :20D PONG 20D 22D + // Drop the first parameter + params.erase(params.begin()); + + // If the target is a server name, translate it to a SID + if (!InspIRCd::IsSID(params[0])) + { + TreeServer* server = Utils->FindServer(params[0]); + if (!server) + { + // We've no idea what this is, log and stop processing + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Received a " + cmd + " with an unknown target: \"" + params[0] + "\", command dropped"); + return false; + } + + params[0] = server->GetID(); + } + } + else if ((cmd == "GLINE") || (cmd == "KLINE") || (cmd == "ELINE") || (cmd == "ZLINE") || (cmd == "QLINE")) + { + // Fix undocumented protocol usage: translate GLINE, ZLINE, etc. into ADDLINE or DELLINE + if ((params.size() != 1) && (params.size() != 3)) + return false; + + parameterlist p; + p.push_back(cmd.substr(0, 1)); + p.push_back(params[0]); + + if (params.size() == 3) + { + cmd = "ADDLINE"; + p.push_back(who->nick); + p.push_back(ConvToStr(ServerInstance->Time())); + p.push_back(ConvToStr(InspIRCd::Duration(params[1]))); + p.push_back(params[2]); + } + else + cmd = "DELLINE"; + + params.swap(p); + } + else if (cmd == "SVSMODE") + { + cmd = "MODE"; + } + else if (cmd == "OPERQUIT") + { + // Translate OPERQUIT into METADATA + if (params.empty()) + return false; + + cmd = "METADATA"; + params.insert(params.begin(), who->uuid); + params.insert(params.begin()+1, "operquit"); + who = MyRoot->ServerUser; + } + else if ((cmd == "TOPIC") && (params.size() >= 2)) + { + // :20DAAAAAC TOPIC #chan :new topic + cmd = "FTOPIC"; + if (!InsertCurrentChannelTS(params)) + return false; + + params.insert(params.begin()+2, ConvToStr(ServerInstance->Time())); + } + else if (cmd == "MODENOTICE") + { + // MODENOTICE is always supported by 2.0 but it's optional in 3.0. + params.insert(params.begin(), "*"); + params.insert(params.begin()+1, cmd); + cmd = "ENCAP"; + } + else if (cmd == "RULES") + { + return false; + } + else if (cmd == "INVITE") + { + // :20D INVITE 22DAAABBB #chan + // :20D INVITE 22DAAABBB #chan 123456789 + // Insert channel timestamp after the channel name; the 3rd parameter, if there, is the invite expiration time + return InsertCurrentChannelTS(params, 1, 2); + } + else if (cmd == "VERSION") + { + // :20D VERSION :InspIRCd-2.0 + // change to + // :20D SINFO version :InspIRCd-2.0 + cmd = "SINFO"; + params.insert(params.begin(), "version"); + } + else if (cmd == "JOIN") + { + // 2.0 allows and forwards legacy JOINs but we don't, so translate them to FJOINs before processing + if ((params.size() != 1) || (IS_SERVER(who))) + return false; // Huh? + + cmd = "FJOIN"; + Channel* chan = ServerInstance->FindChan(params[0]); + params.push_back(ConvToStr(chan ? chan->age : ServerInstance->Time())); + params.push_back("+"); + params.push_back(","); + params.back().append(who->uuid); + who = TreeServer::Get(who)->ServerUser; + } + else if ((cmd == "FMODE") && (params.size() >= 2)) + { + // Translate user mode changes with timestamp to MODE + if (params[0][0] != '#') + { + User* user = ServerInstance->FindUUID(params[0]); + if (!user) + return false; + + // Emulate the old nonsensical behavior + if (user->age < ServerCommand::ExtractTS(params[1])) + return false; + + cmd = "MODE"; + params.erase(params.begin()+1); + } + } + else if ((cmd == "SERVER") && (params.size() > 4)) + { + // This does not affect the initial SERVER line as it is sent before the link state is CONNECTED + // :20D SERVER <name> * 0 <sid> <desc> + // change to + // :20D SERVER <name> <sid> <desc> + + params[1].swap(params[3]); + params.erase(params.begin()+2, params.begin()+4); + + // If the source of this SERVER message is not bursting, then new servers it introduces are bursting + TreeServer* server = TreeServer::Get(who); + if (!server->IsBursting()) + params.insert(params.begin()+2, "burst=" + ConvToStr(((uint64_t)ServerInstance->Time())*1000)); + } + else if (cmd == "BURST") + { + // A server is introducing another one, drop unnecessary BURST + return false; + } + else if (cmd == "SVSWATCH") + { + // SVSWATCH was removed because nothing was using it, but better be sure + return false; + } + else if (cmd == "PUSH") + { + if ((params.size() != 2) || (!this->MyRoot)) + return false; // Huh? + + irc::tokenstream ts(params.back()); + + std::string srcstr; + ts.GetToken(srcstr); + srcstr.erase(0, 1); + + std::string token; + ts.GetToken(token); + + // See if it's a numeric being sent to the target via PUSH + unsigned int numeric_number = 0; + if (token.length() == 3) + numeric_number = ConvToInt(token); + + if ((numeric_number > 0) && (numeric_number < 1000)) + { + // It's a numeric, translate to NUM + + // srcstr must be a valid server name + TreeServer* const numericsource = Utils->FindServer(srcstr); + if (!numericsource) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unable to translate PUSH numeric %s to user %s from 1202 protocol server %s: source \"%s\" doesn't exist", token.c_str(), params[0].c_str(), this->MyRoot->GetName().c_str(), srcstr.c_str()); + return false; + } + + cmd = "NUM"; + + // Second parameter becomes the target uuid + params[0].swap(params[1]); + // Replace first param (now the PUSH payload, not needed) with the source sid + params[0] = numericsource->GetID(); + + params.push_back(InspIRCd::Format("%03u", numeric_number)); + + // Ignore the nickname in the numeric in PUSH + ts.GetToken(token); + + // Rest of the tokens are the numeric parameters, add them to NUM + while (ts.GetToken(token)) + params.push_back(token); + } + else if ((token == "PRIVMSG") || (token == "NOTICE")) + { + // Command is a PRIVMSG/NOTICE + cmd.swap(token); + + // Check if the PRIVMSG/NOTICE target is a nickname + ts.GetToken(token); + if (token.c_str()[0] == '#') + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unable to translate PUSH %s to user %s from 1202 protocol server %s, target \"%s\"", cmd.c_str(), params[0].c_str(), this->MyRoot->GetName().c_str(), token.c_str()); + return false; + } + + // Replace second parameter with the message + ts.GetToken(params[1]); + } + else + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Unable to translate PUSH to user %s from 1202 protocol server %s", params[0].c_str(), this->MyRoot->GetName().c_str()); + return false; + } + + return true; + } + + return true; // Passthru } diff --git a/src/modules/m_spanningtree/delline.cpp b/src/modules/m_spanningtree/delline.cpp index 540ca5079..f790dc885 100644 --- a/src/modules/m_spanningtree/delline.cpp +++ b/src/modules/m_spanningtree/delline.cpp @@ -20,38 +20,18 @@ #include "inspircd.h" #include "xline.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" +#include "commands.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - - -bool TreeSocket::DelLine(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandDelLine::Handle(User* user, std::vector<std::string>& params) { - if (params.size() < 2) - return true; - - std::string setter = "<unknown>"; - - User* user = ServerInstance->FindNick(prefix); - if (user) - setter = user->nick; - else - { - TreeServer* t = Utils->FindServer(prefix); - if (t) - setter = t->GetName(); - } + const std::string& setter = user->nick; - - /* NOTE: No check needed on 'user', this function safely handles NULL */ + // XLineManager::DelLine() returns true if the xline existed, false if it didn't if (ServerInstance->XLines->DelLine(params[1].c_str(), params[0], user)) { ServerInstance->SNO->WriteToSnoMask('X',"%s removed %s%s on %s", setter.c_str(), params[0].c_str(), params[0].length() == 1 ? "-line" : "", params[1].c_str()); - Utils->DoOneToAllButSender(prefix,"DELLINE", params, prefix); + return CMD_SUCCESS; } - return true; + return CMD_FAILURE; } - diff --git a/src/modules/m_spanningtree/encap.cpp b/src/modules/m_spanningtree/encap.cpp index dabfc086b..8059d2a39 100644 --- a/src/modules/m_spanningtree/encap.cpp +++ b/src/modules/m_spanningtree/encap.cpp @@ -18,32 +18,39 @@ #include "inspircd.h" -#include "xline.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" +#include "commands.h" +#include "main.h" /** ENCAP */ -void TreeSocket::Encap(User* who, parameterlist ¶ms) +CmdResult CommandEncap::Handle(User* user, std::vector<std::string>& params) { - if (params.size() > 1) + if (ServerInstance->Config->GetSID() == params[0] || InspIRCd::Match(ServerInstance->Config->ServerName, params[0])) { - if (ServerInstance->Config->GetSID() == params[0] || InspIRCd::Match(ServerInstance->Config->ServerName, params[0])) - { - parameterlist plist(params.begin() + 2, params.end()); - ServerInstance->Parser->CallHandler(params[1], plist, who); - // discard return value, ENCAP shall succeed even if the command does not exist - } - - params[params.size() - 1] = ":" + params[params.size() - 1]; + parameterlist plist(params.begin() + 2, params.end()); - if (params[0].find_first_of("*?") != std::string::npos) + // XXX: Workaround for SVS* commands provided by spanningtree not being registered in the core + if ((params[1] == "SVSNICK") || (params[1] == "SVSJOIN") || (params[1] == "SVSPART")) { - Utils->DoOneToAllButSender(who->uuid, "ENCAP", params, who->server); + ServerCommand* const scmd = Utils->Creator->CmdManager.GetHandler(params[1]); + if (scmd) + scmd->Handle(user, plist); + return CMD_SUCCESS; } - else - Utils->DoOneToOne(who->uuid, "ENCAP", params, params[0]); + + Command* cmd = NULL; + ServerInstance->Parser.CallHandler(params[1], plist, user, &cmd); + // Discard return value, ENCAP shall succeed even if the command does not exist + + if ((cmd) && (cmd->force_manual_route)) + return CMD_FAILURE; } + return CMD_SUCCESS; } +RouteDescriptor CommandEncap::GetRouting(User* user, const std::vector<std::string>& params) +{ + if (params[0].find_first_of("*?") != std::string::npos) + return ROUTE_BROADCAST; + return ROUTE_UNICAST(params[0]); +} diff --git a/src/modules/m_spanningtree/fjoin.cpp b/src/modules/m_spanningtree/fjoin.cpp index 4ec6e1dbb..c292373b3 100644 --- a/src/modules/m_spanningtree/fjoin.cpp +++ b/src/modules/m_spanningtree/fjoin.cpp @@ -25,11 +25,26 @@ #include "treeserver.h" #include "treesocket.h" +/** FJOIN builder for rebuilding incoming FJOINs and splitting them up into multiple messages if necessary + */ +class FwdFJoinBuilder : public CommandFJoin::Builder +{ + TreeServer* const sourceserver; + + public: + FwdFJoinBuilder(Channel* chan, TreeServer* server) + : CommandFJoin::Builder(chan, server) + , sourceserver(server) + { + } + + void add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend); +}; + /** FJOIN, almost identical to TS6 SJOIN, except for nicklist handling. */ -CmdResult CommandFJoin::Handle(const std::vector<std::string>& params, User *srcuser) +CmdResult CommandFJoin::Handle(User* srcuser, std::vector<std::string>& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - /* 1.1 FJOIN works as follows: + /* 1.1+ FJOIN works as follows: * * Each FJOIN is sent along with a timestamp, and the side with the lowest * timestamp 'wins'. From this point on we will refer to this side as the @@ -54,204 +69,276 @@ CmdResult CommandFJoin::Handle(const std::vector<std::string>& params, User *src * The winning side on the other hand will ignore all user modes from the * losing side, so only its own modes get applied. Life is simple for those * who succeed at internets. :-) + * + * Outside of netbursts, the winning side also resyncs the losing side if it + * detects that the other side recreated the channel. + * + * Syntax: + * :<sid> FJOIN <chan> <TS> <modes> :[<member> [<member> ...]] + * The last parameter is a list consisting of zero or more channel members + * (permanent channels may have zero users). Each entry on the list is in the + * following format: + * [[<modes>,]<uuid>[:<membid>] + * <modes> is a concatenation of the mode letters the user has on the channel + * (e.g.: "ov" if the user is opped and voiced). The order of the mode letters + * are not important but if a server ecounters an unknown mode letter, it will + * drop the link to avoid desync. + * + * InspIRCd 2.0 and older required a comma before the uuid even if the user + * had no prefix modes on the channel, InspIRCd 3.0 and later does not require + * a comma in this case anymore. + * + * <membid> is a positive integer representing the id of the membership. + * If not present (in FJOINs coming from pre-1205 servers), 0 is assumed. + * + * Forwarding: + * FJOIN messages are forwarded with the new TS and modes. Prefix modes of + * members on the losing side are not forwarded. + * This is required to only have one server on each side of the network who + * decides the fate of a channel during a network merge. Otherwise, if the + * clock of a server is slightly off it may make a different decision than + * the rest of the network and desync. + * The prefix modes are always forwarded as-is, or not at all. + * One incoming FJOIN may result in more than one FJOIN being generated + * and forwarded mainly due to compatibility reasons with non-InspIRCd + * servers that don't handle more than 512 char long lines. + * + * Forwarding examples: + * Existing channel #chan with TS 1000, modes +n. + * Incoming: :220 FJOIN #chan 1000 +t :o,220AAAAAB:0 + * Forwarded: :220 FJOIN #chan 1000 +nt :o,220AAAAAB:0 + * Merge modes and forward the result. Forward their prefix modes as well. + * + * Existing channel #chan with TS 1000, modes +nt. + * Incoming: :220 FJOIN #CHAN 2000 +i :ov,220AAAAAB:0 o,220AAAAAC:20 + * Forwarded: :220 FJOIN #chan 1000 +nt :,220AAAAAB:0 ,220AAAAAC:20 + * Drop their modes, forward our modes and TS, use our channel name + * capitalization. Don't forward prefix modes. + * */ - irc::modestacker modestack(true); /* Modes to apply from the users in the user list */ - User* who = NULL; /* User we are currently checking */ - std::string channel = params[0]; /* Channel name, as a string */ - time_t TS = atoi(params[1].c_str()); /* Timestamp given to us for remote side */ - irc::tokenstream users((params.size() > 3) ? params[params.size() - 1] : ""); /* users from the user list */ - bool apply_other_sides_modes = true; /* True if we are accepting the other side's modes */ - Channel* chan = ServerInstance->FindChan(channel); /* The channel we're sending joins to */ - bool created = !chan; /* True if the channel doesnt exist here yet */ - std::string item; /* One item in the list of nicks */ - - TreeServer* src_server = Utils->FindServer(srcuser->server); - TreeSocket* src_socket = src_server->GetRoute()->GetSocket(); + time_t TS = ServerCommand::ExtractTS(params[1]); - if (!TS) - { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"*** BUG? *** TS of 0 sent to FJOIN. Are some services authors smoking craq, or is it 1970 again?. Dropped."); - ServerInstance->SNO->WriteToSnoMask('d', "WARNING: The server %s is sending FJOIN with a TS of zero. Total craq. Command was dropped.", srcuser->server.c_str()); - return CMD_INVALID; - } + const std::string& channel = params[0]; + Channel* chan = ServerInstance->FindChan(channel); + bool apply_other_sides_modes = true; + TreeServer* const sourceserver = TreeServer::Get(srcuser); - if (created) + if (!chan) { chan = new Channel(channel, TS); - ServerInstance->SNO->WriteToSnoMask('d', "Creation FJOIN received for %s, timestamp: %lu", chan->name.c_str(), (unsigned long)TS); } else { time_t ourTS = chan->age; - if (TS != ourTS) + { ServerInstance->SNO->WriteToSnoMask('d', "Merge FJOIN received for %s, ourTS: %lu, TS: %lu, difference: %ld", chan->name.c_str(), (unsigned long)ourTS, (unsigned long)TS, (long)(ourTS - TS)); - /* If our TS is less than theirs, we dont accept their modes */ - if (ourTS < TS) - { - ServerInstance->SNO->WriteToSnoMask('d', "NOT Applying modes from other side"); - apply_other_sides_modes = false; - } - else if (ourTS > TS) - { - /* Our TS greater than theirs, clear all our modes from the channel, accept theirs. */ - ServerInstance->SNO->WriteToSnoMask('d', "Removing our modes, accepting remote"); - parameterlist param_list; - if (Utils->AnnounceTSChange) - chan->WriteChannelWithServ(ServerInstance->Config->ServerName, "NOTICE %s :TS for %s changed from %lu to %lu", chan->name.c_str(), channel.c_str(), (unsigned long) ourTS, (unsigned long) TS); - // while the name is equal in case-insensitive compare, it might differ in case; use the remote version - chan->name = channel; - chan->age = TS; - chan->ClearInvites(); - param_list.push_back(channel); - this->RemoveStatus(ServerInstance->FakeClient, param_list); - - // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it. - // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then - // deleted later) as soon as the permchan mode is removed from them. - if (ServerInstance->FindChan(channel) == NULL) + /* If our TS is less than theirs, we dont accept their modes */ + if (ourTS < TS) { - chan = new Channel(channel, TS); + // If the source server isn't bursting then this FJOIN is the result of them recreating the channel with a higher TS. + // This happens if the last user on the channel hops and before the PART propagates a user on another server joins. Fix it by doing a resync. + // Servers behind us won't react this way because the forwarded FJOIN will have the correct TS. + if (!sourceserver->IsBursting()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s recreated channel %s with higher TS, resyncing", sourceserver->GetName().c_str(), chan->name.c_str()); + sourceserver->GetSocket()->SyncChannel(chan); + } + apply_other_sides_modes = false; + } + else if (ourTS > TS) + { + // Our TS is greater than theirs, remove all modes, extensions, etc. from the channel + LowerTS(chan, TS, channel); + + // XXX: If the channel does not exist in the chan hash at this point, create it so the remote modes can be applied on it. + // This happens to 0-user permanent channels on the losing side, because those are removed (from the chan hash, then + // deleted later) as soon as the permchan mode is removed from them. + if (ServerInstance->FindChan(channel) == NULL) + { + chan = new Channel(channel, TS); + } } } - // The silent case here is ourTS == TS, we don't need to remove modes here, just to merge them later on. } - /* First up, apply their modes if they won the TS war */ + // Apply their channel modes if we have to + Modes::ChangeList modechangelist; if (apply_other_sides_modes) { - // Need to use a modestacker here due to maxmodes - irc::modestacker stack(true); - std::vector<std::string>::const_iterator paramit = params.begin() + 3; - const std::vector<std::string>::const_iterator lastparamit = ((params.size() > 3) ? (params.end() - 1) : params.end()); - for (std::string::const_iterator i = params[2].begin(); i != params[2].end(); ++i) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL); - if (!mh) - continue; + ServerInstance->Modes.ModeParamsToChangeList(srcuser, MODETYPE_CHANNEL, params, modechangelist, 2, params.size() - 1); + ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY | ModeParser::MODE_MERGE); + // Reuse for prefix modes + modechangelist.clear(); + } - std::string modeparam; - if ((paramit != lastparamit) && (mh->GetNumParams(true))) - { - modeparam = *paramit; - ++paramit; - } + // Build a new FJOIN for forwarding. Put the correct TS in it and the current modes of the channel + // after applying theirs. If they lost, the prefix modes from their message are not forwarded. + FwdFJoinBuilder fwdfjoin(chan, sourceserver); - stack.Push(*i, modeparam); - } + // Process every member in the message + irc::tokenstream users(params.back()); + std::string item; + Modes::ChangeList* modechangelistptr = (apply_other_sides_modes ? &modechangelist : NULL); + while (users.GetToken(item)) + { + ProcessModeUUIDPair(item, sourceserver, chan, modechangelistptr, fwdfjoin); + } - std::vector<std::string> modelist; + fwdfjoin.finalize(); + fwdfjoin.Forward(sourceserver->GetRoute()); - // Mode parser needs to know what channel to act on. - modelist.push_back(params[0]); + // Set prefix modes on their users if we lost the FJOIN or had equal TS + if (apply_other_sides_modes) + ServerInstance->Modes->Process(srcuser, chan, NULL, modechangelist, ModeParser::MODE_LOCALONLY); - while (stack.GetStackedLine(modelist)) - { - ServerInstance->Modes->Process(modelist, srcuser, true); - modelist.erase(modelist.begin() + 1, modelist.end()); - } + return CMD_SUCCESS; +} + +void CommandFJoin::ProcessModeUUIDPair(const std::string& item, TreeServer* sourceserver, Channel* chan, Modes::ChangeList* modechangelist, FwdFJoinBuilder& fwdfjoin) +{ + std::string::size_type comma = item.find(','); - ServerInstance->Modes->Process(modelist, srcuser, true); + // Comma not required anymore if the user has no modes + const std::string::size_type ubegin = (comma == std::string::npos ? 0 : comma+1); + std::string uuid(item, ubegin, UIDGenerator::UUID_LENGTH); + User* who = ServerInstance->FindUUID(uuid); + if (!who) + { + // Probably KILLed, ignore + return; } - /* Now, process every 'modes,nick' pair */ - while (users.GetToken(item)) + TreeSocket* src_socket = sourceserver->GetSocket(); + /* Check that the user's 'direction' is correct */ + TreeServer* route_back_again = TreeServer::Get(who); + if (route_back_again->GetSocket() != src_socket) { - const char* usr = item.c_str(); - if (usr && *usr) + return; + } + + std::string::const_iterator modeendit = item.begin(); // End of the "ov" mode string + /* Check if the user received at least one mode */ + if ((modechangelist) && (comma != std::string::npos)) + { + modeendit += comma; + /* Iterate through the modes and see if they are valid here, if so, apply */ + for (std::string::const_iterator i = item.begin(); i != modeendit; ++i) { - const char* unparsedmodes = usr; - std::string modes; + ModeHandler* mh = ServerInstance->Modes->FindMode(*i, MODETYPE_CHANNEL); + if (!mh) + throw ProtocolException("Unrecognised mode '" + std::string(1, *i) + "'"); + /* Add any modes this user had to the mode stack */ + modechangelist->push_add(mh, who->nick); + } + } - /* Iterate through all modes for this user and check they are valid. */ - while ((*unparsedmodes) && (*unparsedmodes != ',')) - { - ModeHandler *mh = ServerInstance->Modes->FindMode(*unparsedmodes, MODETYPE_CHANNEL); - if (!mh) - { - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised mode %c, dropping link", *unparsedmodes); - return CMD_INVALID; - } + Membership* memb = chan->ForceJoin(who, NULL, sourceserver->IsBursting()); + if (!memb) + { + // User was already on the channel, forward because of the modes they potentially got + memb = chan->GetUser(who); + if (memb) + fwdfjoin.add(memb, item.begin(), modeendit); + return; + } - modes += *unparsedmodes; - usr++; - unparsedmodes++; - } + // Assign the id to the new Membership + Membership::Id membid = 0; + const std::string::size_type colon = item.rfind(':'); + if (colon != std::string::npos) + membid = Membership::IdFromString(item.substr(colon+1)); + memb->id = membid; - /* Advance past the comma, to the nick */ - usr++; + // Add member to fwdfjoin with prefix modes + fwdfjoin.add(memb, item.begin(), modeendit); +} - /* Check the user actually exists */ - who = ServerInstance->FindUUID(usr); - if (who) - { - /* Check that the user's 'direction' is correct */ - TreeServer* route_back_again = Utils->BestRouteTo(who->server); - if ((!route_back_again) || (route_back_again->GetSocket() != src_socket)) - continue; +void CommandFJoin::RemoveStatus(Channel* c) +{ + Modes::ChangeList changelist; - /* Add any modes this user had to the mode stack */ - for (std::string::iterator x = modes.begin(); x != modes.end(); ++x) - modestack.Push(*x, who->nick); + const ModeParser::ModeHandlerMap& mhs = ServerInstance->Modes->GetModes(MODETYPE_CHANNEL); + for (ModeParser::ModeHandlerMap::const_iterator i = mhs.begin(); i != mhs.end(); ++i) + { + ModeHandler* mh = i->second; - Channel::JoinUser(who, channel.c_str(), true, "", src_server->bursting, TS); - } - else - { - ServerInstance->Logs->Log("m_spanningtree",SPARSE, "Ignored nonexistent user %s in fjoin to %s (probably quit?)", usr, channel.c_str()); - continue; - } - } + // Add the removal of this mode to the changelist. This handles all kinds of modes, including prefix modes. + mh->RemoveMode(c, changelist); } - /* Flush mode stacker if we lost the FJOIN or had equal TS */ - if (apply_other_sides_modes) - { - parameterlist stackresult; - stackresult.push_back(channel); + ServerInstance->Modes->Process(ServerInstance->FakeClient, c, NULL, changelist, ModeParser::MODE_LOCALONLY); +} - while (modestack.GetStackedLine(stackresult)) - { - ServerInstance->SendMode(stackresult, srcuser); - stackresult.erase(stackresult.begin() + 1, stackresult.end()); - } - } - return CMD_SUCCESS; +void CommandFJoin::LowerTS(Channel* chan, time_t TS, const std::string& newname) +{ + if (Utils->AnnounceTSChange) + chan->WriteNotice(InspIRCd::Format("TS for %s changed from %lu to %lu", newname.c_str(), (unsigned long) chan->age, (unsigned long) TS)); + + // While the name is equal in case-insensitive compare, it might differ in case; use the remote version + chan->name = newname; + chan->age = TS; + + // Clear all modes + CommandFJoin::RemoveStatus(chan); + + // Unset all extensions + chan->FreeAllExtItems(); + + // Clear the topic + chan->SetTopic(ServerInstance->FakeClient, std::string(), 0); + chan->setby.clear(); } -void CommandFJoin::RemoveStatus(User* srcuser, parameterlist ¶ms) +CommandFJoin::Builder::Builder(Channel* chan, TreeServer* source) + : CmdBuilder(source->GetID(), "FJOIN") { - if (params.size() < 1) - return; + push(chan->name).push_int(chan->age).push_raw(" +"); + pos = str().size(); + push_raw(chan->ChanModes(true)).push_raw(" :"); +} - Channel* c = ServerInstance->FindChan(params[0]); +void CommandFJoin::Builder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend) +{ + push_raw(mbegin, mend).push_raw(',').push_raw(memb->user->uuid); + push_raw(':').push_raw_int(memb->id); + push_raw(' '); +} - if (c) - { - irc::modestacker stack(false); - parameterlist stackresult; - stackresult.push_back(c->name); +bool CommandFJoin::Builder::has_room(std::string::size_type nummodes) const +{ + return ((str().size() + nummodes + UIDGenerator::UUID_LENGTH + 2 + membid_max_digits + 1) <= maxline); +} - for (char modeletter = 'A'; modeletter <= 'z'; ++modeletter) - { - ModeHandler* mh = ServerInstance->Modes->FindMode(modeletter, MODETYPE_CHANNEL); - - /* Passing a pointer to a modestacker here causes the mode to be put onto the mode stack, - * rather than applied immediately. Module unloads require this to be done immediately, - * for this function we require tidyness instead. Fixes bug #493 - */ - if (mh) - mh->RemoveMode(c, &stack); - } +void CommandFJoin::Builder::clear() +{ + content.erase(pos); + push_raw(" :"); +} - while (stack.GetStackedLine(stackresult)) - { - ServerInstance->SendMode(stackresult, srcuser); - stackresult.erase(stackresult.begin() + 1, stackresult.end()); - } - } +const std::string& CommandFJoin::Builder::finalize() +{ + if (*content.rbegin() == ' ') + content.erase(content.size()-1); + return str(); } +void FwdFJoinBuilder::add(Membership* memb, std::string::const_iterator mbegin, std::string::const_iterator mend) +{ + // Pseudoserver compatibility: + // Some pseudoservers do not handle lines longer than 512 so we split long FJOINs into multiple messages. + // The forwarded FJOIN can end up being longer than the original one if we have more modes set and won, for example. + + // Check if the member fits into the current message. If not, send it and prepare a new one. + if (!has_room(std::distance(mbegin, mend))) + { + finalize(); + Forward(sourceserver); + clear(); + } + // Add the member and their modes exactly as they sent them + CommandFJoin::Builder::add(memb, mbegin, mend); +} diff --git a/src/modules/m_spanningtree/fmode.cpp b/src/modules/m_spanningtree/fmode.cpp index c1e452db6..e6f49c5b9 100644 --- a/src/modules/m_spanningtree/fmode.cpp +++ b/src/modules/m_spanningtree/fmode.cpp @@ -21,73 +21,35 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -/** FMODE command - server mode with timestamp checks */ -CmdResult CommandFMode::Handle(const std::vector<std::string>& params, User *who) +/** FMODE command - channel mode change with timestamp checks */ +CmdResult CommandFMode::Handle(User* who, std::vector<std::string>& params) { - std::string sourceserv = who->server; - - std::vector<std::string> modelist; - time_t TS = 0; - for (unsigned int q = 0; (q < params.size()) && (q < 64); q++) - { - if (q == 1) - { - /* The timestamp is in this position. - * We don't want to pass that up to the - * server->client protocol! - */ - TS = atoi(params[q].c_str()); - } - else - { - /* Everything else is fine to append to the modelist */ - modelist.push_back(params[q]); - } + time_t TS = ServerCommand::ExtractTS(params[1]); - } - /* Extract the TS value of the object, either User or Channel */ - User* dst = ServerInstance->FindNick(params[0]); - Channel* chan = NULL; - time_t ourTS = 0; + Channel* const chan = ServerInstance->FindChan(params[0]); + if (!chan) + // Channel doesn't exist + return CMD_FAILURE; - if (dst) - { - ourTS = dst->age; - } - else - { - chan = ServerInstance->FindChan(params[0]); - if (chan) - { - ourTS = chan->age; - } - else - /* Oops, channel doesnt exist! */ - return CMD_FAILURE; - } + // Extract the TS of the channel in question + time_t ourTS = chan->age; - if (!TS) - { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"*** BUG? *** TS of 0 sent to FMODE. Are some services authors smoking craq, or is it 1970 again?. Dropped."); - ServerInstance->SNO->WriteToSnoMask('d', "WARNING: The server %s is sending FMODE with a TS of zero. Total craq. Mode was dropped.", sourceserv.c_str()); - return CMD_INVALID; - } - - /* TS is equal or less: Merge the mode changes into ours and pass on. + /* If the TS is greater than ours, we drop the mode and don't pass it anywhere. */ - if (TS <= ourTS) - { - bool merge = (TS == ourTS) && IS_SERVER(who); - ServerInstance->Modes->Process(modelist, who, merge); - return CMD_SUCCESS; - } - /* If the TS is greater than ours, we drop the mode and dont pass it anywhere. + if (TS > ourTS) + return CMD_FAILURE; + + /* TS is equal or less: apply the mode change locally and forward the message */ - return CMD_FAILURE; -} + // Turn modes into a Modes::ChangeList; may have more elements than max modes + Modes::ChangeList changelist; + ServerInstance->Modes.ModeParamsToChangeList(who, MODETYPE_CHANNEL, params, changelist, 2); + + ModeParser::ModeProcessFlag flags = ModeParser::MODE_LOCALONLY; + if ((TS == ourTS) && IS_SERVER(who)) + flags |= ModeParser::MODE_MERGE; + ServerInstance->Modes->Process(who, chan, NULL, changelist, flags); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/ftopic.cpp b/src/modules/m_spanningtree/ftopic.cpp index d559c6ae5..de72d162a 100644 --- a/src/modules/m_spanningtree/ftopic.cpp +++ b/src/modules/m_spanningtree/ftopic.cpp @@ -21,31 +21,69 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - /** FTOPIC command */ -CmdResult CommandFTopic::Handle(const std::vector<std::string>& params, User *user) +CmdResult CommandFTopic::Handle(User* user, std::vector<std::string>& params) { - time_t ts = atoi(params[1].c_str()); Channel* c = ServerInstance->FindChan(params[0]); - if (c) + if (!c) + return CMD_FAILURE; + + if (c->age < ServerCommand::ExtractTS(params[1])) + // Our channel TS is older, nothing to do + return CMD_FAILURE; + + // Channel::topicset is initialized to 0 on channel creation, so their ts will always win if we never had a topic + time_t ts = ServerCommand::ExtractTS(params[2]); + if (ts < c->topicset) + return CMD_FAILURE; + + // The topic text is always the last parameter + const std::string& newtopic = params.back(); + + // If there is a setter in the message use that, otherwise use the message source + const std::string& setter = ((params.size() > 4) ? params[3] : (ServerInstance->Config->FullHostInTopic ? user->GetFullHost() : user->nick)); + + /* + * If the topics were updated at the exact same second, accept + * the remote only when it's "bigger" than ours as defined by + * string comparision, so non-empty topics always overridde + * empty topics if their timestamps are equal + * + * Similarly, if the topic texts are equal too, keep one topic + * setter and discard the other + */ + if (ts == c->topicset) { - if ((ts >= c->topicset) || (c->topic.empty())) - { - if (c->topic != params[3]) - { - // Update topic only when it differs from current topic - c->topic.assign(params[3], 0, ServerInstance->Config->Limits.MaxTopic); - c->WriteChannel(user, "TOPIC %s :%s", c->name.c_str(), c->topic.c_str()); - } - - // Always update setter and settime. - c->setby.assign(params[2], 0, 127); - c->topicset = ts; - } + // Discard if their topic text is "smaller" + if (c->topic > newtopic) + return CMD_FAILURE; + + // If the texts are equal in addition to the timestamps, decide which setter to keep + if ((c->topic == newtopic) && (c->setby >= setter)) + return CMD_FAILURE; } + + c->SetTopic(user, newtopic, ts, &setter); return CMD_SUCCESS; } +// Used when bursting and in reply to RESYNC, contains topic setter as the 4th parameter +CommandFTopic::Builder::Builder(Channel* chan) + : CmdBuilder("FTOPIC") +{ + push(chan->name); + push_int(chan->age); + push_int(chan->topicset); + push(chan->setby); + push_last(chan->topic); +} + +// Used when changing the topic, the setter is the message source +CommandFTopic::Builder::Builder(User* user, Channel* chan) + : CmdBuilder(user, "FTOPIC") +{ + push(chan->name); + push_int(chan->age); + push_int(chan->topicset); + push_last(chan->topic); +} diff --git a/src/modules/m_spanningtree/hmac.cpp b/src/modules/m_spanningtree/hmac.cpp index d990e1fbf..2001d560d 100644 --- a/src/modules/m_spanningtree/hmac.cpp +++ b/src/modules/m_spanningtree/hmac.cpp @@ -19,18 +19,12 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "../hash.h" -#include "../ssl.h" -#include "socketengine.h" +#include "modules/hash.h" +#include "modules/ssl.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" #include "link.h" #include "treesocket.h" -#include "resolvers.h" const std::string& TreeSocket::GetOurChallenge() { @@ -57,44 +51,15 @@ std::string TreeSocket::MakePass(const std::string &password, const std::string /* This is a simple (maybe a bit hacky?) HMAC algorithm, thanks to jilles for * suggesting the use of HMAC to secure the password against various attacks. * - * Note: If m_sha256.so is not loaded, we MUST fall back to plaintext with no + * Note: If an sha256 provider is not available, we MUST fall back to plaintext with no * HMAC challenge/response. */ HashProvider* sha256 = ServerInstance->Modules->FindDataService<HashProvider>("hash/sha256"); - if (Utils->ChallengeResponse && sha256 && !challenge.empty()) - { - if (proto_version < 1202) - { - /* This is how HMAC is done in InspIRCd 1.2: - * - * sha256( (pass xor 0x5c) + sha256((pass xor 0x36) + m) ) - * - * 5c and 36 were chosen as part of the HMAC standard, because they - * flip the bits in a way likely to strengthen the function. - */ - std::string hmac1, hmac2; - - for (size_t n = 0; n < password.length(); n++) - { - hmac1.push_back(static_cast<char>(password[n] ^ 0x5C)); - hmac2.push_back(static_cast<char>(password[n] ^ 0x36)); - } - - hmac2.append(challenge); - hmac2 = sha256->hexsum(hmac2); - - std::string hmac = hmac1 + hmac2; - hmac = sha256->hexsum(hmac); - - return "HMAC-SHA256:"+ hmac; - } - else - { - return "AUTH:" + BinToBase64(sha256->hmac(password, challenge)); - } - } - else if (!challenge.empty() && !sha256) - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Not authenticating to server using SHA256/HMAC because we don't have m_sha256 loaded!"); + if (sha256 && !challenge.empty()) + return "AUTH:" + BinToBase64(sha256->hmac(password, challenge)); + + if (!challenge.empty() && !sha256) + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Not authenticating to server using SHA256/HMAC because we don't have an SHA256 provider (e.g. m_sha256) loaded!"); return password; } @@ -104,13 +69,16 @@ bool TreeSocket::ComparePass(const Link& link, const std::string &theirs) capab->auth_fingerprint = !link.Fingerprint.empty(); capab->auth_challenge = !capab->ourchallenge.empty() && !capab->theirchallenge.empty(); - std::string fp; - if (GetIOHook()) + std::string fp = SSLClientCert::GetFingerprint(this); + if (capab->auth_fingerprint) { - SocketCertificateRequest req(this, Utils->Creator); - if (req.cert) + /* Require fingerprint to exist and match */ + if (link.Fingerprint != fp) { - fp = req.cert->GetFingerprint(); + ServerInstance->SNO->WriteToSnoMask('l',"Invalid SSL certificate fingerprint on link %s: need \"%s\" got \"%s\"", + link.Name.c_str(), link.Fingerprint.c_str(), fp.c_str()); + SendError("Invalid SSL certificate fingerprint " + fp + " - expected " + link.Fingerprint); + return false; } } @@ -118,32 +86,24 @@ bool TreeSocket::ComparePass(const Link& link, const std::string &theirs) { std::string our_hmac = MakePass(link.RecvPass, capab->ourchallenge); - /* Straight string compare of hashes */ - if (our_hmac != theirs) + // Use the timing-safe compare function to compare the hashes + if (!InspIRCd::TimingSafeCompare(our_hmac, theirs)) return false; } else { - /* Straight string compare of plaintext */ - if (link.RecvPass != theirs) + // Use the timing-safe compare function to compare the passwords + if (!InspIRCd::TimingSafeCompare(link.RecvPass, theirs)) return false; } - if (capab->auth_fingerprint) + // Tell opers to set up fingerprint verification if it's not already set up and the SSL mod gave us a fingerprint + // this time + if ((!capab->auth_fingerprint) && (!fp.empty())) { - /* Require fingerprint to exist and match */ - if (link.Fingerprint != fp) - { - ServerInstance->SNO->WriteToSnoMask('l',"Invalid SSL fingerprint on link %s: need \"%s\" got \"%s\"", - link.Name.c_str(), link.Fingerprint.c_str(), fp.c_str()); - SendError("Provided invalid SSL fingerprint " + fp + " - expected " + link.Fingerprint); - return false; - } - } - else if (!fp.empty()) - { - ServerInstance->SNO->WriteToSnoMask('l', "SSL fingerprint for link %s is \"%s\". " + ServerInstance->SNO->WriteToSnoMask('l', "SSL certificate fingerprint for link %s is \"%s\". " "You can improve security by specifying this in <link:fingerprint>.", link.Name.c_str(), fp.c_str()); } + return true; } diff --git a/src/modules/m_spanningtree/idle.cpp b/src/modules/m_spanningtree/idle.cpp index 18aeb0ad5..ad58e52f0 100644 --- a/src/modules/m_spanningtree/idle.cpp +++ b/src/modules/m_spanningtree/idle.cpp @@ -18,67 +18,53 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" #include "utils.h" -#include "treeserver.h" -#include "treesocket.h" +#include "commands.h" -bool TreeSocket::Whois(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandIdle::HandleRemote(RemoteUser* issuer, std::vector<std::string>& params) { - if (params.size() < 1) - return true; - User* u = ServerInstance->FindNick(prefix); - if (u) + /** + * There are two forms of IDLE: request and reply. Requests have one parameter, + * replies have more than one. + * + * If this is a request, 'issuer' did a /whois and its server wants to learn the + * idle time of the user in params[0]. + * + * If this is a reply, params[0] is the user who did the whois and params.back() is + * the number of seconds 'issuer' has been idle. + */ + + User* target = ServerInstance->FindUUID(params[0]); + if ((!target) || (target->registered != REG_ALL)) + return CMD_FAILURE; + + LocalUser* localtarget = IS_LOCAL(target); + if (!localtarget) { - // an incoming request - if (params.size() == 1) - { - User* x = ServerInstance->FindNick(params[0]); - if ((x) && (IS_LOCAL(x))) - { - long idle = labs((long)((x->idle_lastmsg) - ServerInstance->Time())); - parameterlist par; - par.push_back(prefix); - par.push_back(ConvToStr(x->signon)); - par.push_back(ConvToStr(idle)); - // ours, we're done, pass it BACK - Utils->DoOneToOne(params[0], "IDLE", par, u->server); - } - else - { - // not ours pass it on - if (x) - Utils->DoOneToOne(prefix, "IDLE", params, x->server); - } - } - else if (params.size() == 3) - { - std::string who_did_the_whois = params[0]; - User* who_to_send_to = ServerInstance->FindNick(who_did_the_whois); - if ((who_to_send_to) && (IS_LOCAL(who_to_send_to)) && (who_to_send_to->registered == REG_ALL)) - { - // an incoming reply to a whois we sent out - std::string nick_whoised = prefix; - unsigned long signon = atoi(params[1].c_str()); - unsigned long idle = atoi(params[2].c_str()); - if ((who_to_send_to) && (IS_LOCAL(who_to_send_to))) - { - ServerInstance->DoWhois(who_to_send_to, u, signon, idle, nick_whoised.c_str()); - } - } - else - { - // not ours, pass it on - if (who_to_send_to) - Utils->DoOneToOne(prefix, "IDLE", params, who_to_send_to->server); - } - } + // Forward to target's server + return CMD_SUCCESS; } - return true; -} + if (params.size() >= 2) + { + ServerInstance->Parser.CallHandler("WHOIS", params, issuer); + } + else + { + // A server is asking us the idle time of our user + unsigned int idle; + if (localtarget->idle_lastmsg >= ServerInstance->Time()) + // Possible case when our clock ticked backwards + idle = 0; + else + idle = ((unsigned int) (ServerInstance->Time() - localtarget->idle_lastmsg)); + + CmdBuilder reply(params[0], "IDLE"); + reply.push_back(issuer->uuid); + reply.push_back(ConvToStr(target->signon)); + reply.push_back(ConvToStr(idle)); + reply.Unicast(issuer); + } + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/ijoin.cpp b/src/modules/m_spanningtree/ijoin.cpp new file mode 100644 index 000000000..c2dbcf7f5 --- /dev/null +++ b/src/modules/m_spanningtree/ijoin.cpp @@ -0,0 +1,75 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2012-2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "commands.h" +#include "utils.h" +#include "treeserver.h" +#include "treesocket.h" + +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() > 3) + { + time_t RemoteTS = ServerCommand::ExtractTS(params[2]); + apply_modes = (RemoteTS <= chan->age); + } + else + apply_modes = false; + + // Join the user and set the membership id to what they sent + Membership* memb = chan->ForceJoin(user, apply_modes ? ¶ms[3] : NULL); + if (!memb) + return CMD_FAILURE; + + memb->id = Membership::IdFromString(params[1]); + return CMD_SUCCESS; +} + +CmdResult CommandResync::HandleServer(TreeServer* server, std::vector<std::string>& params) +{ + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Resyncing " + params[0]); + Channel* chan = ServerInstance->FindChan(params[0]); + if (!chan) + { + // This can happen for a number of reasons, safe to ignore + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Channel does not exist"); + return CMD_FAILURE; + } + + if (!server->IsLocal()) + throw ProtocolException("RESYNC from a server that is not directly connected"); + + // Send all known information about the channel + server->GetSocket()->SyncChannel(chan); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/link.h b/src/modules/m_spanningtree/link.h index 797f108d8..632982623 100644 --- a/src/modules/m_spanningtree/link.h +++ b/src/modules/m_spanningtree/link.h @@ -18,20 +18,19 @@ */ -#ifndef M_SPANNINGTREE_LINK_H -#define M_SPANNINGTREE_LINK_H +#pragma once class Link : public refcountbase { public: reference<ConfigTag> tag; - irc::string Name; + std::string Name; std::string IPAddr; int Port; std::string SendPass; std::string RecvPass; std::string Fingerprint; - std::string AllowMask; + std::vector<std::string> AllowMasks; bool HiddenFromStats; std::string Hook; int Timeout; @@ -51,5 +50,3 @@ class Autoconnect : public refcountbase int position; Autoconnect(ConfigTag* Tag) : tag(Tag) {} }; - -#endif diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp index 967b577b1..6bf9e8044 100644 --- a/src/modules/m_spanningtree/main.cpp +++ b/src/modules/m_spanningtree/main.cpp @@ -21,13 +21,12 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "socket.h" #include "xline.h" +#include "iohook.h" +#include "modules/spanningtree.h" -#include "cachetimer.h" #include "resolvers.h" #include "main.h" #include "utils.h" @@ -35,60 +34,67 @@ #include "link.h" #include "treesocket.h" #include "commands.h" -#include "protocolinterface.h" +#include "translate.h" ModuleSpanningTree::ModuleSpanningTree() - : KeepNickTS(false) + : rconnect(this), rsquit(this), map(this) + , commands(this) + , currmembid(0) + , eventprov(this, "event/spanningtree") + , DNS(this, "DNS") + , loopCall(false) { - Utils = new SpanningTreeUtilities(this); - commands = new SpanningTreeCommands(this); - RefreshTimer = NULL; } SpanningTreeCommands::SpanningTreeCommands(ModuleSpanningTree* module) - : rconnect(module, module->Utils), rsquit(module, module->Utils), - svsjoin(module), svspart(module), svsnick(module), metadata(module), - uid(module), opertype(module), fjoin(module), fmode(module), ftopic(module), - fhost(module), fident(module), fname(module) + : svsjoin(module), svspart(module), svsnick(module), metadata(module), + uid(module), opertype(module), fjoin(module), ijoin(module), resync(module), + fmode(module), ftopic(module), fhost(module), fident(module), fname(module), + away(module), addline(module), delline(module), encap(module), idle(module), + nick(module), ping(module), pong(module), save(module), + server(module), squit(module), snonotice(module), + endburst(module), sinfo(module), num(module) { } +namespace +{ + void SetLocalUsersServer(Server* newserver) + { + // Does not change the server of quitting users because those are not in the list + + ServerInstance->FakeClient->server = newserver; + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) + (*i)->server = newserver; + } + + void ResetMembershipIds() + { + // Set all membership ids to 0 + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); + for (UserManager::LocalList::iterator i = list.begin(); i != list.end(); ++i) + { + LocalUser* user = *i; + for (User::ChanList::iterator j = user->chans.begin(); j != user->chans.end(); ++j) + (*j)->id = 0; + } + } +} + void ModuleSpanningTree::init() { - ServerInstance->Modules->AddService(commands->rconnect); - ServerInstance->Modules->AddService(commands->rsquit); - ServerInstance->Modules->AddService(commands->svsjoin); - ServerInstance->Modules->AddService(commands->svspart); - ServerInstance->Modules->AddService(commands->svsnick); - ServerInstance->Modules->AddService(commands->metadata); - ServerInstance->Modules->AddService(commands->uid); - ServerInstance->Modules->AddService(commands->opertype); - ServerInstance->Modules->AddService(commands->fjoin); - ServerInstance->Modules->AddService(commands->fmode); - ServerInstance->Modules->AddService(commands->ftopic); - ServerInstance->Modules->AddService(commands->fhost); - ServerInstance->Modules->AddService(commands->fident); - ServerInstance->Modules->AddService(commands->fname); - RefreshTimer = new CacheRefreshTimer(Utils); - ServerInstance->Timers->AddTimer(RefreshTimer); - - Implementation eventlist[] = - { - I_OnPreCommand, I_OnGetServerDescription, I_OnUserInvite, I_OnPostTopicChange, - I_OnWallops, I_OnUserNotice, I_OnUserMessage, I_OnBackgroundTimer, I_OnUserJoin, - I_OnChangeHost, I_OnChangeName, I_OnChangeIdent, I_OnUserPart, I_OnUnloadModule, - I_OnUserQuit, I_OnUserPostNick, I_OnUserKick, I_OnRemoteKill, I_OnRehash, I_OnPreRehash, - I_OnOper, I_OnAddLine, I_OnDelLine, I_OnMode, I_OnLoadModule, I_OnStats, - I_OnSetAway, I_OnPostCommand, I_OnUserConnect, I_OnAcceptConnection - }; - ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation)); - - delete ServerInstance->PI; - ServerInstance->PI = new SpanningTreeProtocolInterface(Utils); - loopCall = false; - - // update our local user count - Utils->TreeRoot->SetUserCount(ServerInstance->Users->LocalUserCount()); + ServerInstance->SNO->EnableSnomask('l', "LINK"); + + ResetMembershipIds(); + + Utils = new SpanningTreeUtilities(this); + Utils->TreeRoot = new TreeServer; + + ServerInstance->PI = &protocolinterface; + + delete ServerInstance->FakeClient->server; + SetLocalUsersServer(Utils->TreeRoot); } void ModuleSpanningTree::ShowLinks(TreeServer* Current, User* user, int hops) @@ -98,44 +104,39 @@ void ModuleSpanningTree::ShowLinks(TreeServer* Current, User* user, int hops) { Parent = Current->GetParent()->GetName(); } - for (unsigned int q = 0; q < Current->ChildCount(); q++) + + const TreeServer::ChildServers& children = Current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - if ((Current->GetChild(q)->Hidden) || ((Utils->HideULines) && (ServerInstance->ULine(Current->GetChild(q)->GetName())))) + TreeServer* server = *i; + if ((server->Hidden) || ((Utils->HideULines) && (server->IsULine()))) { - if (IS_OPER(user)) + if (user->IsOper()) { - ShowLinks(Current->GetChild(q),user,hops+1); + ShowLinks(server, user, hops+1); } } else { - ShowLinks(Current->GetChild(q),user,hops+1); + ShowLinks(server, user, hops+1); } } /* Don't display the line if its a uline, hide ulines is on, and the user isnt an oper */ - if ((Utils->HideULines) && (ServerInstance->ULine(Current->GetName())) && (!IS_OPER(user))) + if ((Utils->HideULines) && (Current->IsULine()) && (!user->IsOper())) return; /* Or if the server is hidden and they're not an oper */ - else if ((Current->Hidden) && (!IS_OPER(user))) + else if ((Current->Hidden) && (!user->IsOper())) return; - std::string servername = Current->GetName(); - user->WriteNumeric(364, "%s %s %s :%d %s", user->nick.c_str(), servername.c_str(), - (Utils->FlatLinks && (!IS_OPER(user))) ? ServerInstance->Config->ServerName.c_str() : Parent.c_str(), - (Utils->FlatLinks && (!IS_OPER(user))) ? 0 : hops, - Current->GetDesc().c_str()); -} - -int ModuleSpanningTree::CountServs() -{ - return Utils->serverlist.size(); + user->WriteNumeric(RPL_LINKS, Current->GetName(), + (((Utils->FlatLinks) && (!user->IsOper())) ? ServerInstance->Config->ServerName : Parent), + InspIRCd::Format("%d %s", (((Utils->FlatLinks) && (!user->IsOper())) ? 0 : hops), Current->GetDesc().c_str())); } void ModuleSpanningTree::HandleLinks(const std::vector<std::string>& parameters, User* user) { ShowLinks(Utils->TreeRoot,user,0); - user->WriteNumeric(365, "%s * :End of /LINKS list.",user->nick.c_str()); - return; + user->WriteNumeric(RPL_ENDOFLINKS, '*', "End of /LINKS list."); } std::string ModuleSpanningTree::TimeToStr(time_t secs) @@ -152,79 +153,6 @@ std::string ModuleSpanningTree::TimeToStr(time_t secs) + ConvToStr(secs) + "s"); } -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; - - if (s->GetSocket() && s->GetSocket()->GetLinkState() == DYING) - { - s->GetSocket()->Close(); - goto restart; - } - - // Fix for bug #792, do not ping servers that are not connected yet! - // Remote servers have Socket == NULL and local connected servers have - // Socket->LinkState == CONNECTED - if (s->GetSocket() && s->GetSocket()->GetLinkState() != CONNECTED) - continue; - - // Now do PING checks on all servers - TreeServer *mts = Utils->BestRouteTo(s->GetID()); - - if (mts) - { - // 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); - TreeSocket *tsock = mts->GetSocket(); - - // ... if we can find a proper route to them - if (tsock) - { - tsock->WriteLine(":" + ServerInstance->Config->GetSID() + " PING " + - ServerInstance->Config->GetSID() + " " + s->GetID()); - s->LastPingMsec = ts; - } - } - else - { - // They didn't answer the last ping, if they are locally connected, get rid of them. - TreeSocket *sock = s->GetSocket(); - if (sock) - { - 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 */ - std::string servername = s->GetName(); - ServerInstance->SNO->WriteToSnoMask('l',"Server \002%s\002 has not responded to PING for %d seconds, high latency.", servername.c_str(), Utils->PingWarnTime); - s->Warned = true; - } - } - } -} - void ModuleSpanningTree::ConnectServer(Autoconnect* a, bool on_timer) { if (!a) @@ -266,13 +194,12 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) { bool ipvalid = true; - if (InspIRCd::Match(ServerInstance->Config->ServerName, assign(x->Name), rfc_case_insensitive_map)) + if (InspIRCd::Match(ServerInstance->Config->ServerName, x->Name, ascii_case_insensitive_map)) { ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Not connecting to myself."); return; } - QueryType start_type = DNS_QUERY_AAAA; if (strchr(x->IPAddr.c_str(),':')) { in6_addr n; @@ -289,8 +216,8 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) /* Do we already have an IP? If so, no need to resolve it. */ if (ipvalid) { - /* Gave a hook, but it wasnt one we know */ - TreeSocket* newsocket = new TreeSocket(Utils, x, y, x->IPAddr); + // Create a TreeServer object that will start connecting immediately in the background + TreeSocket* newsocket = new TreeSocket(x, y, x->IPAddr); if (newsocket->GetFd() > -1) { /* Handled automatically on success */ @@ -302,17 +229,30 @@ void ModuleSpanningTree::ConnectServer(Link* x, Autoconnect* y) ServerInstance->GlobalCulls.AddItem(newsocket); } } + else if (!DNS) + { + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Hostname given and m_dns.so is not loaded, unable to resolve.", x->Name.c_str()); + } else { + // Guess start_type from bindip aftype + DNS::QueryType start_type = DNS::QUERY_AAAA; + irc::sockets::sockaddrs bind; + if ((!x->Bind.empty()) && (irc::sockets::aptosa(x->Bind, 0, bind))) + { + if (bind.sa.sa_family == AF_INET) + start_type = DNS::QUERY_A; + } + + ServernameResolver* snr = new ServernameResolver(*DNS, x->IPAddr, x, start_type, y); try { - bool cached = false; - ServernameResolver* snr = new ServernameResolver(Utils, x->IPAddr, x, cached, start_type, y); - ServerInstance->AddResolver(snr, cached); + DNS->Process(snr); } - catch (ModuleException& e) + catch (DNS::Exception& e) { - ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: %s.",x->Name.c_str(), e.GetReason()); + delete snr; + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: %s.",x->Name.c_str(), e.GetReason().c_str()); ConnectServer(y, false); } } @@ -356,224 +296,130 @@ void ModuleSpanningTree::DoConnectTimeout(time_t curtime) ModResult ModuleSpanningTree::HandleVersion(const std::vector<std::string>& parameters, User* user) { - // we've already checked if pcnt > 0, so this is safe + // We've already confirmed that !parameters.empty(), so this is safe TreeServer* found = Utils->FindServerMask(parameters[0]); if (found) { - std::string Version = found->GetVersion(); - user->WriteNumeric(351, "%s :%s",user->nick.c_str(),Version.c_str()); if (found == Utils->TreeRoot) { - ServerInstance->Config->Send005(user); + // Pass to default VERSION handler. + return MOD_RES_PASSTHRU; } + + // If an oper wants to see the version then show the full version string instead of the normal, + // but only if it is non-empty. + // If it's empty it might be that the server is still syncing (full version hasn't arrived yet) + // or the server is a 2.0 server and does not send a full version. + bool showfull = ((user->IsOper()) && (!found->GetFullVersion().empty())); + const std::string& Version = (showfull ? found->GetFullVersion() : found->GetVersion()); + user->WriteNumeric(RPL_VERSION, Version); } else { - user->WriteNumeric(402, "%s %s :No such server",user->nick.c_str(),parameters[0].c_str()); + user->WriteNumeric(ERR_NOSUCHSERVER, parameters[0], "No such server"); } return MOD_RES_DENY; } -/* This method will attempt to get a message to a remote user. - */ -void ModuleSpanningTree::RemoteMessage(User* user, const char* format, ...) -{ - char text[MAXBUF]; - va_list argsPtr; - - va_start(argsPtr, format); - vsnprintf(text, MAXBUF, format, argsPtr); - va_end(argsPtr); - - if (IS_LOCAL(user)) - user->WriteServ("NOTICE %s :%s", user->nick.c_str(), text); - else - ServerInstance->PI->SendUserNotice(user, text); -} - ModResult ModuleSpanningTree::HandleConnect(const std::vector<std::string>& parameters, User* user) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) { Link* x = *i; - if (InspIRCd::Match(x->Name.c_str(),parameters[0], rfc_case_insensitive_map)) + if (InspIRCd::Match(x->Name, parameters[0], ascii_case_insensitive_map)) { - if (InspIRCd::Match(ServerInstance->Config->ServerName, assign(x->Name), rfc_case_insensitive_map)) + if (InspIRCd::Match(ServerInstance->Config->ServerName, x->Name, ascii_case_insensitive_map)) { - RemoteMessage(user, "*** CONNECT: Server \002%s\002 is ME, not connecting.",x->Name.c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Server \002%s\002 is ME, not connecting.", x->Name.c_str())); return MOD_RES_DENY; } - TreeServer* CheckDupe = Utils->FindServer(x->Name.c_str()); + TreeServer* CheckDupe = Utils->FindServer(x->Name); if (!CheckDupe) { - RemoteMessage(user, "*** CONNECT: Connecting to server: \002%s\002 (%s:%d)",x->Name.c_str(),(x->HiddenFromStats ? "<hidden>" : x->IPAddr.c_str()),x->Port); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Connecting to server: \002%s\002 (%s:%d)", x->Name.c_str(), (x->HiddenFromStats ? "<hidden>" : x->IPAddr.c_str()), x->Port)); ConnectServer(x); return MOD_RES_DENY; } else { - std::string servername = CheckDupe->GetParent()->GetName(); - RemoteMessage(user, "*** CONNECT: Server \002%s\002 already exists on the network and is connected via \002%s\002", x->Name.c_str(), servername.c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: Server \002%s\002 already exists on the network and is connected via \002%s\002", x->Name.c_str(), CheckDupe->GetParent()->GetName().c_str())); return MOD_RES_DENY; } } } - RemoteMessage(user, "*** CONNECT: No server matching \002%s\002 could be found in the config file.",parameters[0].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** CONNECT: No server matching \002%s\002 could be found in the config file.", parameters[0].c_str())); return MOD_RES_DENY; } -void ModuleSpanningTree::OnGetServerDescription(const std::string &servername,std::string &description) -{ - TreeServer* s = Utils->FindServer(servername); - if (s) - { - description = s->GetDesc(); - } -} - -void ModuleSpanningTree::OnUserInvite(User* source,User* dest,Channel* channel, time_t expiry) +void ModuleSpanningTree::OnUserInvite(User* source, User* dest, Channel* channel, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) { if (IS_LOCAL(source)) { - parameterlist params; + CmdBuilder params(source, "INVITE"); params.push_back(dest->uuid); params.push_back(channel->name); + params.push_int(channel->age); params.push_back(ConvToStr(expiry)); - Utils->DoOneToMany(source->uuid,"INVITE",params); + params.Broadcast(); } } -void ModuleSpanningTree::OnPostTopicChange(User* user, Channel* chan, const std::string &topic) -{ - // Drop remote events on the floor. - if (!IS_LOCAL(user)) - return; - - parameterlist params; - params.push_back(chan->name); - params.push_back(":"+topic); - Utils->DoOneToMany(user->uuid,"TOPIC",params); -} - -void ModuleSpanningTree::OnWallops(User* user, const std::string &text) +ModResult ModuleSpanningTree::OnPreTopicChange(User* user, Channel* chan, const std::string& topic) { - if (IS_LOCAL(user)) + // XXX: Deny topic changes if the current topic set time is the current time or is in the future because + // other servers will drop our FTOPIC. This restriction will be removed when the protocol is updated. + if ((chan->topicset >= ServerInstance->Time()) && (Utils->serverlist.size() > 1)) { - parameterlist params; - params.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"WALLOPS",params); + user->WriteNumeric(ERR_CHANOPRIVSNEEDED, chan->name, "Retry topic change later"); + return MOD_RES_DENY; } + return MOD_RES_PASSTHRU; } -void ModuleSpanningTree::OnUserNotice(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list) +void ModuleSpanningTree::OnPostTopicChange(User* user, Channel* chan, const std::string &topic) { - /* Server origin */ - if (user == NULL) + // Drop remote events on the floor. + if (!IS_LOCAL(user)) return; - if (target_type == TYPE_USER) - { - User* d = (User*)dest; - if (!IS_LOCAL(d) && IS_LOCAL(user)) - { - parameterlist params; - params.push_back(d->uuid); - params.push_back(":"+text); - Utils->DoOneToOne(user->uuid,"NOTICE",params,d->server); - } - } - else if (target_type == TYPE_CHANNEL) - { - if (IS_LOCAL(user)) - { - Channel *c = (Channel*)dest; - if (c) - { - std::string cname = c->name; - if (status) - cname = status + cname; - TreeServerList list; - Utils->GetListOfServersForChannel(c,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(":"+std::string(user->uuid)+" NOTICE "+cname+" :"+text); - } - } - } - } - else if (target_type == TYPE_SERVER) - { - if (IS_LOCAL(user)) - { - char* target = (char*)dest; - parameterlist par; - par.push_back(target); - par.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"NOTICE",par); - } - } + CommandFTopic::Builder(user, chan).Broadcast(); } -void ModuleSpanningTree::OnUserMessage(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list) +void ModuleSpanningTree::OnUserMessage(User* user, void* dest, int target_type, const std::string& text, char status, const CUList& exempt_list, MessageType msgtype) { - /* Server origin */ - if (user == NULL) + if (!IS_LOCAL(user)) return; + const char* message_type = (msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); if (target_type == TYPE_USER) { - // route private messages which are targetted at clients only to the server - // which needs to receive them - User* d = (User*)dest; - if (!IS_LOCAL(d) && (IS_LOCAL(user))) + User* d = (User*) dest; + if (!IS_LOCAL(d)) { - parameterlist params; + CmdBuilder params(user, message_type); params.push_back(d->uuid); - params.push_back(":"+text); - Utils->DoOneToOne(user->uuid,"PRIVMSG",params,d->server); + params.push_last(text); + params.Unicast(d); } } else if (target_type == TYPE_CHANNEL) { - if (IS_LOCAL(user)) - { - Channel *c = (Channel*)dest; - if (c) - { - std::string cname = c->name; - if (status) - cname = status + cname; - TreeServerList list; - Utils->GetListOfServersForChannel(c,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(":"+std::string(user->uuid)+" PRIVMSG "+cname+" :"+text); - } - } - } + Utils->SendChannelMessage(user->uuid, (Channel*)dest, text, status, exempt_list, message_type); } else if (target_type == TYPE_SERVER) { - if (IS_LOCAL(user)) - { - char* target = (char*)dest; - parameterlist par; - par.push_back(target); - par.push_back(":"+text); - Utils->DoOneToMany(user->uuid,"PRIVMSG",par); - } + char* target = (char*) dest; + CmdBuilder par(user, message_type); + par.push_back(target); + par.push_last(text); + par.Broadcast(); } } void ModuleSpanningTree::OnBackgroundTimer(time_t curtime) { AutoConnectServers(curtime); - DoPingChecks(curtime); DoConnectTimeout(curtime); } @@ -582,25 +428,10 @@ void ModuleSpanningTree::OnUserConnect(LocalUser* user) if (user->quitting) return; - parameterlist params; - params.push_back(user->uuid); - params.push_back(ConvToStr(user->age)); - params.push_back(user->nick); - params.push_back(user->host); - params.push_back(user->dhost); - params.push_back(user->ident); - params.push_back(user->GetIPString()); - params.push_back(ConvToStr(user->signon)); - params.push_back("+"+std::string(user->FormatModes(true))); - params.push_back(":"+user->fullname); - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "UID", params); + CommandUID::Builder(user).Broadcast(); - if (IS_OPER(user)) - { - params.clear(); - params.push_back(user->oper->name); - Utils->DoOneToMany(user->uuid,"OPERTYPE",params); - } + if (user->IsOper()) + CommandOpertype::Builder(user).Broadcast(); for(Extensible::ExtensibleStore::const_iterator i = user->GetExtList().begin(); i != user->GetExtList().end(); i++) { @@ -610,23 +441,36 @@ void ModuleSpanningTree::OnUserConnect(LocalUser* user) ServerInstance->PI->SendMetaData(user, item->name, value); } - Utils->TreeRoot->SetUserCount(1); // increment by 1 + Utils->TreeRoot->UserCount++; } -void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) +void ModuleSpanningTree::OnUserJoin(Membership* memb, bool sync, bool created_by_local, CUList& excepts) { // Only do this for local users - if (IS_LOCAL(memb->user)) + if (!IS_LOCAL(memb->user)) + return; + + // Assign the current membership id to the new Membership and increase it + memb->id = currmembid++; + + if (created_by_local) + { + CommandFJoin::Builder params(memb->chan); + params.add(memb); + params.finalize(); + params.Broadcast(); + } + else { - parameterlist params; - // set up their permissions and the channel TS with FJOIN. - // All users are FJOINed now, because a module may specify - // new joining permissions for the user. + CmdBuilder params(memb->user, "IJOIN"); params.push_back(memb->chan->name); - params.push_back(ConvToStr(memb->chan->age)); - params.push_back(std::string("+") + memb->chan->ChanModes(true)); - params.push_back(memb->modes+","+memb->user->uuid); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FJOIN",params); + params.push_int(memb->id); + if (!memb->modes.empty()) + { + params.push_back(ConvToStr(memb->chan->age)); + params.push_back(memb->modes); + } + params.Broadcast(); } } @@ -635,9 +479,7 @@ void ModuleSpanningTree::OnChangeHost(User* user, const std::string &newhost) if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(newhost); - Utils->DoOneToMany(user->uuid,"FHOST",params); + CmdBuilder(user, "FHOST").push(newhost).Broadcast(); } void ModuleSpanningTree::OnChangeName(User* user, const std::string &gecos) @@ -645,9 +487,7 @@ void ModuleSpanningTree::OnChangeName(User* user, const std::string &gecos) if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(":" + gecos); - Utils->DoOneToMany(user->uuid,"FNAME",params); + CmdBuilder(user, "FNAME").push_last(gecos).Broadcast(); } void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) @@ -655,101 +495,77 @@ void ModuleSpanningTree::OnChangeIdent(User* user, const std::string &ident) if ((user->registered != REG_ALL) || (!IS_LOCAL(user))) return; - parameterlist params; - params.push_back(ident); - Utils->DoOneToMany(user->uuid,"FIDENT",params); + CmdBuilder(user, "FIDENT").push(ident).Broadcast(); } void ModuleSpanningTree::OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) { if (IS_LOCAL(memb->user)) { - parameterlist params; + CmdBuilder params(memb->user, "PART"); params.push_back(memb->chan->name); if (!partmessage.empty()) - params.push_back(":"+partmessage); - Utils->DoOneToMany(memb->user->uuid,"PART",params); + params.push_last(partmessage); + params.Broadcast(); } } void ModuleSpanningTree::OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) { - if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) + if (IS_LOCAL(user)) { - parameterlist params; - 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) { - params.push_back(":"+oper_message); - Utils->DoOneToMany(user->uuid,"OPERQUIT",params); + 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()); } - params.clear(); - params.push_back(":"+reason); - Utils->DoOneToMany(user->uuid,"QUIT",params); } - // Regardless, We need to modify the user Counts.. - TreeServer* SourceServer = Utils->FindServer(user->server); - if (SourceServer) - { - SourceServer->SetUserCount(-1); // decrement by 1 - } + // Regardless, update the UserCount + TreeServer::Get(user)->UserCount--; } void ModuleSpanningTree::OnUserPostNick(User* user, const std::string &oldnick) { if (IS_LOCAL(user)) { - parameterlist params; + // The nick TS is updated by the core, we don't do it + CmdBuilder params(user, "NICK"); params.push_back(user->nick); - - /** IMPORTANT: We don't update the TS if the oldnick is just a case change of the newnick! - */ - if ((irc::string(user->nick.c_str()) != assign(oldnick)) && (!this->KeepNickTS)) - user->age = ServerInstance->Time(); - params.push_back(ConvToStr(user->age)); - Utils->DoOneToMany(user->uuid,"NICK",params); - this->KeepNickTS = false; + params.Broadcast(); } - else if (!loopCall && user->nick == user->uuid) + else if (!loopCall) { - parameterlist params; - params.push_back(user->uuid); - params.push_back(ConvToStr(user->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"SAVE",params); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "WARNING: Changed nick of remote user %s from %s to %s TS %lu by ourselves!", user->uuid.c_str(), oldnick.c_str(), user->nick.c_str(), (unsigned long) user->age); } } void ModuleSpanningTree::OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) { - parameterlist params; + if ((!IS_LOCAL(source)) && (source != ServerInstance->FakeClient)) + return; + + CmdBuilder params(source, "KICK"); params.push_back(memb->chan->name); params.push_back(memb->user->uuid); - params.push_back(":"+reason); - if (IS_LOCAL(source)) - { - Utils->DoOneToMany(source->uuid,"KICK",params); - } - else if (source == ServerInstance->FakeClient) - { - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"KICK",params); - } -} - -void ModuleSpanningTree::OnRemoteKill(User* source, User* dest, const std::string &reason, const std::string &operreason) -{ - if (!IS_LOCAL(source)) - return; // Only start routing if we're origin. - - ServerInstance->OperQuit.set(dest, operreason); - parameterlist params; - params.push_back(":"+operreason); - Utils->DoOneToMany(dest->uuid,"OPERQUIT",params); - params.clear(); - params.push_back(dest->uuid); - params.push_back(":"+reason); - Utils->DoOneToMany(source->uuid,"KILL",params); + // If a remote user is being kicked by us then send the membership id in the kick too + if (!IS_LOCAL(memb->user)) + params.push_int(memb->id); + params.push_last(reason); + params.Broadcast(); } void ModuleSpanningTree::OnPreRehash(User* user, const std::string ¶meter) @@ -757,19 +573,29 @@ void ModuleSpanningTree::OnPreRehash(User* user, const std::string ¶meter) if (loopCall) return; // Don't generate a REHASH here if we're in the middle of processing a message that generated this one - ServerInstance->Logs->Log("remoterehash", DEBUG, "called with param %s", parameter.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "OnPreRehash called with param %s", parameter.c_str()); // Send out to other servers if (!parameter.empty() && parameter[0] != '-') { - parameterlist params; + CmdBuilder params((user ? user->uuid : ServerInstance->Config->GetSID()), "REHASH"); params.push_back(parameter); - Utils->DoOneToAllButSender(user ? user->uuid : ServerInstance->Config->GetSID(), "REHASH", params, user ? user->server : ServerInstance->Config->ServerName); + params.Forward(user ? TreeServer::Get(user)->GetRoute() : NULL); } } -void ModuleSpanningTree::OnRehash(User* user) +void ModuleSpanningTree::ReadConfig(ConfigStatus& status) { + // Did this rehash change the description of this server? + const std::string& newdesc = ServerInstance->Config->ServerDesc; + if (newdesc != Utils->TreeRoot->GetDesc()) + { + // Broadcast a SINFO desc message to let the network know about the new description. This is the description + // string that is sent in the SERVER message initially and shown for example in WHOIS. + // We don't need to update the field itself in the Server object - the core does that. + CommandSInfo::Builder(Utils->TreeRoot, "desc", newdesc).Broadcast(); + } + // Re-read config stuff try { @@ -783,8 +609,8 @@ void ModuleSpanningTree::OnRehash(User* user) std::string msg = "Error in configuration: "; msg.append(e.GetReason()); ServerInstance->SNO->WriteToSnoMask('l', msg); - if (user && !IS_LOCAL(user)) - ServerInstance->PI->SendSNONotice("L", msg); + if (status.srcuser && !IS_LOCAL(status.srcuser)) + ServerInstance->PI->SendSNONotice('L', msg); } } @@ -799,24 +625,41 @@ void ModuleSpanningTree::OnLoadModule(Module* mod) data.push_back('='); data.append(v.link_data); } - ServerInstance->PI->SendMetaData(NULL, "modules", data); + ServerInstance->PI->SendMetaData("modules", data); } void ModuleSpanningTree::OnUnloadModule(Module* mod) { - ServerInstance->PI->SendMetaData(NULL, "modules", "-" + mod->ModuleSourceFile); + if (!Utils) + return; + ServerInstance->PI->SendMetaData("modules", "-" + mod->ModuleSourceFile); + + if (mod == this) + { + // We are being unloaded, inform modules about all servers splitting which cannot be done later when the servers are actually disconnected + const server_hash& servers = Utils->serverlist; + for (server_hash::const_iterator i = servers.begin(); i != servers.end(); ++i) + { + TreeServer* server = i->second; + if (!server->IsRoot()) + FOREACH_MOD_CUSTOM(GetEventProvider(), SpanningTreeEventListener, OnServerSplit, (server)); + } + return; + } + + // Some other module is being unloaded. If it provides an IOHook we use, we must close that server connection now. restart: - unsigned int items = Utils->TreeRoot->ChildCount(); - for(unsigned int x = 0; x < items; x++) + // Close all connections which use an IO hook provided by this module + const TreeServer::ChildServers& list = Utils->TreeRoot->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = list.begin(); i != list.end(); ++i) { - TreeServer* srv = Utils->TreeRoot->GetChild(x); - TreeSocket* sock = srv->GetSocket(); - if (sock && sock->GetIOHook() == mod) + TreeSocket* sock = (*i)->GetSocket(); + if (sock->GetModHook(mod)) { sock->SendError("SSL module unloaded"); sock->Close(); - // XXX: The list we're iterating is modified by TreeSocket::Squit() which is called by Close() + // XXX: The list we're iterating is modified by TreeServer::SQuit() which is called by Close() goto restart; } } @@ -824,169 +667,96 @@ restart: for (SpanningTreeUtilities::TimeoutList::const_iterator i = Utils->timeoutlist.begin(); i != Utils->timeoutlist.end(); ++i) { TreeSocket* sock = i->first; - if (sock->GetIOHook() == mod) + if (sock->GetModHook(mod)) sock->Close(); } } -// note: the protocol does not allow direct umode +o except -// via NICK with 8 params. sending OPERTYPE infers +o modechange -// locally. void ModuleSpanningTree::OnOper(User* user, const std::string &opertype) { if (user->registered != REG_ALL || !IS_LOCAL(user)) return; - parameterlist params; - params.push_back(opertype); - Utils->DoOneToMany(user->uuid,"OPERTYPE",params); + + // Note: The protocol does not allow direct umode +o; + // sending OPERTYPE infers +o modechange locally. + CommandOpertype::Builder(user).Broadcast(); } void ModuleSpanningTree::OnAddLine(User* user, XLine *x) { - if (!x->IsBurstable() || loopCall) + if (!x->IsBurstable() || loopCall || (user && !IS_LOCAL(user))) return; - parameterlist params; - params.push_back(x->type); - params.push_back(x->Displayable()); - params.push_back(ServerInstance->Config->ServerName); - params.push_back(ConvToStr(x->set_time)); - params.push_back(ConvToStr(x->duration)); - params.push_back(":" + x->reason); - if (!user) - { - /* Server-set lines */ - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "ADDLINE", params); - } - else if (IS_LOCAL(user)) - { - /* User-set lines */ - Utils->DoOneToMany(user->uuid, "ADDLINE", params); - } + user = ServerInstance->FakeClient; + + CommandAddLine::Builder(x, user).Broadcast(); } void ModuleSpanningTree::OnDelLine(User* user, XLine *x) { - if (!x->IsBurstable() || loopCall) + if (!x->IsBurstable() || loopCall || (user && !IS_LOCAL(user))) return; - parameterlist params; - params.push_back(x->type); - params.push_back(x->Displayable()); - if (!user) - { - /* Server-unset lines */ - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "DELLINE", params); - } - else if (IS_LOCAL(user)) - { - /* User-unset lines */ - Utils->DoOneToMany(user->uuid, "DELLINE", params); - } -} - -void ModuleSpanningTree::OnMode(User* user, void* dest, int target_type, const parameterlist &text, const std::vector<TranslateType> &translate) -{ - if ((IS_LOCAL(user)) && (user->registered == REG_ALL)) - { - parameterlist params; - std::string output_text; - - ServerInstance->Parser->TranslateUIDs(translate, text, output_text); + user = ServerInstance->FakeClient; - if (target_type == TYPE_USER) - { - User* u = (User*)dest; - params.push_back(u->uuid); - params.push_back(output_text); - Utils->DoOneToMany(user->uuid, "MODE", params); - } - else - { - Channel* c = (Channel*)dest; - params.push_back(c->name); - params.push_back(ConvToStr(c->age)); - params.push_back(output_text); - Utils->DoOneToMany(user->uuid, "FMODE", params); - } - } + CmdBuilder params(user, "DELLINE"); + params.push_back(x->type); + params.push_back(x->Displayable()); + params.Broadcast(); } ModResult ModuleSpanningTree::OnSetAway(User* user, const std::string &awaymsg) { if (IS_LOCAL(user)) - { - parameterlist params; - if (!awaymsg.empty()) - { - params.push_back(ConvToStr(ServerInstance->Time())); - params.push_back(":" + awaymsg); - } - Utils->DoOneToMany(user->uuid, "AWAY", params); - } + CommandAway::Builder(user, awaymsg).Broadcast(); return MOD_RES_PASSTHRU; } -void ModuleSpanningTree::OnRequest(Request& request) -{ - if (!strcmp(request.id, "rehash")) - Utils->Rehash(); -} - -void ModuleSpanningTree::ProtoSendMode(void* opaque, TargetTypeFlags target_type, void* target, const parameterlist &modeline, const std::vector<TranslateType> &translate) +void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode) { - TreeSocket* s = (TreeSocket*)opaque; - std::string output_text; + if (processflags & ModeParser::MODE_LOCALONLY) + return; - ServerInstance->Parser->TranslateUIDs(translate, modeline, output_text); + if (u) + { + if (u->registered != REG_ALL) + return; - if (target) + CmdBuilder params(source, "MODE"); + params.push(u->uuid); + params.push(output_mode); + params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); + params.Broadcast(); + } + else { - if (target_type == TYPE_USER) - { - User* u = (User*)target; - s->WriteLine(":"+ServerInstance->Config->GetSID()+" MODE "+u->uuid+" "+output_text); - } - else if (target_type == TYPE_CHANNEL) - { - Channel* c = (Channel*)target; - s->WriteLine(":"+ServerInstance->Config->GetSID()+" FMODE "+c->name+" "+ConvToStr(c->age)+" "+output_text); - } + CmdBuilder params(source, "FMODE"); + params.push(c->name); + params.push_int(c->age); + params.push(output_mode); + params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); + params.Broadcast(); } } -void ModuleSpanningTree::ProtoSendMetaData(void* opaque, Extensible* target, const std::string &extname, const std::string &extdata) -{ - TreeSocket* s = static_cast<TreeSocket*>(opaque); - User* u = dynamic_cast<User*>(target); - Channel* c = dynamic_cast<Channel*>(target); - if (u) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA "+u->uuid+" "+extname+" :"+extdata); - else if (c) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA "+c->name+" "+extname+" :"+extdata); - else if (!target) - s->WriteLine(":"+ServerInstance->Config->GetSID()+" METADATA * "+extname+" :"+extdata); -} - CullResult ModuleSpanningTree::cull() { - Utils->cull(); - ServerInstance->Timers->DelTimer(RefreshTimer); + if (Utils) + Utils->cull(); return this->Module::cull(); } ModuleSpanningTree::~ModuleSpanningTree() { - delete ServerInstance->PI; - ServerInstance->PI = new ProtocolInterface; + ServerInstance->PI = &ServerInstance->DefaultProtocolInterface; - /* This will also free the listeners */ - delete Utils; + Server* newsrv = new Server(ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc); + SetLocalUsersServer(newsrv); - delete commands; + delete Utils; } Version ModuleSpanningTree::GetVersion() @@ -998,12 +768,13 @@ Version ModuleSpanningTree::GetVersion() * so that any activity it sees is FINAL, e.g. we arent going to send out * a NICK message before m_cloaking has finished putting the +x on the user, * etc etc. - * Therefore, we return PRIORITY_LAST to make sure we end up at the END of + * Therefore, we set our priority to PRIORITY_LAST to make sure we end up at the END of * the module call queue. */ void ModuleSpanningTree::Prioritize() { ServerInstance->Modules->SetPriority(this, PRIORITY_LAST); + ServerInstance->Modules.SetPriority(this, I_OnPreTopicChange, PRIORITY_FIRST); } MODULE_INIT(ModuleSpanningTree) diff --git a/src/modules/m_spanningtree/main.h b/src/modules/m_spanningtree/main.h index 17adc9287..46c21b4e9 100644 --- a/src/modules/m_spanningtree/main.h +++ b/src/modules/m_spanningtree/main.h @@ -21,11 +21,14 @@ */ -#ifndef M_SPANNINGTREE_MAIN_H -#define M_SPANNINGTREE_MAIN_H +#pragma once #include "inspircd.h" -#include <stdarg.h> +#include "event.h" +#include "modules/dns.h" +#include "servercommand.h" +#include "commands.h" +#include "protocolinterface.h" /** If you make a change which breaks the protocol, increment this. * If you completely change the protocol, completely change the number. @@ -36,12 +39,11 @@ * Failure to document your protocol changes will result in a painfully * painful death by pain. You have been warned. */ -const long ProtocolVersion = 1202; -const long MinCompatProtocol = 1201; +const long ProtocolVersion = 1205; +const long MinCompatProtocol = 1202; /** Forward declarations */ -class SpanningTreeCommands; class SpanningTreeUtilities; class CacheRefreshTimer; class TreeServer; @@ -52,47 +54,51 @@ class Autoconnect; */ class ModuleSpanningTree : public Module { - SpanningTreeCommands* commands; + /** Client to server commands, registered in the core + */ + CommandRConnect rconnect; + CommandRSQuit rsquit; + CommandMap map; + + /** Server to server only commands, not registered in the core + */ + SpanningTreeCommands commands; + + /** Next membership id assigned when a local user joins a channel + */ + Membership::Id currmembid; + + /** The specialized ProtocolInterface that is assigned to ServerInstance->PI on load + */ + SpanningTreeProtocolInterface protocolinterface; + + /** Event provider for our events + */ + Events::ModuleEventProvider eventprov; public: - SpanningTreeUtilities* Utils; + dynamic_reference<DNS::Manager> DNS; + + ServerCommandManager CmdManager; - CacheRefreshTimer *RefreshTimer; /** Set to true if inside a spanningtree call, to prevent sending * xlines and other things back to their source */ bool loopCall; - /** If true OnUserPostNick() won't update the nick TS before sending the NICK, - * used when handling SVSNICK. - */ - bool KeepNickTS; - /** Constructor */ ModuleSpanningTree(); - void init(); + void init() CXX11_OVERRIDE; /** Shows /LINKS */ void ShowLinks(TreeServer* Current, User* user, int hops); - /** Counts local and remote servers - */ - int CountServs(); - /** Handle LINKS command */ void HandleLinks(const std::vector<std::string>& parameters, User* user); - /** Show MAP output to a user (recursive) - */ - void ShowMap(TreeServer* Current, User* user, int depth, int &line, char* names, int &maxnamew, char* stats); - - /** Handle MAP command - */ - bool HandleMap(const std::vector<std::string>& parameters, User* user); - /** Handle SQUIT */ ModResult HandleSquit(const std::vector<std::string>& parameters, User* user); @@ -101,10 +107,6 @@ class ModuleSpanningTree : public Module */ ModResult HandleRemoteWhois(const std::vector<std::string>& parameters, User* user); - /** Ping all local servers - */ - void DoPingChecks(time_t curtime); - /** Connect a server locally */ void ConnectServer(Link* x, Autoconnect* y = NULL); @@ -129,60 +131,45 @@ class ModuleSpanningTree : public Module */ ModResult HandleConnect(const std::vector<std::string>& parameters, User* user); - /** Attempt to send a message to a user - */ - void RemoteMessage(User* user, const char* format, ...) CUSTOM_PRINTF(3, 4); - - /** Returns oper-specific MAP information - */ - const std::string MapOperInfo(TreeServer* Current); - /** Display a time as a human readable string */ - std::string TimeToStr(time_t secs); + static std::string TimeToStr(time_t secs); + + const Events::ModuleEventProvider& GetEventProvider() const { return eventprov; } /** ** *** MODULE EVENTS *** **/ - ModResult OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line); - void OnPostCommand(const std::string &command, const std::vector<std::string>& parameters, LocalUser *user, CmdResult result, const std::string &original_line); - void OnGetServerDescription(const std::string &servername,std::string &description); - void OnUserConnect(LocalUser* source); - void OnUserInvite(User* source,User* dest,Channel* channel, time_t); - void OnPostTopicChange(User* user, Channel* chan, const std::string &topic); - void OnWallops(User* user, const std::string &text); - void OnUserNotice(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list); - void OnUserMessage(User* user, void* dest, int target_type, const std::string &text, char status, const CUList &exempt_list); - void OnBackgroundTimer(time_t curtime); - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts); - void OnChangeHost(User* user, const std::string &newhost); - void OnChangeName(User* user, const std::string &gecos); - void OnChangeIdent(User* user, const std::string &ident); - void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts); - void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message); - void OnUserPostNick(User* user, const std::string &oldnick); - void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts); - void OnRemoteKill(User* source, User* dest, const std::string &reason, const std::string &operreason); - void OnPreRehash(User* user, const std::string ¶meter); - void OnRehash(User* user); - void OnOper(User* user, const std::string &opertype); - void OnLine(User* source, const std::string &host, bool adding, char linetype, long duration, const std::string &reason); - void OnAddLine(User *u, XLine *x); - void OnDelLine(User *u, XLine *x); - void OnMode(User* user, void* dest, int target_type, const std::vector<std::string> &text, const std::vector<TranslateType> &translate); - ModResult OnStats(char statschar, User* user, string_list &results); - ModResult OnSetAway(User* user, const std::string &awaymsg); - void ProtoSendMode(void* opaque, TargetTypeFlags target_type, void* target, const std::vector<std::string> &modeline, const std::vector<TranslateType> &translate); - void ProtoSendMetaData(void* opaque, Extensible* target, const std::string &extname, const std::string &extdata); - void OnLoadModule(Module* mod); - void OnUnloadModule(Module* mod); - ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); - void OnRequest(Request& request); + ModResult OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE; + void OnPostCommand(Command*, const std::vector<std::string>& parameters, LocalUser* user, CmdResult result, const std::string& original_line) CXX11_OVERRIDE; + void OnUserConnect(LocalUser* source) CXX11_OVERRIDE; + void OnUserInvite(User* source, User* dest, Channel* channel, time_t timeout, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE; + ModResult OnPreTopicChange(User* user, Channel* chan, const std::string& topic) CXX11_OVERRIDE; + void OnPostTopicChange(User* user, Channel* chan, const std::string &topic) CXX11_OVERRIDE; + void OnUserMessage(User* user, void* dest, int target_type, const std::string& text, char status, const CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE; + void OnBackgroundTimer(time_t curtime) CXX11_OVERRIDE; + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE; + void OnChangeHost(User* user, const std::string &newhost) CXX11_OVERRIDE; + void OnChangeName(User* user, const std::string &gecos) CXX11_OVERRIDE; + void OnChangeIdent(User* user, const std::string &ident) CXX11_OVERRIDE; + void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE; + void OnUserQuit(User* user, const std::string &reason, const std::string &oper_message) CXX11_OVERRIDE; + void OnUserPostNick(User* user, const std::string &oldnick) CXX11_OVERRIDE; + void OnUserKick(User* source, Membership* memb, const std::string &reason, CUList& excepts) CXX11_OVERRIDE; + void OnPreRehash(User* user, const std::string ¶meter) CXX11_OVERRIDE; + void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE; + void OnOper(User* user, const std::string &opertype) CXX11_OVERRIDE; + void OnAddLine(User *u, XLine *x) CXX11_OVERRIDE; + void OnDelLine(User *u, XLine *x) CXX11_OVERRIDE; + ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE; + ModResult OnSetAway(User* user, const std::string &awaymsg) CXX11_OVERRIDE; + void OnLoadModule(Module* mod) CXX11_OVERRIDE; + void OnUnloadModule(Module* mod) CXX11_OVERRIDE; + ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE; + void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode) CXX11_OVERRIDE; CullResult cull(); ~ModuleSpanningTree(); - Version GetVersion(); + Version GetVersion() CXX11_OVERRIDE; void Prioritize(); }; - -#endif diff --git a/src/modules/m_spanningtree/metadata.cpp b/src/modules/m_spanningtree/metadata.cpp index a584f8fa8..47c2f8bc5 100644 --- a/src/modules/m_spanningtree/metadata.cpp +++ b/src/modules/m_spanningtree/metadata.cpp @@ -21,39 +21,76 @@ #include "inspircd.h" #include "commands.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -CmdResult CommandMetadata::Handle(const std::vector<std::string>& params, User *srcuser) +CmdResult CommandMetadata::Handle(User* srcuser, std::vector<std::string>& params) { - std::string value = params.size() < 3 ? "" : params[2]; - ExtensionItem* item = ServerInstance->Extensions.GetItem(params[1]); if (params[0] == "*") { - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(NULL,params[1],value)); + std::string value = params.size() < 3 ? "" : params[2]; + FOREACH_MOD(OnDecodeMetaData, (NULL,params[1],value)); + return CMD_SUCCESS; } - else if (*(params[0].c_str()) == '#') + + if (params[0][0] == '#') { + // Channel METADATA has an additional parameter: the channel TS + // :22D METADATA #channel 12345 extname :extdata + if (params.size() < 3) + throw ProtocolException("Insufficient parameters for channel METADATA"); + Channel* c = ServerInstance->FindChan(params[0]); - if (c) - { - if (item) - item->unserialize(FORMAT_NETWORK, c, value); - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(c,params[1],value)); - } + if (!c) + return CMD_FAILURE; + + time_t ChanTS = ServerCommand::ExtractTS(params[1]); + if (c->age < ChanTS) + // Their TS is newer than ours, discard this command and do not propagate + return CMD_FAILURE; + + std::string value = params.size() < 4 ? "" : params[3]; + + ExtensionItem* item = ServerInstance->Extensions.GetItem(params[2]); + if ((item) && (item->type == ExtensionItem::EXT_CHANNEL)) + item->unserialize(FORMAT_NETWORK, c, value); + FOREACH_MOD(OnDecodeMetaData, (c,params[2],value)); } - else if (*(params[0].c_str()) != '#') + else { User* u = ServerInstance->FindUUID(params[0]); - if ((u) && (!IS_SERVER(u))) + if (u) { - if (item) + ExtensionItem* item = ServerInstance->Extensions.GetItem(params[1]); + std::string value = params.size() < 3 ? "" : params[2]; + + if ((item) && (item->type == ExtensionItem::EXT_USER)) item->unserialize(FORMAT_NETWORK, u, value); - FOREACH_MOD(I_OnDecodeMetaData,OnDecodeMetaData(u,params[1],value)); + FOREACH_MOD(OnDecodeMetaData, (u,params[1],value)); } } return CMD_SUCCESS; } +CommandMetadata::Builder::Builder(User* user, const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push(user->uuid); + push(key); + push_last(val); +} + +CommandMetadata::Builder::Builder(Channel* chan, const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push(chan->name); + push_int(chan->age); + push(key); + push_last(val); +} + +CommandMetadata::Builder::Builder(const std::string& key, const std::string& val) + : CmdBuilder("METADATA") +{ + push("*"); + push(key); + push_last(val); +} diff --git a/src/modules/m_spanningtree/misccommands.cpp b/src/modules/m_spanningtree/misccommands.cpp new file mode 100644 index 000000000..00f31d668 --- /dev/null +++ b/src/modules/m_spanningtree/misccommands.cpp @@ -0,0 +1,42 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2007-2008, 2012 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "main.h" +#include "commands.h" +#include "treeserver.h" + +CmdResult CommandSNONotice::Handle(User* user, std::vector<std::string>& params) +{ + ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + user->nick + ": " + params[1]); + return CMD_SUCCESS; +} + +CmdResult CommandEndBurst::HandleServer(TreeServer* server, std::vector<std::string>& params) +{ + server->FinishBurst(); + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/netburst.cpp b/src/modules/m_spanningtree/netburst.cpp index 3bce90eda..cdafa9ded 100644 --- a/src/modules/m_spanningtree/netburst.cpp +++ b/src/modules/m_spanningtree/netburst.cpp @@ -21,11 +21,79 @@ #include "inspircd.h" #include "xline.h" +#include "listmode.h" #include "treesocket.h" #include "treeserver.h" -#include "utils.h" #include "main.h" +#include "commands.h" + +/** + * Creates FMODE messages, used only when syncing channels + */ +class FModeBuilder : public CmdBuilder +{ + static const size_t maxline = 480; + std::string params; + unsigned int modes; + std::string::size_type startpos; + + public: + FModeBuilder(Channel* chan) + : CmdBuilder("FMODE"), modes(0) + { + push(chan->name).push_int(chan->age).push_raw(" +"); + startpos = str().size(); + } + + /** Add a mode to the message + */ + void push_mode(const char modeletter, const std::string& mask) + { + push_raw(modeletter); + params.push_back(' '); + params.append(mask); + modes++; + } + + /** Remove all modes from the message + */ + void clear() + { + content.erase(startpos); + params.clear(); + modes = 0; + } + + /** Prepare the message for sending, next mode can only be added after clear() + */ + const std::string& finalize() + { + return push_raw(params); + } + + /** Returns true if the given mask can be added to the message, false if the message + * has no room for the mask + */ + bool has_room(const std::string& mask) const + { + return ((str().size() + params.size() + mask.size() + 2 <= maxline) && + (modes < ServerInstance->Config->Limits.MaxModes)); + } + + /** Returns true if this message is empty (has no modes) + */ + bool empty() const + { + return (modes == 0); + } +}; + +struct TreeSocket::BurstState +{ + SpanningTreeProtocolInterface::Server server; + BurstState(TreeSocket* sock) : server(sock) { } +}; /** This function is called when we want to send a netburst to a local * server. There is a set order we must do this, because for example @@ -34,157 +102,98 @@ */ void TreeSocket::DoBurst(TreeServer* s) { - std::string servername = s->GetName(); ServerInstance->SNO->WriteToSnoMask('l',"Bursting to \2%s\2 (Authentication: %s%s).", - servername.c_str(), - capab->auth_fingerprint ? "SSL Fingerprint and " : "", + s->GetName().c_str(), + capab->auth_fingerprint ? "SSL certificate fingerprint and " : "", capab->auth_challenge ? "challenge-response" : "plaintext password"); this->CleanNegotiationInfo(); - this->WriteLine(":" + ServerInstance->Config->GetSID() + " BURST " + ConvToStr(ServerInstance->Time())); - /* send our version string */ - this->WriteLine(":" + ServerInstance->Config->GetSID() + " VERSION :"+ServerInstance->GetVersionString()); - /* Send server tree */ - this->SendServers(Utils->TreeRoot,s,1); - /* Send users and their oper status */ - this->SendUsers(); - /* Send everything else (channel modes, xlines etc) */ - this->SendChannelModes(); + this->WriteLine(CmdBuilder("BURST").push_int(ServerInstance->Time())); + // Introduce all servers behind us + this->SendServers(Utils->TreeRoot, s); + + BurstState bs(this); + // Introduce all users + this->SendUsers(bs); + + // Sync all channels + const chan_hash& chans = ServerInstance->GetChans(); + for (chan_hash::const_iterator i = chans.begin(); i != chans.end(); ++i) + SyncChannel(i->second, bs); + + // Send all xlines this->SendXLines(); - FOREACH_MOD(I_OnSyncNetwork,OnSyncNetwork(Utils->Creator,(void*)this)); - this->WriteLine(":" + ServerInstance->Config->GetSID() + " ENDBURST"); + FOREACH_MOD(OnSyncNetwork, (bs.server)); + this->WriteLine(CmdBuilder("ENDBURST")); ServerInstance->SNO->WriteToSnoMask('l',"Finished bursting to \2"+ s->GetName()+"\2."); + + this->burstsent = true; } -/** Recursively send the server tree with distances as hops. +void TreeSocket::SendServerInfo(TreeServer* from) +{ + // Send public version string + this->WriteLine(CommandSInfo::Builder(from, "version", from->GetVersion())); + + // Send full version string that contains more information and is shown to opers + this->WriteLine(CommandSInfo::Builder(from, "fullversion", from->GetFullVersion())); +} + +/** Recursively send the server tree. * This is used during network burst to inform the other server * (and any of ITS servers too) of what servers we know about. * If at any point any of these servers already exist on the other - * end, our connection may be terminated. The hopcounts given - * by this function are relative, this doesn't matter so long as - * they are all >1, as all the remote servers re-calculate them - * to be relative too, with themselves as hop 0. + * end, our connection may be terminated. */ -void TreeSocket::SendServers(TreeServer* Current, TreeServer* s, int hops) +void TreeSocket::SendServers(TreeServer* Current, TreeServer* s) { - char command[MAXBUF]; - for (unsigned int q = 0; q < Current->ChildCount(); q++) + SendServerInfo(Current); + + const TreeServer::ChildServers& children = Current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - TreeServer* recursive_server = Current->GetChild(q); + TreeServer* recursive_server = *i; if (recursive_server != s) { - std::string recursive_servername = recursive_server->GetName(); - snprintf(command, MAXBUF, ":%s SERVER %s * %d %s :%s", Current->GetID().c_str(), recursive_servername.c_str(), hops, - recursive_server->GetID().c_str(), - recursive_server->GetDesc().c_str()); - this->WriteLine(command); - this->WriteLine(":"+recursive_server->GetID()+" VERSION :"+recursive_server->GetVersion()); + this->WriteLine(CommandServer::Builder(recursive_server)); /* down to next level */ - this->SendServers(recursive_server, s, hops+1); + this->SendServers(recursive_server, s); } } } /** Send one or more FJOINs for a channel of users. - * If the length of a single line is more than 480-NICKMAX - * in length, it is split over multiple lines. + * If the length of a single line is too long, it is split over multiple lines. */ void TreeSocket::SendFJoins(Channel* c) { - std::string buffer; - char list[MAXBUF]; - - size_t curlen, headlen; - curlen = headlen = snprintf(list,MAXBUF,":%s FJOIN %s %lu +%s :", - ServerInstance->Config->GetSID().c_str(), c->name.c_str(), (unsigned long)c->age, c->ChanModes(true)); - int numusers = 0; - char* ptr = list + curlen; - bool looped_once = false; - - const UserMembList *ulist = c->GetUsers(); - std::string modes; - std::string params; + CommandFJoin::Builder fjoin(c); - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { - size_t ptrlen = 0; - std::string modestr = i->second->modes; - - if ((curlen + modestr.length() + i->first->uuid.length() + 4) > 480) + Membership* memb = i->second; + if (!fjoin.has_room(memb)) { - // remove the final space - if (ptr[-1] == ' ') - ptr[-1] = '\0'; - buffer.append(list).append("\r\n"); - curlen = headlen; - ptr = list + headlen; - numusers = 0; + // No room for this user, send the line and prepare a new one + this->WriteLine(fjoin.finalize()); + fjoin.clear(); } - - ptrlen = snprintf(ptr, MAXBUF-curlen, "%s,%s ", modestr.c_str(), i->first->uuid.c_str()); - - looped_once = true; - - curlen += ptrlen; - ptr += ptrlen; - - numusers++; + fjoin.add(memb); } - - // Okay, permanent channels will (of course) need this \r\n anyway, numusers check is if there - // actually were people in the channel (looped_once == true) - if (!looped_once || numusers > 0) - { - // remove the final space - if (ptr[-1] == ' ') - ptr[-1] = '\0'; - buffer.append(list).append("\r\n"); - } - - unsigned int linesize = 1; - for (BanList::iterator b = c->bans.begin(); b != c->bans.end(); b++) - { - unsigned int size = b->data.length() + 2; // "b" and " " - unsigned int nextsize = linesize + size; - - if ((modes.length() >= ServerInstance->Config->Limits.MaxModes) || (nextsize > FMODE_MAX_LENGTH)) - { - /* Wrap */ - buffer.append(":").append(ServerInstance->Config->GetSID()).append(" FMODE ").append(c->name).append(" ").append(ConvToStr(c->age)).append(" +").append(modes).append(params).append("\r\n"); - - modes.clear(); - params.clear(); - linesize = 1; - } - - modes.push_back('b'); - - params.push_back(' '); - params.append(b->data); - - linesize += size; - } - - /* Only send these if there are any */ - if (!modes.empty()) - buffer.append(":").append(ServerInstance->Config->GetSID()).append(" FMODE ").append(c->name).append(" ").append(ConvToStr(c->age)).append(" +").append(modes).append(params); - - this->WriteLine(buffer); + this->WriteLine(fjoin.finalize()); } /** Send all XLines we know about */ void TreeSocket::SendXLines() { - char data[MAXBUF]; - std::string n = ServerInstance->Config->GetSID(); - const char* sn = n.c_str(); - std::vector<std::string> types = ServerInstance->XLines->GetAllTypes(); - time_t current = ServerInstance->Time(); - for (std::vector<std::string>::iterator it = types.begin(); it != types.end(); ++it) + for (std::vector<std::string>::const_iterator it = types.begin(); it != types.end(); ++it) { + /* Expired lines are removed in XLineManager::GetAll() */ XLineLookup* lookup = ServerInstance->XLines->GetAll(*it); + /* lookup cannot be NULL in this case but a check won't hurt */ if (lookup) { for (LookupIter i = lookup->begin(); i != lookup->end(); ++i) @@ -195,96 +204,101 @@ void TreeSocket::SendXLines() if (!i->second->IsBurstable()) break; - /* If it's expired, don't bother to burst it - */ - if (i->second->duration && current > i->second->expiry) - continue; - - snprintf(data,MAXBUF,":%s ADDLINE %s %s %s %lu %lu :%s",sn, it->c_str(), i->second->Displayable(), - i->second->source.c_str(), - (unsigned long)i->second->set_time, - (unsigned long)i->second->duration, - i->second->reason.c_str()); - this->WriteLine(data); + this->WriteLine(CommandAddLine::Builder(i->second)); } } } } -/** Send channel topic, modes and metadata */ -void TreeSocket::SendChannelModes() +void TreeSocket::SendListModes(Channel* chan) { - char data[MAXBUF]; - std::string n = ServerInstance->Config->GetSID(); - const char* sn = n.c_str(); - - for (chan_hash::iterator c = ServerInstance->chanlist->begin(); c != ServerInstance->chanlist->end(); c++) + FModeBuilder fmode(chan); + const ModeParser::ListModeList& listmodes = ServerInstance->Modes->GetListModes(); + for (ModeParser::ListModeList::const_iterator i = listmodes.begin(); i != listmodes.end(); ++i) { - SendFJoins(c->second); - if (!c->second->topic.empty()) + ListModeBase* mh = *i; + ListModeBase::ModeList* list = mh->GetList(chan); + if (!list) + continue; + + // Add all items on the list to the FMODE, send it whenever it becomes too long + const char modeletter = mh->GetModeChar(); + for (ListModeBase::ModeList::const_iterator j = list->begin(); j != list->end(); ++j) { - snprintf(data,MAXBUF,":%s FTOPIC %s %lu %s :%s", sn, c->second->name.c_str(), (unsigned long)c->second->topicset, c->second->setby.c_str(), c->second->topic.c_str()); - this->WriteLine(data); + const std::string& mask = j->mask; + if (!fmode.has_room(mask)) + { + // No room for this mask, send the current line as-is then add the mask to a + // new, empty FMODE message + this->WriteLine(fmode.finalize()); + fmode.clear(); + } + fmode.push_mode(modeletter, mask); } + } - for(Extensible::ExtensibleStore::const_iterator i = c->second->GetExtList().begin(); i != c->second->GetExtList().end(); i++) - { - ExtensionItem* item = i->first; - std::string value = item->serialize(FORMAT_NETWORK, c->second, i->second); - if (!value.empty()) - Utils->Creator->ProtoSendMetaData(this, c->second, item->name, value); - } + if (!fmode.empty()) + this->WriteLine(fmode.finalize()); +} - FOREACH_MOD(I_OnSyncChannel,OnSyncChannel(c->second,Utils->Creator,this)); +/** Send channel users, topic, modes and global metadata */ +void TreeSocket::SyncChannel(Channel* chan, BurstState& bs) +{ + SendFJoins(chan); + + // If the topic was ever set, send it, even if it's empty now + // because a new empty topic should override an old non-empty topic + if (chan->topicset != 0) + this->WriteLine(CommandFTopic::Builder(chan)); + + SendListModes(chan); + + for (Extensible::ExtensibleStore::const_iterator i = chan->GetExtList().begin(); i != chan->GetExtList().end(); i++) + { + ExtensionItem* item = i->first; + std::string value = item->serialize(FORMAT_NETWORK, chan, i->second); + if (!value.empty()) + this->WriteLine(CommandMetadata::Builder(chan, item->name, value)); } + + FOREACH_MOD(OnSyncChannel, (chan, bs.server)); } -/** send all users and their oper state/modes */ -void TreeSocket::SendUsers() +void TreeSocket::SyncChannel(Channel* chan) { - char data[MAXBUF]; - for (user_hash::iterator u = ServerInstance->Users->clientlist->begin(); u != ServerInstance->Users->clientlist->end(); u++) + BurstState bs(this); + SyncChannel(chan, bs); +} + +/** Send all users and their state, including oper and away status and global metadata */ +void TreeSocket::SendUsers(BurstState& bs) +{ + ProtocolInterface::Server& piserver = bs.server; + + const user_hash& users = ServerInstance->Users->GetUsers(); + for (user_hash::const_iterator u = users.begin(); u != users.end(); ++u) { - if (u->second->registered == REG_ALL) - { - TreeServer* theirserver = Utils->FindServer(u->second->server); - if (theirserver) - { - snprintf(data,MAXBUF,":%s UID %s %lu %s %s %s %s %s %lu +%s :%s", - theirserver->GetID().c_str(), /* Prefix: SID */ - u->second->uuid.c_str(), /* 0: UUID */ - (unsigned long)u->second->age, /* 1: TS */ - u->second->nick.c_str(), /* 2: Nick */ - u->second->host.c_str(), /* 3: Displayed Host */ - u->second->dhost.c_str(), /* 4: Real host */ - u->second->ident.c_str(), /* 5: Ident */ - u->second->GetIPString(), /* 6: IP string */ - (unsigned long)u->second->signon, /* 7: Signon time for WHOWAS */ - u->second->FormatModes(true), /* 8...n: Modes and params */ - u->second->fullname.c_str()); /* size-1: GECOS */ - this->WriteLine(data); - if (IS_OPER(u->second)) - { - snprintf(data,MAXBUF,":%s OPERTYPE %s", u->second->uuid.c_str(), u->second->oper->name.c_str()); - this->WriteLine(data); - } - if (IS_AWAY(u->second)) - { - snprintf(data,MAXBUF,":%s AWAY %ld :%s", u->second->uuid.c_str(), (long)u->second->awaytime, u->second->awaymsg.c_str()); - this->WriteLine(data); - } - } + User* user = u->second; + if (user->registered != REG_ALL) + continue; - for(Extensible::ExtensibleStore::const_iterator i = u->second->GetExtList().begin(); i != u->second->GetExtList().end(); i++) - { - ExtensionItem* item = i->first; - std::string value = item->serialize(FORMAT_NETWORK, u->second, i->second); - if (!value.empty()) - Utils->Creator->ProtoSendMetaData(this, u->second, item->name, value); - } + this->WriteLine(CommandUID::Builder(user)); + + if (user->IsOper()) + this->WriteLine(CommandOpertype::Builder(user)); + + if (user->IsAway()) + this->WriteLine(CommandAway::Builder(user)); - FOREACH_MOD(I_OnSyncUser,OnSyncUser(u->second,Utils->Creator,this)); + const Extensible::ExtensibleStore& exts = user->GetExtList(); + for (Extensible::ExtensibleStore::const_iterator i = exts.begin(); i != exts.end(); ++i) + { + ExtensionItem* item = i->first; + std::string value = item->serialize(FORMAT_NETWORK, u->second, i->second); + if (!value.empty()) + this->WriteLine(CommandMetadata::Builder(user, item->name, value)); } + + FOREACH_MOD(OnSyncUser, (user, piserver)); } } - diff --git a/src/modules/m_spanningtree/nick.cpp b/src/modules/m_spanningtree/nick.cpp new file mode 100644 index 000000000..9e290e07f --- /dev/null +++ b/src/modules/m_spanningtree/nick.cpp @@ -0,0 +1,64 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * Copyright (C) 2007-2008, 2012 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org> + * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com> + * Copyright (C) 2008 Thomas Stagner <aquanight@inspircd.org> + * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "main.h" +#include "utils.h" +#include "commands.h" +#include "treeserver.h" + +CmdResult CommandNick::HandleRemote(::RemoteUser* user, std::vector<std::string>& params) +{ + if ((isdigit(params[0][0])) && (params[0] != user->uuid)) + throw ProtocolException("Attempted to change nick to an invalid or non-matching UUID"); + + // Timestamp of the new nick + time_t newts = ServerCommand::ExtractTS(params[1]); + + /* + * On nick messages, check that the nick doesn't already exist here. + * If it does, perform collision logic. + */ + User* x = ServerInstance->FindNickOnly(params[0]); + if ((x) && (x != user) && (x->registered == REG_ALL)) + { + // 'x' is the already existing user using the same nick as params[0] + // 'user' is the user trying to change nick to the in use nick + bool they_change = Utils->DoCollision(x, TreeServer::Get(user), newts, user->ident, user->GetIPString(), user->uuid, "NICK"); + if (they_change) + { + // Remote client lost, or both lost, rewrite this nick change as a change to uuid before + // calling ChangeNick() and forwarding the message + params[0] = user->uuid; + params[1] = ConvToStr(CommandSave::SavedTimestamp); + newts = CommandSave::SavedTimestamp; + } + } + + user->ChangeNick(params[0], newts); + + return CMD_SUCCESS; +} diff --git a/src/modules/m_spanningtree/nickcollide.cpp b/src/modules/m_spanningtree/nickcollide.cpp index 38d59affb..62e200921 100644 --- a/src/modules/m_spanningtree/nickcollide.cpp +++ b/src/modules/m_spanningtree/nickcollide.cpp @@ -19,23 +19,25 @@ #include "inspircd.h" -#include "xline.h" #include "treesocket.h" #include "treeserver.h" #include "utils.h" - -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - +#include "commandbuilder.h" +#include "commands.h" /* * Yes, this function looks a little ugly. * However, in some circumstances we may not have a User, so we need to do things this way. - * Returns 1 if colliding local client, 2 if colliding remote, 3 if colliding both. - * Sends SAVEs as appropriate and forces nickchanges too. + * Returns true if remote or both lost, false otherwise. + * Sends SAVEs as appropriate and forces nick change of the user 'u' if our side loses or if both lose. + * Does not change the nick of the user that is trying to claim the nick of 'u', i.e. the "remote" user. */ -int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remoteident, const std::string &remoteip, const std::string &remoteuid) +bool SpanningTreeUtilities::DoCollision(User* u, TreeServer* server, time_t remotets, const std::string& remoteident, const std::string& remoteip, const std::string& remoteuid, const char* collidecmd) { + // At this point we're sure that a collision happened, increment the counter regardless of who wins + ServerInstance->stats.Collisions++; + /* * Under old protocol rules, we would have had to kill both clients. * Really, this sucks. @@ -56,21 +58,14 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei bool bChangeLocal = true; bool bChangeRemote = true; - /* for brevity, don't use the User - use defines to avoid any copy */ - #define localts u->age - #define localident u->ident - #define localip u->GetIPString() - - /* mmk. let's do this again. */ - if (remotets == localts) + // If the timestamps are not equal only one of the users has to change nick, + // otherwise both have to change + const time_t localts = u->age; + if (remotets != localts) { - /* equal. fuck them both! do nada, let the handler at the bottom figure this out. */ - } - else - { - /* fuck. now it gets complex. */ - /* first, let's see if ident@host matches. */ + const std::string& localident = u->ident; + const std::string& localip = u->GetIPString(); bool SamePerson = (localident == remoteident) && (localip == remoteip); @@ -81,19 +76,22 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei if((SamePerson && remotets < localts) || (!SamePerson && remotets > localts)) { - /* remote needs to change */ + // Only remote needs to change bChangeLocal = false; } else { - /* ours needs to change */ + // Only ours needs to change bChangeRemote = false; } } + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Nick collision on \"%s\" caused by %s: %s/%lu/%s@%s %d <-> %s/%lu/%s@%s %d", u->nick.c_str(), collidecmd, + u->uuid.c_str(), (unsigned long)localts, u->ident.c_str(), u->GetIPString().c_str(), bChangeLocal, + remoteuid.c_str(), (unsigned long)remotets, remoteident.c_str(), remoteip.c_str(), bChangeRemote); + /* - * Cheat a little here. Instead of a dedicated command to change UID, - * use SAVE and accept the losing client with its UID (as we know the SAVE will + * Send SAVE and accept the losing client with its UID (as we know the SAVE will * not fail under any circumstances -- UIDs are netwide exclusive). * * This means that each side of a collide will generate one extra NICK back to where @@ -107,38 +105,23 @@ int TreeSocket::DoCollision(User *u, time_t remotets, const std::string &remotei { /* * Local-side nick needs to change. Just in case we are hub, and - * this "local" nick is actually behind us, send an SAVE out. + * this "local" nick is actually behind us, send a SAVE out. */ - parameterlist params; + CmdBuilder params("SAVE"); params.push_back(u->uuid); params.push_back(ConvToStr(u->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"SAVE",params); - - u->ForceNickChange(u->uuid.c_str()); + params.Broadcast(); - if (!bChangeRemote) - return 1; + u->ChangeNick(u->uuid, CommandSave::SavedTimestamp); } if (bChangeRemote) { - User *remote = ServerInstance->FindUUID(remoteuid); /* - * remote side needs to change. If this happens, we will modify - * the UID or halt the propagation of the nick change command, - * so other servers don't need to see the SAVE + * Remote side needs to change. If this happens, we modify the UID or NICK and + * send back a SAVE to the source. */ - WriteLine(":"+ServerInstance->Config->GetSID()+" SAVE "+remoteuid+" "+ ConvToStr(remotets)); - - if (remote) - { - /* nick change collide. Force change their nick. */ - remote->ForceNickChange(remoteuid.c_str()); - } - - if (!bChangeLocal) - return 2; + CmdBuilder("SAVE").push(remoteuid).push_int(remotets).Unicast(server->ServerUser); } - return 3; + return bChangeRemote; } - diff --git a/src/modules/m_spanningtree/num.cpp b/src/modules/m_spanningtree/num.cpp new file mode 100644 index 000000000..2c8697c9a --- /dev/null +++ b/src/modules/m_spanningtree/num.cpp @@ -0,0 +1,62 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "utils.h" +#include "commands.h" +#include "remoteuser.h" + +CmdResult CommandNum::HandleServer(TreeServer* server, std::vector<std::string>& params) +{ + User* const target = ServerInstance->FindUUID(params[1]); + if (!target) + return CMD_FAILURE; + + LocalUser* const localtarget = IS_LOCAL(target); + if (!localtarget) + return CMD_SUCCESS; + + Numeric::Numeric numeric(ConvToInt(params[2])); + // Passing NULL is ok, in that case the numeric source becomes this server + numeric.SetServer(Utils->FindServerID(params[0])); + numeric.GetParams().insert(numeric.GetParams().end(), params.begin()+3, params.end()); + + localtarget->WriteNumeric(numeric); + return CMD_SUCCESS; +} + +RouteDescriptor CommandNum::GetRouting(User* user, const std::vector<std::string>& params) +{ + return ROUTE_UNICAST(params[1]); +} + +CommandNum::Builder::Builder(SpanningTree::RemoteUser* target, const Numeric::Numeric& numeric) + : CmdBuilder("NUM") +{ + TreeServer* const server = (numeric.GetServer() ? (static_cast<TreeServer*>(numeric.GetServer())) : Utils->TreeRoot); + push(server->GetID()).push(target->uuid).push(InspIRCd::Format("%03u", numeric.GetNumeric())); + const std::vector<std::string>& params = numeric.GetParams(); + if (!params.empty()) + { + for (std::vector<std::string>::const_iterator i = params.begin(); i != params.end()-1; ++i) + push(*i); + push_last(params.back()); + } +} diff --git a/src/modules/m_spanningtree/opertype.cpp b/src/modules/m_spanningtree/opertype.cpp index 97a4de8c2..ab531c171 100644 --- a/src/modules/m_spanningtree/opertype.cpp +++ b/src/modules/m_spanningtree/opertype.cpp @@ -26,15 +26,17 @@ /** Because the core won't let users or even SERVERS set +o, * we use the OPERTYPE command to do this. */ -CmdResult CommandOpertype::Handle(const std::vector<std::string>& params, User *u) +CmdResult CommandOpertype::HandleRemote(RemoteUser* u, std::vector<std::string>& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - std::string opertype = params[0]; - if (!IS_OPER(u)) + const std::string& opertype = params[0]; + if (!u->IsOper()) ServerInstance->Users->all_opers.push_back(u); - u->modes[UM_OPERATOR] = 1; - OperIndex::iterator iter = ServerInstance->Config->oper_blocks.find(" " + opertype); - if (iter != ServerInstance->Config->oper_blocks.end()) + + ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER); + u->SetMode(opermh, true); + + ServerConfig::OperIndex::const_iterator iter = ServerInstance->Config->OperTypes.find(opertype); + if (iter != ServerInstance->Config->OperTypes.end()) u->oper = iter->second; else { @@ -48,12 +50,17 @@ CmdResult CommandOpertype::Handle(const std::vector<std::string>& params, User * * If quiet bursts are enabled, and server is bursting or silent uline (i.e. services), * then do nothing. -- w00t */ - TreeServer* remoteserver = Utils->FindServer(u->server); - if (remoteserver->bursting || ServerInstance->SilentULine(u->server)) + TreeServer* remoteserver = TreeServer::Get(u); + if (remoteserver->IsBehindBursting() || remoteserver->IsSilentULine()) return CMD_SUCCESS; } - ServerInstance->SNO->WriteToSnoMask('O',"From %s: User %s (%s@%s) is now an IRC operator of type %s",u->server.c_str(), u->nick.c_str(),u->ident.c_str(), u->host.c_str(), irc::Spacify(opertype.c_str())); + ServerInstance->SNO->WriteToSnoMask('O',"From %s: User %s (%s@%s) is now an IRC operator of type %s",u->server->GetName().c_str(), u->nick.c_str(),u->ident.c_str(), u->host.c_str(), opertype.c_str()); return CMD_SUCCESS; } +CommandOpertype::Builder::Builder(User* user) + : CmdBuilder(user, "OPERTYPE") +{ + push_last(user->oper->name); +} diff --git a/src/modules/m_spanningtree/override_map.cpp b/src/modules/m_spanningtree/override_map.cpp index 04fa4bcab..a22fa48ac 100644 --- a/src/modules/m_spanningtree/override_map.cpp +++ b/src/modules/m_spanningtree/override_map.cpp @@ -1,6 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * + * Copyright (C) 2014 Adam <Adam@anope.org> * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org> * Copyright (C) 2007-2008 Craig Edwards <craigedwards@brainbox.cc> * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> @@ -19,178 +20,198 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" -const std::string ModuleSpanningTree::MapOperInfo(TreeServer* Current) +CommandMap::CommandMap(Module* Creator) + : Command(Creator, "MAP", 0, 1) { - time_t secs_up = ServerInstance->Time() - Current->age; - return " [Up: " + TimeToStr(secs_up) + (Current->rtt == 0 ? "]" : " Lag: " + ConvToStr(Current->rtt) + "ms]"); + Penalty = 2; } -void ModuleSpanningTree::ShowMap(TreeServer* Current, User* user, int depth, int &line, char* names, int &maxnamew, char* stats) +static inline bool IsHidden(User* user, TreeServer* server) { - ServerInstance->Logs->Log("map",DEBUG,"ShowMap depth %d on line %d", depth, line); - float percent; - - if (ServerInstance->Users->clientlist->size() == 0) + if (!user->IsOper()) { - // If there are no users, WHO THE HELL DID THE /MAP?!?!?! - percent = 0; + if (server->Hidden) + return true; + if (Utils->HideULines && server->IsULine()) + return true; } - else + + return false; +} + +// Calculate the map depth the servers go, and the longest server name +static void GetDepthAndLen(TreeServer* current, unsigned int depth, unsigned int& max_depth, unsigned int& max_len) +{ + if (depth > max_depth) + max_depth = depth; + if (current->GetName().length() > max_len) + max_len = current->GetName().length(); + + const TreeServer::ChildServers& servers = current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = servers.begin(); i != servers.end(); ++i) { - percent = Current->GetUserCount() * 100.0 / ServerInstance->Users->clientlist->size(); + TreeServer* child = *i; + GetDepthAndLen(child, depth + 1, max_depth, max_len); } +} - const std::string operdata = IS_OPER(user) ? MapOperInfo(Current) : ""; - - char* myname = names + 100 * line; - char* mystat = stats + 50 * line; - memset(myname, ' ', depth); - int w = depth; +static std::vector<std::string> GetMap(User* user, TreeServer* current, unsigned int max_len, unsigned int depth) +{ + float percent = 0; - std::string servername = Current->GetName(); - if (IS_OPER(user)) + const user_hash& users = ServerInstance->Users->GetUsers(); + if (!users.empty()) { - w += snprintf(myname + depth, 99 - depth, "%s (%s)", servername.c_str(), Current->GetID().c_str()); + // If there are no users, WHO THE HELL DID THE /MAP?!?!?! + percent = current->UserCount * 100.0 / users.size(); } - else + + std::string buffer = current->GetName(); + if (user->IsOper()) { - w += snprintf(myname + depth, 99 - depth, "%s", servername.c_str()); + buffer += " (" + current->GetID() + ")"; } - memset(myname + w, ' ', 100 - w); - if (w > maxnamew) - maxnamew = w; - snprintf(mystat, 49, "%5d [%5.2f%%]%s", Current->GetUserCount(), percent, operdata.c_str()); - line++; + // Pad with spaces until its at max len, max_len must always be >= my names length + buffer.append(max_len - current->GetName().length(), ' '); + + buffer += InspIRCd::Format("%5d [%5.2f%%]", current->UserCount, percent); - if (IS_OPER(user) || !Utils->FlatLinks) - depth = depth + 2; - for (unsigned int q = 0; q < Current->ChildCount(); q++) + if (user->IsOper()) { - TreeServer* child = Current->GetChild(q); - if (!IS_OPER(user)) { - if (child->Hidden) - continue; - if ((Utils->HideULines) && (ServerInstance->ULine(child->GetName()))) - continue; - } - ShowMap(child, user, depth, line, names, maxnamew, stats); + time_t secs_up = ServerInstance->Time() - current->age; + buffer += " [Up: " + ModuleSpanningTree::TimeToStr(secs_up) + (current->rtt == 0 ? "]" : " Lag: " + ConvToStr(current->rtt) + "ms]"); } -} + std::vector<std::string> map; + map.push_back(buffer); -// Ok, prepare to be confused. -// After much mulling over how to approach this, it struck me that -// the 'usual' way of doing a /MAP isnt the best way. Instead of -// keeping track of a ton of ascii characters, and line by line -// under recursion working out where to place them using multiplications -// and divisons, we instead render the map onto a backplane of characters -// (a character matrix), then draw the branches as a series of "L" shapes -// from the nodes. This is not only friendlier on CPU it uses less stack. -bool ModuleSpanningTree::HandleMap(const std::vector<std::string>& parameters, User* user) -{ - if (parameters.size() > 0) + const TreeServer::ChildServers& servers = current->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = servers.begin(); i != servers.end(); ++i) { - /* Remote MAP, the server is within the 1st parameter */ - TreeServer* s = Utils->FindServerMask(parameters[0]); - bool ret = false; - if (!s) + TreeServer* child = *i; + + if (IsHidden(user, child)) + continue; + + bool last = true; + for (TreeServer::ChildServers::const_iterator j = i + 1; last && j != servers.end(); ++j) + if (!IsHidden(user, *j)) + last = false; + + unsigned int next_len; + + if (user->IsOper() || !Utils->FlatLinks) { - user->WriteNumeric(ERR_NOSUCHSERVER, "%s %s :No such server", user->nick.c_str(), parameters[0].c_str()); - ret = true; + // This child is indented by us, so remove the depth from the max length to align the users properly + next_len = max_len - 2; } - else if (s && s != Utils->TreeRoot) + else { - parameterlist params; - params.push_back(parameters[0]); - - params[0] = s->GetName(); - Utils->DoOneToOne(user->uuid, "MAP", params, s->GetName()); - ret = true; + // This user can not see depth, so max_len remains constant + next_len = max_len; } - // Don't return if s == Utils->TreeRoot (us) - if (ret) - return true; - } + // Build the map for this child + std::vector<std::string> child_map = GetMap(user, child, next_len, depth + 1); - // These arrays represent a virtual screen which we will - // "scratch" draw to, as the console device of an irc - // client does not provide for a proper terminal. - int totusers = ServerInstance->Users->clientlist->size(); - int totservers = this->CountServs(); - int maxnamew = 0; - int line = 0; - char* names = new char[totservers * 100]; - char* stats = new char[totservers * 50]; - - // The only recursive bit is called here. - ShowMap(Utils->TreeRoot,user,0,line,names,maxnamew,stats); - - // Process each line one by one. - for (int l = 1; l < line; l++) - { - char* myname = names + 100 * l; - // scan across the line looking for the start of the - // servername (the recursive part of the algorithm has placed - // the servers at indented positions depending on what they - // are related to) - int first_nonspace = 0; - - while (myname[first_nonspace] == ' ') + for (std::vector<std::string>::const_iterator j = child_map.begin(); j != child_map.end(); ++j) { - first_nonspace++; + const char* prefix; + + if (user->IsOper() || !Utils->FlatLinks) + { + // If this server is not the root child + if (j != child_map.begin()) + { + // If this child is not my last child, then add | + // to be able to "link" the next server in my list to me, and to indent this childs servers + if (!last) + prefix = "| "; + // Otherwise this is my last child, so just use a space as theres nothing else linked to me below this + else + prefix = " "; + } + // If we get here, this server must be the root child + else + { + // If this is the last child, it gets a `- + if (last) + prefix = "`-"; + // Otherwise this isn't the last child, so it gets |- + else + prefix = "|-"; + } + } + else + // User can't see depth, so use no prefix + prefix = ""; + + // Add line to the map + map.push_back(prefix + *j); } + } - first_nonspace--; - - // Draw the `- (corner) section: this may be overwritten by - // another L shape passing along the same vertical pane, becoming - // a |- (branch) section instead. - - myname[first_nonspace] = '-'; - myname[first_nonspace-1] = '`'; - int l2 = l - 1; + return map; +} - // Draw upwards until we hit the parent server, causing possibly - // other corners (`-) to become branches (|-) - while ((names[l2 * 100 + first_nonspace-1] == ' ') || (names[l2 * 100 + first_nonspace-1] == '`')) +CmdResult CommandMap::Handle(const std::vector<std::string>& parameters, User* user) +{ + if (parameters.size() > 0) + { + // Remote MAP, the target server is the 1st parameter + TreeServer* s = Utils->FindServerMask(parameters[0]); + if (!s) { - names[l2 * 100 + first_nonspace-1] = '|'; - l2--; + user->WriteNumeric(ERR_NOSUCHSERVER, parameters[0], "No such server"); + return CMD_FAILURE; } + + if (!s->IsRoot()) + return CMD_SUCCESS; } - float avg_users = totusers * 1.0 / line; + // Max depth and max server name length + unsigned int max_depth = 0; + unsigned int max_len = 0; + GetDepthAndLen(Utils->TreeRoot, 0, max_depth, max_len); - ServerInstance->Logs->Log("map",DEBUG,"local"); - for (int t = 0; t < line; t++) + unsigned int max; + if (user->IsOper() || !Utils->FlatLinks) + { + // Each level of the map is indented by 2 characters, making the max possible line (max_depth * 2) + max_len + max = (max_depth * 2) + max_len; + } + else { - // terminate the string at maxnamew characters - names[100 * t + maxnamew] = '\0'; - user->SendText(":%s %03d %s :%s %s", ServerInstance->Config->ServerName.c_str(), - RPL_MAP, user->nick.c_str(), names + 100 * t, stats + 50 * t); + // This user can't see any depth + max = max_len; } - user->SendText(":%s %03d %s :%d server%s and %d user%s, average %.2f users per server", - ServerInstance->Config->ServerName.c_str(), RPL_MAPUSERS, user->nick.c_str(), - line, (line > 1 ? "s" : ""), totusers, (totusers > 1 ? "s" : ""), avg_users); - user->SendText(":%s %03d %s :End of /MAP", ServerInstance->Config->ServerName.c_str(), - RPL_ENDMAP, user->nick.c_str()); - delete[] names; - delete[] stats; + std::vector<std::string> map = GetMap(user, Utils->TreeRoot, max, 0); + for (std::vector<std::string>::const_iterator i = map.begin(); i != map.end(); ++i) + user->WriteRemoteNumeric(RPL_MAP, *i); + + size_t totusers = ServerInstance->Users->GetUsers().size(); + float avg_users = (float) totusers / Utils->serverlist.size(); - return true; + user->WriteRemoteNumeric(RPL_MAPUSERS, InspIRCd::Format("%u server%s and %u user%s, average %.2f users per server", + (unsigned int)Utils->serverlist.size(), (Utils->serverlist.size() > 1 ? "s" : ""), (unsigned int)totusers, (totusers > 1 ? "s" : ""), avg_users)); + user->WriteRemoteNumeric(RPL_ENDMAP, "End of /MAP"); + + return CMD_SUCCESS; } +RouteDescriptor CommandMap::GetRouting(User* user, const std::vector<std::string>& parameters) +{ + if (!parameters.empty()) + return ROUTE_UNICAST(parameters[0]); + return ROUTE_LOCALONLY; +} diff --git a/src/modules/m_spanningtree/override_squit.cpp b/src/modules/m_spanningtree/override_squit.cpp index 7d01c8149..9cec527d3 100644 --- a/src/modules/m_spanningtree/override_squit.cpp +++ b/src/modules/m_spanningtree/override_squit.cpp @@ -17,48 +17,38 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" #include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" #include "treesocket.h" -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - ModResult ModuleSpanningTree::HandleSquit(const std::vector<std::string>& parameters, User* user) { TreeServer* s = Utils->FindServerMask(parameters[0]); if (s) { - if (s == Utils->TreeRoot) + if (s->IsRoot()) { - user->WriteServ("NOTICE %s :*** SQUIT: Foolish mortal, you cannot make a server SQUIT itself! (%s matches local server name)",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** SQUIT: Foolish mortal, you cannot make a server SQUIT itself! (" + parameters[0] + " matches local server name)"); return MOD_RES_DENY; } - TreeSocket* sock = s->GetSocket(); - - if (sock) + if (s->IsLocal()) { ServerInstance->SNO->WriteToSnoMask('l',"SQUIT: Server \002%s\002 removed from network by %s",parameters[0].c_str(),user->nick.c_str()); - sock->Squit(s,"Server quit by " + user->GetFullRealHost()); - ServerInstance->SE->DelFd(sock); - sock->Close(); + s->SQuit("Server quit by " + user->GetFullRealHost()); } else { - user->WriteServ("NOTICE %s :*** SQUIT may not be used to remove remote servers. Please use RSQUIT instead.",user->nick.c_str()); + user->WriteNotice("*** SQUIT may not be used to remove remote servers. Please use RSQUIT instead."); } } else { - user->WriteServ("NOTICE %s :*** SQUIT: The server \002%s\002 does not exist on the network.",user->nick.c_str(),parameters[0].c_str()); + user->WriteNotice("*** SQUIT: The server \002" + parameters[0] + "\002 does not exist on the network."); } return MOD_RES_DENY; } - diff --git a/src/modules/m_spanningtree/override_stats.cpp b/src/modules/m_spanningtree/override_stats.cpp index 688661b80..9b73837cb 100644 --- a/src/modules/m_spanningtree/override_stats.cpp +++ b/src/modules/m_spanningtree/override_stats.cpp @@ -18,30 +18,42 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" #include "main.h" #include "utils.h" -#include "treeserver.h" #include "link.h" -#include "treesocket.h" -ModResult ModuleSpanningTree::OnStats(char statschar, User* user, string_list &results) +ModResult ModuleSpanningTree::OnStats(Stats::Context& stats) { - if ((statschar == 'c') || (statschar == 'n')) + if ((stats.GetSymbol() == 'c') || (stats.GetSymbol() == 'n')) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i != Utils->LinkBlocks.end(); ++i) { Link* L = *i; - results.push_back(std::string(ServerInstance->Config->ServerName)+" 213 "+user->nick+" "+statschar+" *@"+(L->HiddenFromStats ? "<hidden>" : L->IPAddr)+" * "+(*i)->Name.c_str()+" "+ConvToStr(L->Port)+" "+(L->Hook.empty() ? "plaintext" : L->Hook)); - if (statschar == 'c') - results.push_back(std::string(ServerInstance->Config->ServerName)+" 244 "+user->nick+" H * * "+L->Name.c_str()); + std::string ipaddr = "*@"; + if (L->HiddenFromStats) + ipaddr.append("<hidden>"); + else + ipaddr.append(L->IPAddr); + + const std::string hook = (L->Hook.empty() ? "plaintext" : L->Hook); + stats.AddRow(213, stats.GetSymbol(), ipaddr, '*', L->Name, L->Port, hook); + if (stats.GetSymbol() == 'c') + stats.AddRow(244, 'H', '*', '*', L->Name); + } + return MOD_RES_DENY; + } + else if (stats.GetSymbol() == 'U') + { + ConfigTagList tags = ServerInstance->Config->ConfTags("uline"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + std::string name = i->second->getString("server"); + if (!name.empty()) + stats.AddRow(248, 'U', name); } return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/override_whois.cpp b/src/modules/m_spanningtree/override_whois.cpp index ad8c6a6ef..7f7189854 100644 --- a/src/modules/m_spanningtree/override_whois.cpp +++ b/src/modules/m_spanningtree/override_whois.cpp @@ -16,39 +16,24 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commandbuilder.h" 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)) { - User* remote = ServerInstance->FindNickOnly(parameters[1]); - if (remote && !IS_LOCAL(remote)) - { - parameterlist params; - params.push_back(remote->uuid); - Utils->DoOneToOne(user->uuid,"IDLE",params,remote->server); - 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; - } + CmdBuilder(user, "IDLE").push(remote->uuid).Unicast(remote); + return MOD_RES_DENY; + } + else if (!remote) + { + user->WriteNumeric(Numerics::NoSuchNick(parameters[0])); + user->WriteNumeric(RPL_ENDOFWHOIS, parameters[0], "End of /WHOIS list."); + return MOD_RES_DENY; } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/ping.cpp b/src/modules/m_spanningtree/ping.cpp index aec680b23..878f8af3a 100644 --- a/src/modules/m_spanningtree/ping.cpp +++ b/src/modules/m_spanningtree/ping.cpp @@ -18,44 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" +#include "utils.h" -bool TreeSocket::LocalPing(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandPing::Handle(User* user, std::vector<std::string>& params) { - if (params.size() < 1) - return true; - if (params.size() == 1) - { - std::string stufftobounce = params[0]; - this->WriteLine(":"+ServerInstance->Config->GetSID()+" PONG "+stufftobounce); - return true; - } - else + if (params[0] == ServerInstance->Config->GetSID()) { - std::string forwardto = params[1]; - if (forwardto == ServerInstance->Config->ServerName || forwardto == ServerInstance->Config->GetSID()) - { - // this is a ping for us, send back PONG to the requesting server - params[1] = params[0]; - params[0] = forwardto; - Utils->DoOneToOne(ServerInstance->Config->GetSID(),"PONG",params,params[1]); - } - else - { - // not for us, pass it on :) - Utils->DoOneToOne(prefix,"PING",params,forwardto); - } - return true; + // PING for us, reply with a PONG + CmdBuilder reply("PONG"); + reply.push_back(user->uuid); + if (params.size() >= 2) + // If there is a second parameter, append it + reply.push_back(params[1]); + + reply.Unicast(user); } + return CMD_SUCCESS; } - - diff --git a/src/modules/m_spanningtree/pingtimer.cpp b/src/modules/m_spanningtree/pingtimer.cpp new file mode 100644 index 000000000..1c96259bf --- /dev/null +++ b/src/modules/m_spanningtree/pingtimer.cpp @@ -0,0 +1,102 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +#include "pingtimer.h" +#include "treeserver.h" +#include "commandbuilder.h" + +PingTimer::PingTimer(TreeServer* ts) + : Timer(Utils->PingFreq) + , server(ts) + , state(PS_SENDPING) +{ +} + +PingTimer::State PingTimer::TickInternal() +{ + // Timer expired, take next action based on what happened last time + if (state == PS_SENDPING) + { + // Last ping was answered, send next ping + server->GetSocket()->WriteLine(CmdBuilder("PING").push(server->GetID())); + LastPingMsec = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + // Warn next unless warnings are disabled. If they are, jump straight to timeout. + if (Utils->PingWarnTime) + return PS_WARN; + else + return PS_TIMEOUT; + } + else if (state == PS_WARN) + { + // No pong arrived in PingWarnTime seconds, send a warning to opers + ServerInstance->SNO->WriteToSnoMask('l', "Server \002%s\002 has not responded to PING for %d seconds, high latency.", server->GetName().c_str(), GetInterval()); + return PS_TIMEOUT; + } + else // PS_TIMEOUT + { + // They didn't answer the last ping, if they are locally connected, get rid of them + if (server->IsLocal()) + { + TreeSocket* sock = server->GetSocket(); + sock->SendError("Ping timeout"); + sock->Close(); + } + + // If the server is non-locally connected, don't do anything until we get a PONG. + // This is to avoid pinging the server and warning opers more than once. + // If they do answer eventually, we will move to the PS_SENDPING state and ping them again. + return PS_IDLE; + } +} + +void PingTimer::SetState(State newstate) +{ + state = newstate; + + // Set when should the next Tick() happen based on the state + if (state == PS_SENDPING) + SetInterval(Utils->PingFreq); + else if (state == PS_WARN) + SetInterval(Utils->PingWarnTime); + else if (state == PS_TIMEOUT) + SetInterval(Utils->PingFreq - Utils->PingWarnTime); + + // If state == PS_IDLE, do not set the timer, see above why +} + +bool PingTimer::Tick(time_t currtime) +{ + if (server->IsDead()) + return false; + + SetState(TickInternal()); + return false; +} + +void PingTimer::OnPong() +{ + // Calculate RTT + long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + server->rtt = ts - LastPingMsec; + + // Change state to send ping next, also reschedules the timer appropriately + SetState(PS_SENDPING); +} diff --git a/src/modules/m_spanningtree/pingtimer.h b/src/modules/m_spanningtree/pingtimer.h new file mode 100644 index 000000000..753558689 --- /dev/null +++ b/src/modules/m_spanningtree/pingtimer.h @@ -0,0 +1,77 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +class TreeServer; + +/** Handles PINGing servers and killing them on timeout + */ +class PingTimer : public Timer +{ + enum State + { + /** Send PING next */ + PS_SENDPING, + /** Warn opers next */ + PS_WARN, + /** Kill the server next due to ping timeout */ + PS_TIMEOUT, + /** Do nothing */ + PS_IDLE + }; + + /** Server the timer is interacting with + */ + TreeServer* const server; + + /** What to do when the timer ticks next + */ + State state; + + /** Last ping time in milliseconds, used to calculate round trip time + */ + unsigned long LastPingMsec; + + /** Update internal state and reschedule timer according to the new state + * @param newstate State to change to + */ + void SetState(State newstate); + + /** Process timer tick event + * @return State to change to + */ + State TickInternal(); + + /** Called by the TimerManager when the timer expires + * @param currtime Time now + * @return Always false, we reschedule ourselves instead + */ + bool Tick(time_t currtime) CXX11_OVERRIDE; + + public: + /** Construct the timer. This doesn't schedule the timer. + * @param server TreeServer to interact with + */ + PingTimer(TreeServer* server); + + /** Register a PONG from the server + */ + void OnPong(); +}; diff --git a/src/modules/m_spanningtree/pong.cpp b/src/modules/m_spanningtree/pong.cpp index 5966d05d9..5d97f2af2 100644 --- a/src/modules/m_spanningtree/pong.cpp +++ b/src/modules/m_spanningtree/pong.cpp @@ -18,65 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" +#include "utils.h" -bool TreeSocket::LocalPong(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandPong::HandleServer(TreeServer* server, std::vector<std::string>& params) { - if (params.size() < 1) - return true; - - if (params.size() == 1) + if (server->IsBursting()) { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (ServerSource) - { - ServerSource->SetPingFlag(); - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - ServerSource->rtt = ts - ServerSource->LastPingMsec; - } + ServerInstance->SNO->WriteGlobalSno('l', "Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", server->GetName().c_str()); + server->FinishBurst(); } - else - { - std::string forwardto = params[1]; - if (forwardto == ServerInstance->Config->GetSID() || forwardto == ServerInstance->Config->ServerName) - { - /* - * this is a PONG for us - * if the prefix is a user, check theyre local, and if they are, - * dump the PONG reply back to their fd. If its a server, do nowt. - * Services might want to send these s->s, but we dont need to yet. - */ - User* u = ServerInstance->FindNick(prefix); - if (u) - { - u->WriteServ("PONG %s %s",params[0].c_str(),params[1].c_str()); - } - TreeServer *ServerSource = Utils->FindServer(params[0]); - - if (ServerSource) - { - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - ServerSource->rtt = ts - ServerSource->LastPingMsec; - ServerSource->SetPingFlag(); - } - } - else - { - // not for us, pass it on :) - Utils->DoOneToOne(prefix,"PONG",params,forwardto); - } + if (params[0] == ServerInstance->Config->GetSID()) + { + // PONG for us + server->OnPong(); } - - return true; + return CMD_SUCCESS; } - diff --git a/src/modules/m_spanningtree/postcommand.cpp b/src/modules/m_spanningtree/postcommand.cpp index 3f5d427e1..64ca72977 100644 --- a/src/modules/m_spanningtree/postcommand.cpp +++ b/src/modules/m_spanningtree/postcommand.cpp @@ -17,69 +17,55 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ - -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" +#include "commandbuilder.h" -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -void ModuleSpanningTree::OnPostCommand(const std::string &command, const std::vector<std::string>& parameters, LocalUser *user, CmdResult result, const std::string &original_line) +void ModuleSpanningTree::OnPostCommand(Command* command, const std::vector<std::string>& parameters, LocalUser* user, CmdResult result, const std::string& original_line) { if (result == CMD_SUCCESS) Utils->RouteCommand(NULL, command, parameters, user); } -void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string &command, const parameterlist& parameters, User *user) +void SpanningTreeUtilities::RouteCommand(TreeServer* origin, CommandBase* thiscmd, const parameterlist& parameters, User* user) { - if (!ServerInstance->Parser->IsValidCommand(command, parameters.size(), user)) - return; - - /* We know it's non-null because IsValidCommand returned true */ - Command* thiscmd = ServerInstance->Parser->GetHandler(command); - + const std::string& command = thiscmd->name; RouteDescriptor routing = thiscmd->GetRouting(user, parameters); - - std::string sent_cmd = command; - parameterlist params; - if (routing.type == ROUTE_TYPE_LOCALONLY) - { - /* Broadcast when it's a core command with the default route descriptor and the source is a - * remote user or a remote server - */ + return; - Version ver = thiscmd->creator->GetVersion(); - if ((!(ver.Flags & VF_CORE)) || (IS_LOCAL(user)) || (IS_SERVER(user) == ServerInstance->FakeClient)) - return; + const bool encap = ((routing.type == ROUTE_TYPE_OPT_BCAST) || (routing.type == ROUTE_TYPE_OPT_UCAST)); + CmdBuilder params(user, encap ? "ENCAP" : command.c_str()); + TreeServer* sdest = NULL; - routing = ROUTE_BROADCAST; - } - else if (routing.type == ROUTE_TYPE_OPT_BCAST) + if (routing.type == ROUTE_TYPE_OPT_BCAST) { - params.push_back("*"); + params.push('*'); params.push_back(command); - sent_cmd = "ENCAP"; } - else if (routing.type == ROUTE_TYPE_OPT_UCAST) + else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST) { - TreeServer* sdest = FindServer(routing.serverdest); + sdest = static_cast<TreeServer*>(routing.server); if (!sdest) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Trying to route ENCAP to nonexistent server %s", - routing.serverdest.c_str()); - return; + // Assume the command handler already validated routing.serverdest and have only returned success if the target is something that the + // user executing the command is allowed to look up e.g. target is not an uuid if user is local. + sdest = FindRouteTarget(routing.serverdest); + if (!sdest) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Trying to route %s%s to nonexistent server %s", (encap ? "ENCAP " : ""), command.c_str(), routing.serverdest.c_str()); + return; + } + } + + if (encap) + { + params.push_back(sdest->GetID()); + params.push_back(command); } - params.push_back(sdest->GetID()); - params.push_back(command); - sent_cmd = "ENCAP"; } else { @@ -88,14 +74,13 @@ void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string & if (!(ver.Flags & (VF_COMMON | VF_CORE)) && srcmodule != Creator) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Routed command %s from non-VF_COMMON module %s", + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Routed command %s from non-VF_COMMON module %s", command.c_str(), srcmodule->ModuleSourceFile.c_str()); return; } } - std::string output_text; - ServerInstance->Parser->TranslateUIDs(thiscmd->translation, parameters, output_text, true, thiscmd); + std::string output_text = CommandParser::TranslateUIDs(thiscmd->translation, parameters, true, thiscmd); params.push_back(output_text); @@ -106,59 +91,40 @@ void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string & if (ServerInstance->Modes->FindPrefix(dest[0])) { pfx = dest[0]; - dest = dest.substr(1); + dest.erase(dest.begin()); } if (dest[0] == '#') { Channel* c = ServerInstance->FindChan(dest); if (!c) return; - TreeServerList list; // TODO OnBuildExemptList hook was here - GetListOfServersForChannel(c,list,pfx, CUList()); - std::string data = ":" + user->uuid + " " + sent_cmd; - for (unsigned int x = 0; x < params.size(); x++) - data += " " + params[x]; - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (origin && origin->GetSocket() == Sock) - continue; - if (Sock) - Sock->WriteLine(data); - } + CUList exempts; + SendChannelMessage(user->uuid, c, parameters[1], pfx, exempts, command.c_str(), origin ? origin->GetSocket() : NULL); } else if (dest[0] == '$') { - if (origin) - DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName()); - else - DoOneToMany(user->uuid, sent_cmd, params); + params.Forward(origin); } else { // user target? User* d = ServerInstance->FindNick(dest); - if (!d) + if (!d || IS_LOCAL(d)) return; - TreeServer* tsd = BestRouteTo(d->server); + TreeServer* tsd = TreeServer::Get(d)->GetRoute(); if (tsd == origin) // huh? no routing stuff around in a circle, please. return; - DoOneToOne(user->uuid, sent_cmd, params, d->server); + params.Unicast(d); } } else if (routing.type == ROUTE_TYPE_BROADCAST || routing.type == ROUTE_TYPE_OPT_BCAST) { - if (origin) - DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName()); - else - DoOneToMany(user->uuid, sent_cmd, params); + params.Forward(origin); } else if (routing.type == ROUTE_TYPE_UNICAST || routing.type == ROUTE_TYPE_OPT_UCAST) { - if (origin && routing.serverdest == origin->GetName()) - return; - DoOneToOne(user->uuid, sent_cmd, params, routing.serverdest); + params.Unicast(sdest->ServerUser); } } diff --git a/src/modules/m_spanningtree/precommand.cpp b/src/modules/m_spanningtree/precommand.cpp index b331571ca..4733d0071 100644 --- a/src/modules/m_spanningtree/precommand.cpp +++ b/src/modules/m_spanningtree/precommand.cpp @@ -18,18 +18,9 @@ */ -/* $ModDesc: Provides a spanning tree server link protocol */ - #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std::string>& parameters, LocalUser *user, bool validated, const std::string &original_line) { @@ -45,10 +36,6 @@ ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std { return this->HandleSquit(parameters,user); } - else if (command == "MAP") - { - return this->HandleMap(parameters,user) ? MOD_RES_DENY : MOD_RES_PASSTHRU; - } else if (command == "LINKS") { this->HandleLinks(parameters,user); @@ -64,9 +51,7 @@ ModResult ModuleSpanningTree::OnPreCommand(std::string &command, std::vector<std } else if ((command == "VERSION") && (parameters.size() > 0)) { - this->HandleVersion(parameters,user); - return MOD_RES_DENY; + return this->HandleVersion(parameters,user); } return MOD_RES_PASSTHRU; } - diff --git a/src/modules/m_spanningtree/protocolinterface.cpp b/src/modules/m_spanningtree/protocolinterface.cpp index ca4147fea..be95845a7 100644 --- a/src/modules/m_spanningtree/protocolinterface.cpp +++ b/src/modules/m_spanningtree/protocolinterface.cpp @@ -19,166 +19,105 @@ #include "inspircd.h" -#include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" #include "protocolinterface.h" +#include "commands.h" /* * For documentation on this class, see include/protocol.h. */ -void SpanningTreeProtocolInterface::GetServerList(ProtoServerList &sl) +void SpanningTreeProtocolInterface::GetServerList(ServerList& sl) { - sl.clear(); for (server_hash::iterator i = Utils->serverlist.begin(); i != Utils->serverlist.end(); i++) { - ProtoServer ps; + ServerInfo ps; ps.servername = i->second->GetName(); TreeServer* s = i->second->GetParent(); ps.parentname = s ? s->GetName() : ""; - ps.usercount = i->second->GetUserCount(); - ps.opercount = i->second->GetOperCount(); + ps.usercount = i->second->UserCount; + ps.opercount = i->second->OperCount; ps.gecos = i->second->GetDesc(); ps.latencyms = i->second->rtt; sl.push_back(ps); } } -bool SpanningTreeProtocolInterface::SendEncapsulatedData(const parameterlist &encap) +bool SpanningTreeProtocolInterface::SendEncapsulatedData(const std::string& targetmask, const std::string& cmd, const parameterlist& params, User* source) { - if (encap[0].find_first_of("*?") != std::string::npos) + if (!source) + source = ServerInstance->FakeClient; + + CmdBuilder encap(source, "ENCAP"); + + // Are there any wildcards in the target string? + if (targetmask.find_first_of("*?") != std::string::npos) { - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "ENCAP", encap); - return true; + // Yes, send the target string as-is; servers will decide whether or not it matches them + encap.push(targetmask).push(cmd).insert(params).Broadcast(); } - return Utils->DoOneToOne(ServerInstance->Config->GetSID(), "ENCAP", encap, encap[0]); -} - -void SpanningTreeProtocolInterface::SendMetaData(Extensible* target, const std::string &key, const std::string &data) -{ - parameterlist params; - - User* u = dynamic_cast<User*>(target); - Channel* c = dynamic_cast<Channel*>(target); - if (u) - params.push_back(u->uuid); - else if (c) - params.push_back(c->name); else - params.push_back("*"); + { + // No wildcards which means the target string has to be the name of a known server + TreeServer* server = Utils->FindServer(targetmask); + if (!server) + return false; - params.push_back(key); - params.push_back(":" + data); + // Use the SID of the target in the message instead of the server name + encap.push(server->GetID()).push(cmd).insert(params).Unicast(server->ServerUser); + } - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"METADATA",params); + return true; } -void SpanningTreeProtocolInterface::SendTopic(Channel* channel, std::string &topic) +void SpanningTreeProtocolInterface::BroadcastEncap(const std::string& cmd, const parameterlist& params, User* source, User* omit) { - parameterlist params; - - params.push_back(channel->name); - params.push_back(ConvToStr(ServerInstance->Time())); - params.push_back(ServerInstance->Config->ServerName); - params.push_back(":" + topic); + if (!source) + source = ServerInstance->FakeClient; - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FTOPIC", params); + // 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 SpanningTreeProtocolInterface::SendMode(const std::string &target, const parameterlist &modedata, const std::vector<TranslateType> &translate) +void SpanningTreeProtocolInterface::SendMetaData(User* u, const std::string& key, const std::string& data) { - if (modedata.empty()) - return; - - std::string outdata; - ServerInstance->Parser->TranslateUIDs(translate, modedata, outdata); - - std::string uidtarget; - ServerInstance->Parser->TranslateUIDs(TR_NICK, target, uidtarget); - - parameterlist outlist; - outlist.push_back(uidtarget); - outlist.push_back(outdata); - - User* a = ServerInstance->FindNick(uidtarget); - if (a) - { - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"MODE",outlist); - return; - } - else - { - Channel* c = ServerInstance->FindChan(target); - if (c) - { - outlist.insert(outlist.begin() + 1, ConvToStr(c->age)); - Utils->DoOneToMany(ServerInstance->Config->GetSID(),"FMODE",outlist); - } - } + CommandMetadata::Builder(u, key, data).Broadcast(); } -void SpanningTreeProtocolInterface::SendSNONotice(const std::string &snomask, const std::string &text) +void SpanningTreeProtocolInterface::SendMetaData(Channel* c, const std::string& key, const std::string& data) { - parameterlist p; - p.push_back(snomask); - p.push_back(":" + text); - Utils->DoOneToMany(ServerInstance->Config->GetSID(), "SNONOTICE", p); + CommandMetadata::Builder(c, key, data).Broadcast(); } -void SpanningTreeProtocolInterface::PushToClient(User* target, const std::string &rawline) +void SpanningTreeProtocolInterface::SendMetaData(const std::string& key, const std::string& data) { - parameterlist p; - p.push_back(target->uuid); - p.push_back(":" + rawline); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PUSH", p, target->server); -} - -void SpanningTreeProtocolInterface::SendChannel(Channel* target, char status, const std::string &text) -{ - TreeServerList list; - CUList exempt_list; - Utils->GetListOfServersForChannel(target,list,status,exempt_list); - for (TreeServerList::iterator i = list.begin(); i != list.end(); i++) - { - TreeSocket* Sock = i->second->GetSocket(); - if (Sock) - Sock->WriteLine(text); - } + CommandMetadata::Builder(key, data).Broadcast(); } - -void SpanningTreeProtocolInterface::SendChannelPrivmsg(Channel* target, char status, const std::string &text) +void SpanningTreeProtocolInterface::Server::SendMetaData(const std::string& key, const std::string& data) { - std::string cname = target->name; - if (status) - cname.insert(0, 1, status); - - SendChannel(target, status, ":" + ServerInstance->Config->GetSID()+" PRIVMSG "+cname+" :"+text); + sock->WriteLine(CommandMetadata::Builder(key, data)); } -void SpanningTreeProtocolInterface::SendChannelNotice(Channel* target, char status, const std::string &text) +void SpanningTreeProtocolInterface::SendSNONotice(char snomask, const std::string &text) { - std::string cname = target->name; - if (status) - cname.insert(0, 1, status); - - SendChannel(target, status, ":" + ServerInstance->Config->GetSID()+" NOTICE "+cname+" :"+text); + CmdBuilder("SNONOTICE").push(snomask).push_last(text).Broadcast(); } -void SpanningTreeProtocolInterface::SendUserPrivmsg(User* target, const std::string &text) +void SpanningTreeProtocolInterface::SendMessage(Channel* target, char status, const std::string& text, MessageType msgtype) { - parameterlist p; - p.push_back(target->uuid); - p.push_back(":" + text); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PRIVMSG", p, target->server); + const char* cmd = (msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); + CUList exempt_list; + Utils->SendChannelMessage(ServerInstance->Config->GetSID(), target, text, status, exempt_list, cmd); } -void SpanningTreeProtocolInterface::SendUserNotice(User* target, const std::string &text) +void SpanningTreeProtocolInterface::SendMessage(User* target, const std::string& text, MessageType msgtype) { - parameterlist p; + CmdBuilder p(msgtype == MSG_PRIVMSG ? "PRIVMSG" : "NOTICE"); p.push_back(target->uuid); - p.push_back(":" + text); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "NOTICE", p, target->server); + p.push_last(text); + p.Unicast(target); } diff --git a/src/modules/m_spanningtree/protocolinterface.h b/src/modules/m_spanningtree/protocolinterface.h index 297366893..e7fed5475 100644 --- a/src/modules/m_spanningtree/protocolinterface.h +++ b/src/modules/m_spanningtree/protocolinterface.h @@ -17,32 +17,27 @@ */ -#ifndef M_SPANNINGTREE_PROTOCOLINTERFACE_H -#define M_SPANNINGTREE_PROTOCOLINTERFACE_H - -class SpanningTreeUtilities; -class ModuleSpanningTree; +#pragma once class SpanningTreeProtocolInterface : public ProtocolInterface { - SpanningTreeUtilities* Utils; - void SendChannel(Channel* target, char status, const std::string &text); public: - SpanningTreeProtocolInterface(SpanningTreeUtilities* util) : Utils(util) { } - virtual ~SpanningTreeProtocolInterface() { } - - virtual bool SendEncapsulatedData(const parameterlist &encap); - virtual void SendMetaData(Extensible* target, const std::string &key, const std::string &data); - virtual void SendTopic(Channel* channel, std::string &topic); - virtual void SendMode(const std::string &target, const parameterlist &modedata, const std::vector<TranslateType> &types); - virtual void SendSNONotice(const std::string &snomask, const std::string &text); - virtual void PushToClient(User* target, const std::string &rawline); - virtual void SendChannelPrivmsg(Channel* target, char status, const std::string &text); - virtual void SendChannelNotice(Channel* target, char status, const std::string &text); - virtual void SendUserPrivmsg(User* target, const std::string &text); - virtual void SendUserNotice(User* target, const std::string &text); - virtual void GetServerList(ProtoServerList &sl); -}; + class Server : public ProtocolInterface::Server + { + TreeSocket* const sock; -#endif + public: + Server(TreeSocket* s) : sock(s) { } + void SendMetaData(const std::string& key, const std::string& data) CXX11_OVERRIDE; + }; + bool SendEncapsulatedData(const std::string& targetmask, const std::string& cmd, const parameterlist& params, User* source) CXX11_OVERRIDE; + void BroadcastEncap(const std::string& cmd, const parameterlist& params, User* source, User* omit) CXX11_OVERRIDE; + void SendMetaData(User* user, const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendMetaData(Channel* chan, const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendMetaData(const std::string& key, const std::string& data) CXX11_OVERRIDE; + void SendSNONotice(char snomask, const std::string& text) CXX11_OVERRIDE; + void SendMessage(Channel* target, char status, const std::string& text, MessageType msgtype); + void SendMessage(User* target, const std::string& text, MessageType msgtype); + void GetServerList(ServerList& sl); +}; diff --git a/src/modules/m_spanningtree/push.cpp b/src/modules/m_spanningtree/push.cpp deleted file mode 100644 index b791376ea..000000000 --- a/src/modules/m_spanningtree/push.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::Push(const std::string &prefix, parameterlist ¶ms) -{ - if (params.size() < 2) - return true; - User* u = ServerInstance->FindNick(params[0]); - if (!u) - return true; - if (IS_LOCAL(u)) - { - u->Write(params[1]); - } - else - { - // continue the raw onwards - params[1] = ":" + params[1]; - Utils->DoOneToOne(prefix,"PUSH",params,u->server); - } - return true; -} - diff --git a/src/modules/m_spanningtree/rconnect.cpp b/src/modules/m_spanningtree/rconnect.cpp index d4254cac6..8b8757a07 100644 --- a/src/modules/m_spanningtree/rconnect.cpp +++ b/src/modules/m_spanningtree/rconnect.cpp @@ -19,19 +19,13 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "resolvers.h" #include "main.h" #include "utils.h" -#include "treeserver.h" -#include "link.h" -#include "treesocket.h" #include "commands.h" -CommandRConnect::CommandRConnect (Module* Creator, SpanningTreeUtilities* Util) - : Command(Creator, "RCONNECT", 2), Utils(Util) +CommandRConnect::CommandRConnect (Module* Creator) + : Command(Creator, "RCONNECT", 2) { flags_needed = 'o'; syntax = "<remote-server-mask> <target-server-mask>"; @@ -39,14 +33,11 @@ CommandRConnect::CommandRConnect (Module* Creator, SpanningTreeUtilities* Util) CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, User *user) { - if (IS_LOCAL(user)) + /* First see if the server which is being asked to connect to another server in fact exists */ + if (!Utils->FindServerMask(parameters[0])) { - if (!Utils->FindServerMask(parameters[0])) - { - user->WriteServ("NOTICE %s :*** RCONNECT: Server \002%s\002 isn't connected to the network!", user->nick.c_str(), parameters[0].c_str()); - return CMD_FAILURE; - } - user->WriteServ("NOTICE %s :*** RCONNECT: Sending remote connect to \002%s\002 to connect server \002%s\002.",user->nick.c_str(),parameters[0].c_str(),parameters[1].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** RCONNECT: Server \002%s\002 isn't connected to the network!", parameters[0].c_str())); + return CMD_FAILURE; } /* Is this aimed at our server? */ @@ -58,6 +49,21 @@ CmdResult CommandRConnect::Handle (const std::vector<std::string>& parameters, U para.push_back(parameters[1]); ((ModuleSpanningTree*)(Module*)creator)->HandleConnect(para, user); } + else + { + /* It's not aimed at our server, but if the request originates from our user + * acknowledge that we sent the request. + * + * It's possible that we're asking a server for something that makes no sense + * (e.g. connect to itself or to an already connected server), but we don't check + * for those conditions here, as ModuleSpanningTree::HandleConnect() (which will run + * on the target) does all the checking and error reporting. + */ + if (IS_LOCAL(user)) + { + user->WriteNotice("*** RCONNECT: Sending remote connect to \002 " + parameters[0] + "\002 to connect server \002" + parameters[1] + "\002."); + } + } return CMD_SUCCESS; } diff --git a/src/modules/m_spanningtree/cachetimer.cpp b/src/modules/m_spanningtree/remoteuser.cpp index be438651d..717a6fd9f 100644 --- a/src/modules/m_spanningtree/cachetimer.cpp +++ b/src/modules/m_spanningtree/remoteuser.cpp @@ -1,7 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc> + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -18,24 +18,16 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "cachetimer.h" #include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "link.h" -#include "treesocket.h" +#include "remoteuser.h" -/* $ModDep: m_spanningtree/cachetimer.h m_spanningtree/resolvers.h m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/link.h m_spanningtree/treesocket.h */ - -CacheRefreshTimer::CacheRefreshTimer(SpanningTreeUtilities *Util) : Timer(3600, ServerInstance->Time(), true), Utils(Util) +SpanningTree::RemoteUser::RemoteUser(const std::string& uid, Server* srv) + : ::RemoteUser(uid, srv) { } -void CacheRefreshTimer::Tick(time_t TIME) +void SpanningTree::RemoteUser::WriteRemoteNumeric(const Numeric::Numeric& numeric) { - Utils->RefreshIPCache(); + CommandNum::Builder(this, numeric).Unicast(this); } - diff --git a/src/modules/m_spanningtree/remoteuser.h b/src/modules/m_spanningtree/remoteuser.h new file mode 100644 index 000000000..416f2f760 --- /dev/null +++ b/src/modules/m_spanningtree/remoteuser.h @@ -0,0 +1,32 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +namespace SpanningTree +{ + class RemoteUser; +} + +class SpanningTree::RemoteUser : public ::RemoteUser +{ + public: + RemoteUser(const std::string& uid, Server* srv); + void WriteRemoteNumeric(const Numeric::Numeric& numeric) CXX11_OVERRIDE; +}; diff --git a/src/modules/m_spanningtree/resolvers.cpp b/src/modules/m_spanningtree/resolvers.cpp index d7c4c5227..ded0573af 100644 --- a/src/modules/m_spanningtree/resolvers.cpp +++ b/src/modules/m_spanningtree/resolvers.cpp @@ -19,9 +19,8 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" +#include "cachetimer.h" #include "resolvers.h" #include "main.h" #include "utils.h" @@ -29,29 +28,35 @@ #include "link.h" #include "treesocket.h" -/* $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 */ - /** This class is used to resolve server hostnames during /connect and autoconnect. * As of 1.1, the resolver system is seperated out from BufferedSocket, so we must do this * resolver step first ourselves if we need it. This is totally nonblocking, and will * callback to OnLookupComplete or OnError when completed. Once it has completed we * will have an IP address which we can then use to continue our connection. */ -ServernameResolver::ServernameResolver(SpanningTreeUtilities* Util, const std::string &hostname, Link* x, bool &cached, QueryType qt, Autoconnect* myac) - : Resolver(hostname, qt, cached, Util->Creator), Utils(Util), query(qt), host(hostname), MyLink(x), myautoconnect(myac) +ServernameResolver::ServernameResolver(DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt, Autoconnect* myac) + : DNS::Request(mgr, Utils->Creator, hostname, qt) + , query(qt), host(hostname), MyLink(x), myautoconnect(myac) { } -void ServernameResolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) +void ServernameResolver::OnLookupComplete(const DNS::Query *r) { + const DNS::ResourceRecord* const ans_record = r->FindAnswerOfType(this->question.type); + if (!ans_record) + { + OnError(r); + return; + } + /* Initiate the connection, now that we have an IP to use. * Passing a hostname directly to BufferedSocket causes it to * just bail and set its FD to -1. */ - TreeServer* CheckDupe = Utils->FindServer(MyLink->Name.c_str()); + TreeServer* CheckDupe = Utils->FindServer(MyLink->Name); if (!CheckDupe) /* Check that nobody tried to connect it successfully while we were resolving */ { - TreeSocket* newsocket = new TreeSocket(Utils, MyLink, myautoconnect, result); + TreeSocket* newsocket = new TreeSocket(MyLink, myautoconnect, ans_record->rdata); if (newsocket->GetFd() > -1) { /* We're all OK */ @@ -66,47 +71,83 @@ void ServernameResolver::OnLookupComplete(const std::string &result, unsigned in } } -void ServernameResolver::OnError(ResolverError e, const std::string &errormessage) +void ServernameResolver::OnError(const DNS::Query *r) { - /* Ooops! */ - if (query == DNS_QUERY_AAAA) + if (r->error == DNS::ERROR_UNLOADED) { - bool cached = false; - ServernameResolver* snr = new ServernameResolver(Utils, host, MyLink, cached, DNS_QUERY_A, myautoconnect); - ServerInstance->AddResolver(snr, cached); + // We're being unloaded, skip the snotice and ConnectServer() below to prevent autoconnect creating new sockets return; } - ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Unable to resolve hostname - %s", MyLink->Name.c_str(), errormessage.c_str() ); + + if (query == DNS::QUERY_AAAA) + { + ServernameResolver* snr = new ServernameResolver(this->manager, host, MyLink, DNS::QUERY_A, myautoconnect); + try + { + this->manager->Process(snr); + return; + } + catch (DNS::Exception &) + { + delete snr; + } + } + + ServerInstance->SNO->WriteToSnoMask('l', "CONNECT: Error connecting \002%s\002: Unable to resolve hostname - %s", MyLink->Name.c_str(), this->manager->GetErrorStr(r->error).c_str()); Utils->Creator->ConnectServer(myautoconnect, false); } -SecurityIPResolver::SecurityIPResolver(Module* me, SpanningTreeUtilities* U, const std::string &hostname, Link* x, bool &cached, QueryType qt) - : Resolver(hostname, qt, cached, me), MyLink(x), Utils(U), mine(me), host(hostname), query(qt) +SecurityIPResolver::SecurityIPResolver(Module* me, DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt) + : DNS::Request(mgr, me, hostname, qt) + , MyLink(x), mine(me), host(hostname), query(qt) { } -void SecurityIPResolver::OnLookupComplete(const std::string &result, unsigned int ttl, bool cached) +void SecurityIPResolver::OnLookupComplete(const DNS::Query *r) { for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i != Utils->LinkBlocks.end(); ++i) { Link* L = *i; if (L->IPAddr == host) { - Utils->ValidIPs.push_back(result); + for (std::vector<DNS::ResourceRecord>::const_iterator j = r->answers.begin(); j != r->answers.end(); ++j) + { + const DNS::ResourceRecord& ans_record = *j; + if (ans_record.type == this->question.type) + Utils->ValidIPs.push_back(ans_record.rdata); + } break; } } } -void SecurityIPResolver::OnError(ResolverError e, const std::string &errormessage) +void SecurityIPResolver::OnError(const DNS::Query *r) { - if (query == DNS_QUERY_AAAA) + // This can be called because of us being unloaded but we don't have to do anything differently + if (query == DNS::QUERY_AAAA) { - bool cached = false; - SecurityIPResolver* res = new SecurityIPResolver(mine, Utils, host, MyLink, cached, DNS_QUERY_A); - ServerInstance->AddResolver(res, cached); - return; + SecurityIPResolver* res = new SecurityIPResolver(mine, this->manager, host, MyLink, DNS::QUERY_A); + try + { + this->manager->Process(res); + return; + } + catch (DNS::Exception &) + { + delete res; + } } - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Could not resolve IP associated with Link '%s': %s", - MyLink->Name.c_str(),errormessage.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Could not resolve IP associated with Link '%s': %s", + MyLink->Name.c_str(), this->manager->GetErrorStr(r->error).c_str()); +} + +CacheRefreshTimer::CacheRefreshTimer() + : Timer(3600, true) +{ +} + +bool CacheRefreshTimer::Tick(time_t TIME) +{ + Utils->RefreshIPCache(); + return true; } diff --git a/src/modules/m_spanningtree/resolvers.h b/src/modules/m_spanningtree/resolvers.h index 65b9e7249..782ac86ef 100644 --- a/src/modules/m_spanningtree/resolvers.h +++ b/src/modules/m_spanningtree/resolvers.h @@ -18,30 +18,27 @@ */ -#ifndef M_SPANNINGTREE_RESOLVERS_H -#define M_SPANNINGTREE_RESOLVERS_H +#pragma once -#include "socket.h" #include "inspircd.h" -#include "xline.h" +#include "modules/dns.h" #include "utils.h" #include "link.h" /** Handle resolving of server IPs for the cache */ -class SecurityIPResolver : public Resolver +class SecurityIPResolver : public DNS::Request { private: reference<Link> MyLink; - SpanningTreeUtilities* Utils; Module* mine; std::string host; - QueryType query; + DNS::QueryType query; public: - SecurityIPResolver(Module* me, SpanningTreeUtilities* U, const std::string &hostname, Link* x, bool &cached, QueryType qt); - void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached); - void OnError(ResolverError e, const std::string &errormessage); + SecurityIPResolver(Module* me, DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt); + void OnLookupComplete(const DNS::Query *r) CXX11_OVERRIDE; + void OnError(const DNS::Query *q) CXX11_OVERRIDE; }; /** This class is used to resolve server hostnames during /connect and autoconnect. @@ -50,18 +47,15 @@ class SecurityIPResolver : public Resolver * callback to OnLookupComplete or OnError when completed. Once it has completed we * will have an IP address which we can then use to continue our connection. */ -class ServernameResolver : public Resolver +class ServernameResolver : public DNS::Request { private: - SpanningTreeUtilities* Utils; - QueryType query; + DNS::QueryType query; std::string host; reference<Link> MyLink; reference<Autoconnect> myautoconnect; public: - ServernameResolver(SpanningTreeUtilities* Util, const std::string &hostname, Link* x, bool &cached, QueryType qt, Autoconnect* myac); - void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached); - void OnError(ResolverError e, const std::string &errormessage); + ServernameResolver(DNS::Manager* mgr, const std::string& hostname, Link* x, DNS::QueryType qt, Autoconnect* myac); + void OnLookupComplete(const DNS::Query *r) CXX11_OVERRIDE; + void OnError(const DNS::Query *q) CXX11_OVERRIDE; }; - -#endif diff --git a/src/modules/m_spanningtree/rsquit.cpp b/src/modules/m_spanningtree/rsquit.cpp index 027ae02ab..487db2826 100644 --- a/src/modules/m_spanningtree/rsquit.cpp +++ b/src/modules/m_spanningtree/rsquit.cpp @@ -19,17 +19,14 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "treesocket.h" #include "commands.h" -CommandRSQuit::CommandRSQuit (Module* Creator, SpanningTreeUtilities* Util) - : Command(Creator, "RSQUIT", 1), Utils(Util) +CommandRSQuit::CommandRSQuit(Module* Creator) + : Command(Creator, "RSQUIT", 1) { flags_needed = 'o'; syntax = "<target-server-mask> [reason]"; @@ -38,34 +35,26 @@ CommandRSQuit::CommandRSQuit (Module* Creator, SpanningTreeUtilities* Util) CmdResult CommandRSQuit::Handle (const std::vector<std::string>& parameters, User *user) { TreeServer *server_target; // Server to squit - TreeServer *server_linked; // Server target is linked to server_target = Utils->FindServerMask(parameters[0]); if (!server_target) { - user->WriteServ("NOTICE %s :*** RSQUIT: Server \002%s\002 isn't connected to the network!", user->nick.c_str(), parameters[0].c_str()); + user->WriteRemoteNotice(InspIRCd::Format("*** RSQUIT: Server \002%s\002 isn't connected to the network!", parameters[0].c_str())); return CMD_FAILURE; } - if (server_target == Utils->TreeRoot) + if (server_target->IsRoot()) { - NoticeUser(user, "*** RSQUIT: Foolish mortal, you cannot make a server SQUIT itself! ("+parameters[0]+" matches local server name)"); + user->WriteRemoteNotice(InspIRCd::Format("*** RSQUIT: Foolish mortal, you cannot make a server SQUIT itself! (%s matches local server name)", parameters[0].c_str())); return CMD_FAILURE; } - server_linked = server_target->GetParent(); - - if (server_linked == Utils->TreeRoot) + if (server_target->IsLocal()) { // We have been asked to remove server_target. - TreeSocket* sock = server_target->GetSocket(); - if (sock) - { - const char *reason = parameters.size() == 2 ? parameters[1].c_str() : "No reason"; - ServerInstance->SNO->WriteToSnoMask('l',"RSQUIT: Server \002%s\002 removed from network by %s (%s)", parameters[0].c_str(), user->nick.c_str(), reason); - sock->Squit(server_target, "Server quit by " + user->GetFullRealHost() + " (" + reason + ")"); - sock->Close(); - } + const char* reason = parameters.size() == 2 ? parameters[1].c_str() : "No reason"; + ServerInstance->SNO->WriteToSnoMask('l',"RSQUIT: Server \002%s\002 removed from network by %s (%s)", parameters[0].c_str(), user->nick.c_str(), reason); + server_target->SQuit("Server quit by " + user->GetFullRealHost() + " (" + reason + ")"); } return CMD_SUCCESS; @@ -75,20 +64,3 @@ RouteDescriptor CommandRSQuit::GetRouting(User* user, const std::vector<std::str { return ROUTE_UNICAST(parameters[0]); } - -// XXX use protocol interface instead of rolling our own :) -void CommandRSQuit::NoticeUser(User* user, const std::string &msg) -{ - if (IS_LOCAL(user)) - { - user->WriteServ("NOTICE %s :%s",user->nick.c_str(),msg.c_str()); - } - else - { - parameterlist params; - params.push_back(user->nick); - params.push_back("NOTICE "+ConvToStr(user->nick)+" :"+msg); - Utils->DoOneToOne(ServerInstance->Config->GetSID(), "PUSH", params, user->server); - } -} - diff --git a/src/modules/m_spanningtree/save.cpp b/src/modules/m_spanningtree/save.cpp index 92999b422..7131b49fe 100644 --- a/src/modules/m_spanningtree/save.cpp +++ b/src/modules/m_spanningtree/save.cpp @@ -18,38 +18,24 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" #include "utils.h" -#include "treeserver.h" #include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ +#include "commands.h" /** * SAVE command - force nick change to UID on timestamp match */ -bool TreeSocket::ForceNick(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandSave::Handle(User* user, std::vector<std::string>& params) { - if (params.size() < 2) - return true; + User* u = ServerInstance->FindUUID(params[0]); + if (!u) + return CMD_FAILURE; - User* u = ServerInstance->FindNick(params[0]); time_t ts = atol(params[1].c_str()); - if ((u) && (!IS_SERVER(u)) && (u->age == ts)) - { - Utils->DoOneToAllButSender(prefix,"SAVE",params,prefix); - - if (!u->ForceNickChange(u->uuid.c_str())) - { - ServerInstance->Users->QuitUser(u, "Nickname collision"); - } - } + if (u->age == ts) + u->ChangeNick(u->uuid, SavedTimestamp); - return true; + return CMD_SUCCESS; } - diff --git a/src/modules/m_spanningtree/server.cpp b/src/modules/m_spanningtree/server.cpp index d3033799e..50f63e117 100644 --- a/src/modules/m_spanningtree/server.cpp +++ b/src/modules/m_spanningtree/server.cpp @@ -19,113 +19,102 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" +#include "modules/ssl.h" #include "main.h" #include "utils.h" #include "link.h" #include "treeserver.h" #include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h m_spanningtree/link.h */ +#include "commands.h" /* * Some server somewhere in the network introducing another server. * -- w */ -bool TreeSocket::RemoteServer(const std::string &prefix, parameterlist ¶ms) +CmdResult CommandServer::HandleServer(TreeServer* ParentOfThis, std::vector<std::string>& params) { - if (params.size() < 5) - { - SendError("Protocol error - Not enough parameters for SERVER command"); - return false; - } + const std::string& servername = params[0]; + const std::string& sid = params[1]; + const std::string& description = params.back(); + TreeSocket* socket = ParentOfThis->GetSocket(); - std::string servername = params[0]; - // password is not used for a remote server - // hopcount is not used (ever) - std::string sid = params[3]; - std::string description = params[4]; - TreeServer* ParentOfThis = Utils->FindServer(prefix); - - if (!ParentOfThis) - { - this->SendError("Protocol error - Introduced remote server from unknown server "+prefix); - return false; - } - if (!ServerInstance->IsSID(sid)) + if (!InspIRCd::IsSID(sid)) { - this->SendError("Invalid format server ID: "+sid+"!"); - return false; + socket->SendError("Invalid format server ID: "+sid+"!"); + return CMD_FAILURE; } TreeServer* CheckDupe = Utils->FindServer(servername); if (CheckDupe) { - this->SendError("Server "+servername+" already exists!"); + socket->SendError("Server "+servername+" already exists!"); ServerInstance->SNO->WriteToSnoMask('L', "Server \2"+CheckDupe->GetName()+"\2 being introduced from \2" + ParentOfThis->GetName() + "\2 denied, already exists. Closing link with " + ParentOfThis->GetName()); - return false; + return CMD_FAILURE; } CheckDupe = Utils->FindServer(sid); if (CheckDupe) { - this->SendError("Server ID "+sid+" already exists! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); + socket->SendError("Server ID "+sid+" already exists! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); ServerInstance->SNO->WriteToSnoMask('L', "Server \2"+servername+"\2 being introduced from \2" + ParentOfThis->GetName() + "\2 denied, server ID already exists on the network. Closing link with " + ParentOfThis->GetName()); - return false; + return CMD_FAILURE; } Link* lnk = Utils->FindLink(servername); - TreeServer *Node = new TreeServer(Utils, servername, description, sid, ParentOfThis,NULL, lnk ? lnk->Hidden : false); + TreeServer* Node = new TreeServer(servername, description, sid, ParentOfThis, ParentOfThis->GetSocket(), lnk ? lnk->Hidden : false); + + HandleExtra(Node, params); - ParentOfThis->AddChild(Node); - params[4] = ":" + params[4]; - Utils->DoOneToAllButSender(prefix,"SERVER",params,prefix); ServerInstance->SNO->WriteToSnoMask('L', "Server \002"+ParentOfThis->GetName()+"\002 introduced server \002"+servername+"\002 ("+description+")"); - return true; + return CMD_SUCCESS; } +void CommandServer::HandleExtra(TreeServer* newserver, const std::vector<std::string>& params) +{ + for (std::vector<std::string>::const_iterator i = params.begin() + 2; i != params.end() - 1; ++i) + { + const std::string& prop = *i; + std::string::size_type p = prop.find('='); -/* - * This is used after the other side of a connection has accepted our credentials. - * They are then introducing themselves to us, BEFORE either of us burst. -- w - */ -bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) + std::string key = prop; + std::string val; + if (p != std::string::npos) + { + key.erase(p); + val.assign(prop, p+1, std::string::npos); + } + + if (key == "burst") + newserver->BeginBurst(ConvToUInt64(val)); + } +} + +Link* TreeSocket::AuthRemote(const parameterlist& params) { if (params.size() < 5) { SendError("Protocol error - Not enough parameters for SERVER command"); - return false; + return NULL; } - irc::string servername = params[0].c_str(); - std::string sname = params[0]; - std::string password = params[1]; - std::string sid = params[3]; - std::string description = params[4]; - int hops = atoi(params[2].c_str()); + const std::string& sname = params[0]; + const std::string& password = params[1]; + const std::string& sid = params[3]; + const std::string& description = params.back(); this->SendCapabilities(2); - if (hops) - { - this->SendError("Server too far away for authentication"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, server is too far away for authentication"); - return false; - } - if (!ServerInstance->IsSID(sid)) { this->SendError("Invalid format server ID: "+sid+"!"); - return false; + return NULL; } for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) { Link* x = *i; - if (x->Name != servername && x->Name != "*") // open link allowance + if ((!stdalgo::string::equalsci(x->Name, sname)) && (x->Name != "*")) // open link allowance continue; if (!ComparePass(*x, password)) @@ -134,22 +123,36 @@ bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) continue; } - TreeServer* CheckDupe = Utils->FindServer(sname); - if (CheckDupe) - { - std::string pname = CheckDupe->GetParent() ? CheckDupe->GetParent()->GetName() : "<ourself>"; - SendError("Server "+sname+" already exists on server "+pname+"!"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, already exists on server "+pname); - return false; - } - CheckDupe = Utils->FindServer(sid); - if (CheckDupe) + if (!CheckDuplicate(sname, sid)) + return NULL; + + ServerInstance->SNO->WriteToSnoMask('l',"Verified server connection " + linkID + " ("+description+")"); + + const SSLIOHook* const ssliohook = SSLIOHook::IsSSL(this); + if (ssliohook) { - this->SendError("Server ID "+sid+" already exists on the network! You may want to specify the server ID for the server manually with <server:id> so they do not conflict."); - ServerInstance->SNO->WriteToSnoMask('l',"Server \2"+assign(servername)+"\2 being introduced denied, server ID already exists on the network. Closing link."); - return false; + std::string ciphersuite; + ssliohook->GetCiphersuite(ciphersuite); + ServerInstance->SNO->WriteToSnoMask('l', "Negotiated ciphersuite %s on link %s", ciphersuite.c_str(), x->Name.c_str()); } + return x; + } + + this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); + ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); + return NULL; +} + +/* + * This is used after the other side of a connection has accepted our credentials. + * They are then introducing themselves to us, BEFORE either of us burst. -- w + */ +bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) +{ + const Link* x = AuthRemote(params); + if (x) + { /* * They're in WAIT_AUTH_2 (having accepted our credentials). * Set our state to CONNECTED (since everything's peachy so far) and send our @@ -158,32 +161,17 @@ bool TreeSocket::Outbound_Reply_Server(parameterlist ¶ms) * While we're at it, create a treeserver object so we know about them. * -- w */ - this->LinkState = CONNECTED; - - Utils->timeoutlist.erase(this); - linkID = sname; - - MyRoot = new TreeServer(Utils, sname, description, sid, Utils->TreeRoot, this, x->Hidden); - Utils->TreeRoot->AddChild(MyRoot); - this->DoBurst(MyRoot); - - params[4] = ":" + params[4]; - - /* IMPORTANT: Take password/hmac hash OUT of here before we broadcast the introduction! */ - params[1] = "*"; - Utils->DoOneToAllButSender(ServerInstance->Config->GetSID(),"SERVER",params,sname); + FinishAuth(params[0], params[3], params.back(), x->Hidden); return true; } - this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; } bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid) { - /* Check for fully initialized instances of the server by name */ + // Check if the server name is not in use by a server that's already fully connected TreeServer* CheckDupe = Utils->FindServer(sname); if (CheckDupe) { @@ -193,8 +181,8 @@ bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid return false; } - /* Check for fully initialized instances of the server by id */ - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Looking for dupe SID %s", sid.c_str()); + // Check if the id is not in use by a server that's already fully connected + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Looking for dupe SID %s", sid.c_str()); CheckDupe = Utils->FindServerID(sid); if (CheckDupe) @@ -214,58 +202,14 @@ bool TreeSocket::CheckDuplicate(const std::string& sname, const std::string& sid */ bool TreeSocket::Inbound_Server(parameterlist ¶ms) { - if (params.size() < 5) - { - SendError("Protocol error - Missing SID"); - return false; - } - - irc::string servername = params[0].c_str(); - std::string sname = params[0]; - std::string password = params[1]; - std::string sid = params[3]; - std::string description = params[4]; - int hops = atoi(params[2].c_str()); - - this->SendCapabilities(2); - - if (hops) - { - this->SendError("Server too far away for authentication"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, server is too far away for authentication"); - return false; - } - - if (!ServerInstance->IsSID(sid)) - { - this->SendError("Invalid format server ID: "+sid+"!"); - return false; - } - - for (std::vector<reference<Link> >::iterator i = Utils->LinkBlocks.begin(); i < Utils->LinkBlocks.end(); i++) + const Link* x = AuthRemote(params); + if (x) { - Link* x = *i; - if (x->Name != servername && x->Name != "*") // open link allowance - continue; - - if (!ComparePass(*x, password)) - { - ServerInstance->SNO->WriteToSnoMask('l',"Invalid password on link: %s", x->Name.c_str()); - continue; - } - - if (!CheckDuplicate(sname, sid)) - return false; - - ServerInstance->SNO->WriteToSnoMask('l',"Verified incoming server connection " + linkID + " ("+description+")"); - - this->SendCapabilities(2); - // Save these for later, so when they accept our credentials (indicated by BURST) we remember them this->capab->hidden = x->Hidden; - this->capab->sid = sid; - this->capab->description = description; - this->capab->name = sname; + this->capab->sid = params[3]; + this->capab->description = params.back(); + this->capab->name = params[0]; // Send our details: Our server name and description and hopcount of 0, // along with the sendpass from this block. @@ -276,8 +220,15 @@ bool TreeSocket::Inbound_Server(parameterlist ¶ms) return true; } - this->SendError("Mismatched server name or password (check the other server's snomask output for details - e.g. umode +s +Ll)"); - ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; } +CommandServer::Builder::Builder(TreeServer* server) + : CmdBuilder(server->GetParent()->GetID(), "SERVER") +{ + push(server->GetName()); + push(server->GetID()); + if (server->IsBursting()) + push_property("burst", ConvToStr(server->StartBurst)); + push_last(server->GetDesc()); +} diff --git a/src/modules/m_spanningtree/servercommand.cpp b/src/modules/m_spanningtree/servercommand.cpp new file mode 100644 index 000000000..ef55cd00e --- /dev/null +++ b/src/modules/m_spanningtree/servercommand.cpp @@ -0,0 +1,60 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" +#include "main.h" +#include "servercommand.h" + +ServerCommand::ServerCommand(Module* Creator, const std::string& Name, unsigned int MinParams, unsigned int MaxParams) + : CommandBase(Creator, Name, MinParams, MaxParams) +{ +} + +void ServerCommand::RegisterService() +{ + ModuleSpanningTree* st = static_cast<ModuleSpanningTree*>(static_cast<Module*>(creator)); + st->CmdManager.AddCommand(this); +} + +RouteDescriptor ServerCommand::GetRouting(User* user, const std::vector<std::string>& parameters) +{ + // Broadcast server-to-server commands unless overridden + return ROUTE_BROADCAST; +} + +time_t ServerCommand::ExtractTS(const std::string& tsstr) +{ + time_t TS = ConvToInt(tsstr); + if (!TS) + throw ProtocolException("Invalid TS"); + return TS; +} + +ServerCommand* ServerCommandManager::GetHandler(const std::string& command) const +{ + ServerCommandMap::const_iterator it = commands.find(command); + if (it != commands.end()) + return it->second; + return NULL; +} + +bool ServerCommandManager::AddCommand(ServerCommand* cmd) +{ + return commands.insert(std::make_pair(cmd->name, cmd)).second; +} diff --git a/src/modules/m_spanningtree/servercommand.h b/src/modules/m_spanningtree/servercommand.h new file mode 100644 index 000000000..07dfc4898 --- /dev/null +++ b/src/modules/m_spanningtree/servercommand.h @@ -0,0 +1,104 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2013 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +#include "utils.h" +#include "treeserver.h" + +class ProtocolException : public ModuleException +{ + public: + ProtocolException(const std::string& msg) + : ModuleException("Protocol violation: " + msg) + { + } +}; + +/** Base class for server-to-server commands that may have a (remote) user source or server source. + */ +class ServerCommand : public CommandBase +{ + public: + ServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0); + + /** Register this object in the ServerCommandManager + */ + void RegisterService() CXX11_OVERRIDE; + + virtual CmdResult Handle(User* user, std::vector<std::string>& parameters) = 0; + virtual RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters); + + /** + * Extract the TS from a string. + * @param tsstr The string containing the TS. + * @return The raw timestamp value. + * This function throws a ProtocolException if it considers the TS invalid. Note that the detection of + * invalid timestamps is not designed to be bulletproof, only some cases - like "0" - trigger an exception. + */ + static time_t ExtractTS(const std::string& tsstr); +}; + +/** Base class for server-to-server command handlers which are only valid if their source is a user. + * When a server sends a command of this type and the source is a server (sid), the link is aborted. + */ +template <class T> +class UserOnlyServerCommand : public ServerCommand +{ + public: + UserOnlyServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0) + : ServerCommand(Creator, Name, MinPara, MaxPara) { } + + CmdResult Handle(User* user, std::vector<std::string>& parameters) + { + RemoteUser* remoteuser = IS_REMOTE(user); + if (!remoteuser) + throw ProtocolException("Invalid source"); + return static_cast<T*>(this)->HandleRemote(remoteuser, parameters); + } +}; + +/** Base class for server-to-server command handlers which are only valid if their source is a server. + * When a server sends a command of this type and the source is a user (uuid), the link is aborted. + */ +template <class T> +class ServerOnlyServerCommand : public ServerCommand +{ + public: + ServerOnlyServerCommand(Module* Creator, const std::string& Name, unsigned int MinPara = 0, unsigned int MaxPara = 0) + : ServerCommand(Creator, Name, MinPara, MaxPara) { } + + CmdResult Handle(User* user, std::vector<std::string>& parameters) + { + if (!IS_SERVER(user)) + throw ProtocolException("Invalid source"); + TreeServer* server = TreeServer::Get(user); + return static_cast<T*>(this)->HandleServer(server, parameters); + } +}; + +class ServerCommandManager +{ + typedef TR1NS::unordered_map<std::string, ServerCommand*> ServerCommandMap; + ServerCommandMap commands; + + public: + ServerCommand* GetHandler(const std::string& command) const; + bool AddCommand(ServerCommand* cmd); +}; diff --git a/src/modules/m_spanningtree/sinfo.cpp b/src/modules/m_spanningtree/sinfo.cpp new file mode 100644 index 000000000..0989ea9a5 --- /dev/null +++ b/src/modules/m_spanningtree/sinfo.cpp @@ -0,0 +1,51 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "inspircd.h" + +#include "treeserver.h" +#include "commands.h" + +CmdResult CommandSInfo::HandleServer(TreeServer* server, std::vector<std::string>& params) +{ + const std::string& key = params.front(); + const std::string& value = params.back(); + + if (key == "fullversion") + { + server->SetFullVersion(value); + } + else if (key == "version") + { + server->SetVersion(value); + } + else if (key == "desc") + { + // Only sent when the description of a server changes because of a rehash; not sent on burst + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Server description of " + server->GetName() + " changed: " + value); + server->SetDesc(value); + } + + return CMD_SUCCESS; +} + +CommandSInfo::Builder::Builder(TreeServer* server, const char* key, const std::string& val) + : CmdBuilder(server->GetID(), "SINFO") +{ + push(key).push_last(val); +} diff --git a/src/modules/m_spanningtree/svsjoin.cpp b/src/modules/m_spanningtree/svsjoin.cpp index 416502369..c85e4f412 100644 --- a/src/modules/m_spanningtree/svsjoin.cpp +++ b/src/modules/m_spanningtree/svsjoin.cpp @@ -19,19 +19,13 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" -#include "utils.h" -#include "treeserver.h" #include "commands.h" -CmdResult CommandSVSJoin::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSJoin::Handle(User* user, std::vector<std::string>& parameters) { // Check for valid channel name - if (!ServerInstance->IsChannel(parameters[1].c_str(), ServerInstance->Config->Limits.ChanMax)) + if (!ServerInstance->IsChannel(parameters[1])) return CMD_FAILURE; // Check target exists @@ -40,15 +34,25 @@ CmdResult CommandSVSJoin::Handle(const std::vector<std::string>& parameters, Use return CMD_FAILURE; /* only join if it's local, otherwise just pass it on! */ - if (IS_LOCAL(u)) - Channel::JoinUser(u, parameters[1].c_str(), false, "", false, ServerInstance->Time()); + LocalUser* localuser = IS_LOCAL(u); + if (localuser) + { + bool override = false; + std::string key; + if (parameters.size() >= 3) + { + key = parameters[2]; + if (key.empty()) + override = true; + } + + Channel::JoinUser(localuser, parameters[1], override, key); + } + return CMD_SUCCESS; } RouteDescriptor CommandSVSJoin::GetRouting(User* user, const std::vector<std::string>& parameters) { - User* u = ServerInstance->FindUUID(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/svsnick.cpp b/src/modules/m_spanningtree/svsnick.cpp index 59973202d..84cf8558c 100644 --- a/src/modules/m_spanningtree/svsnick.cpp +++ b/src/modules/m_spanningtree/svsnick.cpp @@ -21,41 +21,50 @@ #include "inspircd.h" #include "main.h" -#include "utils.h" #include "commands.h" -CmdResult CommandSVSNick::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSNick::Handle(User* user, std::vector<std::string>& parameters) { User* u = ServerInstance->FindNick(parameters[0]); if (u && IS_LOCAL(u)) { + // The 4th parameter is optional and it is the expected nick TS of the target user. If this parameter is + // present and it doesn't match the user's nick TS, the SVSNICK is not acted upon. + // This makes it possible to detect the case when services wants to change the nick of a user, but the + // user changes their nick before the SVSNICK arrives, making the SVSNICK nick change (usually to a guest nick) + // unnecessary. Consider the following for example: + // + // 1. test changes nick to Attila which is protected by services + // 2. Services SVSNICKs the user to Guest12345 + // 3. Attila changes nick to Attila_ which isn't protected by services + // 4. SVSNICK arrives + // 5. Attila_ gets his nick changed to Guest12345 unnecessarily + // + // In this case when the SVSNICK is processed the target has already changed his nick to something + // which isn't protected, so changing the nick again to a Guest nick is not desired. + // However, if the expected nick TS parameter is present in the SVSNICK then the nick change in step 5 + // won't happen because the timestamps won't match. + if (parameters.size() > 3) + { + time_t ExpectedTS = ConvToInt(parameters[3]); + if (u->age != ExpectedTS) + return CMD_FAILURE; // Ignore SVSNICK + } + std::string nick = parameters[1]; if (isdigit(nick[0])) nick = u->uuid; - // Don't update the TS if the nick is exactly the same - if (u->nick == nick) - return CMD_FAILURE; - time_t NickTS = ConvToInt(parameters[2]); if (NickTS <= 0) return CMD_FAILURE; - ModuleSpanningTree* st = (ModuleSpanningTree*)(Module*)creator; - st->KeepNickTS = true; - u->age = NickTS; - - if (!u->ForceNickChange(nick.c_str())) + if (!u->ChangeNick(nick, NickTS)) { - /* buh. UID them */ - if (!u->ForceNickChange(u->uuid.c_str())) - { - ServerInstance->Users->QuitUser(u, "Nickname collision"); - } + // Changing to 'nick' failed (it may already be in use), change to the uuid + u->ChangeNick(u->uuid); } - - st->KeepNickTS = false; } return CMD_SUCCESS; @@ -63,8 +72,5 @@ CmdResult CommandSVSNick::Handle(const std::vector<std::string>& parameters, Use RouteDescriptor CommandSVSNick::GetRouting(User* user, const std::vector<std::string>& parameters) { - User* u = ServerInstance->FindNick(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/svspart.cpp b/src/modules/m_spanningtree/svspart.cpp index 3bdf13b25..c4163ef3d 100644 --- a/src/modules/m_spanningtree/svspart.cpp +++ b/src/modules/m_spanningtree/svspart.cpp @@ -19,16 +19,10 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" -#include "main.h" -#include "utils.h" -#include "treeserver.h" #include "commands.h" -CmdResult CommandSVSPart::Handle(const std::vector<std::string>& parameters, User *user) +CmdResult CommandSVSPart::Handle(User* user, std::vector<std::string>& parameters) { User* u = ServerInstance->FindUUID(parameters[0]); if (!u) @@ -48,8 +42,5 @@ CmdResult CommandSVSPart::Handle(const std::vector<std::string>& parameters, Use RouteDescriptor CommandSVSPart::GetRouting(User* user, const std::vector<std::string>& parameters) { - User* u = ServerInstance->FindUUID(parameters[0]); - if (u) - return ROUTE_OPT_UCAST(u->server); - return ROUTE_LOCALONLY; + return ROUTE_OPT_UCAST(parameters[0]); } diff --git a/src/modules/m_spanningtree/operquit.cpp b/src/modules/m_spanningtree/translate.cpp index af2e04ebc..66e1bb35b 100644 --- a/src/modules/m_spanningtree/operquit.cpp +++ b/src/modules/m_spanningtree/translate.cpp @@ -1,7 +1,7 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public @@ -18,28 +18,31 @@ #include "inspircd.h" -#include "xline.h" +#include "translate.h" -#include "treesocket.h" -#include "treeserver.h" -#include "utils.h" - -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - - -bool TreeSocket::OperQuit(const std::string &prefix, parameterlist ¶ms) +std::string Translate::ModeChangeListToParams(const Modes::ChangeList::List& modes) { - if (params.size() < 1) - return true; - - User* u = ServerInstance->FindUUID(prefix); - - if ((u) && (!IS_SERVER(u))) + std::string ret; + for (Modes::ChangeList::List::const_iterator i = modes.begin(); i != modes.end(); ++i) { - ServerInstance->OperQuit.set(u, params[0]); - params[0] = ":" + params[0]; - Utils->DoOneToAllButSender(prefix,"OPERQUIT",params,prefix); + const Modes::Change& item = *i; + ModeHandler* mh = item.mh; + if (!mh->NeedsParam(item.adding)) + continue; + + ret.push_back(' '); + + if (mh->IsPrefixMode()) + { + User* target = ServerInstance->FindNick(item.param); + if (target) + { + ret.append(target->uuid); + continue; + } + } + + ret.append(item.param); } - return true; + return ret; } - diff --git a/src/modules/m_spanningtree/translate.h b/src/modules/m_spanningtree/translate.h new file mode 100644 index 000000000..a2bc6df78 --- /dev/null +++ b/src/modules/m_spanningtree/translate.h @@ -0,0 +1,30 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2014 Attila Molnar <attilamolnar@hush.com> + * + * This file is part of InspIRCd. InspIRCd is free software: you can + * redistribute it and/or modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation, version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#pragma once + +namespace Translate +{ + /** Generate a list of mode parameters suitable for FMODE/MODE from a Modes::ChangeList::List + * @param modes List of mode changes + * @return List of mode parameters built from the input. Does not include the modes themselves, + * only the parameters. + */ + std::string ModeChangeListToParams(const Modes::ChangeList::List& modes); +} diff --git a/src/modules/m_spanningtree/treeserver.cpp b/src/modules/m_spanningtree/treeserver.cpp index 493b05ebf..b29bea134 100644 --- a/src/modules/m_spanningtree/treeserver.cpp +++ b/src/modules/m_spanningtree/treeserver.cpp @@ -21,56 +21,46 @@ #include "inspircd.h" -#include "socket.h" #include "xline.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/spanningtree.h" #include "utils.h" #include "treeserver.h" -/* $ModDep: m_spanningtree/utils.h m_spanningtree/treeserver.h */ - /** We use this constructor only to create the 'root' item, Utils->TreeRoot, which * represents our own server. Therefore, it has no route, no parent, and * no socket associated with it. Its version string is our own local version. */ -TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id) - : ServerName(Name.c_str()), ServerDesc(Desc), Utils(Util), ServerUser(ServerInstance->FakeClient) +TreeServer::TreeServer() + : Server(ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc) + , Parent(NULL), Route(NULL) + , VersionString(ServerInstance->GetVersionString()) + , fullversion(ServerInstance->GetVersionString(true)) + , Socket(NULL), sid(ServerInstance->Config->GetSID()), behind_bursting(0), isdead(false) + , pingtimer(this) + , ServerUser(ServerInstance->FakeClient) + , age(ServerInstance->Time()), UserCount(ServerInstance->Users.LocalUserCount()) + , OperCount(0), rtt(0), StartBurst(0), Hidden(false) { - age = ServerInstance->Time(); - bursting = false; - Parent = NULL; - VersionString.clear(); - ServerUserCount = ServerOperCount = 0; - VersionString = ServerInstance->GetVersionString(); - Route = NULL; - Socket = NULL; /* Fix by brain */ - StartBurst = rtt = 0; - Warned = Hidden = false; AddHashEntry(); - SetID(id); } /** When we create a new server, we call this constructor to initialize it. * This constructor initializes the server's Route and Parent, and sets up - * its ping counters so that it will be pinged one minute from now. + * the ping timer for the server. */ -TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id, TreeServer* Above, TreeSocket* Sock, bool Hide) - : Parent(Above), ServerName(Name.c_str()), ServerDesc(Desc), Socket(Sock), Utils(Util), ServerUser(new FakeUser(id, Name)), Hidden(Hide) +TreeServer::TreeServer(const std::string& Name, const std::string& Desc, const std::string& id, TreeServer* Above, TreeSocket* Sock, bool Hide) + : Server(Name, Desc) + , Parent(Above), Socket(Sock), sid(id), behind_bursting(Parent->behind_bursting), isdead(false) + , pingtimer(this) + , ServerUser(new FakeUser(id, this)) + , age(ServerInstance->Time()), UserCount(0), OperCount(0), rtt(0), StartBurst(0), Hidden(Hide) { - age = ServerInstance->Time(); - bursting = true; - VersionString.clear(); - ServerUserCount = ServerOperCount = 0; - SetNextPingTime(ServerInstance->Time() + Utils->PingFreq); - SetPingFlag(); - Warned = false; - rtt = 0; - - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); - this->StartBurst = ts; - ServerInstance->Logs->Log("m_spanningtree",DEBUG, "Started bursting at time %lu", ts); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "New server %s behind_bursting %u", GetName().c_str(), behind_bursting); + CheckULine(); + + ServerInstance->Timers.AddTimer(&pingtimer); /* find the 'route' for this server (e.g. the one directly connected * to the local server, which we can use to reach it) @@ -124,246 +114,180 @@ TreeServer::TreeServer(SpanningTreeUtilities* Util, std::string Name, std::strin */ this->AddHashEntry(); + Parent->Children.push_back(this); - SetID(id); + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), SpanningTreeEventListener, OnServerLink, (this)); } -const std::string& TreeServer::GetID() +void TreeServer::BeginBurst(uint64_t startms) { - return sid; + behind_bursting++; + + uint64_t now = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + // If the start time is in the future (clocks are not synced) then use current time + if ((!startms) || (startms > now)) + startms = now; + this->StartBurst = startms; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s started bursting at time %s behind_bursting %u", sid.c_str(), ConvToStr(startms).c_str(), behind_bursting); } void TreeServer::FinishBurstInternal() { - this->bursting = false; - SetNextPingTime(ServerInstance->Time() + Utils->PingFreq); - SetPingFlag(); - for(unsigned int q=0; q < ChildCount(); q++) + // Check is needed because 1202 protocol servers don't send the bursting state of a server, so servers + // introduced during a netburst may later send ENDBURST which would normally decrease this counter + if (behind_bursting > 0) + behind_bursting--; + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "FinishBurstInternal() %s behind_bursting %u", GetName().c_str(), behind_bursting); + + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) { - TreeServer* child = GetChild(q); + TreeServer* child = *i; child->FinishBurstInternal(); } } void TreeServer::FinishBurst() { - FinishBurstInternal(); ServerInstance->XLines->ApplyLines(); - long ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); + uint64_t ts = ServerInstance->Time() * 1000 + (ServerInstance->Time_ns() / 1000000); unsigned long bursttime = ts - this->StartBurst; ServerInstance->SNO->WriteToSnoMask(Parent == Utils->TreeRoot ? 'l' : 'L', "Received end of netburst from \2%s\2 (burst time: %lu %s)", - ServerName.c_str(), (bursttime > 10000 ? bursttime / 1000 : bursttime), (bursttime > 10000 ? "secs" : "msecs")); - AddServerEvent(Utils->Creator, ServerName.c_str()); -} + GetName().c_str(), (bursttime > 10000 ? bursttime / 1000 : bursttime), (bursttime > 10000 ? "secs" : "msecs")); -void TreeServer::SetID(const std::string &id) -{ - ServerInstance->Logs->Log("m_spanningtree",DEBUG, "Setting SID to " + id); - sid = id; - Utils->sidlist[sid] = this; + StartBurst = 0; + FinishBurstInternal(); } -int TreeServer::QuitUsers(const std::string &reason) +void TreeServer::SQuitChild(TreeServer* server, const std::string& reason) { - const char* reason_s = reason.c_str(); - std::vector<User*> time_to_die; - for (user_hash::iterator n = ServerInstance->Users->clientlist->begin(); n != ServerInstance->Users->clientlist->end(); n++) + stdalgo::erase(Children, server); + + if (IsRoot()) { - if (n->second->server == ServerName) - { - time_to_die.push_back(n->second); - } + // 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(); } - for (std::vector<User*>::iterator n = time_to_die.begin(); n != time_to_die.end(); n++) + else { - User* a = (User*)*n; - if (!IS_LOCAL(a)) - { - if (this->Utils->quiet_bursts) - a->quietquit = true; - - if (ServerInstance->Config->HideSplits) - ServerInstance->Users->QuitUser(a, "*.net *.split", reason_s); - else - ServerInstance->Users->QuitUser(a, reason_s); - } + ServerInstance->SNO->WriteToSnoMask('L', "Server \002" + server->GetName() + "\002 split from server \002" + GetName() + "\002 with reason: " + reason); } - return time_to_die.size(); -} - -/** This method is used to add the structure to the - * hash_map for linear searches. It is only called - * by the constructors. - */ -void TreeServer::AddHashEntry() -{ - server_hash::iterator iter = Utils->serverlist.find(this->ServerName.c_str()); - if (iter == Utils->serverlist.end()) - Utils->serverlist[this->ServerName.c_str()] = this; -} - -/** This method removes the reference to this object - * from the hash_map which is used for linear searches. - * It is only called by the default destructor. - */ -void TreeServer::DelHashEntry() -{ - server_hash::iterator iter = Utils->serverlist.find(this->ServerName.c_str()); - if (iter != Utils->serverlist.end()) - Utils->serverlist.erase(iter); -} - -/** These accessors etc should be pretty self- - * explanitory. - */ -TreeServer* TreeServer::GetRoute() -{ - return Route; -} - -std::string TreeServer::GetName() -{ - return ServerName.c_str(); -} - -const std::string& TreeServer::GetDesc() -{ - return ServerDesc; -} - -const std::string& TreeServer::GetVersion() -{ - return VersionString; -} - -void TreeServer::SetNextPingTime(time_t t) -{ - this->NextPing = t; - LastPingWasGood = false; -} -time_t TreeServer::NextPingTime() -{ - return NextPing; -} + unsigned int num_lost_servers = 0; + server->SQuitInternal(num_lost_servers); -bool TreeServer::AnsweredLastPing() -{ - return LastPingWasGood; -} + const std::string quitreason = GetName() + " " + server->GetName(); + unsigned int num_lost_users = QuitUsers(quitreason); -void TreeServer::SetPingFlag() -{ - LastPingWasGood = true; -} + 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" : ""); -unsigned int TreeServer::GetUserCount() -{ - return ServerUserCount; -} + // No-op if the socket is already closed (i.e. it called us) + if (server->IsLocal()) + server->GetSocket()->Close(); -void TreeServer::SetUserCount(int diff) -{ - ServerUserCount += diff; + // Add the server to the cull list, the servers behind it are handled by cull() and the destructor + ServerInstance->GlobalCulls.AddItem(server); } -void TreeServer::SetOperCount(int diff) +void TreeServer::SQuitInternal(unsigned int& num_lost_servers) { - ServerOperCount += diff; -} + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Server %s lost in split", GetName().c_str()); -unsigned int TreeServer::GetOperCount() -{ - return ServerOperCount; -} + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + { + TreeServer* server = *i; + server->SQuitInternal(num_lost_servers); + } -TreeSocket* TreeServer::GetSocket() -{ - return Socket; -} + // Mark server as dead + isdead = true; + num_lost_servers++; + RemoveHash(); -TreeServer* TreeServer::GetParent() -{ - return Parent; + if (!Utils->Creator->dying) + FOREACH_MOD_CUSTOM(Utils->Creator->GetEventProvider(), SpanningTreeEventListener, OnServerSplit, (this)); } -void TreeServer::SetVersion(const std::string &Version) +unsigned int TreeServer::QuitUsers(const std::string& reason) { - VersionString = Version; -} + std::string publicreason = ServerInstance->Config->HideSplits ? "*.net *.split" : reason; -unsigned int TreeServer::ChildCount() -{ - return Children.size(); -} - -TreeServer* TreeServer::GetChild(unsigned int n) -{ - if (n < Children.size()) - { - /* Make sure they cant request - * an out-of-range object. After - * all we know what these programmer - * types are like *grin*. - */ - return Children[n]; - } - else + const user_hash& users = ServerInstance->Users->GetUsers(); + unsigned int original_size = users.size(); + for (user_hash::const_iterator i = users.begin(); i != users.end(); ) { - return NULL; + User* user = i->second; + // Increment the iterator now because QuitUser() removes the user from the container + ++i; + TreeServer* server = TreeServer::Get(user); + if (server->IsDead()) + ServerInstance->Users->QuitUser(user, publicreason, &reason); } + return original_size - users.size(); } -void TreeServer::AddChild(TreeServer* Child) +void TreeServer::CheckULine() { - Children.push_back(Child); -} + uline = silentuline = false; -bool TreeServer::DelChild(TreeServer* Child) -{ - std::vector<TreeServer*>::iterator it = std::find(Children.begin(), Children.end(), Child); - if (it != Children.end()) + ConfigTagList tags = ServerInstance->Config->ConfTags("uline"); + for (ConfigIter i = tags.first; i != tags.second; ++i) { - Children.erase(it); - return true; + ConfigTag* tag = i->second; + std::string server = tag->getString("server"); + if (!strcasecmp(server.c_str(), GetName().c_str())) + { + if (this->IsRoot()) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Servers should not uline themselves (at " + tag->getTagLocation() + ")"); + return; + } + + uline = true; + silentuline = tag->getBool("silent"); + break; + } } - return false; } -/** Removes child nodes of this node, and of that node, etc etc. - * This is used during netsplits to automatically tidy up the - * server tree. It is slow, we don't use it for much else. +/** This method is used to add the server to the + * maps for linear searches. It is only called + * by the constructors. */ -bool TreeServer::Tidy() +void TreeServer::AddHashEntry() { - while (1) - { - std::vector<TreeServer*>::iterator a = Children.begin(); - if (a == Children.end()) - return true; - TreeServer* s = *a; - s->Tidy(); - s->cull(); - Children.erase(a); - delete s; - } + Utils->serverlist[GetName()] = this; + Utils->sidlist[sid] = this; } CullResult TreeServer::cull() { - if (ServerUser != ServerInstance->FakeClient) + // Recursively cull all servers that are under us in the tree + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + { + TreeServer* server = *i; + server->cull(); + } + + if (!IsRoot()) ServerUser->cull(); return classbase::cull(); } TreeServer::~TreeServer() { - /* We'd better tidy up after ourselves, eh? */ - this->DelHashEntry(); - if (ServerUser != ServerInstance->FakeClient) + // Recursively delete all servers that are under us in the tree first + for (ChildServers::const_iterator i = Children.begin(); i != Children.end(); ++i) + delete *i; + + // Delete server user unless it's us + if (!IsRoot()) delete ServerUser; +} - server_hash::iterator iter = Utils->sidlist.find(GetID()); - if (iter != Utils->sidlist.end()) - Utils->sidlist.erase(iter); +void TreeServer::RemoveHash() +{ + Utils->sidlist.erase(sid); + Utils->serverlist.erase(GetName()); } diff --git a/src/modules/m_spanningtree/treeserver.h b/src/modules/m_spanningtree/treeserver.h index 60b6d1def..b7e9ee9d9 100644 --- a/src/modules/m_spanningtree/treeserver.h +++ b/src/modules/m_spanningtree/treeserver.h @@ -19,10 +19,10 @@ */ -#ifndef M_SPANNINGTREE_TREESERVER_H -#define M_SPANNINGTREE_TREESERVER_H +#pragma once #include "treesocket.h" +#include "pingtimer.h" /** Each server in the tree is represented by one class of * type TreeServer. A locally connected TreeServer can @@ -38,90 +38,111 @@ * TreeServer items, deleting and inserting them as they * are created and destroyed. */ -class TreeServer : public classbase +class TreeServer : public Server { TreeServer* Parent; /* Parent entry */ TreeServer* Route; /* Route entry */ std::vector<TreeServer*> Children; /* List of child objects */ - irc::string ServerName; /* Server's name */ - std::string ServerDesc; /* Server's description */ std::string VersionString; /* Version string or empty string */ - unsigned int ServerUserCount; /* How many users are on this server? [note: doesn't care about +i] */ - unsigned int ServerOperCount; /* How many opers are on this server? */ - TreeSocket* Socket; /* For directly connected servers this points at the socket object */ - time_t NextPing; /* After this time, the server should be PINGed*/ - bool LastPingWasGood; /* True if the server responded to the last PING with a PONG */ - SpanningTreeUtilities* Utils; /* Utility class */ + + /** Full version string including patch version and other info + */ + std::string fullversion; + + TreeSocket* Socket; /* Socket used to communicate with this server */ std::string sid; /* Server ID */ - /** Set server ID - * @param id Server ID - * @throws CoreException on duplicate ID + /** Counter counting how many servers are bursting in front of this server, including + * this server. Set to parents' value on construction then it is increased if the + * server itself starts bursting. Decreased when a server on the path to this server + * finishes burst. + */ + unsigned int behind_bursting; + + /** True if this server has been lost in a split and is awaiting destruction + */ + bool isdead; + + /** Timer handling PINGing the server and killing it on timeout */ - void SetID(const std::string &id); + PingTimer pingtimer; + + /** This method is used to add this TreeServer to the + * hash maps. It is only called by the constructors. + */ + void AddHashEntry(); + + /** Used by SQuit logic to recursively remove servers + */ + void SQuitInternal(unsigned int& num_lost_servers); + + /** Remove the reference to this server from the hash maps + */ + void RemoveHash(); public: + typedef std::vector<TreeServer*> ChildServers; FakeUser* const ServerUser; /* User representing this server */ - time_t age; + const time_t age; - bool Warned; /* True if we've warned opers about high latency on this server */ - bool bursting; /* whether or not this server is bursting */ + unsigned int UserCount; /* How many users are on this server? [note: doesn't care about +i] */ + unsigned int OperCount; /* How many opers are on this server? */ /** We use this constructor only to create the 'root' item, Utils->TreeRoot, which * represents our own server. Therefore, it has no route, no parent, and * no socket associated with it. Its version string is our own local version. */ - TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id); + TreeServer(); /** When we create a new server, we call this constructor to initialize it. * This constructor initializes the server's Route and Parent, and sets up * its ping counters so that it will be pinged one minute from now. */ - TreeServer(SpanningTreeUtilities* Util, std::string Name, std::string Desc, const std::string &id, TreeServer* Above, TreeSocket* Sock, bool Hide); - - int QuitUsers(const std::string &reason); + TreeServer(const std::string& Name, const std::string& Desc, const std::string& id, TreeServer* Above, TreeSocket* Sock, bool Hide); - /** This method is used to add the structure to the - * hash_map for linear searches. It is only called - * by the constructors. + /** SQuit a server connected to this server, removing the given server and all servers behind it + * @param server Server to squit, must be directly below this server + * @param reason Reason for quitting the server, sent to opers and other servers */ - void AddHashEntry(); + void SQuitChild(TreeServer* server, const std::string& reason); - /** This method removes the reference to this object - * from the hash_map which is used for linear searches. - * It is only called by the default destructor. + /** SQuit this server, removing this server and all servers behind it + * @param reason Reason for quitting the server, sent to opers and other servers */ - void DelHashEntry(); + void SQuit(const std::string& reason) + { + GetParent()->SQuitChild(this, reason); + } + + static unsigned int QuitUsers(const std::string& reason); /** Get route. * The 'route' is defined as the locally- * connected server which can be used to reach this server. */ - TreeServer* GetRoute(); - - /** Get server name - */ - std::string GetName(); + TreeServer* GetRoute() const { return Route; } - /** Get server description (GECOS) + /** Returns true if this server is the tree root (i.e.: us) */ - const std::string& GetDesc(); + bool IsRoot() const { return (this->Parent == NULL); } - /** Get server version string + /** Returns true if this server is locally connected */ - const std::string& GetVersion(); + bool IsLocal() const { return (this->Route == this); } - /** Set time we are next due to ping this server + /** Returns true if the server is awaiting destruction + * @return True if the server is waiting to be culled and deleted, false otherwise */ - void SetNextPingTime(time_t t); + bool IsDead() const { return isdead; } - /** Get the time we are next due to ping this server + /** Get server version string */ - time_t NextPingTime(); + const std::string& GetVersion() const { return VersionString; } - /** Last ping time in milliseconds, used to calculate round trip time + /** Get the full version string of this server + * @return The full version string of this server, including patch version and other info */ - unsigned long LastPingMsec; + const std::string& GetFullVersion() const { return fullversion; } /** Round trip time of last ping */ @@ -129,86 +150,87 @@ class TreeServer : public classbase /** When we recieved BURST from this server, used to calculate total burst time at ENDBURST. */ - unsigned long StartBurst; + uint64_t StartBurst; /** True if this server is hidden */ bool Hidden; - /** True if the server answered their last ping - */ - bool AnsweredLastPing(); - - /** Set the server as responding to its last ping + /** Get the TreeSocket pointer for local servers. + * For remote servers, this returns NULL. */ - void SetPingFlag(); + TreeSocket* GetSocket() const { return Socket; } - /** Get the number of users on this server. + /** Get the parent server. + * For the root node, this returns NULL. */ - unsigned int GetUserCount(); + TreeServer* GetParent() const { return Parent; } - /** Increment or decrement the user count by diff. + /** Set the server version string */ - void SetUserCount(int diff); + void SetVersion(const std::string& verstr) { VersionString = verstr; } - /** Gets the numbers of opers on this server. + /** Set the full version string + * @param verstr The version string to set */ - unsigned int GetOperCount(); + void SetFullVersion(const std::string& verstr) { fullversion = verstr; } - /** Increment or decrement the oper count by diff. + /** Sets the description of this server. Called when the description of a remote server changes + * and we are notified about it. + * @param descstr The description to set */ - void SetOperCount(int diff); + void SetDesc(const std::string& descstr) { description = descstr; } - /** Get the TreeSocket pointer for local servers. - * For remote servers, this returns NULL. + /** Return all child servers */ - TreeSocket* GetSocket(); + const ChildServers& GetChildren() const { return Children; } - /** Get the parent server. - * For the root node, this returns NULL. + /** Get server ID */ - TreeServer* GetParent(); + const std::string& GetID() const { return sid; } - /** Set the server version string + /** Marks a server as having finished bursting and performs appropriate actions. */ - void SetVersion(const std::string &Version); + void FinishBurst(); + /** Recursive call for child servers */ + void FinishBurstInternal(); - /** Return number of child servers + /** (Re)check the uline state of this server */ - unsigned int ChildCount(); + void CheckULine(); - /** Return a child server indexed 0..n + /** Get the bursting state of this server + * @return True if this server is bursting, false if it isn't */ - TreeServer* GetChild(unsigned int n); + bool IsBursting() const { return (StartBurst != 0); } - /** Add a child server + /** Check whether this server is behind a bursting server or is itself bursting. + * This can tell whether a user is on a part of the network that is still bursting. + * @return True if this server is bursting or is behind a server that is bursting, false if it isn't */ - void AddChild(TreeServer* Child); + bool IsBehindBursting() const { return (behind_bursting != 0); } - /** Delete a child server, return false if it didn't exist. + /** Set the bursting state of the server + * @param startms Time the server started bursting, if 0 or omitted, use current time */ - bool DelChild(TreeServer* Child); + void BeginBurst(uint64_t startms = 0); - /** Removes child nodes of this node, and of that node, etc etc. - * This is used during netsplits to automatically tidy up the - * server tree. It is slow, we don't use it for much else. + /** Register a PONG from the server */ - bool Tidy(); + void OnPong() { pingtimer.OnPong(); } - /** Get server ID - */ - const std::string& GetID(); + CullResult cull(); - /** Marks a server as having finished bursting and performs appropriate actions. + /** Destructor, deletes ServerUser unless IsRoot() */ - void FinishBurst(); - /** Recursive call for child servers */ - void FinishBurstInternal(); + ~TreeServer(); - CullResult cull(); - /** Destructor + /** Returns the TreeServer the given user is connected to + * @param user The user whose server to return + * @return The TreeServer this user is connected to. */ - ~TreeServer(); + static TreeServer* Get(User* user) + { + return static_cast<TreeServer*>(user->server); + } }; - -#endif diff --git a/src/modules/m_spanningtree/treesocket.h b/src/modules/m_spanningtree/treesocket.h index efcce5f7a..4887623c1 100644 --- a/src/modules/m_spanningtree/treesocket.h +++ b/src/modules/m_spanningtree/treesocket.h @@ -20,12 +20,9 @@ */ -#ifndef M_SPANNINGTREE_TREESOCKET_H -#define M_SPANNINGTREE_TREESOCKET_H +#pragma once -#include "socket.h" #include "inspircd.h" -#include "xline.h" #include "utils.h" @@ -76,7 +73,7 @@ struct CapabData std::string ourchallenge; /* Challenge sent for challenge/response */ std::string theirchallenge; /* Challenge recv for challenge/response */ int capab_phase; /* Have sent CAPAB already */ - bool auth_fingerprint; /* Did we auth using SSL fingerprint */ + bool auth_fingerprint; /* Did we auth using SSL certificate fingerprint */ bool auth_challenge; /* Did we auth using challenge/response */ // Data saved from incoming SERVER command, for later use when our credentials have been accepted by the other party @@ -92,39 +89,92 @@ struct CapabData */ class TreeSocket : public BufferedSocket { - SpanningTreeUtilities* Utils; /* Utility class */ + struct BurstState; + std::string linkID; /* Description for this link */ ServerState LinkState; /* Link state */ CapabData* capab; /* Link setup data (held until burst is sent) */ TreeServer* MyRoot; /* The server we are talking to */ int proto_version; /* Remote protocol version */ - bool ConnectionFailureShown; /* Set to true if a connection failure message was shown */ - static const unsigned int FMODE_MAX_LENGTH = 350; + /** True if we've sent our burst. + * This only changes the behavior of message translation for 1202 protocol servers and it can be + * removed once 1202 support is dropped. + */ + bool burstsent; /** Checks if the given servername and sid are both free */ bool CheckDuplicate(const std::string& servername, const std::string& sid); + /** Send all ListModeBase modes set on the channel + */ + void SendListModes(Channel* chan); + + /** Send all known information about a channel */ + void SyncChannel(Channel* chan, BurstState& bs); + + /** Send all users and their oper state, away state and metadata */ + void SendUsers(BurstState& bs); + + /** Send all additional info about the given server to this server */ + void SendServerInfo(TreeServer* from); + + /** Find the User source of a command given a prefix and a command string. + * This connection must be fully up when calling this function. + * @param prefix Prefix string to find the source User object for. Can be a sid, a uuid or a server name. + * @param command The command whose source to find. This is required because certain commands (like mode + * changes and kills) must be processed even if their claimed source doesn't exist. If the given command is + * such a command and the source does not exist, the function returns a valid FakeUser that can be used to + * to process the command with. + * @return The command source to use when processing the command or NULL if the source wasn't found. + * Note that the direction of the returned source is not verified. + */ + User* FindSource(const std::string& prefix, const std::string& command); + + /** Finish the authentication phase of this connection. + * Change the state of the connection to CONNECTED, create a TreeServer object for the server on the + * other end of the connection using the details provided in the parameters, and finally send a burst. + * @param remotename Name of the remote server + * @param remotesid SID of the remote server + * @param remotedesc Description of the remote server + * @param hidden True if the remote server is hidden according to the configuration + */ + void FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden); + + /** Authenticate the remote server. + * Validate the parameters and find the link block that matches the remote server. In case of an error, + * an appropriate snotice is generated, an ERROR message is sent and the connection is closed. + * Failing to find a matching link block counts as an error. + * @param params Parameters they sent in the SERVER command + * @return Link block for the remote server, or NULL if an error occurred + */ + Link* AuthRemote(const parameterlist& params); + + /** Write a line on this socket with a new line character appended, skipping all translation for old protocols + * @param line Line to write without a new line character at the end + */ + void WriteLineNoCompat(const std::string& line); + public: - time_t age; + const time_t age; /** Because most of the I/O gubbins are encapsulated within * BufferedSocket, we just call the superclass constructor for * most of the action, and append a few of our own values * to it. */ - TreeSocket(SpanningTreeUtilities* Util, Link* link, Autoconnect* myac, const std::string& ipaddr); + TreeSocket(Link* link, Autoconnect* myac, const std::string& ipaddr); /** When a listening socket gives us a new file descriptor, * we must associate it with a socket without creating a new * connection. This constructor is used for this purpose. */ - TreeSocket(SpanningTreeUtilities* Util, int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); + TreeSocket(int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server); /** Get link state */ - ServerState GetLinkState(); + ServerState GetLinkState() const { return LinkState; } /** Get challenge set in our CAPAB for challenge/response */ @@ -166,11 +216,11 @@ class TreeSocket : public BufferedSocket * to server docs on the inspircd.org site, the other side * will then send back its own server string. */ - virtual void OnConnected(); + void OnConnected(); /** Handle socket error event */ - virtual void OnError(BufferedSocketError e); + void OnError(BufferedSocketError e) CXX11_OVERRIDE; /** Sends an error to the remote server, and displays it locally to show * that it was sent. @@ -180,13 +230,8 @@ class TreeSocket : public BufferedSocket /** Recursively send the server tree with distances as hops. * This is used during network burst to inform the other server * (and any of ITS servers too) of what servers we know about. - * If at any point any of these servers already exist on the other - * end, our connection may be terminated. The hopcounts given - * by this function are relative, this doesn't matter so long as - * they are all >1, as all the remote servers re-calculate them - * to be relative too, with themselves as hop 0. */ - void SendServers(TreeServer* Current, TreeServer* s, int hops); + void SendServers(TreeServer* Current, TreeServer* s); /** Returns module list as a string, filtered by filter * @param filter a module version bitmask, such as VF_COMMON or VF_OPTCOMMON @@ -197,32 +242,12 @@ class TreeSocket : public BufferedSocket */ void SendCapabilities(int phase); - /** Add modules to VF_COMMON list for backwards compatability */ - void CompatAddModules(std::vector<std::string>& modlist); - /* Isolate and return the elements that are different between two lists */ void ListDifference(const std::string &one, const std::string &two, char sep, std::string& mleft, std::string& mright); bool Capab(const parameterlist ¶ms); - /** This function forces this server to quit, removing this server - * and any users on it (and servers and users below that, etc etc). - * It's very slow and pretty clunky, but luckily unless your network - * is having a REAL bad hair day, this function shouldnt be called - * too many times a month ;-) - */ - void SquitServer(std::string &from, TreeServer* Current, int& num_lost_servers, int& num_lost_users); - - /** This is a wrapper function for SquitServer above, which - * does some validation first and passes on the SQUIT to all - * other remaining servers. - */ - void Squit(TreeServer* Current, const std::string &reason); - - /* Used on nick collision ... XXX ugly function HACK */ - int DoCollision(User *u, time_t remotets, const std::string &remoteident, const std::string &remoteip, const std::string &remoteuid); - /** Send one or more FJOINs for a channel of users. * If the length of a single line is more than 480-NICKMAX * in length, it is split over multiple lines. @@ -232,11 +257,8 @@ class TreeSocket : public BufferedSocket /** Send G, Q, Z and E lines */ void SendXLines(); - /** Send channel modes and topics */ - void SendChannelModes(); - - /** send all users and their oper state/modes */ - void SendUsers(); + /** Send all known information about a channel */ + void SyncChannel(Channel* chan); /** This function is called when we want to send a netburst to a local * server. There is a set order we must do this, because for example @@ -252,57 +274,11 @@ class TreeSocket : public BufferedSocket /** Send one or more complete lines down the socket */ - void WriteLine(std::string line); + void WriteLine(const std::string& line); /** Handle ERROR command */ void Error(parameterlist ¶ms); - /** Remote AWAY */ - bool Away(const std::string &prefix, parameterlist ¶ms); - - /** SAVE to resolve nick collisions without killing */ - bool ForceNick(const std::string &prefix, parameterlist ¶ms); - - /** ENCAP command - */ - void Encap(User* who, parameterlist ¶ms); - - /** OPERQUIT command - */ - bool OperQuit(const std::string &prefix, parameterlist ¶ms); - - /** PONG - */ - bool LocalPong(const std::string &prefix, parameterlist ¶ms); - - /** VERSION - */ - bool ServerVersion(const std::string &prefix, parameterlist ¶ms); - - /** ADDLINE - */ - bool AddLine(const std::string &prefix, parameterlist ¶ms); - - /** DELLINE - */ - bool DelLine(const std::string &prefix, parameterlist ¶ms); - - /** WHOIS - */ - bool Whois(const std::string &prefix, parameterlist ¶ms); - - /** PUSH - */ - bool Push(const std::string &prefix, parameterlist ¶ms); - - /** PING - */ - bool LocalPing(const std::string &prefix, parameterlist ¶ms); - - /** <- (remote) <- SERVER - */ - bool RemoteServer(const std::string &prefix, parameterlist ¶ms); - /** (local) -> SERVER */ bool Outbound_Reply_Server(parameterlist ¶ms); @@ -323,15 +299,12 @@ class TreeSocket : public BufferedSocket /** Handle socket timeout from connect() */ - virtual void OnTimeout(); + void OnTimeout(); /** Handle server quit on close */ - virtual void Close(); + void Close(); - /** Returns true if this server was introduced to the rest of the network + /** Fixes messages coming from old servers so the new command handlers understand them */ - bool Introduced(); + bool PreProcessOldProtocolMessage(User*& who, std::string& cmd, std::vector<std::string>& params); }; - -#endif - diff --git a/src/modules/m_spanningtree/treesocket1.cpp b/src/modules/m_spanningtree/treesocket1.cpp index c9729cc0f..370c38d2c 100644 --- a/src/modules/m_spanningtree/treesocket1.cpp +++ b/src/modules/m_spanningtree/treesocket1.cpp @@ -21,80 +21,65 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" +#include "iohook.h" #include "main.h" -#include "../spanningtree.h" +#include "modules/spanningtree.h" #include "utils.h" #include "treeserver.h" #include "link.h" #include "treesocket.h" -#include "resolvers.h" +#include "commands.h" -/** Because most of the I/O gubbins are encapsulated within - * BufferedSocket, we just call the superclass constructor for - * most of the action, and append a few of our own values - * to it. +/** Constructor for outgoing connections. + * Because most of the I/O gubbins are encapsulated within + * BufferedSocket, we just call DoConnect() for most of the action, + * and only do minor initialization tasks ourselves. */ -TreeSocket::TreeSocket(SpanningTreeUtilities* Util, Link* link, Autoconnect* myac, const std::string& ipaddr) - : Utils(Util) +TreeSocket::TreeSocket(Link* link, Autoconnect* myac, const std::string& ipaddr) + : linkID(link->Name), LinkState(CONNECTING), MyRoot(NULL), proto_version(0) + , burstsent(false), age(ServerInstance->Time()) { - age = ServerInstance->Time(); - linkID = assign(link->Name); capab = new CapabData; capab->link = link; capab->ac = myac; capab->capab_phase = 0; - MyRoot = NULL; - proto_version = 0; - ConnectionFailureShown = false; - LinkState = CONNECTING; - if (!link->Hook.empty()) - { - ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_IOHOOK, link->Hook); - if (!prov) - { - SetError("Could not find hook '" + link->Hook + "' for connection to " + linkID); - return; - } - AddIOHook(prov->creator); - } + DoConnect(ipaddr, link->Port, link->Timeout, link->Bind); Utils->timeoutlist[this] = std::pair<std::string, int>(linkID, link->Timeout); SendCapabilities(1); } -/** When a listening socket gives us a new file descriptor, - * we must associate it with a socket without creating a new - * connection. This constructor is used for this purpose. +/** Constructor for incoming connections */ -TreeSocket::TreeSocket(SpanningTreeUtilities* Util, int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) - : BufferedSocket(newfd), Utils(Util) +TreeSocket::TreeSocket(int newfd, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) + : BufferedSocket(newfd) + , linkID("inbound from " + client->addr()), LinkState(WAIT_AUTH_1), MyRoot(NULL), proto_version(0) + , burstsent(false), age(ServerInstance->Time()) { capab = new CapabData; capab->capab_phase = 0; - MyRoot = NULL; - age = ServerInstance->Time(); - LinkState = WAIT_AUTH_1; - proto_version = 0; - ConnectionFailureShown = false; - linkID = "inbound from " + client->addr(); - FOREACH_MOD(I_OnHookIO, OnHookIO(this, via)); - if (GetIOHook()) - GetIOHook()->OnStreamSocketAccept(this, client, server); + for (ListenSocket::IOHookProvList::iterator i = via->iohookprovs.begin(); i != via->iohookprovs.end(); ++i) + { + ListenSocket::IOHookProvRef& iohookprovref = *i; + if (!iohookprovref) + continue; + + iohookprovref->OnAccept(this, client, server); + // IOHook could have encountered a fatal error, e.g. if the TLS ClientHello was already in the queue and there was no common TLS version + if (!getError().empty()) + { + TreeSocket::OnError(I_ERR_OTHER); + return; + } + } + SendCapabilities(1); Utils->timeoutlist[this] = std::pair<std::string, int>(linkID, 30); } -ServerState TreeSocket::GetLinkState() -{ - return this->LinkState; -} - void TreeSocket::CleanNegotiationInfo() { // connect is good, reset the autoconnect block (if used) @@ -114,20 +99,30 @@ CullResult TreeSocket::cull() TreeSocket::~TreeSocket() { - if (capab) - delete capab; + delete capab; } /** When an outbound connection finishes connecting, we receive - * this event, and must send our SERVER string to the other + * this event, and must do CAPAB negotiation with the other * side. If the other side is happy, as outlined in the server * to server docs on the inspircd.org site, the other side - * will then send back its own server string. + * will then send back its own SERVER string eventually. */ void TreeSocket::OnConnected() { if (this->LinkState == CONNECTING) { + if (!capab->link->Hook.empty()) + { + ServiceProvider* prov = ServerInstance->Modules->FindService(SERVICE_IOHOOK, capab->link->Hook); + if (!prov) + { + SetError("Could not find hook '" + capab->link->Hook + "' for connection to " + linkID); + return; + } + static_cast<IOHookProvider*>(prov)->OnConnect(this); + } + ServerInstance->SNO->WriteGlobalSno('l', "Connection to \2%s\2[%s] started.", linkID.c_str(), (capab->link->HiddenFromStats ? "<hidden>" : capab->link->IPAddr.c_str())); this->SendCapabilities(1); @@ -139,6 +134,7 @@ void TreeSocket::OnError(BufferedSocketError e) ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\002%s\002' failed with error: %s", linkID.c_str(), getError().c_str()); LinkState = DYING; + Close(); } void TreeSocket::SendError(const std::string &errormessage) @@ -149,79 +145,31 @@ void TreeSocket::SendError(const std::string &errormessage) SetError(errormessage); } -/** This function forces this server to quit, removing this server - * and any users on it (and servers and users below that, etc etc). - * It's very slow and pretty clunky, but luckily unless your network - * is having a REAL bad hair day, this function shouldnt be called - * too many times a month ;-) - */ -void TreeSocket::SquitServer(std::string &from, TreeServer* Current, int& num_lost_servers, int& num_lost_users) +CmdResult CommandSQuit::HandleServer(TreeServer* server, std::vector<std::string>& params) { - std::string servername = Current->GetName(); - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"SquitServer for %s from %s", - servername.c_str(), from.c_str()); - /* recursively squit the servers attached to 'Current'. - * We're going backwards so we don't remove users - * while we still need them ;) - */ - for (unsigned int q = 0; q < Current->ChildCount(); q++) + TreeServer* quitting = Utils->FindServer(params[0]); + if (!quitting) { - TreeServer* recursive_server = Current->GetChild(q); - this->SquitServer(from,recursive_server, num_lost_servers, num_lost_users); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Squit from unknown server"); + return CMD_FAILURE; } - /* Now we've whacked the kids, whack self */ - num_lost_servers++; - num_lost_users += Current->QuitUsers(from); -} -/** This is a wrapper function for SquitServer above, which - * does some validation first and passes on the SQUIT to all - * other remaining servers. - */ -void TreeSocket::Squit(TreeServer* Current, const std::string &reason) -{ - bool LocalSquit = false; - - if ((Current) && (Current != Utils->TreeRoot)) + CmdResult ret = CMD_SUCCESS; + if (quitting == server) { - DelServerEvent(Utils->Creator, Current->GetName()); + ret = CMD_FAILURE; + server = server->GetParent(); + } + else if (quitting->GetParent() != server) + throw ProtocolException("Attempted to SQUIT a non-directly connected server or the parent"); - if (!Current->GetSocket() || Current->GetSocket()->Introduced()) - { - parameterlist params; - params.push_back(Current->GetID()); - params.push_back(":"+reason); - Utils->DoOneToAllButSender(Current->GetParent()->GetID(),"SQUIT",params,Current->GetID()); - } + server->SQuitChild(quitting, params[1]); - if (Current->GetParent() == Utils->TreeRoot) - { - ServerInstance->SNO->WriteGlobalSno('l', "Server \002"+Current->GetName()+"\002 split: "+reason); - LocalSquit = true; - } - else - { - ServerInstance->SNO->WriteToSnoMask('L', "Server \002"+Current->GetName()+"\002 split from server \002"+Current->GetParent()->GetName()+"\002 with reason: "+reason); - } - int num_lost_servers = 0; - int num_lost_users = 0; - std::string from = Current->GetParent()->GetName()+" "+Current->GetName(); - SquitServer(from, Current, num_lost_servers, num_lost_users); - ServerInstance->SNO->WriteToSnoMask(LocalSquit ? 'l' : 'L', "Netsplit complete, lost \002%d\002 user%s on \002%d\002 server%s.", - num_lost_users, num_lost_users != 1 ? "s" : "", num_lost_servers, num_lost_servers != 1 ? "s" : ""); - Current->Tidy(); - Current->GetParent()->DelChild(Current); - Current->cull(); - const bool ismyroot = (Current == MyRoot); - delete Current; - if (ismyroot) - { - MyRoot = NULL; - Close(); - } - } - else - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Squit from unknown server"); + // XXX: Return CMD_FAILURE when servers SQUIT themselves (i.e. :00S SQUIT 00S :Shutting down) + // to stop this message from being forwarded. + // The squit logic generates a SQUIT message with our sid as the source and sends it to the + // remaining servers. + return ret; } /** This function is called when we receive data from a remote @@ -235,13 +183,24 @@ void TreeSocket::OnDataReady() { std::string::size_type rline = line.find('\r'); if (rline != std::string::npos) - line = line.substr(0,rline); + line.erase(rline); if (line.find('\0') != std::string::npos) { SendError("Read null character from socket"); break; } - ProcessLine(line); + + try + { + ProcessLine(line); + } + catch (CoreException& ex) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error while processing: " + line); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, ex.GetReason()); + SendError(ex.GetReason() + " - check the log file for details"); + } + if (!getError().empty()) break; } @@ -249,8 +208,3 @@ void TreeSocket::OnDataReady() SendError("RecvQ overrun (line too long)"); Utils->Creator->loopCall = false; } - -bool TreeSocket::Introduced() -{ - return (capab == NULL); -} diff --git a/src/modules/m_spanningtree/treesocket2.cpp b/src/modules/m_spanningtree/treesocket2.cpp index acb822fbf..04b850755 100644 --- a/src/modules/m_spanningtree/treesocket2.cpp +++ b/src/modules/m_spanningtree/treesocket2.cpp @@ -23,16 +23,13 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "link.h" #include "treesocket.h" #include "resolvers.h" +#include "commands.h" /* Handle ERROR command */ void TreeSocket::Error(parameterlist ¶ms) @@ -47,10 +44,10 @@ void TreeSocket::Split(const std::string& line, std::string& prefix, std::string if (!tokens.GetToken(prefix)) return; - + if (prefix[0] == ':') { - prefix = prefix.substr(1); + prefix.erase(prefix.begin()); if (prefix.empty()) { @@ -84,7 +81,7 @@ void TreeSocket::ProcessLine(std::string &line) std::string command; parameterlist params; - ServerInstance->Logs->Log("m_spanningtree", RAWIO, "S[%d] I %s", this->GetFd(), line.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_RAWIO, "S[%d] I %s", this->GetFd(), line.c_str()); Split(line, prefix, command, params); @@ -151,7 +148,7 @@ void TreeSocket::ProcessLine(std::string &line) { if (params.size()) { - time_t them = atoi(params[0].c_str()); + time_t them = ConvToInt(params[0]); time_t delta = them - ServerInstance->Time(); if ((delta < -600) || (delta > 600)) { @@ -171,25 +168,7 @@ void TreeSocket::ProcessLine(std::string &line) if (!CheckDuplicate(capab->name, capab->sid)) return; - this->LinkState = CONNECTED; - Utils->timeoutlist.erase(this); - - linkID = capab->name; - - MyRoot = new TreeServer(Utils, capab->name, capab->description, capab->sid, Utils->TreeRoot, this, capab->hidden); - Utils->TreeRoot->AddChild(MyRoot); - - MyRoot->bursting = true; - this->DoBurst(MyRoot); - - parameterlist sparams; - sparams.push_back(MyRoot->GetName()); - sparams.push_back("*"); - sparams.push_back("0"); - sparams.push_back(MyRoot->GetID()); - sparams.push_back(":" + MyRoot->GetDesc()); - Utils->DoOneToAllButSender(ServerInstance->Config->GetSID(), "SERVER", sparams, MyRoot->GetName()); - Utils->DoOneToAllButSender(MyRoot->GetID(), "BURST", params, MyRoot->GetName()); + FinishAuth(capab->name, capab->sid, capab->description, capab->hidden); } else if (command == "ERROR") { @@ -235,52 +214,63 @@ void TreeSocket::ProcessLine(std::string &line) } } -void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params) +User* TreeSocket::FindSource(const std::string& prefix, const std::string& command) { - User* who = ServerInstance->FindUUID(prefix); - std::string direction; + // Empty prefix means the source is the directly connected server that sent this command + if (prefix.empty()) + return MyRoot->ServerUser; - if (!who) + if (prefix.size() == 3) { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (prefix.empty()) - ServerSource = MyRoot; + // Prefix looks like a sid + TreeServer* server = Utils->FindServerID(prefix); + if (server) + return server->ServerUser; + } + else + { + // If the prefix string is a uuid FindUUID() returns the appropriate User object + User* user = ServerInstance->FindUUID(prefix); + if (user) + return user; + } - if (ServerSource) - { - who = ServerSource->ServerUser; - } - else - { - /* It is important that we don't close the link here, unknown prefix can occur - * due to various race conditions such as the KILL message for a user somehow - * crossing the users QUIT further upstream from the server. Thanks jilles! - */ + // Some implementations wrongly send a server name as prefix occasionally, handle that too for now + TreeServer* const server = Utils->FindServer(prefix); + if (server) + return server->ServerUser; - if ((prefix.length() == UUID_LENGTH-1) && (isdigit(prefix[0])) && - ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE"))) - { - /* Special case, we cannot drop these commands as they've been committed already on a - * part of the network by the time we receive them, so in this scenario pretend the - * command came from a server to avoid desync. - */ + /* It is important that we don't close the link here, unknown prefix can occur + * due to various race conditions such as the KILL message for a user somehow + * crossing the users QUIT further upstream from the server. Thanks jilles! + */ - who = ServerInstance->FindUUID(prefix.substr(0, 3)); - if (!who) - who = this->MyRoot->ServerUser; - } - else - { - ServerInstance->Logs->Log("m_spanningtree", DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", - command.c_str(), prefix.c_str()); - return; - } - } + if ((prefix.length() == UIDGenerator::UUID_LENGTH) && (isdigit(prefix[0])) && + ((command == "FMODE") || (command == "MODE") || (command == "KICK") || (command == "TOPIC") || (command == "KILL") || (command == "ADDLINE") || (command == "DELLINE"))) + { + /* Special case, we cannot drop these commands as they've been committed already on a + * part of the network by the time we receive them, so in this scenario pretend the + * command came from a server to avoid desync. + */ + + TreeServer* const usersserver = Utils->FindServerID(prefix.substr(0, 3)); + if (usersserver) + return usersserver->ServerUser; + return this->MyRoot->ServerUser; } - // Make sure prefix is still good - direction = who->server; - prefix = who->uuid; + // Unknown prefix + return NULL; +} + +void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params) +{ + User* who = FindSource(prefix, command); + if (!who) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str()); + return; + } /* * Check for fake direction here, and drop any instances that are found. @@ -298,214 +288,68 @@ void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, * a valid SID or a valid UUID, so that invalid UUID or SID never makes it * to the higher level functions. -- B */ - TreeServer* route_back_again = Utils->BestRouteTo(direction); - if ((!route_back_again) || (route_back_again->GetSocket() != this)) + TreeServer* const server = TreeServer::Get(who); + if (server->GetSocket() != this) { - if (route_back_again) - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"Protocol violation: Fake direction '%s' from connection '%s'", - prefix.c_str(),linkID.c_str()); + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str()); return; } - /* - * First up, check for any malformed commands (e.g. MODE without a timestamp) - * and rewrite commands where necessary (SVSMODE -> MODE for services). -- w - */ - if (command == "SVSMODE") // This isn't in an "else if" so we still force FMODE for changes on channels. - command = "MODE"; - - // TODO move all this into Commands - if (command == "MAP") - { - Utils->Creator->HandleMap(params, who); - } - else if (command == "SERVER") - { - this->RemoteServer(prefix,params); - } - else if (command == "ERROR") - { - this->Error(params); - } - else if (command == "AWAY") - { - this->Away(prefix,params); - } - else if (command == "PING") - { - this->LocalPing(prefix,params); - } - else if (command == "PONG") - { - TreeServer *s = Utils->FindServer(prefix); - if (s && s->bursting) - { - ServerInstance->SNO->WriteGlobalSno('l',"Server \002%s\002 has not finished burst, forcing end of burst (send ENDBURST!)", prefix.c_str()); - s->FinishBurst(); - } - this->LocalPong(prefix,params); - } - else if (command == "VERSION") - { - this->ServerVersion(prefix,params); - } - else if (command == "ADDLINE") - { - this->AddLine(prefix,params); - } - else if (command == "DELLINE") - { - this->DelLine(prefix,params); - } - else if (command == "SAVE") - { - this->ForceNick(prefix,params); - } - else if (command == "OPERQUIT") - { - this->OperQuit(prefix,params); - } - else if (command == "IDLE") - { - this->Whois(prefix,params); - } - else if (command == "PUSH") - { - this->Push(prefix,params); - } - else if (command == "SQUIT") - { - if (params.size() == 2) - { - this->Squit(Utils->FindServer(params[0]),params[1]); - } - } - else if (command == "SNONOTICE") + // Translate commands coming from servers using an older protocol + if (proto_version < ProtocolVersion) { - if (params.size() >= 2) - { - ServerInstance->SNO->WriteToSnoMask(params[0][0], "From " + who->nick + ": "+ params[1]); - params[1] = ":" + params[1]; - Utils->DoOneToAllButSender(prefix, command, params, prefix); - } - } - else if (command == "BURST") - { - // Set prefix server as bursting - TreeServer* ServerSource = Utils->FindServer(prefix); - if (!ServerSource) - { - ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got BURST from a non-server(?): %s", prefix.c_str()); + if (!PreProcessOldProtocolMessage(who, command, params)) return; - } - - ServerSource->bursting = true; - Utils->DoOneToAllButSender(prefix, command, params, prefix); } - else if (command == "ENDBURST") - { - TreeServer* ServerSource = Utils->FindServer(prefix); - if (!ServerSource) - { - ServerInstance->SNO->WriteGlobalSno('l', "WTF: Got ENDBURST from a non-server(?): %s", prefix.c_str()); - return; - } - ServerSource->FinishBurst(); - Utils->DoOneToAllButSender(prefix, command, params, prefix); - } - else if (command == "ENCAP") - { - this->Encap(who, params); - } - else if (command == "NICK") + ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); + CommandBase* cmdbase = scmd; + Command* cmd = NULL; + if (!scmd) { - if (params.size() != 2) - { - SendError("Protocol violation: Wrong number of parameters for NICK message"); - return; - } - - if (IS_SERVER(who)) - { - SendError("Protocol violation: Server changing nick"); - return; - } - - if ((isdigit(params[0][0])) && (params[0] != who->uuid)) - { - SendError("Protocol violation: User changing nick to an invalid UID - " + params[0]); - return; - } - - /* Update timestamp on user when they change nicks */ - who->age = atoi(params[1].c_str()); - - /* - * On nick messages, check that the nick doesnt already exist here. - * If it does, perform collision logic. - */ - bool callfnc = true; - User* x = ServerInstance->FindNickOnly(params[0]); - if ((x) && (x != who) && (x->registered == REG_ALL)) + // Not a special server-to-server command + cmd = ServerInstance->Parser.GetHandler(command); + if (!cmd) { - int collideret = 0; - /* x is local, who is remote */ - collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid); - if (collideret != 1) + if (command == "ERROR") { - // Remote client lost, or both lost, rewrite this nick change as a change to uuid before - // forwarding and don't call ForceNickChange() because DoCollision() has done it already - params[0] = who->uuid; - callfnc = false; + this->Error(params); + return; + } + else if (command == "BURST") + { + // This is sent even when there is no need for it, drop it here for now + return; } - } - if (callfnc) - who->ForceNickChange(params[0].c_str()); - Utils->RouteCommand(route_back_again, command, params, who); - } - else - { - Command* cmd = ServerInstance->Parser->GetHandler(command); - - if (!cmd) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Unrecognised S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Unrecognised command '" + command + "' -- possibly loaded mismatched modules"); - return; - } - if (params.size() < cmd->min_params) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Insufficient parameters for S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Insufficient parameters for command '" + command + "'"); - return; + throw ProtocolException("Unknown command"); } + cmdbase = cmd; + } - if ((!params.empty()) && (params.back().empty()) && (!cmd->allow_empty_last_param)) - { - // the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last - if (params.size()-1 < cmd->min_params) - return; - params.pop_back(); - } + if (params.size() < cmdbase->min_params) + throw ProtocolException("Insufficient parameters"); - CmdResult res = cmd->Handle(params, who); + if ((!params.empty()) && (params.back().empty()) && (!cmdbase->allow_empty_last_param)) + { + // the last param is empty and the command handler doesn't allow that, check if there will be enough params if we drop the last + if (params.size()-1 < cmdbase->min_params) + return; + params.pop_back(); + } + CmdResult res; + if (scmd) + res = scmd->Handle(who, params); + else + { + res = cmd->Handle(params, who); if (res == CMD_INVALID) - { - irc::stringjoiner pmlist(" ", params, 0, params.size() - 1); - ServerInstance->Logs->Log("m_spanningtree", SPARSE, "Error handling S2S command :%s %s %s", - who->uuid.c_str(), command.c_str(), pmlist.GetJoined().c_str()); - SendError("Error handling '" + command + "' -- possibly loaded mismatched modules"); - } - else if (res == CMD_SUCCESS) - Utils->RouteCommand(route_back_again, command, params, who); + throw ProtocolException("Error in command handler"); } + + if (res == CMD_SUCCESS) + Utils->RouteCommand(server->GetRoute(), cmdbase, params, who); } void TreeSocket::OnTimeout() @@ -515,8 +359,10 @@ void TreeSocket::OnTimeout() void TreeSocket::Close() { - if (fd != -1) - ServerInstance->GlobalCulls.AddItem(this); + if (fd < 0) + return; + + ServerInstance->GlobalCulls.AddItem(this); this->BufferedSocket::Close(); SetError("Remote host closed connection"); @@ -524,18 +370,30 @@ void TreeSocket::Close() // If the connection is fully up (state CONNECTED) // then propogate a netsplit to all peers. if (MyRoot) - Squit(MyRoot,getError()); + MyRoot->SQuit(getError()); - if (!ConnectionFailureShown) - { - ConnectionFailureShown = true; - ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str()); + ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' failed.",linkID.c_str()); - time_t server_uptime = ServerInstance->Time() - this->age; - if (server_uptime) - { - std::string timestr = Utils->Creator->TimeToStr(server_uptime); - ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), timestr.c_str()); - } + time_t server_uptime = ServerInstance->Time() - this->age; + if (server_uptime) + { + std::string timestr = ModuleSpanningTree::TimeToStr(server_uptime); + ServerInstance->SNO->WriteGlobalSno('l', "Connection to '\2%s\2' was established for %s", linkID.c_str(), timestr.c_str()); } } + +void TreeSocket::FinishAuth(const std::string& remotename, const std::string& remotesid, const std::string& remotedesc, bool hidden) +{ + this->LinkState = CONNECTED; + Utils->timeoutlist.erase(this); + + linkID = remotename; + + MyRoot = new TreeServer(remotename, remotedesc, remotesid, Utils->TreeRoot, this, hidden); + + // Mark the server as bursting + MyRoot->BeginBurst(); + this->DoBurst(MyRoot); + + CommandServer::Builder(MyRoot).Forward(MyRoot); +} diff --git a/src/modules/m_spanningtree/uid.cpp b/src/modules/m_spanningtree/uid.cpp index 6620dd13a..91c9f3ca8 100644 --- a/src/modules/m_spanningtree/uid.cpp +++ b/src/modules/m_spanningtree/uid.cpp @@ -23,173 +23,145 @@ #include "commands.h" #include "utils.h" -#include "link.h" -#include "treesocket.h" #include "treeserver.h" -#include "resolvers.h" +#include "remoteuser.h" -CmdResult CommandUID::Handle(const parameterlist ¶ms, User* serversrc) +CmdResult CommandUID::HandleServer(TreeServer* remoteserver, std::vector<std::string>& params) { - SpanningTreeUtilities* Utils = ((ModuleSpanningTree*)(Module*)creator)->Utils; - /** Do we have enough parameters: + /** * 0 1 2 3 4 5 6 7 8 9 (n-1) * UID uuid age nick host dhost ident ip.string signon +modes (modepara) :gecos */ - time_t age_t = ConvToInt(params[1]); - time_t signon = ConvToInt(params[7]); + time_t age_t = ServerCommand::ExtractTS(params[1]); + time_t signon = ServerCommand::ExtractTS(params[7]); std::string empty; - std::string modestr(params[8]); - - TreeServer* remoteserver = Utils->FindServer(serversrc->server); - - if (!remoteserver) - return CMD_INVALID; - /* Is this a valid UID, and not misrouted? */ - if (params[0].length() != 9 || params[0].substr(0,3) != serversrc->uuid) - return CMD_INVALID; - /* Check parameters for validity before introducing the client, discovered by dmb */ - if (!age_t) - return CMD_INVALID; - if (!signon) - return CMD_INVALID; - if (modestr[0] != '+') - return CMD_INVALID; - TreeSocket* sock = remoteserver->GetRoute()->GetSocket(); + const std::string& modestr = params[8]; - /* check for collision */ - User* const collideswith = ServerInstance->FindNickOnly(params[2]); + // Check if the length of the uuid is correct and confirm the sid portion of the uuid matches the sid of the server introducing the user + if (params[0].length() != UIDGenerator::UUID_LENGTH || params[0].compare(0, 3, remoteserver->GetID())) + throw ProtocolException("Bogus UUID"); + // Sanity check on mode string: must begin with '+' + if (modestr[0] != '+') + throw ProtocolException("Invalid mode string"); + // See if there is a nick collision + User* collideswith = ServerInstance->FindNickOnly(params[2]); if ((collideswith) && (collideswith->registered != REG_ALL)) { // User that the incoming user is colliding with is not fully registered, we force nick change the // unregistered user to their uuid and tell them what happened - collideswith->WriteFrom(collideswith, "NICK %s", collideswith->uuid.c_str()); - collideswith->WriteNumeric(433, "%s %s :Nickname overruled.", collideswith->nick.c_str(), collideswith->nick.c_str()); - - // Clear the bit before calling User::ChangeNick() to make it NOT run the OnUserPostNick() hook - collideswith->registered &= ~REG_NICK; - collideswith->ChangeNick(collideswith->uuid, true); + LocalUser* const localuser = static_cast<LocalUser*>(collideswith); + localuser->OverruleNick(); } else if (collideswith) { - /* - * Nick collision. - */ - int collide = sock->DoCollision(collideswith, age_t, params[5], params[6], params[0]); - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"*** Collision on %s, collide=%d", params[2].c_str(), collide); - - if (collide != 1) + // The user on this side is registered, handle the collision + bool they_change = Utils->DoCollision(collideswith, remoteserver, age_t, params[5], params[6], params[0], "UID"); + if (they_change) { - /* remote client lost, make sure we change their nick for the hash too - * - * This alters the line that will be sent to other servers, which - * commands normally shouldn't do; hence the required const_cast. - */ - const_cast<parameterlist&>(params)[2] = params[0]; + // The client being introduced needs to change nick to uuid, change the nick in the message before + // processing/forwarding it. Also change the nick TS to CommandSave::SavedTimestamp. + age_t = CommandSave::SavedTimestamp; + params[1] = ConvToStr(CommandSave::SavedTimestamp); + params[2] = params[0]; } } - /* IMPORTANT NOTE: For remote users, we pass the UUID in the constructor. This automatically - * sets it up in the UUID hash for us. + /* For remote users, we pass the UUID they sent to the constructor. + * If the UUID already exists User::User() throws an exception which causes this connection to be closed. */ - User* _new = NULL; - try - { - _new = new RemoteUser(params[0], remoteserver->GetName()); - } - catch (...) - { - ServerInstance->Logs->Log("m_spanningtree", DEFAULT, "Duplicate UUID %s in client introduction", params[0].c_str()); - return CMD_INVALID; - } - (*(ServerInstance->Users->clientlist))[params[2]] = _new; + RemoteUser* _new = new SpanningTree::RemoteUser(params[0], remoteserver); + ServerInstance->Users->clientlist[params[2]] = _new; _new->nick = params[2]; _new->host = params[3]; _new->dhost = params[4]; _new->ident = params[5]; - _new->fullname = params[params.size() - 1]; + _new->fullname = params.back(); _new->registered = REG_ALL; _new->signon = signon; _new->age = age_t; - /* we need to remove the + from the modestring, so we can do our stuff */ - std::string::size_type pos_after_plus = modestr.find_first_not_of('+'); - if (pos_after_plus != std::string::npos) - modestr = modestr.substr(pos_after_plus); - unsigned int paramptr = 9; - for (std::string::iterator v = modestr.begin(); v != modestr.end(); v++) + + for (std::string::const_iterator v = modestr.begin(); v != modestr.end(); ++v) { - /* For each mode thats set, increase counter */ + // Accept more '+' chars, for now + if (*v == '+') + continue; + + /* For each mode thats set, find the mode handler and set it on the new user */ ModeHandler* mh = ServerInstance->Modes->FindMode(*v, MODETYPE_USER); + if (!mh) + throw ProtocolException("Unrecognised mode '" + std::string(1, *v) + "'"); - if (mh) + if (mh->NeedsParam(true)) { - if (mh->GetNumParams(true)) - { - if (paramptr >= params.size() - 1) - return CMD_INVALID; - std::string mp = params[paramptr++]; - /* IMPORTANT NOTE: - * All modes are assumed to succeed here as they are being set by a remote server. - * Modes CANNOT FAIL here. If they DO fail, then the failure is ignored. This is important - * to note as all but one modules currently cannot ever fail in this situation, except for - * m_servprotect which specifically works this way to prevent the mode being set ANYWHERE - * but here, at client introduction. You may safely assume this behaviour is standard and - * will not change in future versions if you want to make use of this protective behaviour - * yourself. - */ - mh->OnModeChange(_new, _new, NULL, mp, true); - } - else - mh->OnModeChange(_new, _new, NULL, empty, true); - _new->SetMode(*v, true); + if (paramptr >= params.size() - 1) + throw ProtocolException("Out of parameters while processing modes"); + std::string mp = params[paramptr++]; + /* IMPORTANT NOTE: + * All modes are assumed to succeed here as they are being set by a remote server. + * Modes CANNOT FAIL here. If they DO fail, then the failure is ignored. This is important + * to note as all but one modules currently cannot ever fail in this situation, except for + * m_servprotect which specifically works this way to prevent the mode being set ANYWHERE + * but here, at client introduction. You may safely assume this behaviour is standard and + * will not change in future versions if you want to make use of this protective behaviour + * yourself. + */ + mh->OnModeChange(_new, _new, NULL, mp, true); } + else + mh->OnModeChange(_new, _new, NULL, empty, true); + _new->SetMode(mh, true); } - /* now we've done with modes processing, put the + back for remote servers */ - if (modestr[0] != '+') - modestr = "+" + modestr; - _new->SetClientIP(params[6].c_str()); - ServerInstance->Users->AddGlobalClone(_new); - remoteserver->SetUserCount(1); // increment by 1 + ServerInstance->Users->AddClone(_new); + remoteserver->UserCount++; bool dosend = true; - if ((Utils->quiet_bursts && remoteserver->bursting) || ServerInstance->SilentULine(_new->server)) + if ((Utils->quiet_bursts && remoteserver->IsBehindBursting()) || _new->server->IsSilentULine()) dosend = false; if (dosend) - ServerInstance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s (%s) [%s]", _new->server.c_str(), _new->GetFullRealHost().c_str(), _new->GetIPString(), _new->fullname.c_str()); + ServerInstance->SNO->WriteToSnoMask('C',"Client connecting at %s: %s (%s) [%s]", remoteserver->GetName().c_str(), _new->GetFullRealHost().c_str(), _new->GetIPString().c_str(), _new->fullname.c_str()); - FOREACH_MOD(I_OnPostConnect,OnPostConnect(_new)); + FOREACH_MOD(OnPostConnect, (_new)); return CMD_SUCCESS; } -CmdResult CommandFHost::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFHost::HandleRemote(RemoteUser* src, std::vector<std::string>& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeDisplayedHost(params[0].c_str()); + src->ChangeDisplayedHost(params[0]); return CMD_SUCCESS; } -CmdResult CommandFIdent::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFIdent::HandleRemote(RemoteUser* src, std::vector<std::string>& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeIdent(params[0].c_str()); + src->ChangeIdent(params[0]); return CMD_SUCCESS; } -CmdResult CommandFName::Handle(const parameterlist ¶ms, User* src) +CmdResult CommandFName::HandleRemote(RemoteUser* src, std::vector<std::string>& params) { - if (IS_SERVER(src)) - return CMD_FAILURE; - src->ChangeName(params[0].c_str()); + src->ChangeName(params[0]); return CMD_SUCCESS; } +CommandUID::Builder::Builder(User* user) + : CmdBuilder(TreeServer::Get(user)->GetID(), "UID") +{ + push(user->uuid); + push_int(user->age); + push(user->nick); + push(user->host); + push(user->dhost); + push(user->ident); + push(user->GetIPString()); + push_int(user->signon); + push(user->GetModeLetters(true)); + push_last(user->fullname); +} diff --git a/src/modules/m_spanningtree/utils.cpp b/src/modules/m_spanningtree/utils.cpp index 367a3b921..c1c32e80a 100644 --- a/src/modules/m_spanningtree/utils.cpp +++ b/src/modules/m_spanningtree/utils.cpp @@ -21,18 +21,16 @@ #include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" #include "main.h" #include "utils.h" #include "treeserver.h" -#include "link.h" #include "treesocket.h" #include "resolvers.h" +#include "commandbuilder.h" + +SpanningTreeUtilities* Utils = NULL; -/* Create server sockets off a listener. */ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) { if (from->bind_tag->getString("type") != "servers") @@ -45,7 +43,7 @@ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from if (*i == "*" || *i == incomingip || irc::sockets::cidr_mask(*i).match(*client)) { /* we don't need to do anything with the pointer, creating it stores it in the necessary places */ - new TreeSocket(Utils, newsock, from, client, server); + new TreeSocket(newsock, from, client, server); return MOD_RES_ALLOW; } } @@ -53,18 +51,12 @@ ModResult ModuleSpanningTree::OnAcceptConnection(int newsock, ListenSocket* from return MOD_RES_DENY; } -/** Yay for fast searches! - * This is hundreds of times faster than recursion - * or even scanning a linked list, especially when - * there are more than a few servers to deal with. - * (read as: lots). - */ TreeServer* SpanningTreeUtilities::FindServer(const std::string &ServerName) { - if (ServerInstance->IsSID(ServerName)) + if (InspIRCd::IsSID(ServerName)) return this->FindServerID(ServerName); - server_hash::iterator iter = serverlist.find(ServerName.c_str()); + server_hash::iterator iter = serverlist.find(ServerName); if (iter != serverlist.end()) { return iter->second; @@ -75,41 +67,8 @@ TreeServer* SpanningTreeUtilities::FindServer(const std::string &ServerName) } } -/** Returns the locally connected server we must route a - * message through to reach server 'ServerName'. This - * only applies to one-to-one and not one-to-many routing. - * See the comments for the constructor of TreeServer - * for more details. - */ -TreeServer* SpanningTreeUtilities::BestRouteTo(const std::string &ServerName) -{ - if (ServerName.c_str() == TreeRoot->GetName() || ServerName == ServerInstance->Config->GetSID()) - return NULL; - TreeServer* Found = FindServer(ServerName); - if (Found) - { - return Found->GetRoute(); - } - else - { - // Cheat a bit. This allows for (better) working versions of routing commands with nick based prefixes, without hassle - User *u = ServerInstance->FindNick(ServerName); - if (u) - { - Found = FindServer(u->server); - if (Found) - return Found->GetRoute(); - } - - return NULL; - } -} - /** Find the first server matching a given glob mask. - * Theres no find-using-glob method of hash_map [awwww :-(] - * so instead, we iterate over the list using an iterator - * and match each one until we get a hit. Yes its slow, - * deal with it. + * We iterate over the list and match each one until we get a hit. */ TreeServer* SpanningTreeUtilities::FindServerMask(const std::string &ServerName) { @@ -130,24 +89,33 @@ TreeServer* SpanningTreeUtilities::FindServerID(const std::string &id) return NULL; } -SpanningTreeUtilities::SpanningTreeUtilities(ModuleSpanningTree* C) : Creator(C) +TreeServer* SpanningTreeUtilities::FindRouteTarget(const std::string& target) { - ServerInstance->Logs->Log("m_spanningtree",DEBUG,"***** Using SID for hash: %s *****", ServerInstance->Config->GetSID().c_str()); + TreeServer* const server = FindServer(target); + if (server) + return server; + + User* const user = ServerInstance->FindNick(target); + if (user) + return TreeServer::Get(user); - this->TreeRoot = new TreeServer(this, ServerInstance->Config->ServerName, ServerInstance->Config->ServerDesc, ServerInstance->Config->GetSID()); - this->ReadConfiguration(); + return NULL; +} + +SpanningTreeUtilities::SpanningTreeUtilities(ModuleSpanningTree* C) + : Creator(C), TreeRoot(NULL) + , PingFreq(60) // XXX: TreeServer constructor reads this and TreeRoot is created before the config is read, so init it to something (value doesn't matter) to avoid a valgrind warning in TimerManager on unload +{ + ServerInstance->Timers.AddTimer(&RefreshTimer); } CullResult SpanningTreeUtilities::cull() { - while (TreeRoot->ChildCount()) + const TreeServer::ChildServers& children = TreeRoot->GetChildren(); + while (!children.empty()) { - TreeServer* child_server = TreeRoot->GetChild(0); - if (child_server) - { - TreeSocket* sock = child_server->GetSocket(); - sock->Close(); - } + TreeSocket* sock = children.front()->GetSocket(); + sock->Close(); } for(std::map<TreeSocket*, std::pair<std::string, int> >::iterator i = timeoutlist.begin(); i != timeoutlist.end(); ++i) @@ -165,26 +133,19 @@ SpanningTreeUtilities::~SpanningTreeUtilities() delete TreeRoot; } -void SpanningTreeUtilities::AddThisServer(TreeServer* server, TreeServerList &list) -{ - if (list.find(server) == list.end()) - list[server] = server; -} - -/* returns a list of DIRECT servernames for a specific channel */ -void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeServerList &list, char status, const CUList &exempt_list) +// Returns a list of DIRECT servers for a specific channel +void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeSocketSet& list, char status, const CUList& exempt_list) { unsigned int minrank = 0; if (status) { - ModeHandler* mh = ServerInstance->Modes->FindPrefix(status); + PrefixMode* mh = ServerInstance->Modes->FindPrefix(status); if (mh) minrank = mh->GetPrefixRank(); } - const UserMembList *ulist = c->GetUsers(); - - for (UserMembCIter i = ulist->begin(); i != ulist->end(); i++) + const Channel::MemberMap& ulist = c->GetUsers(); + for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i) { if (IS_LOCAL(i->first)) continue; @@ -194,86 +155,35 @@ void SpanningTreeUtilities::GetListOfServersForChannel(Channel* c, TreeServerLis if (exempt_list.find(i->first) == exempt_list.end()) { - TreeServer* best = this->BestRouteTo(i->first->server); - if (best) - AddThisServer(best,list); + TreeServer* best = TreeServer::Get(i->first); + list.insert(best->GetSocket()); } } return; } -bool SpanningTreeUtilities::DoOneToAllButSender(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& omit) +void SpanningTreeUtilities::DoOneToAllButSender(const CmdBuilder& params, TreeServer* omitroute) { - TreeServer* omitroute = this->BestRouteTo(omit); - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - unsigned int items = this->TreeRoot->ChildCount(); - for (unsigned int x = 0; x < items; x++) - { - TreeServer* Route = this->TreeRoot->GetChild(x); - // Send the line IF: - // The route has a socket (its a direct connection) - // The route isnt the one to be omitted - // The route isnt the path to the one to be omitted - if ((Route) && (Route->GetSocket()) && (Route->GetName() != omit) && (omitroute != Route)) - { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); - } - } - return true; -} + const std::string& FullLine = params.str(); -bool SpanningTreeUtilities::DoOneToMany(const std::string &prefix, const std::string &command, const parameterlist ¶ms) -{ - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - unsigned int items = this->TreeRoot->ChildCount(); - for (unsigned int x = 0; x < items; x++) + const TreeServer::ChildServers& children = TreeRoot->GetChildren(); + for (TreeServer::ChildServers::const_iterator i = children.begin(); i != children.end(); ++i) { - TreeServer* Route = this->TreeRoot->GetChild(x); - if (Route && Route->GetSocket()) + TreeServer* Route = *i; + // Send the line if the route isn't the path to the one to be omitted + if (Route != omitroute) { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); + Route->GetSocket()->WriteLine(FullLine); } } - return true; } -bool SpanningTreeUtilities::DoOneToOne(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& target) +void SpanningTreeUtilities::DoOneToOne(const CmdBuilder& params, Server* server) { - TreeServer* Route = this->BestRouteTo(target); - if (Route) - { - std::string FullLine = ":" + prefix + " " + command; - unsigned int words = params.size(); - for (unsigned int x = 0; x < words; x++) - { - FullLine = FullLine + " " + params[x]; - } - if (Route && Route->GetSocket()) - { - TreeSocket* Sock = Route->GetSocket(); - if (Sock) - Sock->WriteLine(FullLine); - } - return true; - } - else - { - return false; - } + TreeServer* ts = static_cast<TreeServer*>(server); + TreeSocket* sock = ts->GetSocket(); + if (sock) + sock->WriteLine(params); } void SpanningTreeUtilities::RefreshIPCache() @@ -284,28 +194,27 @@ void SpanningTreeUtilities::RefreshIPCache() Link* L = *i; if (!L->Port) { - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"m_spanningtree: Ignoring a link block without a port."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Ignoring a link block without a port."); /* Invalid link block */ continue; } - if (L->AllowMask.length()) - ValidIPs.push_back(L->AllowMask); + ValidIPs.insert(ValidIPs.end(), L->AllowMasks.begin(), L->AllowMasks.end()); irc::sockets::sockaddrs dummy; bool ipvalid = irc::sockets::aptosa(L->IPAddr, L->Port, dummy); if ((L->IPAddr == "*") || (ipvalid)) ValidIPs.push_back(L->IPAddr); - else + else if (this->Creator->DNS) { + SecurityIPResolver* sr = new SecurityIPResolver(Creator, *this->Creator->DNS, L->IPAddr, L, DNS::QUERY_AAAA); try { - bool cached = false; - SecurityIPResolver* sr = new SecurityIPResolver(Creator, this, L->IPAddr, L, cached, DNS_QUERY_AAAA); - ServerInstance->AddResolver(sr, cached); + this->Creator->DNS->Process(sr); } - catch (...) + catch (DNS::Exception &) { + delete sr; } } } @@ -319,7 +228,6 @@ void SpanningTreeUtilities::ReadConfiguration() HideULines = security->getBool("hideulines"); AnnounceTSChange = options->getBool("announcets"); AllowOptCommon = options->getBool("allowmismatch"); - ChallengeResponse = !security->getBool("disablehmac"); quiet_bursts = ServerInstance->Config->ConfValue("performance")->getBool("quietbursts"); PingWarnTime = options->getInt("pingwarning"); PingFreq = options->getInt("serverpingfreq"); @@ -339,14 +247,18 @@ void SpanningTreeUtilities::ReadConfiguration() reference<Link> L = new Link(tag); std::string linkname = tag->getString("name"); L->Name = linkname.c_str(); - L->AllowMask = tag->getString("allowmask"); + + irc::spacesepstream sep = tag->getString("allowmask"); + for (std::string s; sep.GetToken(s);) + L->AllowMasks.push_back(s); + L->IPAddr = tag->getString("ipaddr"); L->Port = tag->getInt("port"); L->SendPass = tag->getString("sendpass", tag->getString("password")); L->RecvPass = tag->getString("recvpass", tag->getString("password")); L->Fingerprint = tag->getString("fingerprint"); L->HiddenFromStats = tag->getBool("statshidden"); - L->Timeout = tag->getInt("timeout", 30); + L->Timeout = tag->getDuration("timeout", 30); L->Hook = tag->getString("ssl"); L->Bind = tag->getString("bind"); L->Hidden = tag->getBool("hidden"); @@ -355,31 +267,31 @@ void SpanningTreeUtilities::ReadConfiguration() throw ModuleException("Invalid configuration, found a link tag without a name!" + (!L->IPAddr.empty() ? " IP address: "+L->IPAddr : "")); if (L->Name.find('.') == std::string::npos) - throw ModuleException("The link name '"+assign(L->Name)+"' is invalid as it must contain at least one '.' character"); + throw ModuleException("The link name '"+L->Name+"' is invalid as it must contain at least one '.' character"); - if (L->Name.length() > 64) - throw ModuleException("The link name '"+assign(L->Name)+"' is invalid as it is longer than 64 characters"); + if (L->Name.length() > ServerInstance->Config->Limits.MaxHost) + throw ModuleException("The link name '"+L->Name+"' is invalid as it is longer than " + ConvToStr(ServerInstance->Config->Limits.MaxHost) + " characters"); if (L->RecvPass.empty()) - throw ModuleException("Invalid configuration for server '"+assign(L->Name)+"', recvpass not defined"); + throw ModuleException("Invalid configuration for server '"+L->Name+"', recvpass not defined"); if (L->SendPass.empty()) - throw ModuleException("Invalid configuration for server '"+assign(L->Name)+"', sendpass not defined"); + throw ModuleException("Invalid configuration for server '"+L->Name+"', sendpass not defined"); if ((L->SendPass.find(' ') != std::string::npos) || (L->RecvPass.find(' ') != std::string::npos)) - throw ModuleException("Link block '" + assign(L->Name) + "' has a password set that contains a space character which is invalid"); + throw ModuleException("Link block '" + L->Name + "' has a password set that contains a space character which is invalid"); if ((L->SendPass[0] == ':') || (L->RecvPass[0] == ':')) - throw ModuleException("Link block '" + assign(L->Name) + "' has a password set that begins with a colon (:) which is invalid"); + throw ModuleException("Link block '" + L->Name + "' has a password set that begins with a colon (:) which is invalid"); if (L->IPAddr.empty()) { L->IPAddr = "*"; - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Configuration warning: Link block '" + assign(L->Name) + "' has no IP defined! This will allow any IP to connect as this server, and MAY not be what you want."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Configuration warning: Link block '" + L->Name + "' has no IP defined! This will allow any IP to connect as this server, and MAY not be what you want."); } if (!L->Port) - ServerInstance->Logs->Log("m_spanningtree",DEFAULT,"Configuration warning: Link block '" + assign(L->Name) + "' has no port defined, you will not be able to /connect it."); + ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Configuration warning: Link block '" + L->Name + "' has no port defined, you will not be able to /connect it."); L->Fingerprint.erase(std::remove(L->Fingerprint.begin(), L->Fingerprint.end(), ':'), L->Fingerprint.end()); LinkBlocks.push_back(L); @@ -390,7 +302,7 @@ void SpanningTreeUtilities::ReadConfiguration() { ConfigTag* tag = i->second; reference<Autoconnect> A = new Autoconnect(tag); - A->Period = tag->getInt("period"); + A->Period = tag->getDuration("period", 60, 1); A->NextConnectTime = ServerInstance->Time() + A->Period; A->position = -1; irc::spacesepstream ss(tag->getString("server")); @@ -400,11 +312,6 @@ void SpanningTreeUtilities::ReadConfiguration() A->servers.push_back(server); } - if (A->Period <= 0) - { - throw ModuleException("Invalid configuration for autoconnect, period not a positive integer!"); - } - if (A->servers.empty()) { throw ModuleException("Invalid configuration for autoconnect, server cannot be empty!"); @@ -413,6 +320,9 @@ void SpanningTreeUtilities::ReadConfiguration() AutoconnectBlocks.push_back(A); } + for (server_hash::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i) + i->second->CheckULine(); + RefreshIPCache(); } @@ -421,7 +331,7 @@ Link* SpanningTreeUtilities::FindLink(const std::string& name) for (std::vector<reference<Link> >::iterator i = LinkBlocks.begin(); i != LinkBlocks.end(); ++i) { Link* x = *i; - if (InspIRCd::Match(x->Name.c_str(), name.c_str(), rfc_case_insensitive_map)) + if (InspIRCd::Match(x->Name, name, ascii_case_insensitive_map)) { return x; } @@ -429,15 +339,20 @@ Link* SpanningTreeUtilities::FindLink(const std::string& name) return NULL; } -void SpanningTreeUtilities::Rehash() +void SpanningTreeUtilities::SendChannelMessage(const std::string& prefix, Channel* target, const std::string& text, char status, const CUList& exempt_list, const char* message_type, TreeSocket* omit) { - server_hash temp; - for (server_hash::const_iterator i = serverlist.begin(); i != serverlist.end(); ++i) - temp.insert(std::make_pair(i->first, i->second)); - serverlist.swap(temp); - temp.clear(); - - for (server_hash::const_iterator i = sidlist.begin(); i != sidlist.end(); ++i) - temp.insert(std::make_pair(i->first, i->second)); - sidlist.swap(temp); + CmdBuilder msg(prefix, message_type); + msg.push_raw(' '); + if (status != 0) + msg.push_raw(status); + msg.push_raw(target->name).push_last(text); + + TreeSocketSet list; + this->GetListOfServersForChannel(target, list, status, exempt_list); + for (TreeSocketSet::iterator i = list.begin(); i != list.end(); ++i) + { + TreeSocket* Sock = *i; + if (Sock != omit) + Sock->WriteLine(msg); + } } diff --git a/src/modules/m_spanningtree/utils.h b/src/modules/m_spanningtree/utils.h index 5559b3459..a2f7212f6 100644 --- a/src/modules/m_spanningtree/utils.h +++ b/src/modules/m_spanningtree/utils.h @@ -20,36 +20,34 @@ */ -#ifndef M_SPANNINGTREE_UTILS_H -#define M_SPANNINGTREE_UTILS_H +#pragma once #include "inspircd.h" +#include "cachetimer.h" -/* Foward declarations */ class TreeServer; class TreeSocket; class Link; class Autoconnect; class ModuleSpanningTree; class SpanningTreeUtilities; +class CmdBuilder; -/* This hash_map holds the hash equivalent of the server - * tree, used for rapid linear lookups. - */ -#ifdef HASHMAP_DEPRECATED - typedef nspace::hash_map<std::string, TreeServer*, nspace::insensitive, irc::StrHashComp> server_hash; -#else - typedef nspace::hash_map<std::string, TreeServer*, nspace::hash<std::string>, irc::StrHashComp> server_hash; -#endif +extern SpanningTreeUtilities* Utils; -typedef std::map<TreeServer*,TreeServer*> TreeServerList; +/** Associative container type, mapping server names/ids to TreeServers + */ +typedef TR1NS::unordered_map<std::string, TreeServer*, irc::insensitive, irc::StrHashComp> server_hash; /** Contains helper functions and variables for this module, * and keeps them out of the global namespace */ class SpanningTreeUtilities : public classbase { + CacheRefreshTimer RefreshTimer; + public: + typedef std::set<TreeSocket*> TreeSocketSet; typedef std::map<TreeSocket*, std::pair<std::string, int> > TimeoutList; /** Creator module @@ -100,14 +98,6 @@ class SpanningTreeUtilities : public classbase */ std::vector<reference<Autoconnect> > AutoconnectBlocks; - /** True (default) if we are to use challenge-response HMAC - * to authenticate passwords. - * - * NOTE: This defaults to on, but should be turned off if - * you are linking to an older version of inspircd. - */ - bool ChallengeResponse; - /** Ping frequency of server to server links */ int PingFreq; @@ -124,33 +114,33 @@ class SpanningTreeUtilities : public classbase */ ~SpanningTreeUtilities(); - void RouteCommand(TreeServer*, const std::string&, const parameterlist&, User*); + void RouteCommand(TreeServer* origin, CommandBase* cmd, const parameterlist& parameters, User* user); /** Send a message from this server to one other local or remote */ - bool DoOneToOne(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& target); + void DoOneToOne(const CmdBuilder& params, Server* target); /** Send a message from this server to all but one other, local or remote */ - bool DoOneToAllButSender(const std::string &prefix, const std::string &command, const parameterlist ¶ms, const std::string& omit); + void DoOneToAllButSender(const CmdBuilder& params, TreeServer* omit); /** Send a message from this server to all others */ - bool DoOneToMany(const std::string &prefix, const std::string &command, const parameterlist ¶ms); + void DoOneToMany(const CmdBuilder& params); /** Read the spanningtree module's tags from the config file */ void ReadConfiguration(); - /** Add a server to the server list for GetListOfServersForChannel + /** Handle nick collision */ - void AddThisServer(TreeServer* server, TreeServerList &list); + bool DoCollision(User* u, TreeServer* server, time_t remotets, const std::string& remoteident, const std::string& remoteip, const std::string& remoteuid, const char* collidecmd); /** Compile a list of servers which contain members of channel c */ - void GetListOfServersForChannel(Channel* c, TreeServerList &list, char status, const CUList &exempt_list); + void GetListOfServersForChannel(Channel* c, TreeSocketSet& list, char status, const CUList& exempt_list); - /** Find a server by name + /** Find a server by name or SID */ TreeServer* FindServer(const std::string &ServerName); @@ -158,9 +148,10 @@ class SpanningTreeUtilities : public classbase */ TreeServer* FindServerID(const std::string &id); - /** Find a route to a server by name + /** Find a server based on a target string. + * @param target Target string where a command should be routed to. May be a server name, a sid, a nickname or a uuid. */ - TreeServer* BestRouteTo(const std::string &ServerName); + TreeServer* FindRouteTarget(const std::string& target); /** Find a server by glob mask */ @@ -174,10 +165,12 @@ class SpanningTreeUtilities : public classbase */ void RefreshIPCache(); - /** Recreate serverlist and sidlist, this is needed because of m_nationalchars changing - * national_case_insensitive_map which is used by the hash function + /** Sends a PRIVMSG or a NOTICE to a channel obeying an exempt list and an optional prefix */ - void Rehash(); + void SendChannelMessage(const std::string& prefix, Channel* target, const std::string& text, char status, const CUList& exempt_list, const char* message_type, TreeSocket* omit = NULL); }; -#endif +inline void SpanningTreeUtilities::DoOneToMany(const CmdBuilder& params) +{ + DoOneToAllButSender(params, NULL); +} diff --git a/src/modules/m_spanningtree/version.cpp b/src/modules/m_spanningtree/version.cpp deleted file mode 100644 index e08d13e6e..000000000 --- a/src/modules/m_spanningtree/version.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * InspIRCd -- Internet Relay Chat Daemon - * - * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net> - * - * This file is part of InspIRCd. InspIRCd is free software: you can - * redistribute it and/or modify it under the terms of the GNU General Public - * License as published by the Free Software Foundation, version 2. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - - -#include "inspircd.h" -#include "socket.h" -#include "xline.h" -#include "socketengine.h" - -#include "main.h" -#include "utils.h" -#include "treeserver.h" -#include "treesocket.h" - -/* $ModDep: m_spanningtree/main.h m_spanningtree/utils.h m_spanningtree/treeserver.h m_spanningtree/treesocket.h */ - -bool TreeSocket::ServerVersion(const std::string &prefix, parameterlist ¶ms) -{ - if (params.size() < 1) - return true; - - TreeServer* ServerSource = Utils->FindServer(prefix); - - if (ServerSource) - { - ServerSource->SetVersion(params[0]); - } - params[0] = ":" + params[0]; - Utils->DoOneToAllButSender(prefix,"VERSION",params,prefix); - return true; -} - |