]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Implement support for IRCv3 client-to-client tags.
authorPeter Powell <petpow@saberuk.com>
Thu, 6 Sep 2018 09:09:09 +0000 (10:09 +0100)
committerPeter Powell <petpow@saberuk.com>
Tue, 19 Feb 2019 22:53:15 +0000 (22:53 +0000)
docs/conf/modules.conf.example
include/modules/ctctags.h [new file with mode: 0644]
src/coremods/core_serialize_rfc.cpp
src/modules/m_delayjoin.cpp
src/modules/m_ircv3_ctctags.cpp [new file with mode: 0644]
src/modules/m_ircv3_echomessage.cpp
src/modules/m_spanningtree/compat.cpp

index 2fa3b5042b34e9f66a466a241219c33235a8f3e8..3f7e5a9f032ec6fa4df2db9a69ae0ac07ed16138 100644 (file)
 # extension will get the chghost message and won't see host cycling.
 #<module name="ircv3_chghost">
 
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# IRCv3 client-to-client tags module: Provides the message-tags IRCv3
+# extension which allows clients to add extra data to their messages.
+# This is used to support new IRCv3 features such as replies and ids.
+#<module name="ircv3_ctctags">
+
 #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 # IRCv3 echo-message module: Provides the echo-message IRCv3
 # extension which allows capable clients to get an acknowledgement when
diff --git a/include/modules/ctctags.h b/include/modules/ctctags.h
new file mode 100644 (file)
index 0000000..d8798de
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2019 Peter Powell <petpow@saberuk.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "event.h"
+
+namespace CTCTags
+{
+       class EventListener;
+       class TagMessage;
+       class TagMessageDetails;
+}
+
+class CTCTags::TagMessage : public ClientProtocol::Message
+{
+ public:
+       TagMessage(User* source, const Channel* targetchan, const ClientProtocol::TagMap& Tags)
+               : ClientProtocol::Message("TAGMSG", source)
+       {
+               PushParamRef(targetchan->name);
+               AddTags(Tags);
+               SetSideEffect(true);
+       }
+
+       TagMessage(User* source, const User* targetuser, const ClientProtocol::TagMap& Tags)
+               : ClientProtocol::Message("TAGMSG", source)
+       {
+               if (targetuser->registered & REG_NICK)
+                       PushParamRef(targetuser->nick);
+               else
+                       PushParam("*");
+               AddTags(Tags);
+               SetSideEffect(true);
+       }
+
+       TagMessage(User* source, const char* targetstr, const ClientProtocol::TagMap& Tags)
+               : ClientProtocol::Message("TAGMSG", source)
+       {
+               PushParam(targetstr);
+               AddTags(Tags);
+               SetSideEffect(true);
+       }
+};
+
+class CTCTags::TagMessageDetails
+{
+ public:
+       /** Whether to echo the tags at all. */
+       bool echo;
+
+       /* Whether to send the original tags back to clients with echo-message support. */
+       bool echo_original;
+
+       /** The users who are exempted from receiving this message. */
+       CUList exemptions;
+
+       /** IRCv3 message tags sent to the server by the user. */
+       const ClientProtocol::TagMap tags_in;
+
+       /** IRCv3 message tags sent out to users who get this message. */
+       ClientProtocol::TagMap tags_out;
+
+       TagMessageDetails(const ClientProtocol::TagMap& tags)
+               : echo(true)
+               , echo_original(false)
+               , tags_in(tags)
+       {
+       }
+};
+
+class CTCTags::EventListener
+       : public Events::ModuleEventListener
+{
+ protected:
+       EventListener(Module* mod, unsigned int eventprio = DefaultPriority)
+               : ModuleEventListener(mod, "event/tagmsg", eventprio)
+       {
+       }
+
+ public:
+       /** Called before a user sends a tag message to a channel, a user, or a server glob mask.
+        * @param user The user sending the message.
+        * @param target The target of the message. This can either be a channel, a user, or a server
+        *               glob mask.
+        * @param details Details about the message such as the message tags or whether to echo. See the
+        *                TagMessageDetails class for more information.
+        * @return MOD_RES_ALLOW to explicitly allow the message, MOD_RES_DENY to explicitly deny the
+        *         message, or MOD_RES_PASSTHRU to let another module handle the event.
+        */
+       virtual ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, TagMessageDetails& details) { return MOD_RES_PASSTHRU; }
+       
+       /** Called immediately after a user sends a tag message to a channel, a user, or a server glob mask.
+        * @param user The user sending the message.
+        * @param target The target of the message. This can either be a channel, a user, or a server
+        *               glob mask.
+        * @param details Details about the message such as the message tags or whether to echo. See the
+        *                TagMessageDetails class for more information.
+        */
+       virtual void OnUserPostTagMessage(User* user, const MessageTarget& target, const TagMessageDetails& details) { }
+
+       /** Called immediately before a user sends a tag message to a channel, a user, or a server glob mask.
+        * @param user The user sending the message.
+        * @param target The target of the message. This can either be a channel, a user, or a server
+        *               glob mask.
+        * @param details Details about the message such as the message tags or whether to echo. See the
+        *                TagMessageDetails class for more information.
+        */
+       virtual void OnUserTagMessage(User* user, const MessageTarget& target, const TagMessageDetails& details) { }
+
+       /** Called when a tag message sent by a user to a channel, a user, or a server glob mask is blocked.
+        * @param user The user sending the message.
+        * @param target The target of the message. This can either be a channel, a user, or a server
+        *               glob mask.
+        * @param details Details about the message such as the message tags or whether to echo. See the
+        *                TagMessageDetails class for more information.
+        */
+       virtual void OnUserTagMessageBlocked(User* user, const MessageTarget& target, const TagMessageDetails& details) { }
+};
index 6b693bfb9af358db79fbc07741c18ad5ca9ed647..b8d075ab6b33bc83f3becbbd49da3e691189c8ae 100644 (file)
@@ -32,7 +32,7 @@ class RFCSerializer : public ClientProtocol::Serializer
        static const std::string::size_type MAX_CLIENT_MESSAGE_TAG_LENGTH = 4095;
 
        /** The maximum size of server-originated message tags in an outgoing message including the `@`. */
-       static const std::string::size_type MAX_SERVER_MESSAGE_TAG_LENGTH = 511;
+       static const std::string::size_type MAX_SERVER_MESSAGE_TAG_LENGTH = 4095;
 
        static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
 
index 8b06f060ae3c6600bf1401120fbd0532e16b3d6a..469f334393c36142d4976c01453322eba26b0f9f 100644 (file)
@@ -21,6 +21,7 @@
 
 
 #include "inspircd.h"
+#include "modules/ctctags.h"
 
 class DelayJoinMode : public ModeHandler
 {
@@ -72,7 +73,9 @@ class JoinHook : public ClientProtocol::EventHook
 
 }
 
-class ModuleDelayJoin : public Module
+class ModuleDelayJoin 
+       : public Module
+       , public CTCTags::EventListener
 {
  public:
        LocalIntExt unjoined;
@@ -80,7 +83,8 @@ class ModuleDelayJoin : public Module
        DelayJoinMode djm;
 
        ModuleDelayJoin()
-               : unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this)
+               : CTCTags::EventListener(this)
+               , unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this)
                , joinhook(this, unjoined)
                , djm(this, unjoined)
        {
@@ -94,6 +98,7 @@ class ModuleDelayJoin : public Module
        void OnUserKick(User* source, Membership*, const std::string &reason, CUList&) CXX11_OVERRIDE;
        void OnBuildNeighborList(User* source, IncludeChanList& include, std::map<User*, bool>& exception) CXX11_OVERRIDE;
        void OnUserMessage(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE;
+       void OnUserTagMessage(User* user, const MessageTarget& target, const CTCTags::TagMessageDetails& details) CXX11_OVERRIDE;
        ModResult OnRawMode(User* user, Channel* channel, ModeHandler* mh, const std::string& param, bool adding) CXX11_OVERRIDE;
 };
 
@@ -176,6 +181,15 @@ void ModuleDelayJoin::OnBuildNeighborList(User* source, IncludeChanList& include
        }
 }
 
+void ModuleDelayJoin::OnUserTagMessage(User* user, const MessageTarget& target, const CTCTags::TagMessageDetails& details)
+{
+       if (target.type != MessageTarget::TYPE_CHANNEL)
+               return;
+
+       Channel* channel = target.Get<Channel>();
+       djm.RevealUser(user, channel);
+}
+
 void ModuleDelayJoin::OnUserMessage(User* user, const MessageTarget& target, const MessageDetails& details)
 {
        if (target.type != MessageTarget::TYPE_CHANNEL)
diff --git a/src/modules/m_ircv3_ctctags.cpp b/src/modules/m_ircv3_ctctags.cpp
new file mode 100644 (file)
index 0000000..8684642
--- /dev/null
@@ -0,0 +1,347 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2019 Peter Powell <petpow@saberuk.com>
+ *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/cap.h"
+#include "modules/ctctags.h"
+
+class CommandTagMsg : public Command
+{
+ private:
+       Cap::Capability& cap;
+       ChanModeReference moderatedmode;
+       ChanModeReference noextmsgmode;
+       Events::ModuleEventProvider tagevprov;
+       ClientProtocol::EventProvider msgevprov;
+
+       bool FirePreEvents(User* source, MessageTarget& msgtarget, CTCTags::TagMessageDetails& msgdetails)
+       {
+               // Inform modules that a TAGMSG wants to be sent.
+               ModResult modres;
+               FIRST_MOD_RESULT_CUSTOM(tagevprov, CTCTags::EventListener, OnUserPreTagMessage, modres, (source, msgtarget, msgdetails));
+               if (modres == MOD_RES_DENY)
+               {
+                       // Inform modules that a module blocked the TAGMSG.
+                       FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserTagMessageBlocked, (source, msgtarget, msgdetails));
+                       return false;
+               }
+
+               // Inform modules that a TAGMSG is about to be sent.
+               FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserTagMessage, (source, msgtarget, msgdetails));
+               return true;
+       }
+
+       CmdResult FirePostEvent(User* source, const MessageTarget& msgtarget, const CTCTags::TagMessageDetails& msgdetails)
+       {
+               // If the source is local then update its idle time.
+               LocalUser* lsource = IS_LOCAL(source);
+               if (lsource)
+                       lsource->idle_lastmsg = ServerInstance->Time();
+
+               // Inform modules that a TAGMSG was sent.
+               FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserPostTagMessage, (source, msgtarget, msgdetails));
+               return CMD_SUCCESS;
+       }
+
+       CmdResult HandleChannelTarget(User* source, const Params& parameters, const char* target, PrefixMode* pm)
+       {
+               Channel* chan = ServerInstance->FindChan(target);
+               if (!chan)
+               {
+                       // The target channel does not exist.
+                       source->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
+                       return CMD_FAILURE;
+               }
+
+               if (IS_LOCAL(source))
+               {
+                       if (chan->IsModeSet(noextmsgmode) && !chan->HasUser(source))
+                       {
+                               // The noextmsg mode is set and the source is not in the channel.
+                               source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (no external messages)");
+                               return CMD_FAILURE;
+                       }
+
+                       bool no_chan_priv = chan->GetPrefixValue(source) < VOICE_VALUE;
+                       if (no_chan_priv && chan->IsModeSet(moderatedmode))
+                       {
+                               // The moderated mode is set and the source has no status rank.
+                               source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (+m)");
+                               return CMD_FAILURE;
+                       }
+
+                       if (no_chan_priv && ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL && chan->IsBanned(source))
+                       {
+                               // The source is banned in the channel and restrictbannedusers is enabled.
+                               if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY)
+                                       source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)");
+                               return CMD_FAILURE;
+                       }
+               }
+
+               // Fire the pre-message events.
+               MessageTarget msgtarget(chan, pm ? pm->GetPrefix() : 0);
+               CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
+               if (!FirePreEvents(source, msgtarget, msgdetails))
+                       return CMD_FAILURE;
+
+               unsigned int minrank = pm ? pm->GetPrefixRank() : 0;
+               CTCTags::TagMessage message(source, chan, parameters.GetTags());
+               const Channel::MemberMap& userlist = chan->GetUsers();
+               for (Channel::MemberMap::const_iterator iter = userlist.begin(); iter != userlist.end(); ++iter)
+               {
+                       LocalUser* luser = IS_LOCAL(iter->first);
+
+                       // Don't send to remote users or the user who is the source. 
+                       if (!luser || luser == source)
+                               continue;
+
+                       // Don't send to unprivileged or exempt users.
+                       if (iter->second->getRank() < minrank || msgdetails.exemptions.count(luser))
+                               continue;
+
+                       // Send to users if they have the capability.
+                       if (cap.get(luser))
+                               luser->Send(msgevprov, message);
+               }
+               return FirePostEvent(source, msgtarget, msgdetails);
+       }
+
+       CmdResult HandleServerTarget(User* source, const Params& parameters)
+       {
+               // If the source isn't allowed to mass message users then reject
+               // the attempt to mass-message users.
+               if (!source->HasPrivPermission("users/mass-message"))
+                       return CMD_FAILURE;
+
+               // Extract the server glob match from the target parameter.
+               std::string servername(parameters[0], 1);
+
+               // Fire the pre-message events.
+               MessageTarget msgtarget(&servername);
+               CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
+               if (!FirePreEvents(source, msgtarget, msgdetails))
+                       return CMD_FAILURE;
+
+               // If the current server name matches the server name glob then send
+               // the message out to the local users.
+               if (InspIRCd::Match(ServerInstance->Config->ServerName, servername))
+               {
+                       CTCTags::TagMessage message(source, "$*", parameters.GetTags());
+                       const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+                       for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
+                       {
+                               LocalUser* luser = IS_LOCAL(*iter);
+
+                               // Don't send to unregistered users or the user who is the source.
+                               if (luser->registered != REG_ALL || luser == source)
+                                       continue;
+
+                               // Don't send to exempt users.
+                               if (msgdetails.exemptions.count(luser))
+                                       continue;
+
+                               // Send to users if they have the capability.
+                               if (cap.get(luser))
+                                       luser->Send(msgevprov, message);
+                       }
+               }
+
+               // Fire the post-message event.
+               return FirePostEvent(source, msgtarget, msgdetails);
+       }
+
+       CmdResult HandleUserTarget(User* source, const Params& parameters)
+       {
+               User* target;
+               if (IS_LOCAL(source))
+               {
+                       // Local sources can specify either a nick or a nick@server mask as the target.
+                       const char* targetserver = strchr(parameters[0].c_str(), '@');
+                       if (targetserver)
+                       {
+                               // The target is a user on a specific server (e.g. jto@tolsun.oulu.fi).
+                               target = ServerInstance->FindNickOnly(parameters[0].substr(0, targetserver - parameters[0].c_str()));
+                               if (target && strcasecmp(target->server->GetName().c_str(), targetserver + 1))
+                                       target = NULL;
+                       }
+                       else
+                       {
+                               // If the source is a local user then we only look up the target by nick.
+                               target = ServerInstance->FindNickOnly(parameters[0]);
+                       }
+               }
+               else
+               {
+                       // Remote users can only specify a nick or UUID as the target.
+                       target = ServerInstance->FindNick(parameters[0]);
+               }
+
+               if (!target || target->registered != REG_ALL)
+               {
+                       // The target user does not exist or is not fully registered.
+                       source->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
+                       return CMD_FAILURE;
+               }
+
+               // Fire the pre-message events.
+               MessageTarget msgtarget(target);
+               CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
+               if (!FirePreEvents(source, msgtarget, msgdetails))
+                       return CMD_FAILURE;
+
+               LocalUser* const localtarget = IS_LOCAL(target);
+               if (localtarget && cap.get(localtarget))
+               {
+                       // Send to the target if they have the capability and are a local user.
+                       CTCTags::TagMessage message(source, localtarget, parameters.GetTags());
+                       localtarget->Send(msgevprov, message);
+               }
+
+               // Fire the post-message event.
+               return FirePostEvent(source, msgtarget, msgdetails);
+       }
+
+ public:
+       CommandTagMsg(Module* Creator, Cap::Capability& Cap)
+               : Command(Creator, "TAGMSG", 1)
+               , cap(Cap)
+               , moderatedmode(Creator, "moderated")
+               , noextmsgmode(Creator, "noextmsg")
+               , tagevprov(Creator, "event/tagmsg")
+               , msgevprov(Creator, "TAGMSG")
+       {
+               allow_empty_last_param = false;
+       }
+
+       CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
+       {
+               if (CommandParser::LoopCall(user, this, parameters, 0))
+                       return CMD_SUCCESS;
+
+               // Check that the source has the message tags capability.
+               if (IS_LOCAL(user) && !cap.get(user))
+                       return CMD_FAILURE;
+
+               // The target is a server glob.
+               if (parameters[0][0] == '$')
+                       return HandleServerTarget(user, parameters);
+
+               // If the message begins with a status character then look it up.
+               const char* target = parameters[0].c_str();
+               PrefixMode* pmh = ServerInstance->Modes->FindPrefix(target[0]);
+               if (pmh)
+                       target++;
+
+               // The target is a channel name.
+               if (*target == '#')
+                       return HandleChannelTarget(user, parameters, target, pmh);
+
+               // The target is a nickname.
+               return HandleUserTarget(user, parameters);
+       }
+
+       RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
+       {
+               return ROUTE_MESSAGE(parameters[0]);
+       }
+};
+
+class C2CTags : public ClientProtocol::MessageTagProvider
+{
+ private:
+       Cap::Capability& cap;
+
+ public:
+       C2CTags(Module* Creator, Cap::Capability& Cap)
+               : ClientProtocol::MessageTagProvider(Creator)
+               , cap(Cap)
+       {
+       }
+
+       ModResult OnProcessTag(User* user, const std::string& tagname, std::string& tagvalue) CXX11_OVERRIDE
+       {
+               // A client-only tag is prefixed with a plus sign (+) and otherwise conforms
+               // to the format specified in IRCv3.2 tags.
+               if (tagname[0] != '+')
+                       return MOD_RES_PASSTHRU;
+
+               // If the user is local then we check whether they have the message-tags cap
+               // enabled. If not then we reject all client-only tags originating from them.
+               LocalUser* lu = IS_LOCAL(user);
+               if (lu && !cap.get(lu))
+                       return MOD_RES_DENY;
+
+               // Remote users have their client-only tags checked by their local server.
+               return MOD_RES_ALLOW;
+       }
+
+       bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
+       {
+               return cap.get(user);
+       }
+};
+
+class ModuleIRCv3CTCTags
+       : public Module
+       , public CTCTags::EventListener
+{
+ private:
+       Cap::Capability cap;
+       CommandTagMsg cmd;
+       C2CTags c2ctags;
+
+       ModResult CopyClientTags(const ClientProtocol::TagMap& tags_in, ClientProtocol::TagMap& tags_out)
+       {
+               for (ClientProtocol::TagMap::const_iterator i = tags_in.begin(); i != tags_in.end(); ++i)
+               {
+                       const ClientProtocol::MessageTagData& tagdata = i->second;
+                       if (tagdata.tagprov == &c2ctags)
+                               tags_out.insert(*i);
+               }
+               return MOD_RES_PASSTHRU;
+       }
+
+ public:
+       ModuleIRCv3CTCTags()
+               : CTCTags::EventListener(this)
+               , cap(this, "message-tags")
+               , cmd(this, cap)
+               , c2ctags(this, cap)
+       {
+       }
+
+       ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
+       {
+               return CopyClientTags(details.tags_in, details.tags_out);
+       }
+
+       ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
+       {
+               return CopyClientTags(details.tags_in, details.tags_out);
+       }
+
+       Version GetVersion() CXX11_OVERRIDE
+       {
+               return Version("Provides the DRAFT message-tags IRCv3 extension", VF_VENDOR | VF_COMMON);
+       }
+};
+
+MODULE_INIT(ModuleIRCv3CTCTags)
index b407aece4a1af3a4c29519f1424f204b8af0f7bd..3ec534e916725dd7359009e8abe992d49368754a 100644 (file)
 
 #include "inspircd.h"
 #include "modules/cap.h"
+#include "modules/ctctags.h"
 
-class ModuleIRCv3EchoMessage : public Module
+class ModuleIRCv3EchoMessage
+       : public Module
+       , public CTCTags::EventListener
 {
+ private:
        Cap::Capability cap;
+       ClientProtocol::EventProvider tagmsgprov;
 
  public:
        ModuleIRCv3EchoMessage()
-               : cap(this, "echo-message")
+               : CTCTags::EventListener(this)
+               , cap(this, "echo-message")
+               , tagmsgprov(this, "TAGMSG")
        {
        }
 
@@ -64,6 +71,35 @@ class ModuleIRCv3EchoMessage : public Module
                }
        }
 
+       void OnUserPostTagMessage(User* user, const MessageTarget& target, const CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
+       {
+               if (!cap.get(user) || !details.echo)
+                       return;
+
+               // Caps are only set on local users
+               LocalUser* const localuser = static_cast<LocalUser*>(user);
+
+               const ClientProtocol::TagMap& tags = details.echo_original ? details.tags_in : details.tags_out;
+               if (target.type == MessageTarget::TYPE_USER)
+               {
+                       User* destuser = target.Get<User>();
+                       CTCTags::TagMessage message(user, destuser, tags);
+                       localuser->Send(tagmsgprov, message);
+               }
+               else if (target.type == MessageTarget::TYPE_CHANNEL)
+               {
+                       Channel* chan = target.Get<Channel>();
+                       CTCTags::TagMessage message(user, chan, tags);
+                       localuser->Send(tagmsgprov, message);
+               }
+               else
+               {
+                       const std::string* servername = target.Get<std::string>();
+                       CTCTags::TagMessage message(user, servername->c_str(), tags);
+                       localuser->Send(tagmsgprov, message);
+               }
+       }
+
        void OnUserMessageBlocked(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE
        {
                // Prevent spammers from knowing that their spam was blocked.
@@ -71,6 +107,13 @@ class ModuleIRCv3EchoMessage : public Module
                        OnUserPostMessage(user, target, details);
        }
 
+       void OnUserTagMessageBlocked(User* user, const MessageTarget& target, const CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
+       {
+               // Prevent spammers from knowing that their spam was blocked.
+               if (details.echo_original)
+                       OnUserPostTagMessage(user, target, details);
+       }
+
        Version GetVersion() CXX11_OVERRIDE
        {
                return Version("Provides the echo-message IRCv3 extension", VF_VENDOR);
index 17bc7cbc69eb90899b3d9c1ad8af0b2fe4a36081..17b44f896a0155a96def168997390e1f95c1e004 100644 (file)
@@ -309,6 +309,11 @@ void TreeSocket::WriteLine(const std::string& original_line)
                                        push.append(line, 26, std::string::npos);
                                        push.swap(line);
                                }
+                               else if (command == "TAGMSG")
+                               {
+                                       // Drop IRCv3 tag messages as v2 has no message tag support.
+                                       return;
+                               }
                        }
                        WriteLineNoCompat(line);
                        return;