bool TreeSocket::LocalPing(const std::string &prefix, parameterlist ¶ms) { 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 { 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; } }
std::string SpanningTreeUtilities::ConstructLine(const std::string& prefix, const std::string& command, const parameterlist& params) { std::string FullLine; FullLine.reserve(1024); FullLine = ":" + prefix + " " + command; for (parameterlist::const_iterator x = params.begin(); x != params.end(); ++x) { FullLine.push_back(' '); FullLine.append(*x); } return FullLine; }
/* * Someone else is attempting to connect to us if this is called. Validate their credentials etc. * -- w */ 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]; this->SendCapabilities(2); 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++) { 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; // Send our details: Our server name and description and hopcount of 0, // along with the sendpass from this block. this->WriteLine("SERVER "+ServerInstance->Config->ServerName+" "+this->MakePass(x->SendPass, this->GetTheirChallenge())+" 0 "+ServerInstance->Config->GetSID()+" :"+ServerInstance->Config->ServerDesc); // move to the next state, we are now waiting for THEM. this->LinkState = WAIT_AUTH_2; return true; } this->SendError("Invalid credentials"); ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; }
bool TreeSocket::DelLine(const std::string &prefix, parameterlist ¶ms) { 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(); } /* NOTE: No check needed on 'user', this function safely handles NULL */ 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 true; }
/** ENCAP */ void TreeSocket::Encap(User* who, parameterlist ¶ms) { if (params.size() > 1) { 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]; if (params[0].find_first_of("*?") != std::string::npos) { Utils->DoOneToAllButSender(who->uuid, "ENCAP", params, who->server); } else Utils->DoOneToOne(who->uuid, "ENCAP", params, params[0]); } }
void SpanningTreeProtocolInterface::SendMode(User* source, User* u, Channel* c, const parameterlist& modedata, const std::vector<TranslateType>& translate) { parameterlist params; if (u) { if (u->registered != REG_ALL) return; params.push_back(u->uuid); params.insert(params.end(), modedata.begin(), modedata.end()); Utils->DoOneToMany(source->uuid, "MODE", params); } else { params.push_back(c->name); params.push_back(ConvToStr(c->age)); params.push_back(CommandParser::TranslateUIDs(translate, modedata)); Utils->DoOneToMany(source->uuid, "FMODE", params); } }
bool TreeSocket::Away(const std::string &prefix, parameterlist ¶ms) { 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])); if (params.size() > 1) u->awaytime = atoi(params[0].c_str()); else u->awaytime = ServerInstance->Time(); u->awaymsg = params[params.size() - 1]; params[params.size() - 1] = ":" + params[params.size() - 1]; } else { FOREACH_MOD(I_OnSetAway, OnSetAway(u, "")); u->awaymsg.clear(); } Utils->DoOneToAllButSender(prefix,"AWAY",params,u->server); return true; }
/* * Some server somewhere in the network introducing another server. * -- w */ bool TreeSocket::RemoteServer(const std::string &prefix, parameterlist ¶ms) { if (params.size() < 5) { SendError("Protocol error - Not enough parameters for SERVER command"); return false; } 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)) { this->SendError("Invalid format server ID: "+sid+"!"); return false; } TreeServer* CheckDupe = Utils->FindServer(servername); if (CheckDupe) { this->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; } 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."); 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; } Link* lnk = Utils->FindLink(servername); TreeServer *Node = new TreeServer(Utils, servername, description, sid, ParentOfThis,NULL, lnk ? lnk->Hidden : false); 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; }
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; }
/* * 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 * netburst to them, which will trigger their CONNECTED state, and BURST in reply. * * While we're at it, create a treeserver object so we know about them. * -- w */ FinishAuth(params[0], params[3], params.back(), x->Hidden); return true; } return false; }
/** * SAVE command - force nick change to UID on timestamp match */ bool TreeSocket::ForceNick(const std::string &prefix, parameterlist ¶ms) { if (params.size() < 2) return true; 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"); } } return true; }
/* * Someone else is attempting to connect to us if this is called. Validate their credentials etc. * -- w */ bool TreeSocket::Inbound_Server(parameterlist ¶ms) { const Link* x = AuthRemote(params); if (x) { // Save these for later, so when they accept our credentials (indicated by BURST) we remember them this->capab->hidden = x->Hidden; 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. this->WriteLine("SERVER "+ServerInstance->Config->ServerName+" "+this->MakePass(x->SendPass, this->GetTheirChallenge())+" 0 "+ServerInstance->Config->GetSID()+" :"+ServerInstance->Config->ServerDesc); // move to the next state, we are now waiting for THEM. this->LinkState = WAIT_AUTH_2; return true; } return false; }
void TreeSocket::Split(const std::string& line, std::string& prefix, std::string& command, parameterlist& params) { irc::tokenstream tokens(line); if (!tokens.GetToken(prefix)) return; if (prefix[0] == ':') { prefix = prefix.substr(1); if (prefix.empty()) { this->SendError("BUG (?) Empty prefix received: " + line); return; } if (!tokens.GetToken(command)) { this->SendError("BUG (?) Empty command received: " + line); return; } } else { command = prefix; prefix.clear(); } if (command.empty()) this->SendError("BUG (?) Empty command received: " + line); std::string param; while (tokens.GetToken(param)) { params.push_back(param); } }
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. * What is fake direction? Imagine the following server setup: * 0AA <-> 0AB <-> 0AC * Fake direction would be 0AC sending a message to 0AB claiming to be from * 0AA, or something similar. Basically, a message taking a path that *cannot* * be correct. * * When would this be seen? * Well, hopefully never. It could be caused by race conditions, bugs, or * "miscreant" servers, though, so let's check anyway. -- w * * We also check here for totally invalid prefixes (prefixes that are neither * a valid SID or a valid UUID, so that invalid UUID or SID never makes it * to the higher level functions. -- B */ TreeServer* const server = TreeServer::Get(who); if (server->GetSocket() != this) { ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(), linkID.c_str()); return; } // Translate commands coming from servers using an older protocol if (proto_version < ProtocolVersion) { if (!PreProcessOldProtocolMessage(who, command, params)) return; } ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); CommandBase* cmdbase = scmd; Command* cmd = NULL; if (!scmd) { // Not a special server-to-server command cmd = ServerInstance->Parser.GetHandler(command); if (!cmd) { if (command == "ERROR") { this->Error(params); return; } throw ProtocolException("Unknown command"); } cmdbase = cmd; } if (params.size() < cmdbase->min_params) throw ProtocolException("Insufficient parameters"); 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) throw ProtocolException("Error in command handler"); } if (res == CMD_SUCCESS) Utils->RouteCommand(server->GetRoute(), cmdbase, params, who); }
bool TreeSocket::Capab(const parameterlist ¶ms) { if (params.size() < 1) { this->SendError("Invalid number of parameters for CAPAB - Mismatched version"); return false; } if (params[0] == "START") { capab->ModuleList.clear(); capab->OptModuleList.clear(); capab->CapKeys.clear(); if (params.size() > 1) proto_version = atoi(params[1].c_str()); SendCapabilities(2); } else if (params[0] == "END") { std::string reason; /* Compare ModuleList and check CapKeys */ if ((this->capab->ModuleList != this->MyModules(VF_COMMON)) && (this->capab->ModuleList.length())) { std::string diffIneed, diffUneed; ListDifference(this->capab->ModuleList, this->MyModules(VF_COMMON), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { reason = "Modules incorrectly matched on these servers."; if (diffIneed.length()) reason += " Not loaded here:" + diffIneed; if (diffUneed.length()) reason += " Not loaded there:" + diffUneed; this->SendError("CAPAB negotiation failed: "+reason); return false; } } if (this->capab->OptModuleList != this->MyModules(VF_OPTCOMMON) && this->capab->OptModuleList.length()) { std::string diffIneed, diffUneed; ListDifference(this->capab->OptModuleList, this->MyModules(VF_OPTCOMMON), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { if (Utils->AllowOptCommon) { ServerInstance->SNO->WriteToSnoMask('l', "Optional module lists do not match, some commands may not work globally.%s%s%s%s", diffIneed.length() ? " Not loaded here:" : "", diffIneed.c_str(), diffUneed.length() ? " Not loaded there:" : "", diffUneed.c_str()); } else { reason = "Optional modules incorrectly matched on these servers, and options::allowmismatch not set."; if (diffIneed.length()) reason += " Not loaded here:" + diffIneed; if (diffUneed.length()) reason += " Not loaded there:" + diffUneed; this->SendError("CAPAB negotiation failed: "+reason); return false; } } } 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."; if (!capab->ChanModes.empty()) { if (capab->ChanModes != BuildModeList(MODETYPE_CHANNEL)) { std::string diffIneed, diffUneed; ListDifference(capab->ChanModes, BuildModeList(MODETYPE_CHANNEL), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { reason = "Channel modes not matched on these servers."; if (diffIneed.length()) reason += " Not loaded here:" + diffIneed; if (diffUneed.length()) reason += " Not loaded there:" + diffUneed; } } } else if (this->capab->CapKeys.find("CHANMODES") != this->capab->CapKeys.end()) { if (this->capab->CapKeys.find("CHANMODES")->second != ServerInstance->Modes->GiveModeList(MASK_CHANNEL)) reason = "One or more of the channel modes on the remote server are invalid on this server."; } if (!capab->UserModes.empty()) { if (capab->UserModes != BuildModeList(MODETYPE_USER)) { std::string diffIneed, diffUneed; ListDifference(capab->UserModes, BuildModeList(MODETYPE_USER), ' ', diffIneed, diffUneed); if (diffIneed.length() || diffUneed.length()) { reason = "User modes not matched on these servers."; if (diffIneed.length()) reason += " Not loaded here:" + diffIneed; if (diffUneed.length()) reason += " Not loaded there:" + diffUneed; } } } else if (this->capab->CapKeys.find("USERMODES") != this->capab->CapKeys.end()) { if (this->capab->CapKeys.find("USERMODES")->second != ServerInstance->Modes->GiveModeList(MASK_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->Find("m_sha256.so"))) { /* Challenge-response is on now */ this->SetTheirChallenge(n->second); if (!this->GetTheirChallenge().empty() && (this->LinkState == CONNECTING)) { this->SendCapabilities(2); this->WriteLine("SERVER "+ServerInstance->Config->ServerName+" "+this->MakePass(capab->link->SendPass, capab->theirchallenge)+" 0 "+ServerInstance->Config->GetSID()+" :"+ServerInstance->Config->ServerDesc); } } else { /* They didnt specify a challenge or we don't have m_sha256.so, we use plaintext */ if (this->LinkState == CONNECTING) { this->SendCapabilities(2); this->WriteLine("SERVER "+ServerInstance->Config->ServerName+" "+capab->link->SendPass+" 0 "+ServerInstance->Config->GetSID()+" :"+ServerInstance->Config->ServerDesc); } } if (reason.length()) { this->SendError("CAPAB negotiation failed: "+reason); return false; } } else if ((params[0] == "MODULES") && (params.size() == 2)) { if (!capab->ModuleList.length()) { capab->ModuleList = params[1]; } else { capab->ModuleList.push_back(' '); capab->ModuleList.append(params[1]); } } else if ((params[0] == "MODSUPPORT") && (params.size() == 2)) { if (!capab->OptModuleList.length()) { capab->OptModuleList = params[1]; } else { capab->OptModuleList.push_back(' '); capab->OptModuleList.append(params[1]); } } else if ((params[0] == "CHANMODES") && (params.size() == 2)) { capab->ChanModes = params[1]; } else if ((params[0] == "USERMODES") && (params.size() == 2)) { capab->UserModes = params[1]; } else if ((params[0] == "CAPABILITIES") && (params.size() == 2)) { irc::tokenstream capabs(params[1]); std::string item; while (capabs.GetToken(item)) { /* Process each key/value pair */ 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()); capab->CapKeys[var] = value; } } } return true; }
/* Handle ERROR command */ void TreeSocket::Error(parameterlist ¶ms) { std::string msg = params.size() ? params[0] : ""; SetError("received ERROR " + msg); }
void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params) { User* who = ServerInstance->FindUUID(prefix); std::string direction; if (!who) { TreeServer* ServerSource = Utils->FindServer(prefix); if (prefix.empty()) ServerSource = MyRoot; 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! */ 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. */ who = ServerInstance->FindUUID(prefix.substr(0, 3)); if (!who) who = this->MyRoot->ServerUser; } else { ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Command '%s' from unknown prefix '%s'! Dropping entire command.", command.c_str(), prefix.c_str()); return; } } } // Make sure prefix is still good direction = who->server; prefix = who->uuid; /* * Check for fake direction here, and drop any instances that are found. * What is fake direction? Imagine the following server setup: * 0AA <-> 0AB <-> 0AC * Fake direction would be 0AC sending a message to 0AB claiming to be from * 0AA, or something similar. Basically, a message taking a path that *cannot* * be correct. * * When would this be seen? * Well, hopefully never. It could be caused by race conditions, bugs, or * "miscreant" servers, though, so let's check anyway. -- w * * We also check here for totally invalid prefixes (prefixes that are neither * 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)) { if (route_back_again) ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Protocol violation: Fake direction '%s' from connection '%s'", prefix.c_str(),linkID.c_str()); return; } // Translate commands coming from servers using an older protocol if (proto_version < ProtocolVersion) { if (!PreProcessOldProtocolMessage(who, command, params)) return; } ServerCommand* scmd = Utils->Creator->CmdManager.GetHandler(command); CommandBase* cmdbase = scmd; Command* cmd; if (!scmd) { // Not a special server-to-server command cmd = ServerInstance->Parser->GetHandler(command); if (!cmd) { irc::stringjoiner pmlist(params); ServerInstance->Logs->Log(MODNAME, LOG_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; } cmdbase = cmd; } if (params.size() < cmdbase->min_params) { irc::stringjoiner pmlist(params); ServerInstance->Logs->Log(MODNAME, LOG_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; } 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); ServerInstance->Logs->Log(MODNAME, LOG_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, cmdbase, params, who); }
/* * 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) { if (params.size() < 5) { SendError("Protocol error - Not enough parameters for SERVER command"); 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++) { 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; } 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) { 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; } /* * They're in WAIT_AUTH_2 (having accepted our credentials). * Set our state to CONNECTED (since everything's peachy so far) and send our * netburst to them, which will trigger their CONNECTED state, and BURST in reply. * * 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); return true; } this->SendError("Invalid credentials (check the other server's linking snomask for more information)"); ServerInstance->SNO->WriteToSnoMask('l',"Server connection from \2"+sname+"\2 denied, invalid link credentials"); return false; }
CmdResult CommandUID::Handle(const parameterlist ¶ms, User* serversrc) { 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]); std::string empty; const 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(); /* check for collision */ user_hash::iterator iter = ServerInstance->Users->clientlist->find(params[2]); if (iter != ServerInstance->Users->clientlist->end()) { /* * Nick collision. */ int collide = sock->DoCollision(iter->second, age_t, params[5], params[6], params[0]); ServerInstance->Logs->Log("m_spanningtree",LOG_DEBUG,"*** Collision on %s, collide=%d", params[2].c_str(), collide); if (collide != 1) { /* 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]; } } /* IMPORTANT NOTE: For remote users, we pass the UUID in the constructor. This automatically * sets it up in the UUID hash for us. */ User* _new = NULL; try { _new = new RemoteUser(params[0], remoteserver->GetName()); } catch (...) { ServerInstance->Logs->Log("m_spanningtree", LOG_DEFAULT, "Duplicate UUID %s in client introduction", params[0].c_str()); return CMD_INVALID; } (*(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->registered = REG_ALL; _new->signon = signon; _new->age = age_t; unsigned int paramptr = 9; // Accept more '+' chars, for now std::string::size_type pos = modestr.find_first_not_of('+'); for (std::string::const_iterator v = modestr.begin()+pos; v != modestr.end(); ++v) { /* 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) { ServerInstance->Logs->Log("m_spanningtree", LOG_DEFAULT, "Unrecognised mode '%c' for a user in UID, dropping link", *v); return CMD_INVALID; } 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); } _new->SetClientIP(params[6].c_str()); ServerInstance->Users->AddGlobalClone(_new); remoteserver->UserCount++; bool dosend = true; if ((Utils->quiet_bursts && remoteserver->bursting) || ServerInstance->SilentULine(_new->server)) 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().c_str(), _new->fullname.c_str()); FOREACH_MOD(I_OnPostConnect,OnPostConnect(_new)); return CMD_SUCCESS; }
void SpanningTreeUtilities::RouteCommand(TreeServer* origin, const std::string &command, 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); 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 */ Version ver = thiscmd->creator->GetVersion(); if ((!(ver.Flags & VF_CORE)) || (IS_LOCAL(user)) || (IS_SERVER(user) == ServerInstance->FakeClient)) return; routing = ROUTE_BROADCAST; } else if (routing.type == ROUTE_TYPE_OPT_BCAST) { params.push_back("*"); params.push_back(command); sent_cmd = "ENCAP"; } else if (routing.type == ROUTE_TYPE_OPT_UCAST) { TreeServer* sdest = FindServer(routing.serverdest); if (!sdest) { ServerInstance->Logs->Log("m_spanningtree", LOG_DEFAULT, "Trying to route ENCAP to nonexistant server %s", routing.serverdest.c_str()); return; } params.push_back(sdest->GetID()); params.push_back(command); sent_cmd = "ENCAP"; } else { Module* srcmodule = thiscmd->creator; Version ver = srcmodule->GetVersion(); if (!(ver.Flags & (VF_COMMON | VF_CORE)) && srcmodule != Creator) { ServerInstance->Logs->Log("m_spanningtree", 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); params.push_back(output_text); if (routing.type == ROUTE_TYPE_MESSAGE) { char pfx = 0; std::string dest = routing.serverdest; if (ServerInstance->Modes->FindPrefix(dest[0])) { pfx = dest[0]; dest = dest.substr(1); } 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)->GetSocket(); if (origin && origin->GetSocket() == Sock) continue; if (Sock) Sock->WriteLine(data); } } else if (dest[0] == '$') { if (origin) DoOneToAllButSender(user->uuid, sent_cmd, params, origin->GetName()); else DoOneToMany(user->uuid, sent_cmd, params); } else { // user target? User* d = ServerInstance->FindNick(dest); if (!d) return; TreeServer* tsd = BestRouteTo(d->server); if (tsd == origin) // huh? no routing stuff around in a circle, please. return; DoOneToOne(user->uuid, sent_cmd, params, d->server); } } 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); } 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); } }
void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, parameterlist& params) { User* who = ServerInstance->FindUUID(prefix); std::string direction; if (!who) { TreeServer* ServerSource = Utils->FindServer(prefix); if (prefix.empty()) ServerSource = MyRoot; 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! */ 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. */ 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; } } } // Make sure prefix is still good direction = who->server; prefix = who->uuid; /* * Check for fake direction here, and drop any instances that are found. * What is fake direction? Imagine the following server setup: * 0AA <-> 0AB <-> 0AC * Fake direction would be 0AC sending a message to 0AB claiming to be from * 0AA, or something similar. Basically, a message taking a path that *cannot* * be correct. * * When would this be seen? * Well, hopefully never. It could be caused by race conditions, bugs, or * "miscreant" servers, though, so let's check anyway. -- w * * We also check here for totally invalid prefixes (prefixes that are neither * 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)) { if (route_back_again) ServerInstance->Logs->Log("m_spanningtree",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") { 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()); 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") { 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. */ User* x = ServerInstance->FindNickOnly(params[0]); if ((x) && (x != who)) { int collideret = 0; /* x is local, who is remote */ collideret = this->DoCollision(x, who->age, who->ident, who->GetIPString(), who->uuid); if (collideret != 1) { /* * Remote client lost, or both lost, parsing or passing on this * nickchange would be pointless, as the incoming client's server will * soon recieve SVSNICK to change its nick to its UID. :) -- w00t */ return; } } 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; } 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(); } CmdResult 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); } }