diff options
Diffstat (limited to 'src')
44 files changed, 1199 insertions, 562 deletions
diff --git a/src/channels.cpp b/src/channels.cpp index b293e7fad..c9816db4b 100644 --- a/src/channels.cpp +++ b/src/channels.cpp @@ -49,7 +49,8 @@ void Channel::SetTopic(User* u, const std::string& ntopic, time_t topicts, const if (this->topic != ntopic) { this->topic = ntopic; - this->WriteChannel(u, "TOPIC %s :%s", this->name.c_str(), this->topic.c_str()); + ClientProtocol::Messages::Topic topicmsg(u, this, this->topic); + Write(ServerInstance->GetRFCEvents().topic, topicmsg); } // Always update setter and set time @@ -287,18 +288,8 @@ Membership* Channel::ForceJoin(User* user, const std::string* privs, bool bursti CUList except_list; FOREACH_MOD(OnUserJoin, (memb, bursting, created_by_local, except_list)); - this->WriteAllExcept(user, false, 0, except_list, "JOIN :%s", this->name.c_str()); - - /* Theyre not the first ones in here, make sure everyone else sees the modes we gave the user */ - if ((GetUserCounter() > 1) && (!memb->modes.empty())) - { - std::string ms = memb->modes; - for(unsigned int i=0; i < memb->modes.length(); i++) - ms.append(" ").append(user->nick); - - except_list.insert(user); - this->WriteAllExcept(user, !ServerInstance->Config->CycleHostsFromUser, 0, except_list, "MODE %s +%s", this->name.c_str(), ms.c_str()); - } + ClientProtocol::Events::Join joinevent(memb); + this->Write(joinevent, 0, except_list); FOREACH_MOD(OnPostJoin, (memb)); return memb; @@ -397,7 +388,8 @@ bool Channel::PartUser(User* user, std::string& reason) CUList except_list; FOREACH_MOD(OnUserPart, (memb, reason, except_list)); - WriteAllExcept(user, false, 0, except_list, "PART %s%s%s", this->name.c_str(), reason.empty() ? "" : " :", reason.c_str()); + ClientProtocol::Messages::Part partmsg(memb, reason); + Write(ServerInstance->GetRFCEvents().part, partmsg, 0, except_list); // Remove this channel from the user's chanlist user->chans.erase(memb); @@ -413,73 +405,14 @@ void Channel::KickUser(User* src, const MemberMap::iterator& victimiter, const s CUList except_list; FOREACH_MOD(OnUserKick, (src, memb, reason, except_list)); - User* victim = memb->user; - WriteAllExcept(src, false, 0, except_list, "KICK %s %s :%s", name.c_str(), victim->nick.c_str(), reason.c_str()); + ClientProtocol::Messages::Kick kickmsg(src, memb, reason); + Write(ServerInstance->GetRFCEvents().kick, kickmsg, 0, except_list); - victim->chans.erase(memb); + memb->user->chans.erase(memb); this->DelUser(victimiter); } -void Channel::WriteChannel(User* user, const char* text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->WriteChannel(user, textbuffer); -} - -void Channel::WriteChannel(User* user, const std::string &text) -{ - const std::string message = ":" + user->GetFullHost() + " " + text; - - for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) - { - if (IS_LOCAL(i->first)) - i->first->Write(message); - } -} - -void Channel::WriteChannelWithServ(const std::string& ServName, const char* text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->WriteChannelWithServ(ServName, textbuffer); -} - -void Channel::WriteChannelWithServ(const std::string& ServName, const std::string &text) -{ - const std::string message = ":" + (ServName.empty() ? ServerInstance->Config->ServerName : ServName) + " " + text; - - for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) - { - if (IS_LOCAL(i->first)) - i->first->Write(message); - } -} - -/* write formatted text from a source user to all users on a channel except - * for the sender (for privmsg etc) */ -void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->WriteAllExceptSender(user, serversource, status, textbuffer); -} - -void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - textbuffer = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + textbuffer; - this->RawWriteAllExcept(user, serversource, status, except_list, textbuffer); -} - -void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &text) -{ - const std::string message = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + text; - this->RawWriteAllExcept(user, serversource, status, except_list, message); -} - -void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &out) +void Channel::Write(ClientProtocol::Event& protoev, char status, const CUList& except_list) { unsigned int minrank = 0; if (status) @@ -490,24 +423,18 @@ void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CULi } for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++) { - if (IS_LOCAL(i->first) && (except_list.find(i->first) == except_list.end())) + LocalUser* user = IS_LOCAL(i->first); + if ((user) && (!except_list.count(user))) { /* User doesn't have the status we're after */ if (minrank && i->second->getRank() < minrank) continue; - i->first->Write(out); + user->Send(protoev); } } } -void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text) -{ - CUList except_list; - except_list.insert(user); - this->WriteAllExcept(user, serversource, status, except_list, std::string(text)); -} - const char* Channel::ChanModes(bool showkey) { static std::string scratch; @@ -545,9 +472,8 @@ const char* Channel::ChanModes(bool showkey) void Channel::WriteNotice(const std::string& text) { - std::string rawmsg = "NOTICE "; - rawmsg.append(this->name).append(" :").append(text); - WriteChannelWithServ(ServerInstance->Config->ServerName, rawmsg); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this, text, MSG_NOTICE); + Write(ServerInstance->GetRFCEvents().privmsg, privmsg); } /* returns the status character for a given user on a channel, e.g. @ for op, @@ -628,7 +554,10 @@ bool Membership::SetPrefix(PrefixMode* delta_mh, bool adding) void Membership::WriteNotice(const std::string& text) const { - std::string rawmsg = "NOTICE "; - rawmsg.append(chan->name).append(" :").append(text); - user->WriteServ(rawmsg); + LocalUser* const localuser = IS_LOCAL(user); + if (!localuser) + return; + + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this->chan, text, MSG_NOTICE); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } diff --git a/src/clientprotocol.cpp b/src/clientprotocol.cpp new file mode 100644 index 000000000..a732855a5 --- /dev/null +++ b/src/clientprotocol.cpp @@ -0,0 +1,105 @@ +/* + * 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" + +ClientProtocol::Serializer::Serializer(Module* mod, const char* Name) + : DataProvider(mod, std::string("serializer/") + Name) + , evprov(mod, "event/messagetag") +{ +} + +bool ClientProtocol::Serializer::HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const +{ + // Catch and block empty tags + if (tagname.empty()) + return false; + + const ::Events::ModuleEventProvider::SubscriberList& list = evprov.GetSubscribers(); + for (::Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i) + { + MessageTagProvider* const tagprov = static_cast<MessageTagProvider*>(*i); + const ModResult res = tagprov->OnClientProtocolProcessTag(user, tagname, tagvalue); + if (res == MOD_RES_ALLOW) + return tags.insert(std::make_pair(tagname, MessageTagData(tagprov, tagvalue))).second; + else if (res == MOD_RES_DENY) + break; + } + + // No module handles the tag but that's not an error + return true; +} + +ClientProtocol::TagSelection ClientProtocol::Serializer::MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const +{ + TagSelection tagwl; + for (TagMap::const_iterator i = tagmap.begin(); i != tagmap.end(); ++i) + { + const MessageTagData& tagdata = i->second; + if (tagdata.tagprov->ShouldSendTag(user, tagdata)) + tagwl.Select(tagmap, i); + } + return tagwl; +} + +const ClientProtocol::SerializedMessage& ClientProtocol::Serializer::SerializeForUser(LocalUser* user, Message& msg) +{ + if (!msg.msginit_done) + { + msg.msginit_done = true; + FOREACH_MOD_CUSTOM(evprov, MessageTagProvider, OnClientProtocolPopulateTags, (msg)); + } + return msg.GetSerialized(Message::SerializedInfo(this, MakeTagWhitelist(user, msg.GetTags()))); +} + +const ClientProtocol::SerializedMessage& ClientProtocol::Message::GetSerialized(const SerializedInfo& serializeinfo) const +{ + // First check if the serialized line they're asking for is in the cache + for (SerializedList::const_iterator i = serlist.begin(); i != serlist.end(); ++i) + { + const SerializedInfo& curr = i->first; + if (curr == serializeinfo) + return i->second; + } + + // Not cached, generate it and put it in the cache for later use + serlist.push_back(std::make_pair(serializeinfo, serializeinfo.serializer->Serialize(*this, serializeinfo.tagwl))); + return serlist.back().second; +} + +void ClientProtocol::Event::GetMessagesForUser(LocalUser* user, MessageList& messagelist) +{ + if (!eventinit_done) + { + eventinit_done = true; + FOREACH_MOD_CUSTOM(*event, EventHook, OnEventInit, (*this)); + } + + // Most of the time there's only a single message but in rare cases there are more + if (initialmsg) + messagelist.assign(1, initialmsg); + else + messagelist = *initialmsglist; + + // Let modules modify the message list + ModResult res; + FIRST_MOD_RESULT_CUSTOM(*event, EventHook, OnPreEventSend, res, (user, *this, messagelist)); + if (res == MOD_RES_DENY) + messagelist.clear(); +} diff --git a/src/command_parse.cpp b/src/command_parse.cpp index c133c475e..503630d53 100644 --- a/src/command_parse.cpp +++ b/src/command_parse.cpp @@ -93,7 +93,8 @@ bool CommandParser::LoopCall(User* user, Command* handler, const CommandBase::Pa new_parameters[extra] = item; } - CmdResult result = handler->Handle(user, new_parameters); + CommandBase::Params params(new_parameters, parameters.GetTags()); + CmdResult result = handler->Handle(user, params); if (localuser) { // Run the OnPostCommand hook with the last parameter (original line) being empty @@ -152,14 +153,16 @@ CmdResult CommandParser::CallHandler(const std::string& commandname, const Comma { if (cmd) *cmd = n->second; - return n->second->Handle(user, parameters); + + ClientProtocol::TagMap tags; + return n->second->Handle(user, CommandBase::Params(parameters, tags)); } } } return CMD_INVALID; } -void CommandParser::ProcessCommand(LocalUser* user, std::string& command, Command::Params& command_p) +void CommandParser::ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& command_p) { /* find the command, check it exists */ Command* handler = GetHandler(command); @@ -369,44 +372,14 @@ void Command::RegisterService() void CommandParser::ProcessBuffer(LocalUser* user, const std::string& buffer) { - size_t start = buffer.find_first_not_of(" "); - if (start == std::string::npos) - { - // Discourage the user from flooding the server. - user->CommandFloodPenalty += 2000; + ClientProtocol::ParseOutput parseoutput; + if (!user->serializer->Parse(user, buffer, parseoutput)) return; - } - - ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), buffer.c_str()); - - irc::tokenstream tokens(buffer, start); - std::string command; - CommandBase::Params parameters; - - // Get the command name. This will always exist because of the check - // at the start of the function. - tokens.GetMiddle(command); - // If this exists then the client sent a prefix as part of their - // message. Section 2.3 of RFC 1459 technically says we should only - // allow the nick of the client here but in practise everyone just - // ignores it so we will copy them. - if (command[0] == ':' && !tokens.GetMiddle(command)) - { - // Discourage the user from flooding the server. - user->CommandFloodPenalty += 2000; - return; - } - - // We upper-case the command name to ensure consistency internally. + std::string& command = parseoutput.cmd; std::transform(command.begin(), command.end(), command.begin(), ::toupper); - // Build the parameter map. We intentionally do not respect the RFC 1459 - // thirteen parameter limit here. - std::string parameter; - while (tokens.GetTrailing(parameter)) - parameters.push_back(parameter); - + CommandBase::Params parameters(parseoutput.params, parseoutput.tags); ProcessCommand(user, command, parameters); } @@ -425,7 +398,7 @@ CommandParser::CommandParser() { } -std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const std::vector<std::string>& source, bool prefix_final, CommandBase* custom_translator) +std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const CommandBase::Params& source, bool prefix_final, CommandBase* custom_translator) { std::vector<TranslateType>::const_iterator types = to.begin(); std::string dest; diff --git a/src/configreader.cpp b/src/configreader.cpp index 52d349f5c..6c7cb492a 100644 --- a/src/configreader.cpp +++ b/src/configreader.cpp @@ -415,7 +415,6 @@ void ServerConfig::Fill() HideULineKills = security->getBool("hideulinekills"); GenericOper = security->getBool("genericoper"); SyntaxHints = options->getBool("syntaxhints"); - CycleHostsFromUser = options->getBool("cyclehostsfromuser"); FullHostInTopic = options->getBool("hostintopic"); MaxTargets = security->getUInt("maxtargets", 20, 1, 31); DefaultModes = options->getString("defaultmodes", "not"); diff --git a/src/coremods/core_channel/cmd_invite.cpp b/src/coremods/core_channel/cmd_invite.cpp index 89a2f6b69..1b480aa20 100644 --- a/src/coremods/core_channel/cmd_invite.cpp +++ b/src/coremods/core_channel/cmd_invite.cpp @@ -114,10 +114,12 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters) } } - if (IS_LOCAL(u)) + LocalUser* const localtargetuser = IS_LOCAL(u); + if (localtargetuser) { - invapi.Create(IS_LOCAL(u), c, timeout); - u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str()); + invapi.Create(localtargetuser, c, timeout); + ClientProtocol::Messages::Invite invitemsg(user, localtargetuser, c); + localtargetuser->Send(ServerInstance->GetRFCEvents().invite, invitemsg); } if (IS_LOCAL(user)) @@ -156,7 +158,11 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters) FOREACH_MOD(OnUserInvite, (user, u, c, timeout, minrank, excepts)); if (announceinvites != Invite::ANNOUNCE_NONE) - c->WriteAllExcept(user, true, prefix, excepts, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str()); + { + excepts.insert(user); + ClientProtocol::Messages::Privmsg privmsg(ServerInstance->FakeClient, c, InspIRCd::Format("*** %s invited %s into the channel", user->nick.c_str(), u->nick.c_str()), MSG_NOTICE); + c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, prefix, excepts); + } } else if (IS_LOCAL(user)) { diff --git a/src/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp index ccf4d1a6e..4e49ba2b4 100644 --- a/src/coremods/core_channel/core_channel.cpp +++ b/src/coremods/core_channel/core_channel.cpp @@ -22,6 +22,71 @@ #include "invite.h" #include "listmode.h" +namespace +{ +/** Hook that sends a MODE after a JOIN if the user in the JOIN has some modes prefix set. + * This happens e.g. when modules such as operprefix explicitly set prefix modes on the joining + * user, or when a member with prefix modes does a host cycle. + */ +class JoinHook : public ClientProtocol::EventHook +{ + ClientProtocol::Messages::Mode modemsg; + Modes::ChangeList modechangelist; + const User* joininguser; + + public: + /** If true, MODE changes after JOIN will be sourced from the user, rather than the server + */ + bool modefromuser; + + JoinHook(Module* mod) + : ClientProtocol::EventHook(mod, "JOIN") + { + } + + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE + { + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + const Membership& memb = *join.GetMember(); + + modechangelist.clear(); + for (std::string::const_iterator i = memb.modes.begin(); i != memb.modes.end(); ++i) + { + PrefixMode* const pm = ServerInstance->Modes.FindPrefixMode(*i); + if (!pm) + continue; // Shouldn't happen + modechangelist.push_add(pm, memb.user->nick); + } + + if (modechangelist.empty()) + { + // Member got no modes on join + joininguser = NULL; + return; + } + + joininguser = memb.user; + + // Prepare a mode protocol event that we can append to the message list in OnPreEventSend() + modemsg.SetParams(memb.chan, NULL, modechangelist); + if (modefromuser) + modemsg.SetSource(join); + else + modemsg.SetSourceUser(ServerInstance->FakeClient); + } + + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + // If joininguser is NULL then they didn't get any modes on join, skip. + // Also don't show their own modes to them, they get that in the NAMES list not via MODE. + if ((joininguser) && (user != joininguser)) + messagelist.push_back(&modemsg); + return MOD_RES_PASSTHRU; + } +}; + +} + class CoreModChannel : public Module, public CheckExemption::EventListener { Invite::APIImpl invapi; @@ -30,6 +95,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener CommandKick cmdkick; CommandNames cmdnames; CommandTopic cmdtopic; + Events::ModuleEventProvider evprov; + JoinHook joinhook; ModeChannelBan banmode; SimpleChannelModeHandler inviteonlymode; @@ -62,6 +129,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener , cmdkick(this) , cmdnames(this) , cmdtopic(this) + , evprov(this, "event/channel") + , joinhook(this) , banmode(this) , inviteonlymode(this, "inviteonly", 'i') , keymode(this) @@ -88,6 +157,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener ServerInstance->Modules.Detach(events[i], this); } + joinhook.modefromuser = optionstag->getBool("cyclehostsfromuser"); + std::string current; irc::spacesepstream defaultstream(optionstag->getString("exemptchanops")); insp::flat_map<std::string, char> exempts; diff --git a/src/coremods/core_oper/cmd_die.cpp b/src/coremods/core_oper/cmd_die.cpp index d10732952..b25fe2407 100644 --- a/src/coremods/core_oper/cmd_die.cpp +++ b/src/coremods/core_oper/cmd_die.cpp @@ -39,7 +39,8 @@ static void QuitAll() void DieRestart::SendError(const std::string& message) { - const std::string unregline = "ERROR :" + message; + ClientProtocol::Messages::Error errormsg(message); + ClientProtocol::Event errorevent(ServerInstance->GetRFCEvents().error, errormsg); const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { @@ -51,7 +52,7 @@ void DieRestart::SendError(const std::string& message) else { // Unregistered connections receive ERROR, not a NOTICE - user->Write(unregline); + user->Send(errorevent); } } } diff --git a/src/coremods/core_oper/cmd_kill.cpp b/src/coremods/core_oper/cmd_kill.cpp index fd6729f53..5572e5789 100644 --- a/src/coremods/core_oper/cmd_kill.cpp +++ b/src/coremods/core_oper/cmd_kill.cpp @@ -24,12 +24,28 @@ CommandKill::CommandKill(Module* parent) : Command(parent, "KILL", 2, 2) + , protoev(parent, name) { flags_needed = 'o'; syntax = "<nickname> <reason>"; TRANSLATE2(TR_CUSTOM, TR_CUSTOM); } +class KillMessage : public ClientProtocol::Message +{ + public: + KillMessage(ClientProtocol::EventProvider& protoev, User* user, LocalUser* target, const std::string& text) + : ClientProtocol::Message("KILL", NULL) + { + if (ServerInstance->Config->HideKillsServer.empty()) + SetSourceUser(user); + else + SetSource(ServerInstance->Config->HideKillsServer); + + PushParamRef(target->nick); + PushParamRef(text); + } +}; /** Handle /KILL */ @@ -100,10 +116,10 @@ CmdResult CommandKill::Handle(User* user, const Params& parameters) if (IS_LOCAL(target)) { - target->Write(":%s KILL %s :%s", - ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(), - target->nick.c_str(), - parameters[1].c_str()); + LocalUser* localu = IS_LOCAL(target); + KillMessage msg(protoev, user, localu, killreason); + ClientProtocol::Event killevent(protoev, msg); + localu->Send(killevent); this->lastuuid.clear(); } diff --git a/src/coremods/core_oper/core_oper.h b/src/coremods/core_oper/core_oper.h index b069c34b2..db8c4161c 100644 --- a/src/coremods/core_oper/core_oper.h +++ b/src/coremods/core_oper/core_oper.h @@ -60,6 +60,7 @@ class CommandKill : public Command { std::string lastuuid; std::string killreason; + ClientProtocol::EventProvider protoev; public: /** Constructor for kill. @@ -67,8 +68,8 @@ class CommandKill : public Command CommandKill(Module* parent); /** Handle command. - * @param parameters The parameters to the command - * @param user The user issuing the command + * @param user User issuing the command + * @param parameters Parameters to the command * @return A value from CmdResult to indicate command success or failure. */ CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; @@ -87,8 +88,8 @@ class CommandOper : public SplitCommand CommandOper(Module* parent); /** Handle command. - * @param parameters The parameters to the command - * @param user The user issuing the command + * @param user User issuing the command + * @param parameters Parameters to the command * @return A value from CmdResult to indicate command success or failure. */ CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE; @@ -104,8 +105,8 @@ class CommandRehash : public Command CommandRehash(Module* parent); /** Handle command. - * @param parameters The parameters to the command - * @param user The user issuing the command + * @param user User issuing the command + * @param parameters Parameters to the command * @return A value from CmdResult to indicate command success or failure. */ CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; @@ -121,8 +122,8 @@ class CommandRestart : public Command CommandRestart(Module* parent); /** Handle command. - * @param parameters The parameters to the command - * @param user The user issuing the command + * @param user User issuing the command + * @param parameters Parameters to the command * @return A value from CmdResult to indicate command success or failure. */ CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE; diff --git a/src/coremods/core_privmsg.cpp b/src/coremods/core_privmsg.cpp index 6078f5297..2daeef3ad 100644 --- a/src/coremods/core_privmsg.cpp +++ b/src/coremods/core_privmsg.cpp @@ -21,11 +21,6 @@ #include "inspircd.h" -namespace -{ - const char* MessageTypeString[] = { "PRIVMSG", "NOTICE" }; -} - class MessageCommandBase : public Command { ChanModeReference moderatedmode; @@ -35,12 +30,13 @@ class MessageCommandBase : public Command * @param user User sending the message * @param msg The message to send * @param mt Type of the message (MSG_PRIVMSG or MSG_NOTICE) + * @param tags Message tags to include in the outgoing protocol message */ - static void SendAll(User* user, const std::string& msg, MessageType mt); + static void SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags); public: MessageCommandBase(Module* parent, MessageType mt) - : Command(parent, MessageTypeString[mt], 2, 2) + : Command(parent, ClientProtocol::Messages::Privmsg::CommandStrFromMsgType(mt), 2, 2) , moderatedmode(parent, "moderated") , noextmsgmode(parent, "noextmsg") { @@ -52,7 +48,7 @@ class MessageCommandBase : public Command * @param user The user issuing the command * @return A value from CmdResult to indicate command success or failure. */ - CmdResult HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt); + CmdResult HandleMessage(User* user, const Params& parameters, MessageType mt); RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE { @@ -64,18 +60,22 @@ class MessageCommandBase : public Command } }; -void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt) +void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags) { - const std::string message = ":" + user->GetFullHost() + " " + MessageTypeString[mt] + " $* :" + msg; + ClientProtocol::Messages::Privmsg message(ClientProtocol::Messages::Privmsg::nocopy, user, "$*", msg, mt); + message.AddTags(tags); + message.SetSideEffect(true); + ClientProtocol::Event messageevent(ServerInstance->GetRFCEvents().privmsg, message); + const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { if ((*i)->registered == REG_ALL) - (*i)->Write(message); + (*i)->Send(messageevent); } } -CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt) +CmdResult MessageCommandBase::HandleMessage(User* user, const Params& parameters, MessageType mt) { User *dest; Channel *chan; @@ -94,7 +94,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param std::string servername(parameters[0], 1); MessageTarget msgtarget(&servername); - MessageDetails msgdetails(mt, parameters[1]); + MessageDetails msgdetails(mt, parameters[1], parameters.GetTags()); ModResult MOD_RESULT; FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails)); @@ -107,7 +107,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); if (InspIRCd::Match(ServerInstance->Config->ServerName, servername, NULL)) { - SendAll(user, msgdetails.text, mt); + SendAll(user, msgdetails.text, mt, msgdetails.tags_out); } FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); return CMD_SUCCESS; @@ -153,7 +153,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param } MessageTarget msgtarget(chan, status); - MessageDetails msgdetails(mt, parameters[1]); + MessageDetails msgdetails(mt, parameters[1], parameters.GetTags()); msgdetails.exemptions.insert(user); ModResult MOD_RESULT; @@ -173,14 +173,10 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); - if (status) - { - chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %c%s :%s", MessageTypeString[mt], status, chan->name.c_str(), msgdetails.text.c_str()); - } - else - { - chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %s :%s", MessageTypeString[mt], chan->name.c_str(), msgdetails.text.c_str()); - } + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, msgdetails.text, msgdetails.type, msgtarget.status); + privmsg.AddTags(msgdetails.tags_out); + privmsg.SetSideEffect(true); + chan->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, msgtarget.status, msgdetails.exemptions); FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); } @@ -233,7 +229,8 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param } MessageTarget msgtarget(dest); - MessageDetails msgdetails(mt, parameters[1]); + MessageDetails msgdetails(mt, parameters[1], parameters.GetTags()); + ModResult MOD_RESULT; FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails)); @@ -245,10 +242,14 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails)); - if (IS_LOCAL(dest)) + LocalUser* const localtarget = IS_LOCAL(dest); + if (localtarget) { // direct write, same server - dest->WriteFrom(user, "%s %s :%s", MessageTypeString[mt], dest->nick.c_str(), msgdetails.text.c_str()); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, localtarget->nick, msgdetails.text, mt); + privmsg.AddTags(msgdetails.tags_out); + privmsg.SetSideEffect(true); + localtarget->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails)); diff --git a/src/coremods/core_reloadmodule.cpp b/src/coremods/core_reloadmodule.cpp index 23be33af8..383d574bf 100644 --- a/src/coremods/core_reloadmodule.cpp +++ b/src/coremods/core_reloadmodule.cpp @@ -24,18 +24,42 @@ #include "modules/reload.h" static Events::ModuleEventProvider* reloadevprov; +static ClientProtocol::Serializer* dummyserializer; + +class DummySerializer : public ClientProtocol::Serializer +{ + bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE + { + return false; + } + + ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE + { + return ClientProtocol::SerializedMessage(); + } + + public: + DummySerializer(Module* mod) + : ClientProtocol::Serializer(mod, "dummy") + { + } +}; class CommandReloadmodule : public Command { Events::ModuleEventProvider evprov; + DummySerializer dummyser; + public: /** Constructor for reloadmodule. */ CommandReloadmodule(Module* parent) : Command(parent, "RELOADMODULE", 1) , evprov(parent, "event/reloadmodule") + , dummyser(parent) { reloadevprov = &evprov; + dummyserializer = &dummyser; flags_needed = 'o'; syntax = "<modulename>"; } @@ -62,6 +86,7 @@ class DataKeeper { ModeHandler* mh; ExtensionItem* extitem; + ClientProtocol::Serializer* serializer; }; ProviderInfo(ModeHandler* mode) @@ -75,6 +100,12 @@ class DataKeeper , extitem(ei) { } + + ProviderInfo(ClientProtocol::Serializer* ser) + : itemname(ser->name) + , serializer(ser) + { + } }; struct InstanceData @@ -143,7 +174,17 @@ class DataKeeper }; // Data saved for each user - typedef OwnedModesExts UserData; + struct UserData : public OwnedModesExts + { + static const size_t UNUSED_INDEX = (size_t)-1; + size_t serializerindex; + + UserData(User* user, size_t serializeridx) + : OwnedModesExts(user->uuid) + , serializerindex(serializeridx) + { + } + }; /** Module being reloaded */ @@ -157,6 +198,10 @@ class DataKeeper */ std::vector<ProviderInfo> handledexts; + /** Stores all serializers provided by the module + */ + std::vector<ProviderInfo> handledserializers; + /** Stores all of the module data related to users */ std::vector<UserData> userdatalist; @@ -172,6 +217,14 @@ class DataKeeper void SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdatalist); void SaveMemberData(Channel* chan, std::vector<ChanData::MemberData>& memberdatalist); static void SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata); + size_t SaveSerializer(User* user); + + /** Get the index of a ProviderInfo representing the serializer in the handledserializers list. + * If the serializer is not already in the list it is added. + * @param serializer Serializer to get an index to. + * @return Index of the ProviderInfo representing the serializer. + */ + size_t GetSerializerIndex(ClientProtocol::Serializer* serializer); void CreateModeList(ModeType modetype); void DoSaveUsers(); @@ -186,6 +239,10 @@ class DataKeeper */ void LinkModes(ModeType modetype); + /** Link previously saved serializer names to currently available Serializers + */ + void LinkSerializers(); + void DoRestoreUsers(); void DoRestoreChans(); void DoRestoreModules(); @@ -213,6 +270,15 @@ class DataKeeper */ void RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange); + /** Restore previously saved serializer on a User. + * Quit the user if the serializer cannot be restored. + * @param serializerindex Saved serializer index to restore. + * @param user User whose serializer to restore. If not local then calling this method is a no-op. + * @return True if the serializer didn't need restoring or was restored successfully. + * False if the serializer should have been restored but the required serializer is unavailable and the user was quit. + */ + bool RestoreSerializer(size_t serializerindex, User* user); + /** Restore all modes and extensions of all members on a channel * @param chan Channel whose members are being restored * @param memberdata Data to restore @@ -262,16 +328,44 @@ void DataKeeper::DoSaveUsers() // Serialize all extensions attached to the User SaveExtensions(user, currdata.extlist); + // Save serializer name if applicable and get an index to it + size_t serializerindex = SaveSerializer(user); + // Add to list if the user has any modes or extensions set that we are interested in, otherwise we don't // have to do anything with this user when restoring - if (!currdata.empty()) + if ((!currdata.empty()) || (serializerindex != UserData::UNUSED_INDEX)) { - userdatalist.push_back(UserData(user->uuid)); + userdatalist.push_back(UserData(user, serializerindex)); userdatalist.back().swap(currdata); } } } +size_t DataKeeper::GetSerializerIndex(ClientProtocol::Serializer* serializer) +{ + for (size_t i = 0; i < handledserializers.size(); i++) + { + if (handledserializers[i].serializer == serializer) + return i; + } + + handledserializers.push_back(ProviderInfo(serializer)); + return handledserializers.size()-1; +} + +size_t DataKeeper::SaveSerializer(User* user) +{ + LocalUser* const localuser = IS_LOCAL(user); + if ((!localuser) || (!localuser->serializer)) + return UserData::UNUSED_INDEX; + if (localuser->serializer->creator != mod) + return UserData::UNUSED_INDEX; + + const size_t serializerindex = GetSerializerIndex(localuser->serializer); + localuser->serializer = dummyserializer; + return serializerindex; +} + void DataKeeper::SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdata) { const Extensible::ExtensibleStore& setexts = extensible->GetExtList(); @@ -456,6 +550,16 @@ void DataKeeper::LinkExtensions() } } +void DataKeeper::LinkSerializers() +{ + for (std::vector<ProviderInfo>::iterator i = handledserializers.begin(); i != handledserializers.end(); ++i) + { + ProviderInfo& item = *i; + item.serializer = ServerInstance->Modules.FindDataService<ClientProtocol::Serializer>(item.itemname); + VerifyServiceProvider(item.serializer, "Serializer"); + } +} + void DataKeeper::Restore(Module* newmod) { this->mod = newmod; @@ -464,6 +568,7 @@ void DataKeeper::Restore(Module* newmod) LinkExtensions(); LinkModes(MODETYPE_USER); LinkModes(MODETYPE_CHANNEL); + LinkSerializers(); // Restore DoRestoreUsers(); @@ -505,6 +610,30 @@ void DataKeeper::RestoreModes(const std::vector<InstanceData>& list, ModeType mo } } +bool DataKeeper::RestoreSerializer(size_t serializerindex, User* user) +{ + if (serializerindex == UserData::UNUSED_INDEX) + return true; + + // The following checks are redundant + LocalUser* const localuser = IS_LOCAL(user); + if (!localuser) + return true; + if (localuser->serializer != dummyserializer) + return true; + + const ProviderInfo& provinfo = handledserializers[serializerindex]; + if (!provinfo.serializer) + { + // Users cannot exist without a serializer + ServerInstance->Users.QuitUser(user, "Serializer lost in reload"); + return false; + } + + localuser->serializer = provinfo.serializer; + return true; +} + void DataKeeper::DoRestoreUsers() { ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring user data"); @@ -520,6 +649,10 @@ void DataKeeper::DoRestoreUsers() continue; } + // Attempt to restore serializer first, if it fails it's a fatal error and RestoreSerializer() quits them + if (!RestoreSerializer(userdata.serializerindex, user)) + continue; + RestoreObj(userdata, user, MODETYPE_USER, modechange); ServerInstance->Modes.Process(ServerInstance->FakeClient, NULL, user, modechange, ModeParser::MODE_LOCALONLY); modechange.clear(); diff --git a/src/coremods/core_serialize_rfc.cpp b/src/coremods/core_serialize_rfc.cpp new file mode 100644 index 000000000..afa914f3d --- /dev/null +++ b/src/coremods/core_serialize_rfc.cpp @@ -0,0 +1,222 @@ +/* + * 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" + +class RFCSerializer : public ClientProtocol::Serializer +{ + /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters. + */ + static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512; + + static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line); + + public: + RFCSerializer(Module* mod) + : ClientProtocol::Serializer(mod, "rfc") + { + } + + bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE; + ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE; +}; + +bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) +{ + size_t start = line.find_first_not_of(" "); + if (start == std::string::npos) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + + ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str()); + + irc::tokenstream tokens(line, start); + std::string token; + + // This will always exist because of the check at the start of the function. + tokens.GetMiddle(token); + if (token[0] == '@') + { + // Line begins with message tags, parse them. + std::string tagval; + irc::sepstream ss(token.substr(1), ';'); + while (ss.GetToken(token)) + { + // Two or more tags with the same key must not be sent, but if a client violates that we accept + // the first occurence of duplicate tags and ignore all later occurences. + // + // Another option is to reject the message entirely but there is no standard way of doing that. + const std::string::size_type p = token.find('='); + if (p != std::string::npos) + { + // Tag has a value + tagval.assign(token, p+1, std::string::npos); + token.erase(p); + } + else + tagval.clear(); + + HandleTag(user, token, tagval, parseoutput.tags); + } + + + // Try to read the prefix or command name. + if (!tokens.GetMiddle(token)) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + } + + if (token[0] == ':') + { + // If this exists then the client sent a prefix as part of their + // message. Section 2.3 of RFC 1459 technically says we should only + // allow the nick of the client here but in practise everyone just + // ignores it so we will copy them. + + // Try to read the command name. + if (!tokens.GetMiddle(token)) + { + // Discourage the user from flooding the server. + user->CommandFloodPenalty += 2000; + return false; + } + } + + parseoutput.cmd.assign(token); + + // Build the parameter map. We intentionally do not respect the RFC 1459 + // thirteen parameter limit here. + while (tokens.GetTrailing(token)) + parseoutput.params.push_back(token); + + return true; +} + +void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line) +{ + char prefix = '@'; // First tag name is prefixed with a '@' + for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i) + { + if (!tagwl.IsSelected(tags, i)) + continue; + + const std::string::size_type prevsize = line.size(); + line.push_back(prefix); + prefix = ';'; // Remaining tags are prefixed with ';' + line.append(i->first); + const std::string& val = i->second.value; + if (!val.empty()) + { + line.push_back('='); + line.append(val); + } + + // The tags part of the message mustn't grow longer than what is allowed by the spec. If it does, + // remove last tag and stop adding more tags. + // + // One is subtracted from the limit before comparing because there must be a ' ' char after the last tag + // which also counts towards the limit. + if (line.size() > MAX_MESSAGE_TAG_LENGTH-1) + { + line.erase(prevsize); + break; + } + } + + if (!line.empty()) + line.push_back(' '); +} + +ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const +{ + std::string line; + SerializeTags(msg.GetTags(), tagwl, line); + + // Save position for length calculation later + const std::string::size_type rfcmsg_begin = line.size(); + + if (msg.GetSource()) + { + line.push_back(':'); + line.append(*msg.GetSource()); + line.push_back(' '); + } + line.append(msg.GetCommand()); + + const ClientProtocol::Message::ParamList& params = msg.GetParams(); + if (!params.empty()) + { + for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i) + { + const std::string& param = *i; + line.push_back(' '); + line.append(param); + } + + line.append(" :", 2).append(params.back()); + } + + // Truncate if too long + std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2; + if (line.length() - rfcmsg_begin > maxline) + line.erase(rfcmsg_begin + maxline); + + line.append("\r\n", 2); + return line; +} + +class ModuleCoreRFCSerializer : public Module +{ + RFCSerializer rfcserializer; + + public: + ModuleCoreRFCSerializer() + : rfcserializer(this) + { + } + + void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE + { + if (type != ExtensionItem::EXT_USER) + return; + + LocalUser* const user = IS_LOCAL(static_cast<User*>(item)); + if ((user) && (user->serializer == &rfcserializer)) + ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading"); + } + + void OnUserInit(LocalUser* user) CXX11_OVERRIDE + { + if (!user->serializer) + user->serializer = &rfcserializer; + } + + Version GetVersion() + { + return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR); + } +}; + +MODULE_INIT(ModuleCoreRFCSerializer) diff --git a/src/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp index 6e4e547c1..e4e7a5056 100644 --- a/src/coremods/core_user/core_user.cpp +++ b/src/coremods/core_user/core_user.cpp @@ -57,13 +57,13 @@ class CommandPass : public SplitCommand /** Handle /PING. */ -class CommandPing : public Command +class CommandPing : public SplitCommand { public: /** Constructor for ping. */ CommandPing(Module* parent) - : Command(parent, "PING", 1, 2) + : SplitCommand(parent, "PING", 1, 2) { syntax = "<servername> [:<servername>]"; } @@ -73,9 +73,10 @@ class CommandPing : public Command * @param user The user issuing the command * @return A value from CmdResult to indicate command success or failure. */ - CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE + CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE { - user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str()); + ClientProtocol::Messages::Pong pong(parameters[0]); + user->Send(ServerInstance->GetRFCEvents().pong, pong); return CMD_SUCCESS; } }; diff --git a/src/coremods/core_wallops.cpp b/src/coremods/core_wallops.cpp index 856fcea74..26a00a47d 100644 --- a/src/coremods/core_wallops.cpp +++ b/src/coremods/core_wallops.cpp @@ -25,6 +25,7 @@ class CommandWallops : public Command { SimpleUserModeHandler wallopsmode; + ClientProtocol::EventProvider protoevprov; public: /** Constructor for wallops. @@ -32,6 +33,7 @@ class CommandWallops : public Command CommandWallops(Module* parent) : Command(parent, "WALLOPS", 1, 1) , wallopsmode(parent, "wallops", 'w') + , protoevprov(parent, name) { flags_needed = 'o'; syntax = "<any-text>"; @@ -52,15 +54,16 @@ class CommandWallops : public Command CmdResult CommandWallops::Handle(User* user, const Params& parameters) { - std::string wallop("WALLOPS :"); - wallop.append(parameters[0]); + ClientProtocol::Message msg("WALLOPS", user); + msg.PushParamRef(parameters[0]); + ClientProtocol::Event wallopsevent(protoevprov, msg); const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) { - User* t = *i; - if (t->IsModeSet(wallopsmode)) - t->WriteFrom(user, wallop); + LocalUser* curr = *i; + if (curr->IsModeSet(wallopsmode)) + curr->Send(wallopsevent); } return CMD_SUCCESS; diff --git a/src/inspircd.cpp b/src/inspircd.cpp index bb27c718e..aa3fb9612 100644 --- a/src/inspircd.cpp +++ b/src/inspircd.cpp @@ -271,6 +271,16 @@ InspIRCd::InspIRCd(int argc, char** argv) : srandom(TIME.tv_nsec ^ TIME.tv_sec); #endif + { + ServiceProvider* provs[] = + { + &rfcevents.numeric, &rfcevents.join, &rfcevents.part, &rfcevents.kick, &rfcevents.quit, &rfcevents.nick, + &rfcevents.mode, &rfcevents.topic, &rfcevents.privmsg, &rfcevents.invite, &rfcevents.ping, &rfcevents.pong, + &rfcevents.error + }; + Modules.AddServices(provs, sizeof(provs)/sizeof(provs[0])); + } + struct option longopts[] = { { "nofork", no_argument, &do_nofork, 1 }, diff --git a/src/mode.cpp b/src/mode.cpp index 9d17f5be8..71fce24d8 100644 --- a/src/mode.cpp +++ b/src/mode.cpp @@ -438,14 +438,9 @@ void ModeParser::Process(User* user, Channel* targetchannel, User* targetuser, M unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags, unsigned int beginindex) { - LastParse.clear(); LastChangeList.clear(); unsigned int modes_processed = 0; - std::string output_mode; - std::string output_parameters; - - char output_pm = '\0'; // current output state, '+' or '-' Modes::ChangeList::List& list = changelist.getlist(); for (Modes::ChangeList::List::iterator i = list.begin()+beginindex; i != list.end(); ++i) { @@ -478,43 +473,30 @@ unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* if (ma != MODEACTION_ALLOW) continue; - char needed_pm = item.adding ? '+' : '-'; - if (needed_pm != output_pm) - { - output_pm = needed_pm; - output_mode.append(1, output_pm); - } - output_mode.push_back(mh->GetModeChar()); - - if (!item.param.empty()) - { - output_parameters.push_back(' '); - output_parameters.append(item.param); - } LastChangeList.push(mh, item.adding, item.param); - if ((output_mode.length() + output_parameters.length() > 450) - || (output_mode.length() > 100) - || (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes)) + if (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes) { /* mode sequence is getting too long */ break; } } - if (!output_mode.empty()) + if (!LastChangeList.empty()) { - LastParse = targetchannel ? targetchannel->name : targetuser->nick; - LastParse.append(" "); - LastParse.append(output_mode); - LastParse.append(output_parameters); - + ClientProtocol::Events::Mode modeevent(user, targetchannel, targetuser, LastChangeList); if (targetchannel) - targetchannel->WriteChannel(user, "MODE " + LastParse); + { + targetchannel->Write(modeevent); + } else - targetuser->WriteFrom(user, "MODE " + LastParse); + { + LocalUser* localtarget = IS_LOCAL(targetuser); + if (localtarget) + localtarget->Send(modeevent); + } - FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags, output_mode)); + FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags)); } return modes_processed; diff --git a/src/modules.cpp b/src/modules.cpp index 8f2d874dc..7912fb569 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -79,7 +79,7 @@ void Module::OnUserPart(Membership*, std::string&, CUList&) { DetachEvent(I_OnU void Module::OnPreRehash(User*, const std::string&) { DetachEvent(I_OnPreRehash); } void Module::OnModuleRehash(User*, const std::string&) { DetachEvent(I_OnModuleRehash); } ModResult Module::OnUserPreJoin(LocalUser*, Channel*, const std::string&, std::string&, const std::string&) { DetachEvent(I_OnUserPreJoin); return MOD_RES_PASSTHRU; } -void Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag, const std::string&) { DetachEvent(I_OnMode); } +void Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag) { DetachEvent(I_OnMode); } void Module::OnOper(User*, const std::string&) { DetachEvent(I_OnOper); } void Module::OnPostOper(User*, const std::string&, const std::string &) { DetachEvent(I_OnPostOper); } void Module::OnPostDeoper(User*) { DetachEvent(I_OnPostDeoper); } @@ -138,6 +138,7 @@ ModResult Module::OnSendWhoLine(User*, const std::vector<std::string>&, User*, M void Module::OnSetUserIP(LocalUser*) { DetachEvent(I_OnSetUserIP); } void Module::OnServiceAdd(ServiceProvider&) { DetachEvent(I_OnServiceAdd); } void Module::OnServiceDel(ServiceProvider&) { DetachEvent(I_OnServiceDel); } +ModResult Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; } #ifdef INSPIRCD_ENABLE_TESTSUITE void Module::OnRunTestSuite() { } diff --git a/src/modules/m_alias.cpp b/src/modules/m_alias.cpp index 76ccc6ebc..75ab57e94 100644 --- a/src/modules/m_alias.cpp +++ b/src/modules/m_alias.cpp @@ -129,7 +129,7 @@ class ModuleAlias : public Module return word; } - std::string CreateRFCMessage(const std::string& command, Command::Params& parameters) + std::string CreateRFCMessage(const std::string& command, CommandBase::Params& parameters) { std::string message(command); for (CommandBase::Params::const_iterator iter = parameters.begin(); iter != parameters.end();) diff --git a/src/modules/m_auditorium.cpp b/src/modules/m_auditorium.cpp index 7acbd2fff..8485f1d7a 100644 --- a/src/modules/m_auditorium.cpp +++ b/src/modules/m_auditorium.cpp @@ -32,6 +32,29 @@ class AuditoriumMode : public SimpleChannelModeHandler } }; +class ModuleAuditorium; + +namespace +{ + +/** Hook handler for join client protocol events. + * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY). + * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module + * such as delayjoin or hostcycle generates a join. + */ +class JoinHook : public ClientProtocol::EventHook +{ + ModuleAuditorium* const parentmod; + bool active; + + public: + JoinHook(ModuleAuditorium* mod); + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE; + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE; +}; + +} + class ModuleAuditorium : public Module { CheckExemption::EventProvider exemptionprov; @@ -39,11 +62,13 @@ class ModuleAuditorium : public Module bool OpsVisible; bool OpsCanSee; bool OperCanSee; + JoinHook joinhook; public: ModuleAuditorium() : exemptionprov(this) , aum(this) + , joinhook(this) { } @@ -115,11 +140,6 @@ class ModuleAuditorium : public Module } } - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE - { - BuildExcept(memb, excepts); - } - void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE { BuildExcept(memb, excepts); @@ -165,4 +185,25 @@ class ModuleAuditorium : public Module } }; +JoinHook::JoinHook(ModuleAuditorium* mod) + : ClientProtocol::EventHook(mod, "JOIN", 10) + , parentmod(mod) +{ +} + +void JoinHook::OnEventInit(const ClientProtocol::Event& ev) +{ + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + active = !parentmod->IsVisible(join.GetMember()); +} + +ModResult JoinHook::OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) +{ + if (!active) + return MOD_RES_PASSTHRU; + + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + return ((parentmod->CanSee(user, join.GetMember())) ? MOD_RES_PASSTHRU : MOD_RES_DENY); +} + MODULE_INIT(ModuleAuditorium) diff --git a/src/modules/m_cap.cpp b/src/modules/m_cap.cpp index 80e70d3e5..922061757 100644 --- a/src/modules/m_cap.cpp +++ b/src/modules/m_cap.cpp @@ -341,16 +341,35 @@ void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, co managerimpl->HandleReq(user, caplist); } +class CapMessage : public Cap::MessageBase +{ + public: + CapMessage(LocalUser* user, const std::string& subcmd, const std::string& result) + : Cap::MessageBase(subcmd) + { + SetUser(user); + PushParamRef(result); + } +}; + class CommandCap : public SplitCommand { Events::ModuleEventProvider evprov; Cap::ManagerImpl manager; + ClientProtocol::EventProvider protoevprov; - static void DisplayResult(LocalUser* user, std::string& result) + void DisplayResult(LocalUser* user, const std::string& subcmd, std::string& result) { if (*result.rbegin() == ' ') result.erase(result.end()-1); - user->WriteCommand("CAP", result); + DisplayResult2(user, subcmd, result); + } + + void DisplayResult2(LocalUser* user, const std::string& subcmd, const std::string& result) + { + CapMessage msg(user, subcmd, result); + ClientProtocol::Event ev(protoevprov, msg); + user->Send(ev); } public: @@ -360,6 +379,7 @@ class CommandCap : public SplitCommand : SplitCommand(mod, "CAP", 1) , evprov(mod, "event/cap") , manager(mod, evprov) + , protoevprov(mod, name) , holdext("cap_hold", ExtensionItem::EXT_USER, mod) { works_before_reg = true; @@ -378,9 +398,8 @@ class CommandCap : public SplitCommand if (parameters.size() < 2) return CMD_FAILURE; - std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :"); - result.append(parameters[1]); - user->WriteCommand("CAP", result); + const std::string replysubcmd = (manager.HandleReq(user, parameters[1]) ? "ACK" : "NAK"); + DisplayResult2(user, replysubcmd, parameters[1]); } else if (subcommand == "END") { @@ -392,16 +411,16 @@ class CommandCap : public SplitCommand if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302")) manager.Set302Protocol(user); - std::string result = subcommand + " :"; + std::string result; // Show values only if supports v3.2 and doing LS manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY))); - DisplayResult(user, result); + DisplayResult(user, subcommand, result); } else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY)) { - std::string result = "ACK :"; + std::string result; manager.HandleClear(user, result); - DisplayResult(user, result); + DisplayResult(user, "ACK", result); } else { diff --git a/src/modules/m_chanhistory.cpp b/src/modules/m_chanhistory.cpp index 081731126..0c3945346 100644 --- a/src/modules/m_chanhistory.cpp +++ b/src/modules/m_chanhistory.cpp @@ -22,8 +22,15 @@ struct HistoryItem { time_t ts; - std::string line; - HistoryItem(const std::string& Line) : ts(ServerInstance->Time()), line(Line) {} + std::string text; + std::string sourcemask; + + HistoryItem(User* source, const std::string& Text) + : ts(ServerInstance->Time()) + , text(Text) + , sourcemask(source->GetFullHost()) + { + } }; struct HistoryList @@ -136,8 +143,7 @@ class ModuleChanHistory : public Module HistoryList* list = m.ext.get(c); if (list) { - const std::string line = ":" + user->GetFullHost() + " PRIVMSG " + c->name + " :" + details.text; - list->lines.push_back(HistoryItem(line)); + list->lines.push_back(HistoryItem(user, details.text)); if (list->lines.size() > list->maxlen) list->lines.pop_front(); } @@ -146,7 +152,8 @@ class ModuleChanHistory : public Module void OnPostJoin(Membership* memb) CXX11_OVERRIDE { - if (IS_REMOTE(memb->user)) + LocalUser* localuser = IS_LOCAL(memb->user); + if (!localuser) return; if (memb->user->IsModeSet(botmode) && !dobots) @@ -169,8 +176,12 @@ class ModuleChanHistory : public Module for(std::deque<HistoryItem>::iterator i = list->lines.begin(); i != list->lines.end(); ++i) { - if (i->ts >= mintime) - memb->user->Write(i->line); + const HistoryItem& item = *i; + if (item.ts >= mintime) + { + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, item.sourcemask, memb->chan, item.text); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); + } } } diff --git a/src/modules/m_chanlog.cpp b/src/modules/m_chanlog.cpp index f618a539c..85e7ca2eb 100644 --- a/src/modules/m_chanlog.cpp +++ b/src/modules/m_chanlog.cpp @@ -70,7 +70,8 @@ class ModuleChanLog : public Module Channel *c = ServerInstance->FindChan(it->second); if (c) { - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), snotice.c_str()); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->Config->ServerName, c, snotice); + c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg); ServerInstance->PI->SendMessage(c, 0, snotice); } } diff --git a/src/modules/m_cloaking.cpp b/src/modules/m_cloaking.cpp index c277759d1..b9ff085c3 100644 --- a/src/modules/m_cloaking.cpp +++ b/src/modules/m_cloaking.cpp @@ -313,7 +313,13 @@ class ModuleCloaking : public Module if (u->IsModeSet(cu) && !cu.active) { u->SetMode(cu, false); - u->WriteCommand("MODE", "-" + ConvToStr(cu.GetModeChar())); + + if (!IS_LOCAL(u)) + return; + Modes::ChangeList modechangelist; + modechangelist.push_remove(&cu); + ClientProtocol::Events::Mode modeevent(ServerInstance->FakeClient, NULL, u, modechangelist); + static_cast<LocalUser*>(u)->Send(modeevent); } cu.active = false; } diff --git a/src/modules/m_conn_waitpong.cpp b/src/modules/m_conn_waitpong.cpp index b4441c88c..f2e9590c8 100644 --- a/src/modules/m_conn_waitpong.cpp +++ b/src/modules/m_conn_waitpong.cpp @@ -46,8 +46,10 @@ class ModuleWaitPong : public Module ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE { std::string pingrpl = ServerInstance->GenRandomStr(10); - - user->Write("PING :%s", pingrpl.c_str()); + { + ClientProtocol::Messages::Ping pingmsg(pingrpl); + user->Send(ServerInstance->GetRFCEvents().ping, pingmsg); + } if(sendsnotice) user->WriteNotice("*** If you are having problems connecting due to ping timeouts, please type /quote PONG " + pingrpl + " or /raw PONG " + pingrpl + " now."); diff --git a/src/modules/m_delayjoin.cpp b/src/modules/m_delayjoin.cpp index f9cd837d7..7c557eb35 100644 --- a/src/modules/m_delayjoin.cpp +++ b/src/modules/m_delayjoin.cpp @@ -33,14 +33,50 @@ class DelayJoinMode : public ModeHandler ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE; }; + +namespace +{ + +/** Hook handler for join client protocol events. + * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY). + * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module + * such as hostcycle generates a join. + */ +class JoinHook : public ClientProtocol::EventHook +{ + const LocalIntExt& unjoined; + + public: + JoinHook(Module* mod, const LocalIntExt& unjoinedref) + : ClientProtocol::EventHook(mod, "JOIN", 10) + , unjoined(unjoinedref) + { + } + + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + const User* const u = join.GetMember()->user; + if ((unjoined.get(u)) && (u != user)) + return MOD_RES_DENY; + return MOD_RES_PASSTHRU; + } +}; + +} + class ModuleDelayJoin : public Module { DelayJoinMode djm; + void RevealUser(User* user, Channel* chan); public: LocalIntExt unjoined; + JoinHook joinhook; + ModuleDelayJoin() : djm(this) , unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this) + , joinhook(this, unjoined) { } @@ -68,7 +104,7 @@ ModeAction DelayJoinMode::OnModeChange(User* source, User* dest, Channel* channe * they remain permanently invisible on this channel! */ MessageTarget msgtarget(channel, 0); - MessageDetails msgdetails(MSG_PRIVMSG, ""); + MessageDetails msgdetails(MSG_PRIVMSG, "", ClientProtocol::TagMap()); const Channel::MemberMap& users = channel->GetUsers(); for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n) { @@ -111,10 +147,7 @@ static void populate(CUList& except, Membership* memb) void ModuleDelayJoin::OnUserJoin(Membership* memb, bool sync, bool created, CUList& except) { if (memb->chan->IsModeSet(djm)) - { unjoined.set(memb, 1); - populate(except, memb); - } } void ModuleDelayJoin::OnUserPart(Membership* memb, std::string &partmessage, CUList& except) @@ -147,20 +180,20 @@ void ModuleDelayJoin::OnUserMessage(User* user, const MessageTarget& target, con return; Channel* channel = target.Get<Channel>(); + RevealUser(user, channel); +} - Membership* memb = channel->GetUser(user); +void ModuleDelayJoin::RevealUser(User* user, Channel* chan) +{ + Membership* memb = chan->GetUser(user); if (!memb || !unjoined.set(memb, 0)) return; /* Display the join to everyone else (the user who joined got it earlier) */ - channel->WriteAllExceptSender(user, false, 0, "JOIN %s", channel->name.c_str()); - - std::string ms = memb->modes; - for(unsigned int i=0; i < memb->modes.length(); i++) - ms.append(" ").append(user->nick); - - if (ms.length() > 0) - channel->WriteAllExceptSender(user, false, 0, "MODE %s +%s", channel->name.c_str(), ms.c_str()); + CUList except_list; + except_list.insert(user); + ClientProtocol::Events::Join joinevent(memb); + chan->Write(joinevent, 0, except_list); } /* make the user visible if he receives any mode change */ @@ -182,9 +215,7 @@ ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, ModeHandler* if (!dest) return MOD_RES_PASSTHRU; - Membership* memb = channel->GetUser(dest); - if (memb && unjoined.set(memb, 0)) - channel->WriteAllExceptSender(dest, false, 0, "JOIN %s", channel->name.c_str()); + RevealUser(dest, channel); return MOD_RES_PASSTHRU; } diff --git a/src/modules/m_hostcycle.cpp b/src/modules/m_hostcycle.cpp index 0f7405dcc..a3c81df6e 100644 --- a/src/modules/m_hostcycle.cpp +++ b/src/modules/m_hostcycle.cpp @@ -24,13 +24,16 @@ class ModuleHostCycle : public Module { Cap::Reference chghostcap; + const std::string quitmsghost; + const std::string quitmsgident; /** Send fake quit/join/mode messages for host or ident cycle. */ - void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg) + void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const std::string& reason) { - // GetFullHost() returns the original data at the time this function is called - const std::string quitline = ":" + user->GetFullHost() + " QUIT :" + quitmsg; + // The user has the original ident/host at the time this function is called + ClientProtocol::Messages::Quit quitmsg(user, reason); + ClientProtocol::Event quitevent(ServerInstance->GetRFCEvents().quit, quitmsg); already_sent_t silent_id = ServerInstance->Users.NextAlreadySentId(); already_sent_t seen_id = ServerInstance->Users.NextAlreadySentId(); @@ -50,7 +53,7 @@ class ModuleHostCycle : public Module if (i->second) { u->already_sent = seen_id; - u->Write(quitline); + u->Send(quitevent); } else { @@ -65,17 +68,8 @@ class ModuleHostCycle : public Module { Membership* memb = *i; Channel* c = memb->chan; - const std::string joinline = ":" + newfullhost + " JOIN " + c->name; - std::string modeline; - if (!memb->modes.empty()) - { - modeline = ":" + (ServerInstance->Config->CycleHostsFromUser ? newfullhost : ServerInstance->Config->ServerName) - + " MODE " + c->name + " +" + memb->modes; - - for (size_t j = 0; j < memb->modes.length(); j++) - modeline.append(" ").append(user->nick); - } + ClientProtocol::Events::Join joinevent(memb, newfullhost); const Channel::MemberMap& ulist = c->GetUsers(); for (Channel::MemberMap::const_iterator j = ulist.begin(); j != ulist.end(); ++j) @@ -90,13 +84,11 @@ class ModuleHostCycle : public Module if (u->already_sent != seen_id) { - u->Write(quitline); + u->Send(quitevent); u->already_sent = seen_id; } - u->Write(joinline); - if (!memb->modes.empty()) - u->Write(modeline); + u->Send(joinevent); } } } @@ -104,17 +96,19 @@ class ModuleHostCycle : public Module public: ModuleHostCycle() : chghostcap(this, "chghost") + , quitmsghost("Changing host") + , quitmsgident("Changing ident") { } void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE { - DoHostCycle(user, newident, user->GetDisplayedHost(), "Changing ident"); + DoHostCycle(user, newident, user->GetDisplayedHost(), quitmsgident); } void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE { - DoHostCycle(user, user->ident, newhost, "Changing host"); + DoHostCycle(user, user->ident, newhost, quitmsghost); } Version GetVersion() CXX11_OVERRIDE diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp index 92e8a0881..14b1cf8a1 100644 --- a/src/modules/m_ircv3.cpp +++ b/src/modules/m_ircv3.cpp @@ -22,24 +22,110 @@ #include "modules/cap.h" #include "modules/ircv3.h" +class AwayMessage : public ClientProtocol::Message +{ + public: + AwayMessage(User* user) + : ClientProtocol::Message("AWAY", user) + { + SetParams(user, user->awaymsg); + } + + AwayMessage() + : ClientProtocol::Message("AWAY") + { + } + + void SetParams(User* user, const std::string& awaymsg) + { + // Going away: 1 parameter which is the away reason + // Back from away: no parameter + if (!awaymsg.empty()) + PushParam(awaymsg); + } +}; + +class JoinHook : public ClientProtocol::EventHook +{ + ClientProtocol::Events::Join extendedjoinmsg; + + public: + const std::string asterisk; + ClientProtocol::EventProvider awayprotoev; + AwayMessage awaymsg; + Cap::Capability extendedjoincap; + Cap::Capability awaycap; + + JoinHook(Module* mod) + : ClientProtocol::EventHook(mod, "JOIN") + , asterisk(1, '*') + , awayprotoev(mod, "AWAY") + , extendedjoincap(mod, "extended-join") + , awaycap(mod, "away-notify") + { + } + + void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE + { + const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev); + + // An extended join has two extra parameters: + // First the account name of the joining user or an asterisk if the user is not logged in. + // The second parameter is the realname of the joining user. + + Membership* const memb = join.GetMember(); + const std::string* account = &asterisk; + const AccountExtItem* const accountext = GetAccountExtItem(); + if (accountext) + { + const std::string* accountname = accountext->get(memb->user); + if (accountname) + account = accountname; + } + + extendedjoinmsg.ClearParams(); + extendedjoinmsg.SetSource(join); + extendedjoinmsg.PushParamRef(memb->chan->name); + extendedjoinmsg.PushParamRef(*account); + extendedjoinmsg.PushParamRef(memb->user->GetRealName()); + + awaymsg.ClearParams(); + if ((memb->user->IsAway()) && (awaycap.IsActive())) + { + awaymsg.SetSource(join); + awaymsg.SetParams(memb->user, memb->user->awaymsg); + } + } + + ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE + { + if (extendedjoincap.get(user)) + messagelist.front() = &extendedjoinmsg; + + if ((!awaymsg.GetParams().empty()) && (awaycap.get(user))) + messagelist.push_back(&awaymsg); + + return MOD_RES_PASSTHRU; + } +}; + class ModuleIRCv3 : public Module , public AccountEventListener , public Away::EventListener { Cap::Capability cap_accountnotify; - Cap::Capability cap_awaynotify; - Cap::Capability cap_extendedjoin; + JoinHook joinhook; - CUList last_excepts; + ClientProtocol::EventProvider accountprotoev; public: ModuleIRCv3() : AccountEventListener(this) , Away::EventListener(this) , cap_accountnotify(this, "account-notify") - , cap_awaynotify(this, "away-notify") - , cap_extendedjoin(this, "extended-join") + , joinhook(this) + , accountprotoev(this, "ACCOUNT") { } @@ -47,141 +133,41 @@ class ModuleIRCv3 { ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3"); cap_accountnotify.SetActive(conf->getBool("accountnotify", true)); - cap_awaynotify.SetActive(conf->getBool("awaynotify", true)); - cap_extendedjoin.SetActive(conf->getBool("extendedjoin", true)); + joinhook.awaycap.SetActive(conf->getBool("awaynotify", true)); + joinhook.extendedjoincap.SetActive(conf->getBool("extendedjoin", true)); } void OnAccountChange(User* user, const std::string& newaccount) CXX11_OVERRIDE { - // :nick!user@host ACCOUNT account - // or - // :nick!user@host ACCOUNT * - std::string line = ":" + user->GetFullHost() + " ACCOUNT "; - if (newaccount.empty()) - line += "*"; - else - line += newaccount; - - IRCv3::WriteNeighborsWithCap(user, line, cap_accountnotify); - } - - void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE - { - // Remember who is not going to see the JOIN because of other modules - if ((cap_awaynotify.IsActive()) && (memb->user->IsAway())) - last_excepts = excepts; - - if (!cap_extendedjoin.IsActive()) - return; - - /* - * Send extended joins to clients who have the extended-join capability. - * An extended join looks like this: - * - * :nick!user@host JOIN #chan account :realname - * - * account is the joining user's account if he's logged in, otherwise it's an asterisk (*). - */ - - std::string line; - std::string mode; - - const Channel::MemberMap& userlist = memb->chan->GetUsers(); - for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it) - { - // Send the extended join line if the current member is local, has the extended-join cap and isn't excepted - User* member = IS_LOCAL(it->first); - if ((member) && (cap_extendedjoin.get(member)) && (excepts.find(member) == excepts.end())) - { - // Construct the lines we're going to send if we haven't constructed them already - if (line.empty()) - { - bool has_account = false; - line = ":" + memb->user->GetFullHost() + " JOIN " + memb->chan->name + " "; - const AccountExtItem* accountext = GetAccountExtItem(); - if (accountext) - { - std::string* accountname; - accountname = accountext->get(memb->user); - if (accountname) - { - line += *accountname; - has_account = true; - } - } - - if (!has_account) - line += "*"; - - line += " :" + memb->user->GetRealName(); - - // If the joining user received privileges from another module then we must send them as well, - // since silencing the normal join means the MODE will be silenced as well - if (!memb->modes.empty()) - { - const std::string& modefrom = ServerInstance->Config->CycleHostsFromUser ? memb->user->GetFullHost() : ServerInstance->Config->ServerName; - mode = ":" + modefrom + " MODE " + memb->chan->name + " +" + memb->modes; - - for (unsigned int i = 0; i < memb->modes.length(); i++) - mode += " " + memb->user->nick; - } - } - - // Write the JOIN and the MODE, if any - member->Write(line); - if ((!mode.empty()) && (member != memb->user)) - member->Write(mode); - - // Prevent the core from sending the JOIN and MODE to this user - excepts.insert(it->first); - } - } + // Logged in: 1 parameter which is the account name + // Logged out: 1 parameter which is a "*" + ClientProtocol::Message msg("ACCOUNT", user); + const std::string& param = (newaccount.empty() ? joinhook.asterisk : newaccount); + msg.PushParamRef(param); + ClientProtocol::Event accountevent(accountprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, accountevent, cap_accountnotify); } void OnUserAway(User* user) CXX11_OVERRIDE { - if (!cap_awaynotify.IsActive()) + if (!joinhook.awaycap.IsActive()) return; // Going away: n!u@h AWAY :reason - const std::string line = ":" + user->GetFullHost() + " AWAY :" + user->awaymsg; - IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify); + AwayMessage msg(user); + ClientProtocol::Event awayevent(joinhook.awayprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap); } void OnUserBack(User* user) CXX11_OVERRIDE { - if (!cap_awaynotify.IsActive()) + if (!joinhook.awaycap.IsActive()) return; // Back from away: n!u@h AWAY - const std::string line = ":" + user->GetFullHost() + " AWAY"; - IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify); - } - - void OnPostJoin(Membership *memb) CXX11_OVERRIDE - { - if ((!cap_awaynotify.IsActive()) || (!memb->user->IsAway())) - return; - - std::string line = ":" + memb->user->GetFullHost() + " AWAY :" + memb->user->awaymsg; - - const Channel::MemberMap& userlist = memb->chan->GetUsers(); - for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it) - { - // Send the away notify line if the current member is local, has the away-notify cap and isn't excepted - User* member = IS_LOCAL(it->first); - if ((member) && (cap_awaynotify.get(member)) && (last_excepts.find(member) == last_excepts.end()) && (it->second != memb)) - { - member->Write(line); - } - } - - last_excepts.clear(); - } - - void Prioritize() CXX11_OVERRIDE - { - ServerInstance->Modules->SetPriority(this, I_OnUserJoin, PRIORITY_LAST); + AwayMessage msg(user); + ClientProtocol::Event awayevent(joinhook.awayprotoev, msg); + IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap); } Version GetVersion() CXX11_OVERRIDE diff --git a/src/modules/m_ircv3_capnotify.cpp b/src/modules/m_ircv3_capnotify.cpp index 93c30df12..757b0858f 100644 --- a/src/modules/m_ircv3_capnotify.cpp +++ b/src/modules/m_ircv3_capnotify.cpp @@ -46,19 +46,53 @@ class CapNotify : public Cap::Capability } }; +class CapNotifyMessage : public Cap::MessageBase +{ + public: + CapNotifyMessage(bool add, const std::string& capname) + : Cap::MessageBase((add ? "NEW" : "DEL")) + { + PushParamRef(capname); + } +}; + +class CapNotifyValueMessage : public Cap::MessageBase +{ + std::string s; + const std::string::size_type pos; + + public: + CapNotifyValueMessage(const std::string& capname) + : Cap::MessageBase("NEW") + , s(capname) + , pos(s.size()+1) + { + s.push_back('='); + PushParamRef(s); + } + + void SetCapValue(const std::string& capvalue) + { + s.erase(pos); + s.append(capvalue); + InvalidateCache(); + } +}; + class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener { CapNotify capnotify; std::string reloadedmod; std::vector<std::string> reloadedcaps; + ClientProtocol::EventProvider protoev; void Send(const std::string& capname, Cap::Capability* cap, bool add) { - std::string msg = (add ? "NEW :" : "DEL :"); - msg.append(capname); - std::string msgwithval = msg; - msgwithval.push_back('='); - std::string::size_type msgpos = msgwithval.size(); + CapNotifyMessage msg(add, capname); + CapNotifyValueMessage msgwithval(capname); + + ClientProtocol::Event event(protoev, msg); + ClientProtocol::Event eventwithval(protoev, msgwithval); const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers(); for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i) @@ -73,13 +107,14 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re const std::string* capvalue = cap->GetValue(user); if ((capvalue) && (!capvalue->empty())) { - msgwithval.append(*capvalue); - user->WriteCommand("CAP", msgwithval); - msgwithval.erase(msgpos); + msgwithval.SetUser(user); + msgwithval.SetCapValue(*capvalue); + user->Send(eventwithval); continue; } } - user->WriteCommand("CAP", msg); + msg.SetUser(user); + user->Send(event); } } @@ -88,6 +123,7 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re : Cap::EventListener(this) , ReloadModule::EventListener(this) , capnotify(this) + , protoev(this, "CAP_NOTIFY") { } diff --git a/src/modules/m_ircv3_chghost.cpp b/src/modules/m_ircv3_chghost.cpp index 0a9e055b4..aa53612cb 100644 --- a/src/modules/m_ircv3_chghost.cpp +++ b/src/modules/m_ircv3_chghost.cpp @@ -24,17 +24,21 @@ class ModuleIRCv3ChgHost : public Module { Cap::Capability cap; + ClientProtocol::EventProvider protoevprov; void DoChgHost(User* user, const std::string& ident, const std::string& host) { - std::string line(1, ':'); - line.append(user->GetFullHost()).append(" CHGHOST ").append(ident).append(1, ' ').append(host); - IRCv3::WriteNeighborsWithCap(user, line, cap); + ClientProtocol::Message msg("CHGHOST", user); + msg.PushParamRef(ident); + msg.PushParamRef(host); + ClientProtocol::Event protoev(protoevprov, msg); + IRCv3::WriteNeighborsWithCap(user, protoev, cap); } public: ModuleIRCv3ChgHost() : cap(this, "chghost") + , protoevprov(this, "CHGHOST") { } diff --git a/src/modules/m_ircv3_echomessage.cpp b/src/modules/m_ircv3_echomessage.cpp index 056b02194..702552ea7 100644 --- a/src/modules/m_ircv3_echomessage.cpp +++ b/src/modules/m_ircv3_echomessage.cpp @@ -21,8 +21,6 @@ #include "inspircd.h" #include "modules/cap.h" -static const char* MessageTypeStringSp[] = { "PRIVMSG ", "NOTICE " }; - class ModuleIRCv3EchoMessage : public Module { Cap::Capability cap; @@ -38,27 +36,31 @@ class ModuleIRCv3EchoMessage : public Module if (!cap.get(user)) return; - std::string msg = MessageTypeStringSp[details.type]; + // Caps are only set on local users + LocalUser* const localuser = static_cast<LocalUser*>(user); + + const std::string& text = details.echooriginal ? details.originaltext : details.text; if (target.type == MessageTarget::TYPE_USER) { User* destuser = target.Get<User>(); - msg.append(destuser->nick); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, destuser, text, details.type); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } else if (target.type == MessageTarget::TYPE_CHANNEL) { - if (target.status) - msg.push_back(target.status); - Channel* chan = target.Get<Channel>(); - msg.append(chan->name); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, text, details.type, target.status); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } else { const std::string* servername = target.Get<std::string>(); - msg.append(*servername); + ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, *servername, text, details.type); + privmsg.AddTags(details.tags_in); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg); } - msg.append(" :").append(details.echooriginal ? details.originaltext : details.text); - user->WriteFrom(user, msg); } void OnUserMessageBlocked(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE diff --git a/src/modules/m_ircv3_invitenotify.cpp b/src/modules/m_ircv3_invitenotify.cpp index 3783ff33c..bcb6f51d5 100644 --- a/src/modules/m_ircv3_invitenotify.cpp +++ b/src/modules/m_ircv3_invitenotify.cpp @@ -32,8 +32,8 @@ class ModuleIRCv3InviteNotify : public Module void OnUserInvite(User* source, User* dest, Channel* chan, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE { - std::string msg = "INVITE "; - msg.append(dest->nick).append(1, ' ').append(chan->name); + ClientProtocol::Messages::Invite invitemsg(source, dest, chan); + ClientProtocol::Event inviteevent(ServerInstance->GetRFCEvents().invite, invitemsg); const Channel::MemberMap& users = chan->GetUsers(); for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i) { @@ -47,8 +47,10 @@ class ModuleIRCv3InviteNotify : public Module if (memb->getRank() < notifyrank) continue; + // Caps are only set on local users + LocalUser* const localuser = static_cast<LocalUser*>(user); // Send and add the user to the exceptions so they won't get the NOTICE invite announcement message - user->WriteFrom(source, msg); + localuser->Send(inviteevent); notifyexcepts.insert(user); } } diff --git a/src/modules/m_knock.cpp b/src/modules/m_knock.cpp index a0a8455a8..1b66e01a4 100644 --- a/src/modules/m_knock.cpp +++ b/src/modules/m_knock.cpp @@ -81,7 +81,13 @@ class CommandKnock : public Command c->WriteNotice(InspIRCd::Format("User %s is KNOCKing on %s (%s)", user->nick.c_str(), c->name.c_str(), parameters[1].c_str())); if (sendnumeric) - c->WriteChannelWithServ(ServerInstance->Config->ServerName, "710 %s %s %s :is KNOCKing: %s", c->name.c_str(), c->name.c_str(), user->GetFullHost().c_str(), parameters[1].c_str()); + { + Numeric::Numeric numeric(710); + numeric.push(c->name).push(user->GetFullHost()).push("is KNOCKing: " + parameters[1]); + + ClientProtocol::Messages::Numeric numericmsg(numeric, c->name); + c->Write(ServerInstance->GetRFCEvents().numeric, numericmsg); + } user->WriteNotice("KNOCKing on " + c->name); return CMD_SUCCESS; diff --git a/src/modules/m_ldapoper.cpp b/src/modules/m_ldapoper.cpp index 094b37744..cde5b00d7 100644 --- a/src/modules/m_ldapoper.cpp +++ b/src/modules/m_ldapoper.cpp @@ -48,7 +48,8 @@ class LDAPOperBase : public LDAPInterface CommandBase::Params params; params.push_back(opername); params.push_back(password); - oper_command->Handle(user, params); + ClientProtocol::TagMap tags; + oper_command->Handle(user, CommandBase::Params(params, tags)); } void Fallback() diff --git a/src/modules/m_passforward.cpp b/src/modules/m_passforward.cpp index e3f8624fc..08d3533cd 100644 --- a/src/modules/m_passforward.cpp +++ b/src/modules/m_passforward.cpp @@ -91,8 +91,8 @@ class ModulePassForward : public Module } std::string tmp; - FormatStr(tmp,forwardmsg, user); - user->WriteServ(tmp); + FormatStr(tmp, forwardmsg, user); + ServerInstance->Parser.ProcessBuffer(user, tmp); tmp.clear(); FormatStr(tmp,forwardcmd, user); diff --git a/src/modules/m_samode.cpp b/src/modules/m_samode.cpp index 195b767ab..8f28a7e9c 100644 --- a/src/modules/m_samode.cpp +++ b/src/modules/m_samode.cpp @@ -26,6 +26,8 @@ */ class CommandSamode : public Command { + bool logged; + public: bool active; CommandSamode(Module* Creator) : Command(Creator,"SAMODE", 2) @@ -55,24 +57,29 @@ class CommandSamode : public Command Modes::ChangeList emptychangelist; ServerInstance->Modes->ProcessSingle(ServerInstance->FakeClient, NULL, ServerInstance->FakeClient, emptychangelist); + logged = false; this->active = true; - CmdResult result = ServerInstance->Parser.CallHandler("MODE", parameters, user); + ServerInstance->Parser.CallHandler("MODE", parameters, user); this->active = false; - if (result == CMD_SUCCESS) + if (!logged) { - // If lastparse is empty and the MODE command handler returned CMD_SUCCESS then - // the client queried the list of a listmode (e.g. /SAMODE #chan b), which was - // handled internally by the MODE command handler. + // If we haven't logged anything yet then the client queried the list of a listmode + // (e.g. /SAMODE #chan b), which was handled internally by the MODE command handler. // - // Viewing the modes of a user or a channel can also result in CMD_SUCCESS, but + // Viewing the modes of a user or a channel could also result in this, but // that is not possible with /SAMODE because we require at least 2 parameters. - const std::string& lastparse = ServerInstance->Modes.GetLastParse(); - ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + (lastparse.empty() ? stdalgo::string::join(parameters) : lastparse)); + LogUsage(user, stdalgo::string::join(parameters)); } return CMD_SUCCESS; } + + void LogUsage(const User* user, const std::string& text) + { + logged = true; + ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + text); + } }; class ModuleSaMode : public Module @@ -96,6 +103,25 @@ class ModuleSaMode : public Module return MOD_RES_PASSTHRU; } + void OnMode(User* user, User* destuser, Channel* destchan, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE + { + if (!cmd.active) + return; + + std::string logtext = (destuser ? destuser->nick : destchan->name); + logtext.push_back(' '); + logtext += ClientProtocol::Messages::Mode::ToModeLetters(modes); + + for (Modes::ChangeList::List::const_iterator i = modes.getlist().begin(); i != modes.getlist().end(); ++i) + { + const Modes::Change& item = *i; + if (!item.param.empty()) + logtext.append(1, ' ').append(item.param); + } + + cmd.LogUsage(user, logtext); + } + void Prioritize() CXX11_OVERRIDE { Module *override = ServerInstance->Modules->Find("m_override.so"); diff --git a/src/modules/m_sasl.cpp b/src/modules/m_sasl.cpp index d37e1c90f..480f8f6db 100644 --- a/src/modules/m_sasl.cpp +++ b/src/modules/m_sasl.cpp @@ -145,7 +145,7 @@ static Events::ModuleEventProvider* saslevprov; static void SendSASL(LocalUser* user, const std::string& agent, char mode, const std::vector<std::string>& parameters) { - CommandBase::Params params(parameters.size() + 3); + CommandBase::Params params; params.push_back(user->uuid); params.push_back(agent); params.push_back(ConvToStr(mode)); @@ -157,6 +157,8 @@ static void SendSASL(LocalUser* user, const std::string& agent, char mode, const } } +static ClientProtocol::EventProvider* g_protoev; + /** * Tracks SASL authentication state like charybdis does. --nenolod */ @@ -223,7 +225,15 @@ class SaslAuthenticator return this->state; if (msg[2] == "C") - this->user->Write("AUTHENTICATE %s", msg[3].c_str()); + { + ClientProtocol::Message authmsg("AUTHENTICATE"); + authmsg.PushParamRef(msg[3]); + + ClientProtocol::Event authevent(*g_protoev, authmsg); + LocalUser* const localuser = IS_LOCAL(user); + if (localuser) + localuser->Send(authevent); + } else if (msg[2] == "D") { this->state = SASL_DONE; @@ -377,6 +387,7 @@ class ModuleSASL : public Module CommandAuthenticate auth; CommandSASL sasl; Events::ModuleEventProvider sasleventprov; + ClientProtocol::EventProvider protoev; public: ModuleSASL() @@ -386,8 +397,10 @@ class ModuleSASL : public Module , auth(this, authExt, cap) , sasl(this, authExt) , sasleventprov(this, "event/sasl") + , protoev(this, auth.name) { saslevprov = &sasleventprov; + g_protoev = &protoev; } void init() CXX11_OVERRIDE diff --git a/src/modules/m_showfile.cpp b/src/modules/m_showfile.cpp index 565aaf78b..99c545140 100644 --- a/src/modules/m_showfile.cpp +++ b/src/modules/m_showfile.cpp @@ -63,13 +63,14 @@ class CommandShowFile : public Command user->WriteRemoteNumeric(endnumeric, endtext.c_str()); } - else + else if (IS_LOCAL(user)) { - const char* msgcmd = (method == SF_MSG ? "PRIVMSG" : "NOTICE"); + LocalUser* const localuser = IS_LOCAL(user); for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i) { const std::string& line = *i; - user->WriteCommand(msgcmd, ":" + line); + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, line, ((method == SF_MSG) ? MSG_PRIVMSG : MSG_NOTICE)); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); } } return CMD_SUCCESS; diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp index 3c9089115..0ff180a83 100644 --- a/src/modules/m_spanningtree/main.cpp +++ b/src/modules/m_spanningtree/main.cpp @@ -731,7 +731,7 @@ void ModuleSpanningTree::OnUserBack(User* user) CommandAway::Builder(user).Broadcast(); } -void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode) +void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) { if (processflags & ModeParser::MODE_LOCALONLY) return; @@ -743,7 +743,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes:: CmdBuilder params(source, "MODE"); params.push(u->uuid); - params.push(output_mode); + params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes)); params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); params.Broadcast(); } @@ -752,7 +752,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes:: CmdBuilder params(source, "FMODE"); params.push(c->name); params.push_int(c->age); - params.push(output_mode); + params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes)); params.push_raw(Translate::ModeChangeListToParams(modes.getlist())); params.Broadcast(); } diff --git a/src/modules/m_spanningtree/main.h b/src/modules/m_spanningtree/main.h index 4a65f1c37..60f819e9c 100644 --- a/src/modules/m_spanningtree/main.h +++ b/src/modules/m_spanningtree/main.h @@ -172,7 +172,7 @@ class ModuleSpanningTree 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; + void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE; CullResult cull() CXX11_OVERRIDE; ~ModuleSpanningTree(); Version GetVersion() CXX11_OVERRIDE; diff --git a/src/modules/m_spanningtree/treesocket2.cpp b/src/modules/m_spanningtree/treesocket2.cpp index 3bc1fc71a..513fa6dbf 100644 --- a/src/modules/m_spanningtree/treesocket2.cpp +++ b/src/modules/m_spanningtree/treesocket2.cpp @@ -343,7 +343,8 @@ void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command, res = scmd->Handle(who, params); else { - res = cmd->Handle(who, params); + ClientProtocol::TagMap tags; + res = cmd->Handle(who, CommandBase::Params(params, tags)); if (res == CMD_INVALID) throw ProtocolException("Error in command handler"); } diff --git a/src/modules/m_sqloper.cpp b/src/modules/m_sqloper.cpp index 2b298f662..da538caef 100644 --- a/src/modules/m_sqloper.cpp +++ b/src/modules/m_sqloper.cpp @@ -143,7 +143,8 @@ class OperQuery : public SQL::Query return; // Now handle /OPER. - oper_command->Handle(user, params); + ClientProtocol::TagMap tags; + oper_command->Handle(user, CommandBase::Params(params, tags)); } else { diff --git a/src/modules/m_timedbans.cpp b/src/modules/m_timedbans.cpp index ffb84a44f..058028f61 100644 --- a/src/modules/m_timedbans.cpp +++ b/src/modules/m_timedbans.cpp @@ -113,7 +113,6 @@ class CommandTban : public Command return CMD_FAILURE; } - CUList tmp; T.mask = mask; T.expire = expire + (IS_REMOTE(user) ? 5 : 0); T.chan = channel; @@ -124,7 +123,8 @@ class CommandTban : public Command PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h'); char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@'; - channel->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, tmp, "NOTICE %s :%s", channel->name.c_str(), addban.c_str()); + ClientProtocol::Messages::Privmsg notice(ServerInstance->FakeClient, channel, addban, MSG_NOTICE); + channel->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar); ServerInstance->PI->SendChannelNotice(channel, pfxchar, addban); return CMD_SUCCESS; } @@ -210,13 +210,13 @@ class ModuleTimedBans : public Module std::string mask = i->mask; Channel* cr = i->chan; { - CUList empty; const std::string expiry = "*** Timed ban on " + cr->name + " expired."; // If halfop is loaded, send notice to halfops and above, otherwise send to ops and above PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h'); char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@'; - cr->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, empty, "NOTICE %s :%s", cr->name.c_str(), expiry.c_str()); + ClientProtocol::Messages::Privmsg notice(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, cr, expiry, MSG_NOTICE); + cr->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar); ServerInstance->PI->SendChannelNotice(cr, pfxchar, expiry); Modes::ChangeList setban; diff --git a/src/usermanager.cpp b/src/usermanager.cpp index 02c030a42..7466f385b 100644 --- a/src/usermanager.cpp +++ b/src/usermanager.cpp @@ -28,21 +28,23 @@ namespace { class WriteCommonQuit : public User::ForEachNeighborHandler { - std::string line; - std::string operline; + ClientProtocol::Messages::Quit quitmsg; + ClientProtocol::Event quitevent; + ClientProtocol::Messages::Quit operquitmsg; + ClientProtocol::Event operquitevent; void Execute(LocalUser* user) CXX11_OVERRIDE { - user->Write(user->IsOper() ? operline : line); + user->Send(user->IsOper() ? operquitevent : quitevent); } public: WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg) - : line(":" + user->GetFullHost() + " QUIT :") - , operline(line) + : quitmsg(user, msg) + , quitevent(ServerInstance->GetRFCEvents().quit, quitmsg) + , operquitmsg(user, opermsg) + , operquitevent(ServerInstance->GetRFCEvents().quit, operquitmsg) { - line += msg; - operline += opermsg; user->ForEachNeighbor(*this, false); } }; @@ -177,7 +179,12 @@ void UserManager::QuitUser(User* user, const std::string& quitreason, const std: user->quitting = true; ServerInstance->Logs->Log("USERS", LOG_DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str()); - user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str()); + LocalUser* const localuser = IS_LOCAL(user); + if (localuser) + { + ClientProtocol::Messages::Error errormsg(InspIRCd::Format("Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str())); + localuser->Send(ServerInstance->GetRFCEvents().error, errormsg); + } std::string reason; reason.assign(quitreason, 0, ServerInstance->Config->Limits.MaxQuit); @@ -264,12 +271,13 @@ void UserManager::ServerNoticeAll(const char* text, ...) { std::string message; VAFORMAT(message, text, text); - message = "NOTICE $" + ServerInstance->Config->ServerName + " :" + message; + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, ServerInstance->Config->ServerName, message, MSG_NOTICE); + ClientProtocol::Event msgevent(ServerInstance->GetRFCEvents().privmsg, msg); for (LocalList::const_iterator i = local_users.begin(); i != local_users.end(); ++i) { - User* t = *i; - t->WriteServ(message); + LocalUser* user = *i; + user->Send(msgevent); } } @@ -320,8 +328,8 @@ void UserManager::DoBackgroundUserStuff() this->QuitUser(curr, message); continue; } - - curr->Write("PING :" + ServerInstance->Config->ServerName); + ClientProtocol::Messages::Ping ping; + curr->Send(ServerInstance->GetRFCEvents().ping, ping); curr->lastping = 0; curr->nping = ServerInstance->Time() + curr->MyClass->GetPingTime(); } diff --git a/src/users.cpp b/src/users.cpp index 737a3fa5c..e05ef1853 100644 --- a/src/users.cpp +++ b/src/users.cpp @@ -26,6 +26,8 @@ #include "inspircd.h" #include "xline.h" +ClientProtocol::MessageList LocalUser::sendmsglist; + bool User::IsNoticeMaskSet(unsigned char sm) { if (!isalpha(sm)) @@ -87,6 +89,7 @@ User::User(const std::string& uid, Server* srv, UserType type) LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* servaddr) : User(ServerInstance->UIDGen.GetUID(), ServerInstance->FakeClient->server, USERTYPE_LOCAL) , eh(this) + , serializer(NULL) , bytes_in(0) , bytes_out(0) , cmds_in(0) @@ -359,7 +362,16 @@ void User::Oper(OperInfo* info) this->SetMode(opermh, true); this->oper = info; - this->WriteCommand("MODE", "+o"); + + LocalUser* localuser = IS_LOCAL(this); + if (localuser) + { + Modes::ChangeList changelist; + changelist.push_add(opermh); + ClientProtocol::Events::Mode modemsg(ServerInstance->FakeClient, NULL, localuser, changelist); + localuser->Send(modemsg); + } + FOREACH_MOD(OnOper, (this, info->name)); std::string opername; @@ -384,7 +396,7 @@ void User::Oper(OperInfo* info) ServerInstance->Users->all_opers.push_back(this); // Expand permissions from config for faster lookup - if (IS_LOCAL(this)) + if (localuser) oper->init(); FOREACH_MOD(OnPostOper, (this, oper->name, opername)); @@ -573,7 +585,10 @@ void LocalUser::FullConnect() ServerInstance->Parser.CallHandler(command, parameters, this); if (ServerInstance->Config->RawLog) - WriteServ("PRIVMSG %s :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.", nick.c_str()); + { + ClientProtocol::Messages::Privmsg rawlogmsg(ServerInstance->FakeClient, this, "*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded."); + this->Send(ServerInstance->GetRFCEvents().privmsg, rawlogmsg); + } /* * We don't set REG_ALL until triggering OnUserConnect, so some module events don't spew out stuff @@ -651,7 +666,11 @@ bool User::ChangeNick(const std::string& newnick, time_t newts) } if (this->registered == REG_ALL) - this->WriteCommon("NICK %s", newnick.c_str()); + { + ClientProtocol::Messages::Nick nickmsg(this, newnick); + ClientProtocol::Event nickevent(ServerInstance->GetRFCEvents().nick, nickmsg); + this->WriteCommonRaw(nickevent, true); + } const std::string oldnick = nick; nick = newnick; @@ -667,7 +686,10 @@ bool User::ChangeNick(const std::string& newnick, time_t newts) void LocalUser::OverruleNick() { - this->WriteFrom(this, "NICK %s", this->uuid.c_str()); + { + ClientProtocol::Messages::Nick nickmsg(this, this->uuid); + this->Send(ServerInstance->GetRFCEvents().nick, nickmsg); + } this->WriteNumeric(ERR_NICKNAMEINUSE, this->nick, "Nickname overruled."); // Clear the bit before calling ChangeNick() to make it NOT run the OnUserPostNick() hook @@ -763,35 +785,24 @@ void LocalUser::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_elin } } -static std::string wide_newline("\r\n"); - -void User::Write(const std::string& text) -{ -} - -void User::Write(const char *text, ...) -{ -} - -void LocalUser::Write(const std::string& text) +void LocalUser::Write(const ClientProtocol::SerializedMessage& text) { if (!SocketEngine::BoundsCheckFd(&eh)) return; - // The maximum size of an IRC message minus the terminating CR+LF. - const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2; - if (text.length() > maxmessage) + if (ServerInstance->Config->RawLog) { - // This should happen rarely or never. Crop the string at MaxLine and try again. - std::string try_again(text, 0, maxmessage); - Write(try_again); - return; - } + if (text.empty()) + return; + + std::string::size_type nlpos = text.find_first_of("\r\n", 0, 2); + if (nlpos == std::string::npos) + nlpos = text.length(); // TODO is this ok, test it - ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str()); + ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %.*s", uuid.c_str(), (int) nlpos, text.c_str()); + } eh.AddWriteBuf(text); - eh.AddWriteBuf(wide_newline); const size_t bytessent = text.length() + 2; ServerInstance->stats.Sent += bytessent; @@ -799,53 +810,47 @@ void LocalUser::Write(const std::string& text) this->cmds_out++; } -/** Write() - */ -void LocalUser::Write(const char *text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->Write(textbuffer); -} - -void User::WriteServ(const std::string& text) -{ - this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str()); -} - -/** WriteServ() - * Same as Write(), except `text' is prefixed with `:server.name '. - */ -void User::WriteServ(const char* text, ...) +void LocalUser::Send(ClientProtocol::Event& protoev) { - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->WriteServ(textbuffer); -} + if (!serializer) + return; -void User::WriteCommand(const char* command, const std::string& text) -{ - this->WriteServ(command + (this->registered & REG_NICK ? " " + this->nick : " *") + " " + text); + // In the most common case a static LocalUser field, sendmsglist, is passed to the event to be + // populated. The list is cleared before returning. + // To handle re-enters, if sendmsglist is non-empty upon entering the method then a temporary + // list is used instead of the static one. + if (sendmsglist.empty()) + { + Send(protoev, sendmsglist); + sendmsglist.clear(); + } + else + { + ClientProtocol::MessageList msglist; + Send(protoev, msglist); + } } -namespace +void LocalUser::Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist) { - std::string BuildNumeric(const std::string& source, User* targetuser, unsigned int num, const Command::Params& params) + // Modules can personalize the messages sent per user for the event + protoev.GetMessagesForUser(this, msglist); + for (ClientProtocol::MessageList::const_iterator i = msglist.begin(); i != msglist.end(); ++i) { - const char* const target = (targetuser->registered & REG_NICK ? targetuser->nick.c_str() : "*"); - std::string raw = InspIRCd::Format(":%s %03u %s", source.c_str(), num, target); - if (!params.empty()) - { - for (std::vector<std::string>::const_iterator i = params.begin(); i != params.end()-1; ++i) - raw.append(1, ' ').append(*i); - raw.append(" :").append(params.back()); - } - return raw; + ClientProtocol::Message& curr = **i; + ModResult res; + FIRST_MOD_RESULT(OnUserWrite, res, (this, curr)); + if (res != MOD_RES_DENY) + Write(serializer->SerializeForUser(this, curr)); } } void User::WriteNumeric(const Numeric::Numeric& numeric) { + LocalUser* const localuser = IS_LOCAL(this); + if (!localuser) + return; + ModResult MOD_RESULT; FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric)); @@ -853,24 +858,8 @@ void User::WriteNumeric(const Numeric::Numeric& numeric) if (MOD_RESULT == MOD_RES_DENY) return; - const std::string& servername = (numeric.GetServer() ? numeric.GetServer()->GetName() : ServerInstance->Config->ServerName); - this->Write(BuildNumeric(servername, this, numeric.GetNumeric(), numeric.GetParams())); -} - -void User::WriteFrom(User *user, const std::string &text) -{ - const std::string message = ":" + user->GetFullHost() + " " + text; - this->Write(message); -} - - -/* write text from an originating user to originating user */ - -void User::WriteFrom(User *user, const char* text, ...) -{ - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - this->WriteFrom(user, textbuffer); + ClientProtocol::Messages::Numeric numericmsg(numeric, localuser); + localuser->Send(ServerInstance->GetRFCEvents().numeric, numericmsg); } void User::WriteRemoteNotice(const std::string& text) @@ -887,32 +876,24 @@ namespace { class WriteCommonRawHandler : public User::ForEachNeighborHandler { - const std::string& msg; + ClientProtocol::Event& ev; void Execute(LocalUser* user) CXX11_OVERRIDE { - user->Write(msg); + user->Send(ev); } public: - WriteCommonRawHandler(const std::string& message) - : msg(message) + WriteCommonRawHandler(ClientProtocol::Event& protoev) + : ev(protoev) { } }; } -void User::WriteCommon(const char* text, ...) +void User::WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self) { - std::string textbuffer; - VAFORMAT(textbuffer, text, text); - textbuffer = ":" + this->GetFullHost() + " " + textbuffer; - this->WriteCommonRaw(textbuffer, true); -} - -void User::WriteCommonRaw(const std::string &line, bool include_self) -{ - WriteCommonRawHandler handler(line); + WriteCommonRawHandler handler(protoev); ForEachNeighbor(handler, include_self); } @@ -1203,6 +1184,16 @@ void User::PurgeEmptyChannels() this->UnOper(); } +void User::WriteNotice(const std::string& text) +{ + LocalUser* const localuser = IS_LOCAL(this); + if (!localuser) + return; + + ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, text, MSG_NOTICE); + localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); +} + const std::string& FakeUser::GetFullHost() { if (!ServerInstance->Config->HideServer.empty()) |