summaryrefslogtreecommitdiff
path: root/src/coremods
diff options
context:
space:
mode:
authorPeter Powell <petpow@saberuk.com>2018-08-13 20:17:46 +0100
committerPeter Powell <petpow@saberuk.com>2018-08-13 21:51:11 +0100
commit58a0a7e01422e62de1565a8eb0a1febdc463d04d (patch)
tree8861789deefe9df3524690de8ccd11e5366f1f2e /src/coremods
parente2a820cce21342478653a34cf8ce2b593128d035 (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.cpp14
-rw-r--r--src/coremods/core_channel/core_channel.cpp71
-rw-r--r--src/coremods/core_oper/cmd_die.cpp5
-rw-r--r--src/coremods/core_oper/cmd_kill.cpp24
-rw-r--r--src/coremods/core_oper/core_oper.h17
-rw-r--r--src/coremods/core_privmsg.cpp53
-rw-r--r--src/coremods/core_reloadmodule.cpp139
-rw-r--r--src/coremods/core_serialize_rfc.cpp222
-rw-r--r--src/coremods/core_user/core_user.cpp9
-rw-r--r--src/coremods/core_wallops.cpp13
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;