diff options
author | Peter Powell <petpow@saberuk.com> | 2018-08-13 20:17:46 +0100 |
---|---|---|
committer | Peter Powell <petpow@saberuk.com> | 2018-08-13 21:51:11 +0100 |
commit | 58a0a7e01422e62de1565a8eb0a1febdc463d04d (patch) | |
tree | 8861789deefe9df3524690de8ccd11e5366f1f2e /src/coremods | |
parent | e2a820cce21342478653a34cf8ce2b593128d035 (diff) |
Implement IRCv3 message tag support.
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
Diffstat (limited to 'src/coremods')
-rw-r--r-- | src/coremods/core_channel/cmd_invite.cpp | 14 | ||||
-rw-r--r-- | src/coremods/core_channel/core_channel.cpp | 71 | ||||
-rw-r--r-- | src/coremods/core_oper/cmd_die.cpp | 5 | ||||
-rw-r--r-- | src/coremods/core_oper/cmd_kill.cpp | 24 | ||||
-rw-r--r-- | src/coremods/core_oper/core_oper.h | 17 | ||||
-rw-r--r-- | src/coremods/core_privmsg.cpp | 53 | ||||
-rw-r--r-- | src/coremods/core_reloadmodule.cpp | 139 | ||||
-rw-r--r-- | src/coremods/core_serialize_rfc.cpp | 222 | ||||
-rw-r--r-- | src/coremods/core_user/core_user.cpp | 9 | ||||
-rw-r--r-- | src/coremods/core_wallops.cpp | 13 |
10 files changed, 511 insertions, 56 deletions
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; |