summaryrefslogtreecommitdiff
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
parente2a820cce21342478653a34cf8ce2b593128d035 (diff)
Implement IRCv3 message tag support.
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
-rw-r--r--include/channels.h71
-rw-r--r--include/clientprotocol.h726
-rw-r--r--include/clientprotocolevent.h78
-rw-r--r--include/clientprotocolmsg.h677
-rw-r--r--include/command_parse.h2
-rw-r--r--include/configreader.h5
-rw-r--r--include/ctables.h35
-rw-r--r--include/event.h33
-rw-r--r--include/inspircd.h19
-rw-r--r--include/message.h9
-rw-r--r--include/mode.h12
-rw-r--r--include/modechange.h17
-rw-r--r--include/modules.h7
-rw-r--r--include/modules/cap.h19
-rw-r--r--include/modules/ircv3.h64
-rw-r--r--include/stdalgo.h82
-rw-r--r--include/typedefs.h28
-rw-r--r--include/users.h90
-rw-r--r--src/channels.cpp113
-rw-r--r--src/clientprotocol.cpp105
-rw-r--r--src/command_parse.cpp49
-rw-r--r--src/configreader.cpp1
-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
-rw-r--r--src/inspircd.cpp10
-rw-r--r--src/mode.cpp42
-rw-r--r--src/modules.cpp3
-rw-r--r--src/modules/m_alias.cpp2
-rw-r--r--src/modules/m_auditorium.cpp51
-rw-r--r--src/modules/m_cap.cpp37
-rw-r--r--src/modules/m_chanhistory.cpp25
-rw-r--r--src/modules/m_chanlog.cpp3
-rw-r--r--src/modules/m_cloaking.cpp8
-rw-r--r--src/modules/m_conn_waitpong.cpp6
-rw-r--r--src/modules/m_delayjoin.cpp63
-rw-r--r--src/modules/m_hostcycle.cpp34
-rw-r--r--src/modules/m_ircv3.cpp230
-rw-r--r--src/modules/m_ircv3_capnotify.cpp54
-rw-r--r--src/modules/m_ircv3_chghost.cpp10
-rw-r--r--src/modules/m_ircv3_echomessage.cpp24
-rw-r--r--src/modules/m_ircv3_invitenotify.cpp8
-rw-r--r--src/modules/m_knock.cpp8
-rw-r--r--src/modules/m_ldapoper.cpp3
-rw-r--r--src/modules/m_passforward.cpp4
-rw-r--r--src/modules/m_samode.cpp42
-rw-r--r--src/modules/m_sasl.cpp17
-rw-r--r--src/modules/m_showfile.cpp7
-rw-r--r--src/modules/m_spanningtree/main.cpp6
-rw-r--r--src/modules/m_spanningtree/main.h2
-rw-r--r--src/modules/m_spanningtree/treesocket2.cpp3
-rw-r--r--src/modules/m_sqloper.cpp3
-rw-r--r--src/modules/m_timedbans.cpp8
-rw-r--r--src/usermanager.cpp34
-rw-r--r--src/users.cpp179
62 files changed, 3020 insertions, 715 deletions
diff --git a/include/channels.h b/include/channels.h
index 365cdeabd..0557a5898 100644
--- a/include/channels.h
+++ b/include/channels.h
@@ -232,75 +232,22 @@ class CoreExport Channel : public Extensible
*/
Membership* ForceJoin(User* user, const std::string* privs = NULL, bool bursting = false, bool created_by_local = false);
- /** Write to a channel, from a user, using va_args for text
- * @param user User whos details to prefix the line with
- * @param text A printf-style format string which builds the output line without prefix
- * @param ... Zero or more POD types
- */
- void WriteChannel(User* user, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
- /** Write to a channel, from a user, using std::string for text
- * @param user User whos details to prefix the line with
- * @param text A std::string containing the output line without prefix
- */
- void WriteChannel(User* user, const std::string &text);
-
- /** Write to a channel, from a server, using va_args for text
- * @param ServName Server name to prefix the line with
- * @param text A printf-style format string which builds the output line without prefix
- * @param ... Zero or more POD type
- */
- void WriteChannelWithServ(const std::string& ServName, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
- /** Write to a channel, from a server, using std::string for text
- * @param ServName Server name to prefix the line with
- * @param text A std::string containing the output line without prefix
- */
- void WriteChannelWithServ(const std::string& ServName, const std::string &text);
-
- /** Write to all users on a channel except a specific user, using va_args for text.
- * Internally, this calls WriteAllExcept().
- * @param user User whos details to prefix the line with, and to omit from receipt of the message
- * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
- * use the nick!user\@host of the user.
- * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
- * @param text A printf-style format string which builds the output line without prefix
- * @param ... Zero or more POD type
- */
- void WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...) CUSTOM_PRINTF(5, 6);
-
- /** Write to all users on a channel except a list of users, using va_args for text
- * @param user User whos details to prefix the line with, and to omit from receipt of the message
- * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
- * use the nick!user\@host of the user.
- * @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
- * @param except_list A list of users NOT to send the text to
- * @param text A printf-style format string which builds the output line without prefix
- * @param ... Zero or more POD type
- */
- void WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...) CUSTOM_PRINTF(6, 7);
-
- /** Write to all users on a channel except a specific user, using std::string for text.
- * Internally, this calls WriteAllExcept().
- * @param user User whos details to prefix the line with, and to omit from receipt of the message
- * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
- * use the nick!user\@host of the user.
+ /** Write to all users on a channel except some users
+ * @param protoev Event to send, may contain any number of messages.
* @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
* @param text A std::string containing the output line without prefix
+ * @param except_list List of users not to send to
*/
- void WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text);
+ void Write(ClientProtocol::Event& protoev, char status = 0, const CUList& except_list = CUList());
- /** Write to all users on a channel except a list of users, using std::string for text
- * @param user User whos details to prefix the line with, and to omit from receipt of the message
- * @param serversource If this parameter is true, use the local server name as the source of the text, otherwise,
- * use the nick!user\@host of the user.
+ /** Write to all users on a channel except some users.
+ * @param protoevprov Protocol event provider for the message.
+ * @param msg Message to send.
* @param status The status of the users to write to, e.g. '@' or '%'. Use a value of 0 to write to everyone
- * @param except_list A list of users NOT to send the text to
* @param text A std::string containing the output line without prefix
+ * @param except_list List of users not to send to
*/
- void WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string& text);
- /** Write a line of text that already includes the source */
- void RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string& text);
+ void Write(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg, char status = 0, const CUList& except_list = CUList());
/** Return the channel's modes with parameters.
* @param showkey If this is set to true, the actual key is shown,
diff --git a/include/clientprotocol.h b/include/clientprotocol.h
new file mode 100644
index 000000000..a3efcf984
--- /dev/null
+++ b/include/clientprotocol.h
@@ -0,0 +1,726 @@
+/*
+ * 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/>.
+ */
+
+
+#pragma once
+
+#include "event.h"
+
+namespace ClientProtocol
+{
+ class EventHook;
+ class MessageSource;
+ struct RFCEvents;
+ struct ParseOutput;
+ class TagSelection;
+}
+
+/** Contains a message parsed from wire format.
+ * Used by Serializer::Parse().
+ */
+struct ClientProtocol::ParseOutput
+{
+ /** Command name, must not be empty.
+ */
+ std::string cmd;
+
+ /** Parameter list, may be empty.
+ */
+ ClientProtocol::ParamList params;
+
+ /** Message tags, may be empty.
+ */
+ ClientProtocol::TagMap tags;
+};
+
+/** A selection of zero or more tags in a TagMap.
+ */
+class ClientProtocol::TagSelection
+{
+ std::bitset<64> selection;
+
+ public:
+ /** Check if a tag is selected.
+ * @param tags TagMap the tag is in. The TagMap must contain the same tags as it had when the tag
+ * was selected with Select(), otherwise the result is not meaningful.
+ * @param it Iterator to the tag to check.
+ * @return True if the tag is selected, false otherwise.
+ */
+ bool IsSelected(const TagMap& tags, TagMap::const_iterator it) const
+ {
+ const size_t index = std::distance(tags.begin(), it);
+ return ((index < selection.size()) && (selection[index]));
+ }
+
+ /** Select a tag.
+ * @param tags TagMap the tag is in. This parameter must be the same every time the method is called.
+ * The TagMap must not be altered otherwise the results of IsSelected() is not meaningful.
+ * @param it Iterator to the tag to mark as selected.
+ */
+ void Select(const TagMap& tags, TagMap::const_iterator it)
+ {
+ const size_t index = std::distance(tags.begin(), it);
+ if (index < selection.size())
+ selection[index] = true;
+ }
+
+ /** Check if a TagSelection is equivalent to this object.
+ * @param other Other TagSelection object to compare this with.
+ * @return True if the objects are equivalent, false if they aren't.
+ */
+ bool operator==(const TagSelection& other) const
+ {
+ return (this->selection == other.selection);
+ }
+};
+
+class ClientProtocol::MessageSource
+{
+ User* sourceuser;
+ const std::string* sourcestr;
+
+ public:
+ /** Constructor, sets the source to be the full host of a user or sets it to be nothing.
+ * The actual source string when serializing will be obtained from User::GetFullHost() if the user is non-NULL.
+ * @param Sourceuser User to set as source of the message. If NULL, the message won't have a source when serialized.
+ * Optional, defaults to NULL.
+ */
+ MessageSource(User* Sourceuser = NULL)
+ {
+ SetSourceUser(Sourceuser);
+ }
+
+ /** Constructor, sets the source to the supplied string and optionally sets the source user.
+ * @param Sourcestr String to use as message source for serialization purposes. Must remain valid
+ * as long as this object is alive.
+ * @param Sourceuser User to set as source. Optional, defaults to NULL. It will not be used for serialization but
+ * if provided it may be used internally, for example to create message tags.
+ * Useful when the source string is synthesized but it is still related to a User.
+ */
+ MessageSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+ {
+ SetSource(Sourcestr, Sourceuser);
+ }
+
+ /** Get the source of this message as a string.
+ * @return Pointer to the message source string or NULL if there is no source.
+ */
+ const std::string* GetSource() const
+ {
+ // Return string if there's one explicitly set
+ if (sourcestr)
+ return sourcestr;
+ if (sourceuser)
+ return &sourceuser->GetFullHost();
+ return NULL;
+ }
+
+ /** Get the source User.
+ * This shouldn't be used for serialization, use GetSource() for that.
+ * @return User pointer if the message has a source user, NULL otherwise.
+ */
+ User* GetSourceUser() const { return sourceuser; }
+
+ /** Set the source of this message to a User.
+ * See the one parameter constructor for a more detailed description.
+ * @param Sourceuser User to set as source.
+ */
+ void SetSourceUser(User* Sourceuser)
+ {
+ sourceuser = Sourceuser;
+ sourcestr = NULL;
+ }
+
+ /** Set the source string and optionally source user.
+ * See the two parameter constructor for a more detailed description.
+ * @param Sourcestr String source, to be used for serialization purposes. Must remain valid as long
+ * as this object is alive.
+ * @param Sourceuser Source user to set, optional.
+ */
+ void SetSource(const std::string& Sourcestr, User* Sourceuser = NULL)
+ {
+ sourcestr = &Sourcestr;
+ sourceuser = Sourceuser;
+ }
+
+ /** Copy the source from a MessageSource object.
+ * @param other MessageSource object to copy from.
+ */
+ void SetSource(const MessageSource& other)
+ {
+ sourcestr = other.sourcestr;
+ sourceuser = other.sourceuser;
+ }
+};
+
+/** Outgoing client protocol message.
+ * Represents a high level client protocol message which is turned into raw or wire format
+ * by a Serializer. Messages can be serialized into different format by different serializers.
+ *
+ * Messages are created on demand and are disposed of after they have been sent.
+ *
+ * All messages have a command name, a list of parameters and a map of tags, the last two can be empty.
+ * They also always have a source, see class MessageSource.
+ */
+class ClientProtocol::Message : public ClientProtocol::MessageSource
+{
+ public:
+ /** Contains information required to identify a specific version of a serialized message.
+ */
+ struct SerializedInfo
+ {
+ const Serializer* serializer;
+ TagSelection tagwl;
+
+ /** Constructor.
+ * @param Ser Serializer used to serialize the message.
+ * @param Tagwl Tag whitelist used to serialize the message.
+ */
+ SerializedInfo(const Serializer* Ser, const TagSelection& Tagwl)
+ : serializer(Ser)
+ , tagwl(Tagwl)
+ {
+ }
+
+ /** Check if a SerializedInfo object is equivalent to this object.
+ * @param other Other SerializedInfo object.
+ * @return True if other is equivalent to this object, false otherwise.
+ */
+ bool operator==(const SerializedInfo& other) const
+ {
+ return ((serializer == other.serializer) && (tagwl == other.tagwl));
+ }
+ };
+
+ class Param
+ {
+ const std::string* ptr;
+ insp::aligned_storage<std::string> str;
+ bool owned;
+
+ void InitFrom(const Param& other)
+ {
+ owned = other.owned;
+ if (owned)
+ new(str) std::string(*other.str);
+ else
+ ptr = other.ptr;
+ }
+
+ public:
+ operator const std::string&() const { return (owned ? *str : *ptr); }
+
+ Param()
+ : ptr(NULL)
+ , owned(false)
+ {
+ }
+
+ Param(const std::string& s)
+ : ptr(&s)
+ , owned(false)
+ {
+ }
+
+ Param(int, const char* s)
+ : owned(true)
+ {
+ new(str) std::string(s);
+ }
+
+ Param(int, const std::string& s)
+ : owned(true)
+ {
+ new(str) std::string(s);
+ }
+
+ Param(const Param& other)
+ {
+ InitFrom(other);
+ }
+
+ ~Param()
+ {
+ using std::string;
+ if (owned)
+ str->~string();
+ }
+
+ Param& operator=(const Param& other)
+ {
+ if (&other == this)
+ return *this;
+
+ using std::string;
+ if (owned)
+ str->~string();
+ InitFrom(other);
+ return *this;
+ }
+
+ bool IsOwned() const { return owned; }
+ };
+
+ typedef std::vector<Param> ParamList;
+
+ private:
+ typedef std::vector<std::pair<SerializedInfo, SerializedMessage> > SerializedList;
+
+ ParamList params;
+ TagMap tags;
+ std::string command;
+ bool msginit_done;
+ mutable SerializedList serlist;
+ bool sideeffect;
+
+ protected:
+ /** Set command string.
+ * @param cmd Command string to set.
+ */
+ void SetCommand(const char* cmd)
+ {
+ command.clear();
+ if (cmd)
+ command = cmd;
+ }
+
+ public:
+ /** Constructor.
+ * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+ * with SetCommand() before the message is serialized.
+ * @param Sourceuser See the one parameter constructor of MessageSource for description.
+ */
+ Message(const char* cmd, User* Sourceuser = NULL)
+ : ClientProtocol::MessageSource(Sourceuser)
+ , command(cmd ? cmd : std::string())
+ , msginit_done(false)
+ , sideeffect(false)
+ {
+ params.reserve(8);
+ serlist.reserve(8);
+ }
+
+ /** Constructor.
+ * @param cmd Command name, e.g. "JOIN", "NICK". May be NULL. If NULL, the command must be set
+ * with SetCommand() before the message is serialized.
+ * @param Sourcestr See the two parameter constructor of MessageSource for description.
+ * Must remain valid as long as this object is alive.
+ * @param Sourceuser See the two parameter constructor of MessageSource for description.
+ */
+ Message(const char* cmd, const std::string& Sourcestr, User* Sourceuser = NULL)
+ : ClientProtocol::MessageSource(Sourcestr, Sourceuser)
+ , command(cmd ? cmd : std::string())
+ , msginit_done(false)
+ , sideeffect(false)
+ {
+ params.reserve(8);
+ serlist.reserve(8);
+ }
+
+ /** Get the parameters of this message.
+ * @return List of parameters.
+ */
+ const ParamList& GetParams() const { return params; }
+
+ /** Get a map of tags attached to this message.
+ * The map contains the tag providers that attached the tag to the message.
+ * @return Map of tags.
+ */
+ const TagMap& GetTags() const { return tags; }
+
+ /** Get the command string.
+ * @return Command string, e.g. "NICK", "001".
+ */
+ const char* GetCommand() const { return command.c_str(); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add, will be copied.
+ */
+ void PushParam(const char* str) { params.push_back(Param(0, str)); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add, will be copied.
+ */
+ void PushParam(const std::string& str) { params.push_back(Param(0, str)); }
+
+ /** Add a parameter to the parameter list.
+ * @param str String to add.
+ * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+ */
+ void PushParamRef(const std::string& str) { params.push_back(str); }
+
+ /** Add a placeholder parameter to the parameter list.
+ * Placeholder parameters must be filled in later with actual parameters using ReplaceParam() or ReplaceParamRef().
+ */
+ void PushParamPlaceholder() { params.push_back(Param()); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with, will be copied.
+ */
+ void ReplaceParam(unsigned int index, const char* str) { params[index] = Param(0, str); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with, will be copied.
+ */
+ void ReplaceParam(unsigned int index, const std::string& str) { params[index] = Param(0, str); }
+
+ /** Replace a parameter or a placeholder that is already in the parameter list.
+ * @param index Index of the parameter to replace. Must be less than GetParams().size().
+ * @param str String to replace the parameter or placeholder with.
+ * The string will NOT be copied, it must remain alive until ClearParams() is called or until the object is destroyed.
+ */
+ void ReplaceParamRef(unsigned int index, const std::string& str) { params[index] = Param(str); }
+
+ /** Add a tag.
+ * @param tagname Raw name of the tag to use in the protocol.
+ * @param tagprov Provider of the tag.
+ * @param val Tag value. If empty no value will be sent with the tag.
+ * @param tagdata Tag provider specific data, will be passed to MessageTagProvider::ShouldSendTag(). Optional, defaults to NULL.
+ */
+ void AddTag(const std::string& tagname, MessageTagProvider* tagprov, const std::string& val, void* tagdata = NULL)
+ {
+ tags.insert(std::make_pair(tagname, MessageTagData(tagprov, val, tagdata)));
+ }
+
+ /** Add all tags in a TagMap to the tags in this message. Existing tags will not be overwritten.
+ * @param newtags New tags to add.
+ */
+ void AddTags(const ClientProtocol::TagMap& newtags)
+ {
+ tags.insert(newtags.begin(), newtags.end());
+ }
+
+ /** Get the message in a serialized form.
+ * @param serializeinfo Information about which exact serialized form of the message is the caller asking for
+ * (which serializer to use and which tags to include).
+ * @return Serialized message according to serializeinfo. The returned reference remains valid until the
+ * next call to this method.
+ */
+ const SerializedMessage& GetSerialized(const SerializedInfo& serializeinfo) const;
+
+ /** Clear the parameter list and tags.
+ */
+ void ClearParams()
+ {
+ msginit_done = false;
+ params.clear();
+ tags.clear();
+ InvalidateCache();
+ }
+
+ /** Remove all serialized messages.
+ * If a parameter is changed after the message has been sent at least once, this method must be called before
+ * serializing the message again to ensure the cache won't contain stale data.
+ */
+ void InvalidateCache()
+ {
+ serlist.clear();
+ }
+
+ void CopyAll()
+ {
+ size_t j = 0;
+ for (ParamList::iterator i = params.begin(); i != params.end(); ++i, j++)
+ {
+ Param& curr = *i;
+ if (!curr.IsOwned())
+ ReplaceParam(j, curr);
+ }
+ }
+
+ void SetSideEffect(bool Sideeffect) { sideeffect = Sideeffect; }
+ bool IsSideEffect() const { return sideeffect; }
+
+ friend class Serializer;
+};
+
+/** Client protocol event class.
+ * All messages sent to a user must be part of an event. A single event may result in more than one protocol message
+ * being sent, for example a join event may result in a JOIN and a MODE protocol message sent to members of the channel
+ * if the joining user has some prefix modes set.
+ *
+ * Event hooks attached to a specific event can alter the messages sent for that event.
+ */
+class ClientProtocol::Event
+{
+ EventProvider* event;
+ Message* initialmsg;
+ const MessageList* initialmsglist;
+ bool eventinit_done;
+
+ public:
+ /** Constructor.
+ * @param protoevent Protocol event provider the event is an instance of.
+ */
+ Event(EventProvider& protoeventprov)
+ : event(&protoeventprov)
+ , initialmsg(NULL)
+ , initialmsglist(NULL)
+ , eventinit_done(false)
+ {
+ }
+
+ /** Constructor.
+ * @param protoevent Protocol event provider the event is an instance of.
+ * @param msg Message to include in this event by default.
+ */
+ Event(EventProvider& protoeventprov, ClientProtocol::Message& msg)
+ : event(&protoeventprov)
+ , initialmsg(&msg)
+ , initialmsglist(NULL)
+ , eventinit_done(false)
+ {
+ }
+
+ /** Set a single message as the initial message in the event.
+ * Modules may alter this later.
+ */
+ void SetMessage(Message* msg)
+ {
+ initialmsg = msg;
+ initialmsglist = NULL;
+ }
+
+ /** Set a list of messages as the initial messages in the event.
+ * Modules may alter this later.
+ */
+ void SetMessageList(const MessageList& msglist)
+ {
+ initialmsg = NULL;
+ initialmsglist = &msglist;
+ }
+
+ /** Get a list of messages to send to a user.
+ * The exact messages sent to a user are determined by the initial message(s) set and hooks.
+ * @param user User to get the messages for.
+ * @param messagelist List to fill in with messages to send to the user for the event
+ */
+ void GetMessagesForUser(LocalUser* user, MessageList& messagelist);
+};
+
+/** Base class for message tag providers.
+ * All message tags belong to a message tag provider. Message tag providers can populate messages
+ * with tags before the message is sent and they have the job of determining whether a user should
+ * get a message tag or be allowed to send one.
+ */
+class ClientProtocol::MessageTagProvider : public Events::ModuleEventListener
+{
+ public:
+ /** Constructor.
+ * @param mod Module owning the provider.
+ */
+ MessageTagProvider(Module* mod)
+ : Events::ModuleEventListener(mod, "event/messagetag")
+ {
+ }
+
+ /** Called when a message is ready to be sent to give the tag provider a chance to add tags to the message.
+ * To add tags call Message::AddTag(). If the provided tag or tags have been added already elsewhere or if the
+ * provider doesn't want its tag(s) to be on the message, the implementation doesn't have to do anything special.
+ * The default implementation does nothing.
+ * @param msg Message to be populated with tags.
+ */
+ virtual void OnClientProtocolPopulateTags(ClientProtocol::Message& msg)
+ {
+ }
+
+ /** Called for each tag that the server receives from a client in a message.
+ * @param user User that sent the tag.
+ * @param tagname Name of the tag.
+ * @param value Value of the tag, empty string if the tag has no value. May be modified.
+ * @return MOD_RES_ALLOW to accept the tag with the value in 'value', MOD_RES_DENY to reject the tag and act as if it wasn't sent,
+ * MOD_RES_PASSTHRU to make no decision. If no hooks accept a tag, the tag is rejected.
+ * The default implementation returns MOD_RES_PASSTHRU.
+ */
+ virtual ModResult OnClientProtocolProcessTag(LocalUser* user, const std::string& tagname, std::string& tagvalue)
+ {
+ return MOD_RES_PASSTHRU;
+ }
+
+ /** Called whenever a user is about to receive a message that has a tag attached which is provided by this provider
+ * to determine whether or not the user should get the tag.
+ * @param user User in question.
+ * @param tagdata Tag in question.
+ * @return True if the tag should be sent to the user, false otherwise.
+ */
+ virtual bool ShouldSendTag(LocalUser* user, const MessageTagData& tagdata) = 0;
+};
+
+/** Base class for client protocol event hooks.
+ * A protocol event hook is attached to a single event type. It has the ability to alter or block messages
+ * sent to users which belong to the event the hook is attached to.
+ */
+class ClientProtocol::EventHook : public Events::ModuleEventListener
+{
+ public:
+ static std::string GetEventName(const std::string& name)
+ {
+ return "event/protoevent_" + name;
+ }
+
+ /** Constructor.
+ * @param mod Owner of the hook.
+ * @param name Name of the event to hook.
+ * @param priority Priority of the hook. Determines the order in which hooks for the same event get called.
+ * Optional.
+ */
+ EventHook(Module* mod, const std::string& name, unsigned int priority = Events::ModuleEventListener::DefaultPriority)
+ : Events::ModuleEventListener(mod, GetEventName(name), priority)
+ {
+ }
+
+ /** Called exactly once before an event is sent to any user.
+ * The default implementation doesn't do anything.
+ * @param ev Event being sent to one or more users.
+ */
+ virtual void OnEventInit(const ClientProtocol::Event& ev)
+ {
+ }
+
+ /** Called for each user that may receive the event.
+ * The hook may alter the messages sent to the user and decide whether further hooks should be executed.
+ * @param user User the message list is being prepared to be sent to.
+ * @param ev Event associated with the messages.
+ * @param messagelist List of messages to send to the user. The hook can alter this in any way it sees fit, for example it can replace messages,
+ * add new messages, etc. The list must not be empty when the method returns.
+ * @return MOD_RES_PASSTHRU to run hooks after the called hook or if no hooks are after the called hook, send the messages in messagelist to the user.
+ * MOD_RES_DENY to not send any messages to the user and to not run other hooks,
+ * MOD_RES_ALLOW to send the messages in messagelist to the user and to not run other hooks.
+ */
+ virtual ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) = 0;
+};
+
+/** Event provider for client protocol events.
+ * Protocol event hooks can be attached to the instances of these providers. The core has event
+ * providers for most common IRC events defined in RFC1459.
+ */
+class ClientProtocol::EventProvider : public Events::ModuleEventProvider
+{
+ public:
+ /** Constructor.
+ * @param Mod Module that owns the event provider.
+ * @param eventname Name of the event this provider is for, e.g. "JOIN", "PART", "NUMERIC".
+ * Should match command name if applicable.
+ */
+ EventProvider(Module* Mod, const std::string& eventname)
+ : Events::ModuleEventProvider(Mod, ClientProtocol::EventHook::GetEventName(eventname))
+ {
+ }
+};
+
+/** Commonly used client protocol events.
+ * Available via InspIRCd::GetRFCEvents().
+ */
+struct ClientProtocol::RFCEvents
+{
+ EventProvider numeric;
+ EventProvider join;
+ EventProvider part;
+ EventProvider kick;
+ EventProvider quit;
+ EventProvider nick;
+ EventProvider mode;
+ EventProvider topic;
+ EventProvider privmsg;
+ EventProvider invite;
+ EventProvider ping;
+ EventProvider pong;
+ EventProvider error;
+
+ RFCEvents()
+ : numeric(NULL, "NUMERIC")
+ , join(NULL, "JOIN")
+ , part(NULL, "PART")
+ , kick(NULL, "KICK")
+ , quit(NULL, "QUIT")
+ , nick(NULL, "NICK")
+ , mode(NULL, "MODE")
+ , topic(NULL, "TOPIC")
+ , privmsg(NULL, "PRIVMSG")
+ , invite(NULL, "INVITE")
+ , ping(NULL, "PING")
+ , pong(NULL, "PONG")
+ , error(NULL, "ERROR")
+ {
+ }
+};
+
+/** Base class for client protocol serializers.
+ * A serializer has to implement serialization and parsing of protocol messages to/from wire format.
+ */
+class CoreExport ClientProtocol::Serializer : public DataProvider
+{
+ Events::ModuleEventProvider evprov;
+
+ /** Make a white list containing which tags a user should get.
+ * @param user User in question.
+ * @param tagmap Tag map that contains all possible tags.
+ * @return Whitelist of tags to send to the user.
+ */
+ TagSelection MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const;
+
+ public:
+ /** Constructor.
+ * @param mod Module owning the serializer.
+ * @param Name Name of the serializer, e.g. "rfc".
+ */
+ Serializer(Module* mod, const char* Name);
+
+ /** Handle a tag in a message being parsed. Call this method for each parsed tag.
+ * @param user User sending the tag.
+ * @param tagname Name of the tag.
+ * @param tagvalue Tag value, may be empty.
+ * @param tags TagMap to place the tag into, if it gets accepted.
+ * @return True if no error occured, false if the tag name is invalid or if this tag already exists.
+ */
+ bool HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const;
+
+ /** Serialize a message for a user.
+ * @param user User to serialize the message for.
+ * @param msg Message to serialize.
+ * @return Raw serialized message, only containing the appropriate tags for the user.
+ * The reference is guaranteed to be valid as long as the Message object is alive and until the same
+ * Message is serialized for another user.
+ */
+ const SerializedMessage& SerializeForUser(LocalUser* user, Message& msg);
+
+ /** Serialize a high level protocol message into wire format.
+ * @param msg High level message to serialize. Contains all necessary information about the message, including all possible tags.
+ * @param tagwl Message tags to include in the serialized message. Tags attached to the message but not included in the whitelist must not
+ * appear in the output. This is because each user may get a different set of tags for the same message.
+ * @return Protocol message in wire format. Must contain message delimiter as well, if any (e.g. CRLF for RFC1459).
+ */
+ virtual std::string Serialize(const Message& msg, const TagSelection& tagwl) const = 0;
+
+ /** Parse a protocol message from wire format.
+ * @param user Source of the message.
+ * @param line Raw protocol message.
+ * @param parseoutput Output of the parser.
+ * @return True if the message was parsed successfully into parseoutput and should be processed, false to drop the message.
+ */
+ virtual bool Parse(LocalUser* user, const std::string& line, ParseOutput& parseoutput) = 0;
+};
+
+inline ClientProtocol::MessageTagData::MessageTagData(MessageTagProvider* prov, const std::string& val, void* data)
+ : tagprov(prov)
+ , value(val)
+ , provdata(data)
+{
+}
diff --git a/include/clientprotocolevent.h b/include/clientprotocolevent.h
new file mode 100644
index 000000000..6b89d0f72
--- /dev/null
+++ b/include/clientprotocolevent.h
@@ -0,0 +1,78 @@
+/*
+ * 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/>.
+ */
+
+
+#pragma once
+
+namespace ClientProtocol
+{
+ namespace Events
+ {
+ struct Join;
+ class Mode;
+ }
+}
+
+struct ClientProtocol::Events::Join : public ClientProtocol::Messages::Join, public ClientProtocol::Event
+{
+ Join()
+ : ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+ {
+ }
+
+ Join(Membership* Memb)
+ : ClientProtocol::Messages::Join(Memb)
+ , ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+ {
+ }
+
+ Join(Membership* Memb, const std::string& Sourcestr)
+ : ClientProtocol::Messages::Join(Memb, Sourcestr)
+ , ClientProtocol::Event(ServerInstance->GetRFCEvents().join, *this)
+ {
+ }
+};
+
+class ClientProtocol::Events::Mode : public ClientProtocol::Event
+{
+ std::list<ClientProtocol::Messages::Mode> modelist;
+ std::vector<Message*> modemsgplist;
+ const Modes::ChangeList& modechanges;
+
+ public:
+ static void BuildMessages(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist, std::list<ClientProtocol::Messages::Mode>& modelist, std::vector<Message*>& modemsgplist)
+ {
+ // Build as many MODEs as necessary
+ for (Modes::ChangeList::List::const_iterator i = changelist.getlist().begin(); i != changelist.getlist().end(); i = modelist.back().GetEndIterator())
+ {
+ modelist.push_back(ClientProtocol::Messages::Mode(source, Chantarget, Usertarget, changelist, i));
+ modemsgplist.push_back(&modelist.back());
+ }
+ }
+
+ Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+ : ClientProtocol::Event(ServerInstance->GetRFCEvents().mode)
+ , modechanges(changelist)
+ {
+ BuildMessages(source, Chantarget, Usertarget, changelist, modelist, modemsgplist);
+ SetMessageList(modemsgplist);
+ }
+
+ const Modes::ChangeList& GetChangeList() const { return modechanges; }
+ const std::list<ClientProtocol::Messages::Mode>& GetMessages() const { return modelist; }
+};
diff --git a/include/clientprotocolmsg.h b/include/clientprotocolmsg.h
new file mode 100644
index 000000000..d1562d7d1
--- /dev/null
+++ b/include/clientprotocolmsg.h
@@ -0,0 +1,677 @@
+/*
+ * 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/>.
+ */
+
+
+#pragma once
+
+namespace ClientProtocol
+{
+ namespace Messages
+ {
+ class Numeric;
+ class Join;
+ struct Part;
+ struct Kick;
+ struct Quit;
+ struct Nick;
+ class Mode;
+ struct Topic;
+ class Privmsg;
+ struct Invite;
+ struct Ping;
+ struct Pong;
+ struct Error;
+ }
+}
+
+/** Numeric message.
+ * Doesn't have a fixed command name, it's always a 3 digit number padded with zeroes if necessary.
+ * The first parameter is the target of the numeric which is almost always the nick of the user
+ * the numeric will be sent to.
+ */
+class ClientProtocol::Messages::Numeric : public ClientProtocol::Message
+{
+ char numericstr[4];
+
+ void InitCommand(unsigned int number)
+ {
+ snprintf(numericstr, sizeof(numericstr), "%03u", number);
+ SetCommand(numericstr);
+ }
+
+ void InitFromNumeric(const ::Numeric::Numeric& numeric)
+ {
+ InitCommand(numeric.GetNumeric());
+ for (std::vector<std::string>::const_iterator i = numeric.GetParams().begin(); i != numeric.GetParams().end(); ++i)
+ PushParamRef(*i);
+ }
+
+ public:
+ /** Constructor, target is a User.
+ * @param num Numeric object to send. Must remain valid as long as this object is alive and must not be modified.
+ * @param user User to send the numeric to. May be unregistered, must remain valid as long as this object is alive.
+ */
+ Numeric(const ::Numeric::Numeric& num, User* user)
+ : ClientProtocol::Message(NULL, (num.GetServer() ? num.GetServer()->GetName() : ServerInstance->Config->ServerName))
+ {
+ if (user->registered & REG_NICK)
+ PushParamRef(user->nick);
+ else
+ PushParam("*");
+ InitFromNumeric(num);
+ }
+
+ /** Constructor, target is a string.
+ * @param num Numeric object to send. Must remain valid as long as this object is alive and must not be modified.
+ * @param target Target string, must stay valid as long as this object is alive.
+ */
+ Numeric(const ::Numeric::Numeric& num, const std::string& target)
+ : ClientProtocol::Message(NULL, (num.GetServer() ? num.GetServer()->GetName() : ServerInstance->Config->ServerName))
+ {
+ PushParamRef(target);
+ InitFromNumeric(num);
+ }
+
+ /** Constructor. Only the numeric number has to be specified.
+ * @param num Numeric number.
+ */
+ Numeric(unsigned int num)
+ : ClientProtocol::Message(NULL, ServerInstance->Config->ServerName)
+ {
+ InitCommand(num);
+ PushParam("*");
+ }
+};
+
+/** JOIN message.
+ * Sent when a user joins a channel.
+ */
+class ClientProtocol::Messages::Join : public ClientProtocol::Message
+{
+ Membership* memb;
+
+ public:
+ /** Constructor. Does not populate parameters, call SetParams() before sending the message.
+ */
+ Join()
+ : ClientProtocol::Message("JOIN")
+ , memb(NULL)
+ {
+ }
+
+ /** Constructor.
+ * @param Memb Membership of the joining user.
+ */
+ Join(Membership* Memb)
+ : ClientProtocol::Message("JOIN", Memb->user)
+ {
+ SetParams(Memb);
+ }
+
+ /** Constructor.
+ * @param Memb Membership of the joining user.
+ * @param sourcestrref Message source string, must remain valid as long as this object is alive.
+ */
+ Join(Membership* Memb, const std::string& sourcestrref)
+ : ClientProtocol::Message("JOIN", sourcestrref, Memb->user)
+ {
+ SetParams(Memb);
+ }
+
+ /** Populate parameters from a Membership
+ * @param Memb Membership of the joining user.
+ */
+ void SetParams(Membership* Memb)
+ {
+ memb = Memb;
+ PushParamRef(memb->chan->name);
+ }
+
+ /** Get the Membership of the joining user.
+ * @return Membership of the joining user.
+ */
+ Membership* GetMember() const { return memb; }
+};
+
+/** PART message.
+ * Sent when a user parts a channel.
+ */
+struct ClientProtocol::Messages::Part : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param memb Member parting.
+ * @param reason Part reason, may be empty. If non-empty, must remain valid as long as this object is alive.
+ */
+ Part(Membership* memb, const std::string& reason)
+ : ClientProtocol::Message("PART", memb->user)
+ {
+ PushParamRef(memb->chan->name);
+ if (!reason.empty())
+ PushParamRef(reason);
+ }
+};
+
+/** KICK message.
+ * Sent when a user is kicked from a channel.
+ */
+struct ClientProtocol::Messages::Kick : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param source User that does the kick.
+ * @param memb Membership of the user being kicked.
+ * @param reason Kick reason. Must remain valid as long as this object is alive.
+ */
+ Kick(User* source, Membership* memb, const std::string& reason)
+ : ClientProtocol::Message("KICK", source)
+ {
+ PushParamRef(memb->chan->name);
+ PushParamRef(memb->user->nick);
+ PushParamRef(reason);
+ }
+};
+
+/** QUIT message.
+ * Sent when a user quits.
+ */
+struct ClientProtocol::Messages::Quit : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param source User quitting.
+ * @param reason Quit reason, may be empty. Must remain valid as long as this object is alive.
+ */
+ Quit(User* source, const std::string& reason)
+ : ClientProtocol::Message("QUIT", source)
+ {
+ if (!reason.empty())
+ PushParamRef(reason);
+ }
+};
+
+/** NICK message.
+ * Sent when a user changes their nickname.
+ */
+struct ClientProtocol::Messages::Nick : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param source User changing nicks.
+ * @param newnick New nickname. Must remain valid as long as this object is alive.
+ */
+ Nick(User* source, const std::string& newnick)
+ : ClientProtocol::Message("NICK", source)
+ {
+ PushParamRef(newnick);
+ }
+};
+
+/** MODE message.
+ * Sent when modes are changed on a user or channel.
+ */
+class ClientProtocol::Messages::Mode : public ClientProtocol::Message
+{
+ Channel* chantarget;
+ User* usertarget;
+ Modes::ChangeList::List::const_iterator beginit;
+ Modes::ChangeList::List::const_iterator lastit;
+
+ /** Convert a range of a mode change list to mode letters and '+', '-' symbols.
+ * @param list Mode change list.
+ * @param maxlinelen Maximum output length.
+ * @param beginit Iterator to the first element in 'list' to process.
+ * @param lastit Iterator which is set to the first element not processed due to length limitations by the method.
+ */
+ static std::string ToModeLetters(const Modes::ChangeList::List& list, std::string::size_type maxlinelen, Modes::ChangeList::List::const_iterator beginit, Modes::ChangeList::List::const_iterator& lastit)
+ {
+ std::string ret;
+ std::string::size_type paramlength = 0;
+ char output_pm = '\0'; // current output state, '+' or '-'
+
+ Modes::ChangeList::List::const_iterator i;
+ for (i = beginit; i != list.end(); ++i)
+ {
+ const Modes::Change& item = *i;
+
+ const char needed_pm = (item.adding ? '+' : '-');
+ if (needed_pm != output_pm)
+ {
+ output_pm = needed_pm;
+ ret.push_back(output_pm);
+ }
+
+ if (!item.param.empty())
+ paramlength += item.param.length() + 1;
+ if (ret.length() + 1 + paramlength > maxlinelen)
+ {
+ // Mode sequence is getting too long
+ const char c = *ret.rbegin();
+ if ((c == '+') || (c == '-'))
+ ret.erase(ret.size()-1);
+ break;
+ }
+
+ ret.push_back(item.mh->GetModeChar());
+ }
+
+ lastit = i;
+ return ret;
+ }
+
+ /** Push mode parameters for modes that have one, starting at beginit to lastit (not including lastit).
+ */
+ void PushModeParams()
+ {
+ for (Modes::ChangeList::List::const_iterator i = beginit; i != lastit; ++i)
+ {
+ const Modes::Change& item = *i;
+ if (!item.param.empty())
+ PushParamRef(item.param);
+ }
+ }
+
+ public:
+ /** Convert an entire mode change list into mode letters and '+' and '-' characters.
+ * @param changelist Mode change list to convert into mode letters.
+ * @return Mode letters.
+ */
+ static std::string ToModeLetters(const Modes::ChangeList& changelist)
+ {
+ // TODO: This assumes that std::string::max_size() >= UINT_MAX
+ Modes::ChangeList::List::const_iterator dummy;
+ return ToModeLetters(changelist.getlist(), UINT_MAX, changelist.getlist().begin(), dummy);
+ }
+
+ /** Constructor, populate parameters starting from a given position in a mode change list.
+ * @param source User doing the mode change.
+ * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+ * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+ * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+ * @param beginiter Starting position of mode changes in 'changelist'.
+ */
+ Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist, Modes::ChangeList::List::const_iterator beginiter)
+ : ClientProtocol::Message("MODE", source)
+ , chantarget(Chantarget)
+ , usertarget(Usertarget)
+ , beginit(beginiter)
+ {
+ PushParamRef(GetStrTarget());
+ PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+ PushModeParams();
+ }
+
+ /** Constructor, populate parameters starting from the beginning of a mode change list.
+ * @param source User doing the mode change.
+ * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+ * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+ * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+ */
+ Mode(User* source, Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+ : ClientProtocol::Message("MODE", source)
+ , chantarget(Chantarget)
+ , usertarget(Usertarget)
+ , beginit(changelist.getlist().begin())
+ {
+ PushParamRef(GetStrTarget());
+ PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+ PushModeParams();
+ }
+
+ /** Constructor. Does not populate parameters, call SetParams() before sending the message.
+ * The message source is set to the local server.
+ */
+ Mode()
+ : ClientProtocol::Message("MODE", ServerInstance->FakeClient)
+ , chantarget(NULL)
+ , usertarget(NULL)
+ {
+ }
+
+ /** Set parameters
+ * @param Chantarget Channel target of the mode change. May be NULL if Usertarget is non-NULL.
+ * @param Usertarget User target of the mode change. May be NULL if Chantarget is non-NULL.
+ * @param changelist Mode change list. Must remain valid and unchanged as long as this object is alive or until the next SetParams() call.
+ */
+ void SetParams(Channel* Chantarget, User* Usertarget, const Modes::ChangeList& changelist)
+ {
+ ClearParams();
+
+ chantarget = Chantarget;
+ usertarget = Usertarget;
+ beginit = changelist.getlist().begin();
+
+ PushParamRef(GetStrTarget());
+ PushParam(ToModeLetters(changelist.getlist(), 450, beginit, lastit));
+ PushModeParams();
+ }
+
+ /** Get first mode change included in this MODE message.
+ * @return Iterator to the first mode change that is included in this MODE message.
+ */
+ Modes::ChangeList::List::const_iterator GetBeginIterator() const { return beginit; }
+
+ /** Get first mode change not included in this MODE message.
+ * @return Iterator to the first mode change that is not included in this MODE message.
+ */
+ Modes::ChangeList::List::const_iterator GetEndIterator() const { return lastit; }
+
+ /** Get mode change target as a string.
+ * This is the name of the channel if the mode change targets a channel or the nickname of the user
+ * if the target is a user.
+ * @return Name of target as a string.
+ */
+ const std::string& GetStrTarget() const { return (chantarget ? chantarget->name : usertarget->nick); }
+
+ /** Get user target.
+ * @return User target or NULL if the mode change targets a channel.
+ */
+ User* GetUserTarget() const { return usertarget; }
+
+ /** Get channel target.
+ * @return Channel target or NULL if the mode change targets a user.
+ */
+ Channel* GetChanTarget() const { return chantarget; }
+};
+
+/** TOPIC message.
+ */
+struct ClientProtocol::Messages::Topic : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param source User changing the topic.
+ * @param chan Channel the topic is being changed on.
+ * @param newtopic New topic. May be empty, must remain valid as long as this object is alive.
+ */
+ Topic(User* source, const Channel* chan, const std::string& newtopic)
+ : ClientProtocol::Message("TOPIC", source)
+ {
+ PushParamRef(chan->name);
+ PushParamRef(newtopic);
+ }
+};
+
+/** PRIVMSG and NOTICE message.
+ */
+class ClientProtocol::Messages::Privmsg : public ClientProtocol::Message
+{
+ void PushTargetChan(char status, const Channel* targetchan)
+ {
+ if (status)
+ {
+ std::string rawtarget(1, status);
+ rawtarget.append(targetchan->name);
+ PushParam(rawtarget);
+ }
+ else
+ {
+ PushParamRef(targetchan->name);
+ }
+ }
+
+ void PushTargetUser(const User* targetuser)
+ {
+ if (targetuser->registered & REG_NICK)
+ PushParamRef(targetuser->nick);
+ else
+ PushParam("*");
+ }
+
+ public:
+ /** Used to differentiate constructors that copy the text from constructors that do not.
+ */
+ enum NoCopy { nocopy };
+
+ /** Get command name from MessageType.
+ * @param mt Message type to get command name for.
+ * @return Command name for the message type.
+ */
+ static const char* CommandStrFromMsgType(MessageType mt)
+ {
+ return ((mt == MSG_PRIVMSG) ? "PRIVMSG" : "NOTICE");
+ }
+
+ /** Constructor, user source, string target, copies text.
+ * @param source Source user.
+ * @param target Privmsg target string.
+ * @param text Privmsg text, will be copied.
+ * @param mt Message type.
+ */
+ Privmsg(User* source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushParam(target);
+ PushParam(text);
+ }
+
+ /** Constructor, user source, user target, copies text.
+ * @param source Source user.
+ * @param targetchan Target channel.
+ * @param text Privmsg text, will be copied.
+ * @param mt Message type.
+ * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+ */
+ Privmsg(User* source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetChan(status, targetchan);
+ PushParam(text);
+ }
+
+ /** Constructor, user source, user target, copies text.
+ * @param source Source user.
+ * @param targetuser Target user.
+ * @param text Privmsg text, will be copied.
+ * @param mt Message type.
+ */
+ Privmsg(User* source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetUser(targetuser);
+ PushParam(text);
+ }
+
+ /** Constructor, string source, string target, copies text.
+ * @param source Source user.
+ * @param targetuser Target user.
+ * @param text Privmsg text, will be copied.
+ * @param mt Message type.
+ */
+ Privmsg(const std::string& source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushParam(target);
+ PushParam(text);
+ }
+
+ /** Constructor, string source, channel target, copies text.
+ * @param source Source string.
+ * @param targetchan Target channel.
+ * @param text Privmsg text, will be copied.
+ * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+ * @param mt Message type.
+ */
+ Privmsg(const std::string& source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetChan(status, targetchan);
+ PushParam(text);
+ }
+
+ /** Constructor, string source, user target, copies text.
+ * @param source Source string.
+ * @param targetuser Target user.
+ * @param text Privmsg text, will be copied.
+ * @param mt Message type.
+ */
+ Privmsg(const std::string& source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetUser(targetuser);
+ PushParam(text);
+ }
+
+ /** Constructor, user source, string target, copies text.
+ * @param source Source user.
+ * @param targetuser Target string.
+ * @param text Privmsg text, will not be copied.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, User* source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushParam(target);
+ PushParamRef(text);
+ }
+
+ /** Constructor, user source, channel target, does not copy text.
+ * @param source Source user.
+ * @param targetchan Target channel.
+ * @param text Privmsg text, will not be copied.
+ * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, User* source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetChan(status, targetchan);
+ PushParamRef(text);
+ }
+
+ /** Constructor, user source, user target, does not copy text.
+ * @param source Source user.
+ * @param targetuser Target user.
+ * @param text Privmsg text, will not be copied.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, User* source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetUser(targetuser);
+ PushParamRef(text);
+ }
+
+ /** Constructor, string source, string target, does not copy text.
+ * @param source Source string.
+ * @param targetuser Target string.
+ * @param text Privmsg text, will not be copied.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, const std::string& source, const std::string& target, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushParam(target);
+ PushParamRef(text);
+ }
+
+ /** Constructor, string source, channel target, does not copy text.
+ * @param source Source string.
+ * @param targetchan Target channel.
+ * @param text Privmsg text, will not be copied.
+ * @param status Prefix character for status messages. If non-zero the message is a status message. Optional, defaults to 0.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, const std::string& source, const Channel* targetchan, const std::string& text, MessageType mt = MSG_PRIVMSG, char status = 0)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetChan(status, targetchan);
+ PushParamRef(text);
+ }
+
+ /** Constructor, string source, user target, does not copy text.
+ * @param source Source string.
+ * @param targetchan Target user.
+ * @param text Privmsg text, will not be copied.
+ * @param mt Message type.
+ */
+ Privmsg(NoCopy, const std::string& source, const User* targetuser, const std::string& text, MessageType mt = MSG_PRIVMSG)
+ : ClientProtocol::Message(CommandStrFromMsgType(mt), source)
+ {
+ PushTargetUser(targetuser);
+ PushParamRef(text);
+ }
+};
+
+/** INVITE message.
+ * Sent when a user is invited to join a channel.
+ */
+struct ClientProtocol::Messages::Invite : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param source User inviting the target user.
+ * @param target User being invited by source.
+ * @param chan Channel the target user is being invited to.
+ */
+ Invite(User* source, User* target, Channel* chan)
+ : ClientProtocol::Message("INVITE", source)
+ {
+ PushParamRef(target->nick);
+ PushParamRef(chan->name);
+ }
+};
+
+/** PING message.
+ * Used to check if a connection is still alive.
+ */
+struct ClientProtocol::Messages::Ping : public ClientProtocol::Message
+{
+ /** Constructor.
+ * The ping cookie is the name of the local server.
+ */
+ Ping()
+ : ClientProtocol::Message("PING")
+ {
+ PushParamRef(ServerInstance->Config->ServerName);
+ }
+
+ /** Constructor.
+ * @param cookie Ping cookie. Must remain valid as long as this object is alive.
+ */
+ Ping(const std::string& cookie)
+ : ClientProtocol::Message("PING")
+ {
+ PushParamRef(cookie);
+ }
+};
+
+/** PONG message.
+ * Sent as a reply to PING.
+ */
+struct ClientProtocol::Messages::Pong : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param cookie Ping cookie. Must remain valid as long as this object is alive.
+ */
+ Pong(const std::string& cookie)
+ : ClientProtocol::Message("PONG", ServerInstance->Config->ServerName)
+ {
+ PushParamRef(ServerInstance->Config->ServerName);
+ PushParamRef(cookie);
+ }
+};
+
+/** ERROR message.
+ * Sent to clients upon disconnection.
+ */
+struct ClientProtocol::Messages::Error : public ClientProtocol::Message
+{
+ /** Constructor.
+ * @param text Error text.
+ */
+ Error(const std::string& text)
+ : ClientProtocol::Message("ERROR")
+ {
+ PushParam(text);
+ }
+};
diff --git a/include/command_parse.h b/include/command_parse.h
index f5cb47620..98484ca54 100644
--- a/include/command_parse.h
+++ b/include/command_parse.h
@@ -38,7 +38,7 @@ class CoreExport CommandParser
* @param command The name of the command.
* @param parameters The parameters to the command.
*/
- void ProcessCommand(LocalUser* user, std::string& command, Command::Params& parameters);
+ void ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& parameters);
/** Command list, a hash_map of command names to Command*
*/
diff --git a/include/configreader.h b/include/configreader.h
index 81ec014a0..4cb051eff 100644
--- a/include/configreader.h
+++ b/include/configreader.h
@@ -430,11 +430,6 @@ class CoreExport ServerConfig
*/
std::string CaseMapping;
- /** If set to true, the CycleHosts mode change will be sourced from the user,
- * rather than the server
- */
- bool CycleHostsFromUser;
-
/** If set to true, the full nick!user\@host will be shown in the TOPIC command
* for who set the topic last. If false, only the nick is shown.
*/
diff --git a/include/ctables.h b/include/ctables.h
index c34e4abeb..8be40cc54 100644
--- a/include/ctables.h
+++ b/include/ctables.h
@@ -110,7 +110,40 @@ struct RouteDescriptor
class CoreExport CommandBase : public ServiceProvider
{
public:
- typedef std::vector<std::string> Params;
+ /** Encapsulates parameters to a command. */
+ class Params : public std::vector<std::string>
+ {
+ private:
+ /* IRCv3 message tags. */
+ ClientProtocol::TagMap tags;
+
+ public:
+ /** Initializes a new instance from parameter and tag references.
+ * @param paramsref Message parameters.
+ * @param tagsref IRCv3 message tags.
+ */
+ Params(const std::vector<std::string>& paramsref, const ClientProtocol::TagMap& tagsref)
+ : std::vector<std::string>(paramsref)
+ , tags(tagsref)
+ {
+ }
+
+ /** Initializes a new instance from parameter iterators.
+ * @param first The first element in the parameter array.
+ * @param last The last element in the parameter array.
+ */
+ template<typename Iterator>
+ Params(Iterator first, Iterator last)
+ : std::vector<std::string>(first, last)
+ {
+ }
+
+ /** Initializes a new empty instance. */
+ Params() { }
+
+ /** Retrieves the IRCv3 message tags. */
+ const ClientProtocol::TagMap& GetTags() const { return tags; }
+ };
/** User flags needed to execute the command or 0
*/
diff --git a/include/event.h b/include/event.h
index c9bad7d04..73a45a541 100644
--- a/include/event.h
+++ b/include/event.h
@@ -34,7 +34,12 @@ namespace Events
class Events::ModuleEventProvider : public ServiceProvider, private dynamic_reference_base::CaptureHook
{
public:
- typedef std::vector<ModuleEventListener*> SubscriberList;
+ struct Comp
+ {
+ bool operator()(ModuleEventListener* one, ModuleEventListener* two) const;
+ };
+
+ typedef insp::flat_multiset<ModuleEventListener*, Comp> SubscriberList;
/** Constructor
* @param mod Module providing the event(s)
@@ -84,20 +89,25 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
*/
dynamic_reference_nocheck<ModuleEventProvider> prov;
+ const unsigned int eventpriority;
+
/** Called by the dynref when the event provider becomes available
*/
void OnCapture() CXX11_OVERRIDE
{
- prov->subscribers.push_back(this);
+ prov->subscribers.insert(this);
}
public:
+ static const unsigned int DefaultPriority = 100;
+
/** Constructor
* @param mod Module subscribing
* @param eventid Identifier of the event to subscribe to
*/
- ModuleEventListener(Module* mod, const std::string& eventid)
+ ModuleEventListener(Module* mod, const std::string& eventid, unsigned int eventprio = DefaultPriority)
: prov(mod, eventid)
+ , eventpriority(eventprio)
{
prov.SetCaptureHook(this);
// If the dynamic_reference resolved at construction our capture handler wasn't called
@@ -108,18 +118,25 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
~ModuleEventListener()
{
if (prov)
- stdalgo::erase(prov->subscribers, this);
+ prov->subscribers.erase(this);
}
+
+ friend struct ModuleEventProvider::Comp;
};
+inline bool Events::ModuleEventProvider::Comp::operator()(Events::ModuleEventListener* one, Events::ModuleEventListener* two) const
+{
+ return (one->eventpriority < two->eventpriority);
+}
+
/**
* Run the given hook provided by a module
*
* FOREACH_MOD_CUSTOM(accountevprov, AccountEventListener, OnAccountChange, MOD_RESULT, (user, newaccount))
*/
#define FOREACH_MOD_CUSTOM(prov, listenerclass, func, params) do { \
- const Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
- for (Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
+ const ::Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
+ for (::Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
{ \
listenerclass* _t = static_cast<listenerclass*>(*_i); \
_t->func params ; \
@@ -135,8 +152,8 @@ class Events::ModuleEventListener : private dynamic_reference_base::CaptureHook
*/
#define FIRST_MOD_RESULT_CUSTOM(prov, listenerclass, func, result, params) do { \
result = MOD_RES_PASSTHRU; \
- const Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
- for (Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
+ const ::Events::ModuleEventProvider::SubscriberList& _handlers = (prov).GetSubscribers(); \
+ for (::Events::ModuleEventProvider::SubscriberList::const_iterator _i = _handlers.begin(); _i != _handlers.end(); ++_i) \
{ \
listenerclass* _t = static_cast<listenerclass*>(*_i); \
result = _t->func params ; \
diff --git a/include/inspircd.h b/include/inspircd.h
index 00093e52b..90ee6ca8d 100644
--- a/include/inspircd.h
+++ b/include/inspircd.h
@@ -91,6 +91,7 @@ struct fakederef
#include "filelogger.h"
#include "message.h"
#include "modules.h"
+#include "clientprotocol.h"
#include "threadengine.h"
#include "configreader.h"
#include "inspstring.h"
@@ -193,6 +194,8 @@ class CoreExport InspIRCd
*/
char ReadBuffer[65535];
+ ClientProtocol::RFCEvents rfcevents;
+
/** Check we aren't running as root, and exit if we are
* with exit code EXIT_STATUS_ROOT.
*/
@@ -565,6 +568,8 @@ class CoreExport InspIRCd
{
return this->ReadBuffer;
}
+
+ ClientProtocol::RFCEvents& GetRFCEvents() { return rfcevents; }
};
ENTRYPOINT;
@@ -590,4 +595,18 @@ inline void stdalgo::culldeleter::operator()(classbase* item)
ServerInstance->GlobalCulls.AddItem(item);
}
+inline void Channel::Write(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg, char status, const CUList& except_list)
+{
+ ClientProtocol::Event event(protoevprov, msg);
+ Write(event, status, except_list);
+}
+
+inline void LocalUser::Send(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg)
+{
+ ClientProtocol::Event event(protoevprov, msg);
+ Send(event);
+}
+
#include "numericbuilder.h"
+#include "clientprotocolmsg.h"
+#include "clientprotocolevent.h"
diff --git a/include/message.h b/include/message.h
index fb9e7619f..1799e6119 100644
--- a/include/message.h
+++ b/include/message.h
@@ -41,15 +41,22 @@ class CoreExport MessageDetails
/* The original message as sent by the user. */
const std::string originaltext;
+ /** 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;
+
/** The message which will be sent to clients. */
std::string text;
/** The type of message. */
const MessageType type;
- MessageDetails(MessageType mt, const std::string& msg)
+ MessageDetails(MessageType mt, const std::string& msg, const ClientProtocol::TagMap& tags)
: echooriginal(false)
, originaltext(msg)
+ , tags_in(tags)
, text(msg)
, type(mt)
{
diff --git a/include/mode.h b/include/mode.h
index 4fa78bcac..ac23adc33 100644
--- a/include/mode.h
+++ b/include/mode.h
@@ -614,11 +614,6 @@ class CoreExport ModeParser : public fakederef<ModeParser>
*/
ModeHandler::Id AllocateModeId(ModeType mt);
- /** The string representing the last set of modes to be parsed.
- * Use GetLastParse() to get this value, to be used for display purposes.
- */
- std::string LastParse;
-
/** Cached mode list for use in 004 numeric
*/
TR1NS::array<std::string, 3> Cached004ModeList;
@@ -681,13 +676,6 @@ class CoreExport ModeParser : public fakederef<ModeParser>
/** Gets the last mode change to be processed. */
const Modes::ChangeList& GetLastChangeList() const { return LastChangeList; }
- /** Get the last string to be processed, as it was sent to the user or channel.
- * Use this to display a string you just sent to be parsed, as the actual output
- * may be different to what you sent after it has been 'cleaned up' by the parser.
- * @return Last parsed string, as seen by users.
- */
- const std::string& GetLastParse() const { return LastParse; }
-
/** Add a mode to the mode parser.
* Throws a ModuleException if the mode cannot be added.
*/
diff --git a/include/modechange.h b/include/modechange.h
index 885c22900..9ec105e73 100644
--- a/include/modechange.h
+++ b/include/modechange.h
@@ -54,6 +54,23 @@ class Modes::ChangeList
typedef std::vector<Change> List;
/** Add a new mode to be changed to this ChangeList
+ * @param change Mode change to add
+ */
+ void push(const Modes::Change& change)
+ {
+ items.push_back(change);
+ }
+
+ /** Insert multiple mode changes to the ChangeList
+ * @param first Iterator to the first change to insert
+ * @param last Iterator to the first change to not insert
+ */
+ void push(List::const_iterator first, List::const_iterator last)
+ {
+ items.insert(items.end(), first, last);
+ }
+
+ /** Add a new mode to be changed to this ChangeList
* @param mh Mode handler
* @param adding True if this mode is being set, false if removed
* @param param Mode parameter
diff --git a/include/modules.h b/include/modules.h
index 4e5648512..ba84c4ccc 100644
--- a/include/modules.h
+++ b/include/modules.h
@@ -227,7 +227,7 @@ enum Implementation
I_OnBuildNeighborList, I_OnGarbageCollect, I_OnSetConnectClass,
I_OnUserMessage, I_OnPassCompare, I_OnNamesListItem, I_OnNumeric,
I_OnPreRehash, I_OnModuleRehash, I_OnSendWhoLine, I_OnChangeIdent, I_OnSetUserIP,
- I_OnServiceAdd, I_OnServiceDel,
+ I_OnServiceAdd, I_OnServiceDel, I_OnUserWrite,
I_END
};
@@ -561,9 +561,8 @@ class CoreExport Module : public classbase, public usecountbase
* @param changelist The changed modes.
* @param processflags Flags passed to ModeParser::Process(), see ModeParser::ModeProcessFlags
* for the possible flags.
- * @param output_mode Changed modes, including '+' and '-' characters, not including any parameters
*/
- virtual void OnMode(User* user, User* usertarget, Channel* chantarget, const Modes::ChangeList& changelist, ModeParser::ModeProcessFlag processflags, const std::string& output_mode);
+ virtual void OnMode(User* user, User* usertarget, Channel* chantarget, const Modes::ChangeList& changelist, ModeParser::ModeProcessFlag processflags);
/** Allows module data, sent via ProtoSendMetaData, to be decoded again by a receiving module.
* Please see src/modules/m_swhois.cpp for a working example of how to use this method call.
@@ -953,6 +952,8 @@ class CoreExport Module : public classbase, public usecountbase
* @param service ServiceProvider being unregistered.
*/
virtual void OnServiceDel(ServiceProvider& service);
+
+ virtual ModResult OnUserWrite(LocalUser* user, ClientProtocol::Message& msg);
};
/** ModuleManager takes care of all things module-related
diff --git a/include/modules/cap.h b/include/modules/cap.h
index 8299d14ae..6dcb9f3bc 100644
--- a/include/modules/cap.h
+++ b/include/modules/cap.h
@@ -313,4 +313,23 @@ namespace Cap
return false;
}
};
+
+ class MessageBase : public ClientProtocol::Message
+ {
+ public:
+ MessageBase(const std::string& subcmd)
+ : ClientProtocol::Message("CAP", ServerInstance->Config->ServerName)
+ {
+ PushParamPlaceholder();
+ PushParam(subcmd);
+ }
+
+ void SetUser(LocalUser* user)
+ {
+ if (user->registered & REG_NICK)
+ ReplaceParamRef(0, user->nick);
+ else
+ ReplaceParam(0, "*");
+ }
+ };
}
diff --git a/include/modules/ircv3.h b/include/modules/ircv3.h
index e03ee16fa..338abdeba 100644
--- a/include/modules/ircv3.h
+++ b/include/modules/ircv3.h
@@ -19,27 +19,83 @@
#pragma once
+#include "modules/cap.h"
+
namespace IRCv3
{
class WriteNeighborsWithCap;
+ template <typename T>
+ class CapTag;
}
class IRCv3::WriteNeighborsWithCap : public User::ForEachNeighborHandler
{
const Cap::Capability& cap;
- const std::string& msg;
+ ClientProtocol::Event& protoev;
void Execute(LocalUser* user) CXX11_OVERRIDE
{
if (cap.get(user))
- user->Write(msg);
+ user->Send(protoev);
}
public:
- WriteNeighborsWithCap(User* user, const std::string& message, const Cap::Capability& capability)
+ WriteNeighborsWithCap(User* user, ClientProtocol::Event& ev, const Cap::Capability& capability)
: cap(capability)
- , msg(message)
+ , protoev(ev)
{
user->ForEachNeighbor(*this, false);
}
};
+
+/** Base class for simple message tags.
+ * Message tags provided by classes derived from this class will be sent to clients that have negotiated
+ * a client capability, also managed by this class.
+ *
+ * Derived classes specify the name of the capability and the message tag and provide a public GetValue()
+ * method with the following signature: const std::string* GetValue(ClientProtocol::Message& msg).
+ * The returned value determines whether to attach the tag to the message. If it is NULL, the tag won't
+ * be attached. If it is non-NULL the tag will be attached with the value in the string. If the string is
+ * empty the tag is attached without a value.
+ *
+ * Providers inheriting from this class don't accept incoming tags by default.
+ *
+ * For more control, inherit from ClientProtocol::MessageTagProvider directly.
+ *
+ * Template parameter T is the derived class.
+ */
+template <typename T>
+class IRCv3::CapTag : public ClientProtocol::MessageTagProvider
+{
+ Cap::Capability cap;
+ const std::string tagname;
+
+ bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
+ {
+ return cap.get(user);
+ }
+
+ void OnClientProtocolPopulateTags(ClientProtocol::Message& msg) CXX11_OVERRIDE
+ {
+ T& tag = static_cast<T&>(*this);
+ const std::string* const val = tag.GetValue(msg);
+ if (val)
+ msg.AddTag(tagname, this, *val);
+ }
+
+ public:
+ /** Constructor.
+ * @param mod Module that owns the tag.
+ * @param capname Name of the client capability.
+ * A client capability with this name will be created. It will be available to all clients and it won't
+ * have a value.
+ * See Cap::Capability for more info on client capabilities.
+ * @param Tagname Name of the message tag, to use in the protocol.
+ */
+ CapTag(Module* mod, const std::string& capname, const std::string& Tagname)
+ : ClientProtocol::MessageTagProvider(mod)
+ , cap(mod, capname)
+ , tagname(Tagname)
+ {
+ }
+};
diff --git a/include/stdalgo.h b/include/stdalgo.h
index bb5e12262..d69f50bb2 100644
--- a/include/stdalgo.h
+++ b/include/stdalgo.h
@@ -210,4 +210,86 @@ namespace stdalgo
{
return (std::find(cont.begin(), cont.end(), val) != cont.end());
}
+
+ namespace string
+ {
+ /**
+ * Escape a string
+ * @param str String to escape
+ * @param out Output, must not be the same string as str
+ */
+ template <char from, char to, char esc>
+ inline void escape(const std::string& str, std::string& out)
+ {
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+ {
+ char c = *i;
+ if (c == esc)
+ out.append(2, esc);
+ else
+ {
+ if (c == from)
+ {
+ out.push_back(esc);
+ c = to;
+ }
+ out.push_back(c);
+ }
+ }
+ }
+
+ /**
+ * Escape a string using the backslash character as the escape character
+ * @param str String to escape
+ * @param out Output, must not be the same string as str
+ */
+ template <char from, char to>
+ inline void escape(const std::string& str, std::string& out)
+ {
+ escape<from, to, '\\'>(str, out);
+ }
+
+ /**
+ * Unescape a string
+ * @param str String to unescape
+ * @param out Output, must not be the same string as str
+ * @return True if the string was unescaped, false if an invalid escape sequence is present in the input in which case out will contain a partially unescaped string
+ */
+ template<char from, char to, char esc>
+ inline bool unescape(const std::string& str, std::string& out)
+ {
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i)
+ {
+ char c = *i;
+ if (c == '\\')
+ {
+ ++i;
+ if (i == str.end())
+ return false;
+
+ char nextc = *i;
+ if (nextc == esc)
+ c = esc;
+ else if (nextc != to)
+ return false; // Invalid escape sequence
+ else
+ c = from;
+ }
+ out.push_back(c);
+ }
+ return true;
+ }
+
+ /**
+ * Unescape a string using the backslash character as the escape character
+ * @param str String to unescape
+ * @param out Output, must not be the same string as str
+ * @return True if the string was unescaped, false if an invalid escape sequence is present in the input in which case out will contain a partially unescaped string
+ */
+ template <char from, char to>
+ inline bool unescape(const std::string& str, std::string& out)
+ {
+ return unescape<from, to, '\\'>(str, out);
+ }
+ }
}
diff --git a/include/typedefs.h b/include/typedefs.h
index 9a015d445..20fc596be 100644
--- a/include/typedefs.h
+++ b/include/typedefs.h
@@ -49,6 +49,34 @@ class XLineFactory;
struct ConnectClass;
struct ModResult;
+namespace ClientProtocol
+{
+ class Event;
+ class EventProvider;
+ class Message;
+ class MessageTagProvider;
+ class Serializer;
+
+ typedef std::vector<Message*> MessageList;
+ typedef std::vector<std::string> ParamList;
+ typedef std::string SerializedMessage;
+
+ struct MessageTagData
+ {
+ MessageTagProvider* tagprov;
+ std::string value;
+ void* provdata;
+
+ MessageTagData(MessageTagProvider* prov, const std::string& val, void* data = NULL);
+ };
+
+ /** Map of message tag values and providers keyed by their name.
+ * Sorted in descending order to ensure tag names beginning with symbols (such as '+') come later when iterating
+ * the container than tags with a normal name.
+ */
+ typedef insp::flat_map<std::string, MessageTagData, std::greater<std::string> > TagMap;
+}
+
#include "hashcomp.h"
#include "base.h"
diff --git a/include/users.h b/include/users.h
index 21a35645d..e8f5399e8 100644
--- a/include/users.h
+++ b/include/users.h
@@ -498,41 +498,10 @@ class CoreExport User : public Extensible
*/
void UnOper();
- /** Write text to this user, appending CR/LF. Works on local users only.
- * @param text A std::string to send to the user
- */
- virtual void Write(const std::string &text);
-
- /** Write text to this user, appending CR/LF.
- * Works on local users only.
- * @param text The format string for text to send to the user
- * @param ... POD-type format arguments
- */
- virtual void Write(const char *text, ...) CUSTOM_PRINTF(2, 3);
-
- /** Write text to this user, appending CR/LF and prepending :server.name
- * Works on local users only.
- * @param text A std::string to send to the user
- */
- void WriteServ(const std::string& text);
-
- /** Write text to this user, appending CR/LF and prepending :server.name
- * Works on local users only.
- * @param text The format string for text to send to the user
- * @param ... POD-type format arguments
- */
- void WriteServ(const char* text, ...) CUSTOM_PRINTF(2, 3);
-
- /** Sends a command to this user.
- * @param command The command to be sent.
- * @param text The message to send.
- */
- void WriteCommand(const char* command, const std::string& text);
-
/** Sends a server notice to this user.
* @param text The contents of the message to send.
*/
- void WriteNotice(const std::string& text) { this->WriteCommand("NOTICE", ":" + text); }
+ void WriteNotice(const std::string& text);
/** Send a NOTICE message from the local server to the user.
* @param text Text to send
@@ -643,30 +612,11 @@ class CoreExport User : public Extensible
WriteNumeric(n);
}
- /** Write text to this user, appending CR/LF and prepending :nick!user\@host of the user provided in the first parameter.
- * @param user The user to prepend the :nick!user\@host of
- * @param text A std::string to send to the user
- */
- void WriteFrom(User *user, const std::string &text);
-
- /** Write text to this user, appending CR/LF and prepending :nick!user\@host of the user provided in the first parameter.
- * @param user The user to prepend the :nick!user\@host of
- * @param text The format string for text to send to the user
- * @param ... POD-type format arguments
- */
- void WriteFrom(User *user, const char* text, ...) CUSTOM_PRINTF(3, 4);
-
/** Write to all users that can see this user (including this user in the list if include_self is true), appending CR/LF
- * @param line A std::string to send to the users
+ * @param protoev Protocol event to send, may contain any number of messages.
* @param include_self Should the message be sent back to the author?
*/
- void WriteCommonRaw(const std::string &line, bool include_self = true);
-
- /** Write to all users that can see this user (including this user in the list), appending CR/LF
- * @param text The format string for text to send to the users
- * @param ... POD-type format arguments
- */
- void WriteCommon(const char* text, ...) CUSTOM_PRINTF(2, 3);
+ void WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self = true);
/** Execute a function once for each local neighbor of this user. By default, the neighbors of a user are the users
* who have at least one common channel with the user. Modules are allowed to alter the set of neighbors freely.
@@ -750,12 +700,32 @@ typedef unsigned int already_sent_t;
class CoreExport LocalUser : public User, public insp::intrusive_list_node<LocalUser>
{
+ /** Add a serialized message to the send queue of the user.
+ * @param serialized Bytes to add.
+ */
+ void Write(const ClientProtocol::SerializedMessage& serialized);
+
+ /** Send a protocol event to the user, consisting of one or more messages.
+ * @param protoev Event to send, may contain any number of messages.
+ * @param msglist Message list used temporarily internally to pass to hooks and store messages
+ * before Write().
+ */
+ void Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist);
+
+ /** Message list, can be passed to the two parameter Send().
+ */
+ static ClientProtocol::MessageList sendmsglist;
+
public:
LocalUser(int fd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server);
CullResult cull() CXX11_OVERRIDE;
UserIOHandler eh;
+ /** Serializer to use when communicating with the user
+ */
+ ClientProtocol::Serializer* serializer;
+
/** Stats counter for bytes inbound
*/
unsigned int bytes_in;
@@ -850,9 +820,6 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local
void SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_eline = true) CXX11_OVERRIDE;
- void Write(const std::string& text) CXX11_OVERRIDE;
- void Write(const char*, ...) CXX11_OVERRIDE CUSTOM_PRINTF(2, 3);
-
/** Send a NOTICE message from the local server to the user.
* The message will be sent even if the user is connected to a remote server.
* @param text Text to send
@@ -890,6 +857,17 @@ class CoreExport LocalUser : public User, public insp::intrusive_list_node<Local
* isn't registered.
*/
void OverruleNick();
+
+ /** Send a protocol event to the user, consisting of one or more messages.
+ * @param protoev Event to send, may contain any number of messages.
+ */
+ void Send(ClientProtocol::Event& protoev);
+
+ /** Send a single message to the user.
+ * @param protoevprov Protocol event provider.
+ * @param msg Message to send.
+ */
+ void Send(ClientProtocol::EventProvider& protoevprov, ClientProtocol::Message& msg);
};
class RemoteUser : public User
diff --git a/src/channels.cpp b/src/channels.cpp
index b293e7fad..c9816db4b 100644
--- a/src/channels.cpp
+++ b/src/channels.cpp
@@ -49,7 +49,8 @@ void Channel::SetTopic(User* u, const std::string& ntopic, time_t topicts, const
if (this->topic != ntopic)
{
this->topic = ntopic;
- this->WriteChannel(u, "TOPIC %s :%s", this->name.c_str(), this->topic.c_str());
+ ClientProtocol::Messages::Topic topicmsg(u, this, this->topic);
+ Write(ServerInstance->GetRFCEvents().topic, topicmsg);
}
// Always update setter and set time
@@ -287,18 +288,8 @@ Membership* Channel::ForceJoin(User* user, const std::string* privs, bool bursti
CUList except_list;
FOREACH_MOD(OnUserJoin, (memb, bursting, created_by_local, except_list));
- this->WriteAllExcept(user, false, 0, except_list, "JOIN :%s", this->name.c_str());
-
- /* Theyre not the first ones in here, make sure everyone else sees the modes we gave the user */
- if ((GetUserCounter() > 1) && (!memb->modes.empty()))
- {
- std::string ms = memb->modes;
- for(unsigned int i=0; i < memb->modes.length(); i++)
- ms.append(" ").append(user->nick);
-
- except_list.insert(user);
- this->WriteAllExcept(user, !ServerInstance->Config->CycleHostsFromUser, 0, except_list, "MODE %s +%s", this->name.c_str(), ms.c_str());
- }
+ ClientProtocol::Events::Join joinevent(memb);
+ this->Write(joinevent, 0, except_list);
FOREACH_MOD(OnPostJoin, (memb));
return memb;
@@ -397,7 +388,8 @@ bool Channel::PartUser(User* user, std::string& reason)
CUList except_list;
FOREACH_MOD(OnUserPart, (memb, reason, except_list));
- WriteAllExcept(user, false, 0, except_list, "PART %s%s%s", this->name.c_str(), reason.empty() ? "" : " :", reason.c_str());
+ ClientProtocol::Messages::Part partmsg(memb, reason);
+ Write(ServerInstance->GetRFCEvents().part, partmsg, 0, except_list);
// Remove this channel from the user's chanlist
user->chans.erase(memb);
@@ -413,73 +405,14 @@ void Channel::KickUser(User* src, const MemberMap::iterator& victimiter, const s
CUList except_list;
FOREACH_MOD(OnUserKick, (src, memb, reason, except_list));
- User* victim = memb->user;
- WriteAllExcept(src, false, 0, except_list, "KICK %s %s :%s", name.c_str(), victim->nick.c_str(), reason.c_str());
+ ClientProtocol::Messages::Kick kickmsg(src, memb, reason);
+ Write(ServerInstance->GetRFCEvents().kick, kickmsg, 0, except_list);
- victim->chans.erase(memb);
+ memb->user->chans.erase(memb);
this->DelUser(victimiter);
}
-void Channel::WriteChannel(User* user, const char* text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->WriteChannel(user, textbuffer);
-}
-
-void Channel::WriteChannel(User* user, const std::string &text)
-{
- const std::string message = ":" + user->GetFullHost() + " " + text;
-
- for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
- {
- if (IS_LOCAL(i->first))
- i->first->Write(message);
- }
-}
-
-void Channel::WriteChannelWithServ(const std::string& ServName, const char* text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->WriteChannelWithServ(ServName, textbuffer);
-}
-
-void Channel::WriteChannelWithServ(const std::string& ServName, const std::string &text)
-{
- const std::string message = ":" + (ServName.empty() ? ServerInstance->Config->ServerName : ServName) + " " + text;
-
- for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
- {
- if (IS_LOCAL(i->first))
- i->first->Write(message);
- }
-}
-
-/* write formatted text from a source user to all users on a channel except
- * for the sender (for privmsg etc) */
-void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const char* text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->WriteAllExceptSender(user, serversource, status, textbuffer);
-}
-
-void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const char* text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- textbuffer = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + textbuffer;
- this->RawWriteAllExcept(user, serversource, status, except_list, textbuffer);
-}
-
-void Channel::WriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &text)
-{
- const std::string message = ":" + (serversource ? ServerInstance->Config->ServerName : user->GetFullHost()) + " " + text;
- this->RawWriteAllExcept(user, serversource, status, except_list, message);
-}
-
-void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CUList &except_list, const std::string &out)
+void Channel::Write(ClientProtocol::Event& protoev, char status, const CUList& except_list)
{
unsigned int minrank = 0;
if (status)
@@ -490,24 +423,18 @@ void Channel::RawWriteAllExcept(User* user, bool serversource, char status, CULi
}
for (MemberMap::iterator i = userlist.begin(); i != userlist.end(); i++)
{
- if (IS_LOCAL(i->first) && (except_list.find(i->first) == except_list.end()))
+ LocalUser* user = IS_LOCAL(i->first);
+ if ((user) && (!except_list.count(user)))
{
/* User doesn't have the status we're after */
if (minrank && i->second->getRank() < minrank)
continue;
- i->first->Write(out);
+ user->Send(protoev);
}
}
}
-void Channel::WriteAllExceptSender(User* user, bool serversource, char status, const std::string& text)
-{
- CUList except_list;
- except_list.insert(user);
- this->WriteAllExcept(user, serversource, status, except_list, std::string(text));
-}
-
const char* Channel::ChanModes(bool showkey)
{
static std::string scratch;
@@ -545,9 +472,8 @@ const char* Channel::ChanModes(bool showkey)
void Channel::WriteNotice(const std::string& text)
{
- std::string rawmsg = "NOTICE ";
- rawmsg.append(this->name).append(" :").append(text);
- WriteChannelWithServ(ServerInstance->Config->ServerName, rawmsg);
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this, text, MSG_NOTICE);
+ Write(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
/* returns the status character for a given user on a channel, e.g. @ for op,
@@ -628,7 +554,10 @@ bool Membership::SetPrefix(PrefixMode* delta_mh, bool adding)
void Membership::WriteNotice(const std::string& text) const
{
- std::string rawmsg = "NOTICE ";
- rawmsg.append(chan->name).append(" :").append(text);
- user->WriteServ(rawmsg);
+ LocalUser* const localuser = IS_LOCAL(user);
+ if (!localuser)
+ return;
+
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, this->chan, text, MSG_NOTICE);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
diff --git a/src/clientprotocol.cpp b/src/clientprotocol.cpp
new file mode 100644
index 000000000..a732855a5
--- /dev/null
+++ b/src/clientprotocol.cpp
@@ -0,0 +1,105 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+ClientProtocol::Serializer::Serializer(Module* mod, const char* Name)
+ : DataProvider(mod, std::string("serializer/") + Name)
+ , evprov(mod, "event/messagetag")
+{
+}
+
+bool ClientProtocol::Serializer::HandleTag(LocalUser* user, const std::string& tagname, std::string& tagvalue, TagMap& tags) const
+{
+ // Catch and block empty tags
+ if (tagname.empty())
+ return false;
+
+ const ::Events::ModuleEventProvider::SubscriberList& list = evprov.GetSubscribers();
+ for (::Events::ModuleEventProvider::SubscriberList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ MessageTagProvider* const tagprov = static_cast<MessageTagProvider*>(*i);
+ const ModResult res = tagprov->OnClientProtocolProcessTag(user, tagname, tagvalue);
+ if (res == MOD_RES_ALLOW)
+ return tags.insert(std::make_pair(tagname, MessageTagData(tagprov, tagvalue))).second;
+ else if (res == MOD_RES_DENY)
+ break;
+ }
+
+ // No module handles the tag but that's not an error
+ return true;
+}
+
+ClientProtocol::TagSelection ClientProtocol::Serializer::MakeTagWhitelist(LocalUser* user, const TagMap& tagmap) const
+{
+ TagSelection tagwl;
+ for (TagMap::const_iterator i = tagmap.begin(); i != tagmap.end(); ++i)
+ {
+ const MessageTagData& tagdata = i->second;
+ if (tagdata.tagprov->ShouldSendTag(user, tagdata))
+ tagwl.Select(tagmap, i);
+ }
+ return tagwl;
+}
+
+const ClientProtocol::SerializedMessage& ClientProtocol::Serializer::SerializeForUser(LocalUser* user, Message& msg)
+{
+ if (!msg.msginit_done)
+ {
+ msg.msginit_done = true;
+ FOREACH_MOD_CUSTOM(evprov, MessageTagProvider, OnClientProtocolPopulateTags, (msg));
+ }
+ return msg.GetSerialized(Message::SerializedInfo(this, MakeTagWhitelist(user, msg.GetTags())));
+}
+
+const ClientProtocol::SerializedMessage& ClientProtocol::Message::GetSerialized(const SerializedInfo& serializeinfo) const
+{
+ // First check if the serialized line they're asking for is in the cache
+ for (SerializedList::const_iterator i = serlist.begin(); i != serlist.end(); ++i)
+ {
+ const SerializedInfo& curr = i->first;
+ if (curr == serializeinfo)
+ return i->second;
+ }
+
+ // Not cached, generate it and put it in the cache for later use
+ serlist.push_back(std::make_pair(serializeinfo, serializeinfo.serializer->Serialize(*this, serializeinfo.tagwl)));
+ return serlist.back().second;
+}
+
+void ClientProtocol::Event::GetMessagesForUser(LocalUser* user, MessageList& messagelist)
+{
+ if (!eventinit_done)
+ {
+ eventinit_done = true;
+ FOREACH_MOD_CUSTOM(*event, EventHook, OnEventInit, (*this));
+ }
+
+ // Most of the time there's only a single message but in rare cases there are more
+ if (initialmsg)
+ messagelist.assign(1, initialmsg);
+ else
+ messagelist = *initialmsglist;
+
+ // Let modules modify the message list
+ ModResult res;
+ FIRST_MOD_RESULT_CUSTOM(*event, EventHook, OnPreEventSend, res, (user, *this, messagelist));
+ if (res == MOD_RES_DENY)
+ messagelist.clear();
+}
diff --git a/src/command_parse.cpp b/src/command_parse.cpp
index c133c475e..503630d53 100644
--- a/src/command_parse.cpp
+++ b/src/command_parse.cpp
@@ -93,7 +93,8 @@ bool CommandParser::LoopCall(User* user, Command* handler, const CommandBase::Pa
new_parameters[extra] = item;
}
- CmdResult result = handler->Handle(user, new_parameters);
+ CommandBase::Params params(new_parameters, parameters.GetTags());
+ CmdResult result = handler->Handle(user, params);
if (localuser)
{
// Run the OnPostCommand hook with the last parameter (original line) being empty
@@ -152,14 +153,16 @@ CmdResult CommandParser::CallHandler(const std::string& commandname, const Comma
{
if (cmd)
*cmd = n->second;
- return n->second->Handle(user, parameters);
+
+ ClientProtocol::TagMap tags;
+ return n->second->Handle(user, CommandBase::Params(parameters, tags));
}
}
}
return CMD_INVALID;
}
-void CommandParser::ProcessCommand(LocalUser* user, std::string& command, Command::Params& command_p)
+void CommandParser::ProcessCommand(LocalUser* user, std::string& command, CommandBase::Params& command_p)
{
/* find the command, check it exists */
Command* handler = GetHandler(command);
@@ -369,44 +372,14 @@ void Command::RegisterService()
void CommandParser::ProcessBuffer(LocalUser* user, const std::string& buffer)
{
- size_t start = buffer.find_first_not_of(" ");
- if (start == std::string::npos)
- {
- // Discourage the user from flooding the server.
- user->CommandFloodPenalty += 2000;
+ ClientProtocol::ParseOutput parseoutput;
+ if (!user->serializer->Parse(user, buffer, parseoutput))
return;
- }
-
- ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), buffer.c_str());
-
- irc::tokenstream tokens(buffer, start);
- std::string command;
- CommandBase::Params parameters;
-
- // Get the command name. This will always exist because of the check
- // at the start of the function.
- tokens.GetMiddle(command);
- // If this exists then the client sent a prefix as part of their
- // message. Section 2.3 of RFC 1459 technically says we should only
- // allow the nick of the client here but in practise everyone just
- // ignores it so we will copy them.
- if (command[0] == ':' && !tokens.GetMiddle(command))
- {
- // Discourage the user from flooding the server.
- user->CommandFloodPenalty += 2000;
- return;
- }
-
- // We upper-case the command name to ensure consistency internally.
+ std::string& command = parseoutput.cmd;
std::transform(command.begin(), command.end(), command.begin(), ::toupper);
- // Build the parameter map. We intentionally do not respect the RFC 1459
- // thirteen parameter limit here.
- std::string parameter;
- while (tokens.GetTrailing(parameter))
- parameters.push_back(parameter);
-
+ CommandBase::Params parameters(parseoutput.params, parseoutput.tags);
ProcessCommand(user, command, parameters);
}
@@ -425,7 +398,7 @@ CommandParser::CommandParser()
{
}
-std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const std::vector<std::string>& source, bool prefix_final, CommandBase* custom_translator)
+std::string CommandParser::TranslateUIDs(const std::vector<TranslateType>& to, const CommandBase::Params& source, bool prefix_final, CommandBase* custom_translator)
{
std::vector<TranslateType>::const_iterator types = to.begin();
std::string dest;
diff --git a/src/configreader.cpp b/src/configreader.cpp
index 52d349f5c..6c7cb492a 100644
--- a/src/configreader.cpp
+++ b/src/configreader.cpp
@@ -415,7 +415,6 @@ void ServerConfig::Fill()
HideULineKills = security->getBool("hideulinekills");
GenericOper = security->getBool("genericoper");
SyntaxHints = options->getBool("syntaxhints");
- CycleHostsFromUser = options->getBool("cyclehostsfromuser");
FullHostInTopic = options->getBool("hostintopic");
MaxTargets = security->getUInt("maxtargets", 20, 1, 31);
DefaultModes = options->getString("defaultmodes", "not");
diff --git a/src/coremods/core_channel/cmd_invite.cpp b/src/coremods/core_channel/cmd_invite.cpp
index 89a2f6b69..1b480aa20 100644
--- a/src/coremods/core_channel/cmd_invite.cpp
+++ b/src/coremods/core_channel/cmd_invite.cpp
@@ -114,10 +114,12 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters)
}
}
- if (IS_LOCAL(u))
+ LocalUser* const localtargetuser = IS_LOCAL(u);
+ if (localtargetuser)
{
- invapi.Create(IS_LOCAL(u), c, timeout);
- u->WriteFrom(user,"INVITE %s :%s",u->nick.c_str(),c->name.c_str());
+ invapi.Create(localtargetuser, c, timeout);
+ ClientProtocol::Messages::Invite invitemsg(user, localtargetuser, c);
+ localtargetuser->Send(ServerInstance->GetRFCEvents().invite, invitemsg);
}
if (IS_LOCAL(user))
@@ -156,7 +158,11 @@ CmdResult CommandInvite::Handle(User* user, const Params& parameters)
FOREACH_MOD(OnUserInvite, (user, u, c, timeout, minrank, excepts));
if (announceinvites != Invite::ANNOUNCE_NONE)
- c->WriteAllExcept(user, true, prefix, excepts, "NOTICE %s :*** %s invited %s into the channel", c->name.c_str(), user->nick.c_str(), u->nick.c_str());
+ {
+ excepts.insert(user);
+ ClientProtocol::Messages::Privmsg privmsg(ServerInstance->FakeClient, c, InspIRCd::Format("*** %s invited %s into the channel", user->nick.c_str(), u->nick.c_str()), MSG_NOTICE);
+ c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, prefix, excepts);
+ }
}
else if (IS_LOCAL(user))
{
diff --git a/src/coremods/core_channel/core_channel.cpp b/src/coremods/core_channel/core_channel.cpp
index ccf4d1a6e..4e49ba2b4 100644
--- a/src/coremods/core_channel/core_channel.cpp
+++ b/src/coremods/core_channel/core_channel.cpp
@@ -22,6 +22,71 @@
#include "invite.h"
#include "listmode.h"
+namespace
+{
+/** Hook that sends a MODE after a JOIN if the user in the JOIN has some modes prefix set.
+ * This happens e.g. when modules such as operprefix explicitly set prefix modes on the joining
+ * user, or when a member with prefix modes does a host cycle.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+ ClientProtocol::Messages::Mode modemsg;
+ Modes::ChangeList modechangelist;
+ const User* joininguser;
+
+ public:
+ /** If true, MODE changes after JOIN will be sourced from the user, rather than the server
+ */
+ bool modefromuser;
+
+ JoinHook(Module* mod)
+ : ClientProtocol::EventHook(mod, "JOIN")
+ {
+ }
+
+ void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
+ {
+ const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+ const Membership& memb = *join.GetMember();
+
+ modechangelist.clear();
+ for (std::string::const_iterator i = memb.modes.begin(); i != memb.modes.end(); ++i)
+ {
+ PrefixMode* const pm = ServerInstance->Modes.FindPrefixMode(*i);
+ if (!pm)
+ continue; // Shouldn't happen
+ modechangelist.push_add(pm, memb.user->nick);
+ }
+
+ if (modechangelist.empty())
+ {
+ // Member got no modes on join
+ joininguser = NULL;
+ return;
+ }
+
+ joininguser = memb.user;
+
+ // Prepare a mode protocol event that we can append to the message list in OnPreEventSend()
+ modemsg.SetParams(memb.chan, NULL, modechangelist);
+ if (modefromuser)
+ modemsg.SetSource(join);
+ else
+ modemsg.SetSourceUser(ServerInstance->FakeClient);
+ }
+
+ ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+ {
+ // If joininguser is NULL then they didn't get any modes on join, skip.
+ // Also don't show their own modes to them, they get that in the NAMES list not via MODE.
+ if ((joininguser) && (user != joininguser))
+ messagelist.push_back(&modemsg);
+ return MOD_RES_PASSTHRU;
+ }
+};
+
+}
+
class CoreModChannel : public Module, public CheckExemption::EventListener
{
Invite::APIImpl invapi;
@@ -30,6 +95,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
CommandKick cmdkick;
CommandNames cmdnames;
CommandTopic cmdtopic;
+ Events::ModuleEventProvider evprov;
+ JoinHook joinhook;
ModeChannelBan banmode;
SimpleChannelModeHandler inviteonlymode;
@@ -62,6 +129,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
, cmdkick(this)
, cmdnames(this)
, cmdtopic(this)
+ , evprov(this, "event/channel")
+ , joinhook(this)
, banmode(this)
, inviteonlymode(this, "inviteonly", 'i')
, keymode(this)
@@ -88,6 +157,8 @@ class CoreModChannel : public Module, public CheckExemption::EventListener
ServerInstance->Modules.Detach(events[i], this);
}
+ joinhook.modefromuser = optionstag->getBool("cyclehostsfromuser");
+
std::string current;
irc::spacesepstream defaultstream(optionstag->getString("exemptchanops"));
insp::flat_map<std::string, char> exempts;
diff --git a/src/coremods/core_oper/cmd_die.cpp b/src/coremods/core_oper/cmd_die.cpp
index d10732952..b25fe2407 100644
--- a/src/coremods/core_oper/cmd_die.cpp
+++ b/src/coremods/core_oper/cmd_die.cpp
@@ -39,7 +39,8 @@ static void QuitAll()
void DieRestart::SendError(const std::string& message)
{
- const std::string unregline = "ERROR :" + message;
+ ClientProtocol::Messages::Error errormsg(message);
+ ClientProtocol::Event errorevent(ServerInstance->GetRFCEvents().error, errormsg);
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
{
@@ -51,7 +52,7 @@ void DieRestart::SendError(const std::string& message)
else
{
// Unregistered connections receive ERROR, not a NOTICE
- user->Write(unregline);
+ user->Send(errorevent);
}
}
}
diff --git a/src/coremods/core_oper/cmd_kill.cpp b/src/coremods/core_oper/cmd_kill.cpp
index fd6729f53..5572e5789 100644
--- a/src/coremods/core_oper/cmd_kill.cpp
+++ b/src/coremods/core_oper/cmd_kill.cpp
@@ -24,12 +24,28 @@
CommandKill::CommandKill(Module* parent)
: Command(parent, "KILL", 2, 2)
+ , protoev(parent, name)
{
flags_needed = 'o';
syntax = "<nickname> <reason>";
TRANSLATE2(TR_CUSTOM, TR_CUSTOM);
}
+class KillMessage : public ClientProtocol::Message
+{
+ public:
+ KillMessage(ClientProtocol::EventProvider& protoev, User* user, LocalUser* target, const std::string& text)
+ : ClientProtocol::Message("KILL", NULL)
+ {
+ if (ServerInstance->Config->HideKillsServer.empty())
+ SetSourceUser(user);
+ else
+ SetSource(ServerInstance->Config->HideKillsServer);
+
+ PushParamRef(target->nick);
+ PushParamRef(text);
+ }
+};
/** Handle /KILL
*/
@@ -100,10 +116,10 @@ CmdResult CommandKill::Handle(User* user, const Params& parameters)
if (IS_LOCAL(target))
{
- target->Write(":%s KILL %s :%s",
- ServerInstance->Config->HideKillsServer.empty() ? user->GetFullHost().c_str() : ServerInstance->Config->HideKillsServer.c_str(),
- target->nick.c_str(),
- parameters[1].c_str());
+ LocalUser* localu = IS_LOCAL(target);
+ KillMessage msg(protoev, user, localu, killreason);
+ ClientProtocol::Event killevent(protoev, msg);
+ localu->Send(killevent);
this->lastuuid.clear();
}
diff --git a/src/coremods/core_oper/core_oper.h b/src/coremods/core_oper/core_oper.h
index b069c34b2..db8c4161c 100644
--- a/src/coremods/core_oper/core_oper.h
+++ b/src/coremods/core_oper/core_oper.h
@@ -60,6 +60,7 @@ class CommandKill : public Command
{
std::string lastuuid;
std::string killreason;
+ ClientProtocol::EventProvider protoev;
public:
/** Constructor for kill.
@@ -67,8 +68,8 @@ class CommandKill : public Command
CommandKill(Module* parent);
/** Handle command.
- * @param parameters The parameters to the command
- * @param user The user issuing the command
+ * @param user User issuing the command
+ * @param parameters Parameters to the command
* @return A value from CmdResult to indicate command success or failure.
*/
CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
@@ -87,8 +88,8 @@ class CommandOper : public SplitCommand
CommandOper(Module* parent);
/** Handle command.
- * @param parameters The parameters to the command
- * @param user The user issuing the command
+ * @param user User issuing the command
+ * @param parameters Parameters to the command
* @return A value from CmdResult to indicate command success or failure.
*/
CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE;
@@ -104,8 +105,8 @@ class CommandRehash : public Command
CommandRehash(Module* parent);
/** Handle command.
- * @param parameters The parameters to the command
- * @param user The user issuing the command
+ * @param user User issuing the command
+ * @param parameters Parameters to the command
* @return A value from CmdResult to indicate command success or failure.
*/
CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
@@ -121,8 +122,8 @@ class CommandRestart : public Command
CommandRestart(Module* parent);
/** Handle command.
- * @param parameters The parameters to the command
- * @param user The user issuing the command
+ * @param user User issuing the command
+ * @param parameters Parameters to the command
* @return A value from CmdResult to indicate command success or failure.
*/
CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
diff --git a/src/coremods/core_privmsg.cpp b/src/coremods/core_privmsg.cpp
index 6078f5297..2daeef3ad 100644
--- a/src/coremods/core_privmsg.cpp
+++ b/src/coremods/core_privmsg.cpp
@@ -21,11 +21,6 @@
#include "inspircd.h"
-namespace
-{
- const char* MessageTypeString[] = { "PRIVMSG", "NOTICE" };
-}
-
class MessageCommandBase : public Command
{
ChanModeReference moderatedmode;
@@ -35,12 +30,13 @@ class MessageCommandBase : public Command
* @param user User sending the message
* @param msg The message to send
* @param mt Type of the message (MSG_PRIVMSG or MSG_NOTICE)
+ * @param tags Message tags to include in the outgoing protocol message
*/
- static void SendAll(User* user, const std::string& msg, MessageType mt);
+ static void SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags);
public:
MessageCommandBase(Module* parent, MessageType mt)
- : Command(parent, MessageTypeString[mt], 2, 2)
+ : Command(parent, ClientProtocol::Messages::Privmsg::CommandStrFromMsgType(mt), 2, 2)
, moderatedmode(parent, "moderated")
, noextmsgmode(parent, "noextmsg")
{
@@ -52,7 +48,7 @@ class MessageCommandBase : public Command
* @param user The user issuing the command
* @return A value from CmdResult to indicate command success or failure.
*/
- CmdResult HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt);
+ CmdResult HandleMessage(User* user, const Params& parameters, MessageType mt);
RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
{
@@ -64,18 +60,22 @@ class MessageCommandBase : public Command
}
};
-void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt)
+void MessageCommandBase::SendAll(User* user, const std::string& msg, MessageType mt, const ClientProtocol::TagMap& tags)
{
- const std::string message = ":" + user->GetFullHost() + " " + MessageTypeString[mt] + " $* :" + msg;
+ ClientProtocol::Messages::Privmsg message(ClientProtocol::Messages::Privmsg::nocopy, user, "$*", msg, mt);
+ message.AddTags(tags);
+ message.SetSideEffect(true);
+ ClientProtocol::Event messageevent(ServerInstance->GetRFCEvents().privmsg, message);
+
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
{
if ((*i)->registered == REG_ALL)
- (*i)->Write(message);
+ (*i)->Send(messageevent);
}
}
-CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Params& parameters, MessageType mt)
+CmdResult MessageCommandBase::HandleMessage(User* user, const Params& parameters, MessageType mt)
{
User *dest;
Channel *chan;
@@ -94,7 +94,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
std::string servername(parameters[0], 1);
MessageTarget msgtarget(&servername);
- MessageDetails msgdetails(mt, parameters[1]);
+ MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
ModResult MOD_RESULT;
FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
@@ -107,7 +107,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
if (InspIRCd::Match(ServerInstance->Config->ServerName, servername, NULL))
{
- SendAll(user, msgdetails.text, mt);
+ SendAll(user, msgdetails.text, mt, msgdetails.tags_out);
}
FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
return CMD_SUCCESS;
@@ -153,7 +153,7 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
}
MessageTarget msgtarget(chan, status);
- MessageDetails msgdetails(mt, parameters[1]);
+ MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
msgdetails.exemptions.insert(user);
ModResult MOD_RESULT;
@@ -173,14 +173,10 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
- if (status)
- {
- chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %c%s :%s", MessageTypeString[mt], status, chan->name.c_str(), msgdetails.text.c_str());
- }
- else
- {
- chan->WriteAllExcept(user, false, status, msgdetails.exemptions, "%s %s :%s", MessageTypeString[mt], chan->name.c_str(), msgdetails.text.c_str());
- }
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, msgdetails.text, msgdetails.type, msgtarget.status);
+ privmsg.AddTags(msgdetails.tags_out);
+ privmsg.SetSideEffect(true);
+ chan->Write(ServerInstance->GetRFCEvents().privmsg, privmsg, msgtarget.status, msgdetails.exemptions);
FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
}
@@ -233,7 +229,8 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
}
MessageTarget msgtarget(dest);
- MessageDetails msgdetails(mt, parameters[1]);
+ MessageDetails msgdetails(mt, parameters[1], parameters.GetTags());
+
ModResult MOD_RESULT;
FIRST_MOD_RESULT(OnUserPreMessage, MOD_RESULT, (user, msgtarget, msgdetails));
@@ -245,10 +242,14 @@ CmdResult MessageCommandBase::HandleMessage(User* user, const CommandBase::Param
FOREACH_MOD(OnUserMessage, (user, msgtarget, msgdetails));
- if (IS_LOCAL(dest))
+ LocalUser* const localtarget = IS_LOCAL(dest);
+ if (localtarget)
{
// direct write, same server
- dest->WriteFrom(user, "%s %s :%s", MessageTypeString[mt], dest->nick.c_str(), msgdetails.text.c_str());
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, localtarget->nick, msgdetails.text, mt);
+ privmsg.AddTags(msgdetails.tags_out);
+ privmsg.SetSideEffect(true);
+ localtarget->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
FOREACH_MOD(OnUserPostMessage, (user, msgtarget, msgdetails));
diff --git a/src/coremods/core_reloadmodule.cpp b/src/coremods/core_reloadmodule.cpp
index 23be33af8..383d574bf 100644
--- a/src/coremods/core_reloadmodule.cpp
+++ b/src/coremods/core_reloadmodule.cpp
@@ -24,18 +24,42 @@
#include "modules/reload.h"
static Events::ModuleEventProvider* reloadevprov;
+static ClientProtocol::Serializer* dummyserializer;
+
+class DummySerializer : public ClientProtocol::Serializer
+{
+ bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE
+ {
+ return false;
+ }
+
+ ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE
+ {
+ return ClientProtocol::SerializedMessage();
+ }
+
+ public:
+ DummySerializer(Module* mod)
+ : ClientProtocol::Serializer(mod, "dummy")
+ {
+ }
+};
class CommandReloadmodule : public Command
{
Events::ModuleEventProvider evprov;
+ DummySerializer dummyser;
+
public:
/** Constructor for reloadmodule.
*/
CommandReloadmodule(Module* parent)
: Command(parent, "RELOADMODULE", 1)
, evprov(parent, "event/reloadmodule")
+ , dummyser(parent)
{
reloadevprov = &evprov;
+ dummyserializer = &dummyser;
flags_needed = 'o';
syntax = "<modulename>";
}
@@ -62,6 +86,7 @@ class DataKeeper
{
ModeHandler* mh;
ExtensionItem* extitem;
+ ClientProtocol::Serializer* serializer;
};
ProviderInfo(ModeHandler* mode)
@@ -75,6 +100,12 @@ class DataKeeper
, extitem(ei)
{
}
+
+ ProviderInfo(ClientProtocol::Serializer* ser)
+ : itemname(ser->name)
+ , serializer(ser)
+ {
+ }
};
struct InstanceData
@@ -143,7 +174,17 @@ class DataKeeper
};
// Data saved for each user
- typedef OwnedModesExts UserData;
+ struct UserData : public OwnedModesExts
+ {
+ static const size_t UNUSED_INDEX = (size_t)-1;
+ size_t serializerindex;
+
+ UserData(User* user, size_t serializeridx)
+ : OwnedModesExts(user->uuid)
+ , serializerindex(serializeridx)
+ {
+ }
+ };
/** Module being reloaded
*/
@@ -157,6 +198,10 @@ class DataKeeper
*/
std::vector<ProviderInfo> handledexts;
+ /** Stores all serializers provided by the module
+ */
+ std::vector<ProviderInfo> handledserializers;
+
/** Stores all of the module data related to users
*/
std::vector<UserData> userdatalist;
@@ -172,6 +217,14 @@ class DataKeeper
void SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdatalist);
void SaveMemberData(Channel* chan, std::vector<ChanData::MemberData>& memberdatalist);
static void SaveListModes(Channel* chan, ListModeBase* lm, size_t index, ModesExts& currdata);
+ size_t SaveSerializer(User* user);
+
+ /** Get the index of a ProviderInfo representing the serializer in the handledserializers list.
+ * If the serializer is not already in the list it is added.
+ * @param serializer Serializer to get an index to.
+ * @return Index of the ProviderInfo representing the serializer.
+ */
+ size_t GetSerializerIndex(ClientProtocol::Serializer* serializer);
void CreateModeList(ModeType modetype);
void DoSaveUsers();
@@ -186,6 +239,10 @@ class DataKeeper
*/
void LinkModes(ModeType modetype);
+ /** Link previously saved serializer names to currently available Serializers
+ */
+ void LinkSerializers();
+
void DoRestoreUsers();
void DoRestoreChans();
void DoRestoreModules();
@@ -213,6 +270,15 @@ class DataKeeper
*/
void RestoreModes(const std::vector<InstanceData>& list, ModeType modetype, Modes::ChangeList& modechange);
+ /** Restore previously saved serializer on a User.
+ * Quit the user if the serializer cannot be restored.
+ * @param serializerindex Saved serializer index to restore.
+ * @param user User whose serializer to restore. If not local then calling this method is a no-op.
+ * @return True if the serializer didn't need restoring or was restored successfully.
+ * False if the serializer should have been restored but the required serializer is unavailable and the user was quit.
+ */
+ bool RestoreSerializer(size_t serializerindex, User* user);
+
/** Restore all modes and extensions of all members on a channel
* @param chan Channel whose members are being restored
* @param memberdata Data to restore
@@ -262,16 +328,44 @@ void DataKeeper::DoSaveUsers()
// Serialize all extensions attached to the User
SaveExtensions(user, currdata.extlist);
+ // Save serializer name if applicable and get an index to it
+ size_t serializerindex = SaveSerializer(user);
+
// Add to list if the user has any modes or extensions set that we are interested in, otherwise we don't
// have to do anything with this user when restoring
- if (!currdata.empty())
+ if ((!currdata.empty()) || (serializerindex != UserData::UNUSED_INDEX))
{
- userdatalist.push_back(UserData(user->uuid));
+ userdatalist.push_back(UserData(user, serializerindex));
userdatalist.back().swap(currdata);
}
}
}
+size_t DataKeeper::GetSerializerIndex(ClientProtocol::Serializer* serializer)
+{
+ for (size_t i = 0; i < handledserializers.size(); i++)
+ {
+ if (handledserializers[i].serializer == serializer)
+ return i;
+ }
+
+ handledserializers.push_back(ProviderInfo(serializer));
+ return handledserializers.size()-1;
+}
+
+size_t DataKeeper::SaveSerializer(User* user)
+{
+ LocalUser* const localuser = IS_LOCAL(user);
+ if ((!localuser) || (!localuser->serializer))
+ return UserData::UNUSED_INDEX;
+ if (localuser->serializer->creator != mod)
+ return UserData::UNUSED_INDEX;
+
+ const size_t serializerindex = GetSerializerIndex(localuser->serializer);
+ localuser->serializer = dummyserializer;
+ return serializerindex;
+}
+
void DataKeeper::SaveExtensions(Extensible* extensible, std::vector<InstanceData>& extdata)
{
const Extensible::ExtensibleStore& setexts = extensible->GetExtList();
@@ -456,6 +550,16 @@ void DataKeeper::LinkExtensions()
}
}
+void DataKeeper::LinkSerializers()
+{
+ for (std::vector<ProviderInfo>::iterator i = handledserializers.begin(); i != handledserializers.end(); ++i)
+ {
+ ProviderInfo& item = *i;
+ item.serializer = ServerInstance->Modules.FindDataService<ClientProtocol::Serializer>(item.itemname);
+ VerifyServiceProvider(item.serializer, "Serializer");
+ }
+}
+
void DataKeeper::Restore(Module* newmod)
{
this->mod = newmod;
@@ -464,6 +568,7 @@ void DataKeeper::Restore(Module* newmod)
LinkExtensions();
LinkModes(MODETYPE_USER);
LinkModes(MODETYPE_CHANNEL);
+ LinkSerializers();
// Restore
DoRestoreUsers();
@@ -505,6 +610,30 @@ void DataKeeper::RestoreModes(const std::vector<InstanceData>& list, ModeType mo
}
}
+bool DataKeeper::RestoreSerializer(size_t serializerindex, User* user)
+{
+ if (serializerindex == UserData::UNUSED_INDEX)
+ return true;
+
+ // The following checks are redundant
+ LocalUser* const localuser = IS_LOCAL(user);
+ if (!localuser)
+ return true;
+ if (localuser->serializer != dummyserializer)
+ return true;
+
+ const ProviderInfo& provinfo = handledserializers[serializerindex];
+ if (!provinfo.serializer)
+ {
+ // Users cannot exist without a serializer
+ ServerInstance->Users.QuitUser(user, "Serializer lost in reload");
+ return false;
+ }
+
+ localuser->serializer = provinfo.serializer;
+ return true;
+}
+
void DataKeeper::DoRestoreUsers()
{
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Restoring user data");
@@ -520,6 +649,10 @@ void DataKeeper::DoRestoreUsers()
continue;
}
+ // Attempt to restore serializer first, if it fails it's a fatal error and RestoreSerializer() quits them
+ if (!RestoreSerializer(userdata.serializerindex, user))
+ continue;
+
RestoreObj(userdata, user, MODETYPE_USER, modechange);
ServerInstance->Modes.Process(ServerInstance->FakeClient, NULL, user, modechange, ModeParser::MODE_LOCALONLY);
modechange.clear();
diff --git a/src/coremods/core_serialize_rfc.cpp b/src/coremods/core_serialize_rfc.cpp
new file mode 100644
index 000000000..afa914f3d
--- /dev/null
+++ b/src/coremods/core_serialize_rfc.cpp
@@ -0,0 +1,222 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+class RFCSerializer : public ClientProtocol::Serializer
+{
+ /** Maximum size of the message tags portion of the message, including the `@` and the trailing space characters.
+ */
+ static const std::string::size_type MAX_MESSAGE_TAG_LENGTH = 512;
+
+ static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
+
+ public:
+ RFCSerializer(Module* mod)
+ : ClientProtocol::Serializer(mod, "rfc")
+ {
+ }
+
+ bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE;
+ ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE;
+};
+
+bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
+{
+ size_t start = line.find_first_not_of(" ");
+ if (start == std::string::npos)
+ {
+ // Discourage the user from flooding the server.
+ user->CommandFloodPenalty += 2000;
+ return false;
+ }
+
+ ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str());
+
+ irc::tokenstream tokens(line, start);
+ std::string token;
+
+ // This will always exist because of the check at the start of the function.
+ tokens.GetMiddle(token);
+ if (token[0] == '@')
+ {
+ // Line begins with message tags, parse them.
+ std::string tagval;
+ irc::sepstream ss(token.substr(1), ';');
+ while (ss.GetToken(token))
+ {
+ // Two or more tags with the same key must not be sent, but if a client violates that we accept
+ // the first occurence of duplicate tags and ignore all later occurences.
+ //
+ // Another option is to reject the message entirely but there is no standard way of doing that.
+ const std::string::size_type p = token.find('=');
+ if (p != std::string::npos)
+ {
+ // Tag has a value
+ tagval.assign(token, p+1, std::string::npos);
+ token.erase(p);
+ }
+ else
+ tagval.clear();
+
+ HandleTag(user, token, tagval, parseoutput.tags);
+ }
+
+
+ // Try to read the prefix or command name.
+ if (!tokens.GetMiddle(token))
+ {
+ // Discourage the user from flooding the server.
+ user->CommandFloodPenalty += 2000;
+ return false;
+ }
+ }
+
+ if (token[0] == ':')
+ {
+ // If this exists then the client sent a prefix as part of their
+ // message. Section 2.3 of RFC 1459 technically says we should only
+ // allow the nick of the client here but in practise everyone just
+ // ignores it so we will copy them.
+
+ // Try to read the command name.
+ if (!tokens.GetMiddle(token))
+ {
+ // Discourage the user from flooding the server.
+ user->CommandFloodPenalty += 2000;
+ return false;
+ }
+ }
+
+ parseoutput.cmd.assign(token);
+
+ // Build the parameter map. We intentionally do not respect the RFC 1459
+ // thirteen parameter limit here.
+ while (tokens.GetTrailing(token))
+ parseoutput.params.push_back(token);
+
+ return true;
+}
+
+void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
+{
+ char prefix = '@'; // First tag name is prefixed with a '@'
+ for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
+ {
+ if (!tagwl.IsSelected(tags, i))
+ continue;
+
+ const std::string::size_type prevsize = line.size();
+ line.push_back(prefix);
+ prefix = ';'; // Remaining tags are prefixed with ';'
+ line.append(i->first);
+ const std::string& val = i->second.value;
+ if (!val.empty())
+ {
+ line.push_back('=');
+ line.append(val);
+ }
+
+ // The tags part of the message mustn't grow longer than what is allowed by the spec. If it does,
+ // remove last tag and stop adding more tags.
+ //
+ // One is subtracted from the limit before comparing because there must be a ' ' char after the last tag
+ // which also counts towards the limit.
+ if (line.size() > MAX_MESSAGE_TAG_LENGTH-1)
+ {
+ line.erase(prevsize);
+ break;
+ }
+ }
+
+ if (!line.empty())
+ line.push_back(' ');
+}
+
+ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
+{
+ std::string line;
+ SerializeTags(msg.GetTags(), tagwl, line);
+
+ // Save position for length calculation later
+ const std::string::size_type rfcmsg_begin = line.size();
+
+ if (msg.GetSource())
+ {
+ line.push_back(':');
+ line.append(*msg.GetSource());
+ line.push_back(' ');
+ }
+ line.append(msg.GetCommand());
+
+ const ClientProtocol::Message::ParamList& params = msg.GetParams();
+ if (!params.empty())
+ {
+ for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
+ {
+ const std::string& param = *i;
+ line.push_back(' ');
+ line.append(param);
+ }
+
+ line.append(" :", 2).append(params.back());
+ }
+
+ // Truncate if too long
+ std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2;
+ if (line.length() - rfcmsg_begin > maxline)
+ line.erase(rfcmsg_begin + maxline);
+
+ line.append("\r\n", 2);
+ return line;
+}
+
+class ModuleCoreRFCSerializer : public Module
+{
+ RFCSerializer rfcserializer;
+
+ public:
+ ModuleCoreRFCSerializer()
+ : rfcserializer(this)
+ {
+ }
+
+ void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
+ {
+ if (type != ExtensionItem::EXT_USER)
+ return;
+
+ LocalUser* const user = IS_LOCAL(static_cast<User*>(item));
+ if ((user) && (user->serializer == &rfcserializer))
+ ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading");
+ }
+
+ void OnUserInit(LocalUser* user) CXX11_OVERRIDE
+ {
+ if (!user->serializer)
+ user->serializer = &rfcserializer;
+ }
+
+ Version GetVersion()
+ {
+ return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleCoreRFCSerializer)
diff --git a/src/coremods/core_user/core_user.cpp b/src/coremods/core_user/core_user.cpp
index 6e4e547c1..e4e7a5056 100644
--- a/src/coremods/core_user/core_user.cpp
+++ b/src/coremods/core_user/core_user.cpp
@@ -57,13 +57,13 @@ class CommandPass : public SplitCommand
/** Handle /PING.
*/
-class CommandPing : public Command
+class CommandPing : public SplitCommand
{
public:
/** Constructor for ping.
*/
CommandPing(Module* parent)
- : Command(parent, "PING", 1, 2)
+ : SplitCommand(parent, "PING", 1, 2)
{
syntax = "<servername> [:<servername>]";
}
@@ -73,9 +73,10 @@ class CommandPing : public Command
* @param user The user issuing the command
* @return A value from CmdResult to indicate command success or failure.
*/
- CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
+ CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
{
- user->WriteServ("PONG %s :%s", ServerInstance->Config->ServerName.c_str(), parameters[0].c_str());
+ ClientProtocol::Messages::Pong pong(parameters[0]);
+ user->Send(ServerInstance->GetRFCEvents().pong, pong);
return CMD_SUCCESS;
}
};
diff --git a/src/coremods/core_wallops.cpp b/src/coremods/core_wallops.cpp
index 856fcea74..26a00a47d 100644
--- a/src/coremods/core_wallops.cpp
+++ b/src/coremods/core_wallops.cpp
@@ -25,6 +25,7 @@
class CommandWallops : public Command
{
SimpleUserModeHandler wallopsmode;
+ ClientProtocol::EventProvider protoevprov;
public:
/** Constructor for wallops.
@@ -32,6 +33,7 @@ class CommandWallops : public Command
CommandWallops(Module* parent)
: Command(parent, "WALLOPS", 1, 1)
, wallopsmode(parent, "wallops", 'w')
+ , protoevprov(parent, name)
{
flags_needed = 'o';
syntax = "<any-text>";
@@ -52,15 +54,16 @@ class CommandWallops : public Command
CmdResult CommandWallops::Handle(User* user, const Params& parameters)
{
- std::string wallop("WALLOPS :");
- wallop.append(parameters[0]);
+ ClientProtocol::Message msg("WALLOPS", user);
+ msg.PushParamRef(parameters[0]);
+ ClientProtocol::Event wallopsevent(protoevprov, msg);
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
{
- User* t = *i;
- if (t->IsModeSet(wallopsmode))
- t->WriteFrom(user, wallop);
+ LocalUser* curr = *i;
+ if (curr->IsModeSet(wallopsmode))
+ curr->Send(wallopsevent);
}
return CMD_SUCCESS;
diff --git a/src/inspircd.cpp b/src/inspircd.cpp
index bb27c718e..aa3fb9612 100644
--- a/src/inspircd.cpp
+++ b/src/inspircd.cpp
@@ -271,6 +271,16 @@ InspIRCd::InspIRCd(int argc, char** argv) :
srandom(TIME.tv_nsec ^ TIME.tv_sec);
#endif
+ {
+ ServiceProvider* provs[] =
+ {
+ &rfcevents.numeric, &rfcevents.join, &rfcevents.part, &rfcevents.kick, &rfcevents.quit, &rfcevents.nick,
+ &rfcevents.mode, &rfcevents.topic, &rfcevents.privmsg, &rfcevents.invite, &rfcevents.ping, &rfcevents.pong,
+ &rfcevents.error
+ };
+ Modules.AddServices(provs, sizeof(provs)/sizeof(provs[0]));
+ }
+
struct option longopts[] =
{
{ "nofork", no_argument, &do_nofork, 1 },
diff --git a/src/mode.cpp b/src/mode.cpp
index 9d17f5be8..71fce24d8 100644
--- a/src/mode.cpp
+++ b/src/mode.cpp
@@ -438,14 +438,9 @@ void ModeParser::Process(User* user, Channel* targetchannel, User* targetuser, M
unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User* targetuser, Modes::ChangeList& changelist, ModeProcessFlag flags, unsigned int beginindex)
{
- LastParse.clear();
LastChangeList.clear();
unsigned int modes_processed = 0;
- std::string output_mode;
- std::string output_parameters;
-
- char output_pm = '\0'; // current output state, '+' or '-'
Modes::ChangeList::List& list = changelist.getlist();
for (Modes::ChangeList::List::iterator i = list.begin()+beginindex; i != list.end(); ++i)
{
@@ -478,43 +473,30 @@ unsigned int ModeParser::ProcessSingle(User* user, Channel* targetchannel, User*
if (ma != MODEACTION_ALLOW)
continue;
- char needed_pm = item.adding ? '+' : '-';
- if (needed_pm != output_pm)
- {
- output_pm = needed_pm;
- output_mode.append(1, output_pm);
- }
- output_mode.push_back(mh->GetModeChar());
-
- if (!item.param.empty())
- {
- output_parameters.push_back(' ');
- output_parameters.append(item.param);
- }
LastChangeList.push(mh, item.adding, item.param);
- if ((output_mode.length() + output_parameters.length() > 450)
- || (output_mode.length() > 100)
- || (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes))
+ if (LastChangeList.size() >= ServerInstance->Config->Limits.MaxModes)
{
/* mode sequence is getting too long */
break;
}
}
- if (!output_mode.empty())
+ if (!LastChangeList.empty())
{
- LastParse = targetchannel ? targetchannel->name : targetuser->nick;
- LastParse.append(" ");
- LastParse.append(output_mode);
- LastParse.append(output_parameters);
-
+ ClientProtocol::Events::Mode modeevent(user, targetchannel, targetuser, LastChangeList);
if (targetchannel)
- targetchannel->WriteChannel(user, "MODE " + LastParse);
+ {
+ targetchannel->Write(modeevent);
+ }
else
- targetuser->WriteFrom(user, "MODE " + LastParse);
+ {
+ LocalUser* localtarget = IS_LOCAL(targetuser);
+ if (localtarget)
+ localtarget->Send(modeevent);
+ }
- FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags, output_mode));
+ FOREACH_MOD(OnMode, (user, targetuser, targetchannel, LastChangeList, flags));
}
return modes_processed;
diff --git a/src/modules.cpp b/src/modules.cpp
index 8f2d874dc..7912fb569 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -79,7 +79,7 @@ void Module::OnUserPart(Membership*, std::string&, CUList&) { DetachEvent(I_OnU
void Module::OnPreRehash(User*, const std::string&) { DetachEvent(I_OnPreRehash); }
void Module::OnModuleRehash(User*, const std::string&) { DetachEvent(I_OnModuleRehash); }
ModResult Module::OnUserPreJoin(LocalUser*, Channel*, const std::string&, std::string&, const std::string&) { DetachEvent(I_OnUserPreJoin); return MOD_RES_PASSTHRU; }
-void Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag, const std::string&) { DetachEvent(I_OnMode); }
+void Module::OnMode(User*, User*, Channel*, const Modes::ChangeList&, ModeParser::ModeProcessFlag) { DetachEvent(I_OnMode); }
void Module::OnOper(User*, const std::string&) { DetachEvent(I_OnOper); }
void Module::OnPostOper(User*, const std::string&, const std::string &) { DetachEvent(I_OnPostOper); }
void Module::OnPostDeoper(User*) { DetachEvent(I_OnPostDeoper); }
@@ -138,6 +138,7 @@ ModResult Module::OnSendWhoLine(User*, const std::vector<std::string>&, User*, M
void Module::OnSetUserIP(LocalUser*) { DetachEvent(I_OnSetUserIP); }
void Module::OnServiceAdd(ServiceProvider&) { DetachEvent(I_OnServiceAdd); }
void Module::OnServiceDel(ServiceProvider&) { DetachEvent(I_OnServiceDel); }
+ModResult Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; }
#ifdef INSPIRCD_ENABLE_TESTSUITE
void Module::OnRunTestSuite() { }
diff --git a/src/modules/m_alias.cpp b/src/modules/m_alias.cpp
index 76ccc6ebc..75ab57e94 100644
--- a/src/modules/m_alias.cpp
+++ b/src/modules/m_alias.cpp
@@ -129,7 +129,7 @@ class ModuleAlias : public Module
return word;
}
- std::string CreateRFCMessage(const std::string& command, Command::Params& parameters)
+ std::string CreateRFCMessage(const std::string& command, CommandBase::Params& parameters)
{
std::string message(command);
for (CommandBase::Params::const_iterator iter = parameters.begin(); iter != parameters.end();)
diff --git a/src/modules/m_auditorium.cpp b/src/modules/m_auditorium.cpp
index 7acbd2fff..8485f1d7a 100644
--- a/src/modules/m_auditorium.cpp
+++ b/src/modules/m_auditorium.cpp
@@ -32,6 +32,29 @@ class AuditoriumMode : public SimpleChannelModeHandler
}
};
+class ModuleAuditorium;
+
+namespace
+{
+
+/** Hook handler for join client protocol events.
+ * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY).
+ * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module
+ * such as delayjoin or hostcycle generates a join.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+ ModuleAuditorium* const parentmod;
+ bool active;
+
+ public:
+ JoinHook(ModuleAuditorium* mod);
+ void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE;
+ ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE;
+};
+
+}
+
class ModuleAuditorium : public Module
{
CheckExemption::EventProvider exemptionprov;
@@ -39,11 +62,13 @@ class ModuleAuditorium : public Module
bool OpsVisible;
bool OpsCanSee;
bool OperCanSee;
+ JoinHook joinhook;
public:
ModuleAuditorium()
: exemptionprov(this)
, aum(this)
+ , joinhook(this)
{
}
@@ -115,11 +140,6 @@ class ModuleAuditorium : public Module
}
}
- void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE
- {
- BuildExcept(memb, excepts);
- }
-
void OnUserPart(Membership* memb, std::string &partmessage, CUList& excepts) CXX11_OVERRIDE
{
BuildExcept(memb, excepts);
@@ -165,4 +185,25 @@ class ModuleAuditorium : public Module
}
};
+JoinHook::JoinHook(ModuleAuditorium* mod)
+ : ClientProtocol::EventHook(mod, "JOIN", 10)
+ , parentmod(mod)
+{
+}
+
+void JoinHook::OnEventInit(const ClientProtocol::Event& ev)
+{
+ const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+ active = !parentmod->IsVisible(join.GetMember());
+}
+
+ModResult JoinHook::OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist)
+{
+ if (!active)
+ return MOD_RES_PASSTHRU;
+
+ const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+ return ((parentmod->CanSee(user, join.GetMember())) ? MOD_RES_PASSTHRU : MOD_RES_DENY);
+}
+
MODULE_INIT(ModuleAuditorium)
diff --git a/src/modules/m_cap.cpp b/src/modules/m_cap.cpp
index 80e70d3e5..922061757 100644
--- a/src/modules/m_cap.cpp
+++ b/src/modules/m_cap.cpp
@@ -341,16 +341,35 @@ void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, co
managerimpl->HandleReq(user, caplist);
}
+class CapMessage : public Cap::MessageBase
+{
+ public:
+ CapMessage(LocalUser* user, const std::string& subcmd, const std::string& result)
+ : Cap::MessageBase(subcmd)
+ {
+ SetUser(user);
+ PushParamRef(result);
+ }
+};
+
class CommandCap : public SplitCommand
{
Events::ModuleEventProvider evprov;
Cap::ManagerImpl manager;
+ ClientProtocol::EventProvider protoevprov;
- static void DisplayResult(LocalUser* user, std::string& result)
+ void DisplayResult(LocalUser* user, const std::string& subcmd, std::string& result)
{
if (*result.rbegin() == ' ')
result.erase(result.end()-1);
- user->WriteCommand("CAP", result);
+ DisplayResult2(user, subcmd, result);
+ }
+
+ void DisplayResult2(LocalUser* user, const std::string& subcmd, const std::string& result)
+ {
+ CapMessage msg(user, subcmd, result);
+ ClientProtocol::Event ev(protoevprov, msg);
+ user->Send(ev);
}
public:
@@ -360,6 +379,7 @@ class CommandCap : public SplitCommand
: SplitCommand(mod, "CAP", 1)
, evprov(mod, "event/cap")
, manager(mod, evprov)
+ , protoevprov(mod, name)
, holdext("cap_hold", ExtensionItem::EXT_USER, mod)
{
works_before_reg = true;
@@ -378,9 +398,8 @@ class CommandCap : public SplitCommand
if (parameters.size() < 2)
return CMD_FAILURE;
- std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :");
- result.append(parameters[1]);
- user->WriteCommand("CAP", result);
+ const std::string replysubcmd = (manager.HandleReq(user, parameters[1]) ? "ACK" : "NAK");
+ DisplayResult2(user, replysubcmd, parameters[1]);
}
else if (subcommand == "END")
{
@@ -392,16 +411,16 @@ class CommandCap : public SplitCommand
if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
manager.Set302Protocol(user);
- std::string result = subcommand + " :";
+ std::string result;
// Show values only if supports v3.2 and doing LS
manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY)));
- DisplayResult(user, result);
+ DisplayResult(user, subcommand, result);
}
else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
{
- std::string result = "ACK :";
+ std::string result;
manager.HandleClear(user, result);
- DisplayResult(user, result);
+ DisplayResult(user, "ACK", result);
}
else
{
diff --git a/src/modules/m_chanhistory.cpp b/src/modules/m_chanhistory.cpp
index 081731126..0c3945346 100644
--- a/src/modules/m_chanhistory.cpp
+++ b/src/modules/m_chanhistory.cpp
@@ -22,8 +22,15 @@
struct HistoryItem
{
time_t ts;
- std::string line;
- HistoryItem(const std::string& Line) : ts(ServerInstance->Time()), line(Line) {}
+ std::string text;
+ std::string sourcemask;
+
+ HistoryItem(User* source, const std::string& Text)
+ : ts(ServerInstance->Time())
+ , text(Text)
+ , sourcemask(source->GetFullHost())
+ {
+ }
};
struct HistoryList
@@ -136,8 +143,7 @@ class ModuleChanHistory : public Module
HistoryList* list = m.ext.get(c);
if (list)
{
- const std::string line = ":" + user->GetFullHost() + " PRIVMSG " + c->name + " :" + details.text;
- list->lines.push_back(HistoryItem(line));
+ list->lines.push_back(HistoryItem(user, details.text));
if (list->lines.size() > list->maxlen)
list->lines.pop_front();
}
@@ -146,7 +152,8 @@ class ModuleChanHistory : public Module
void OnPostJoin(Membership* memb) CXX11_OVERRIDE
{
- if (IS_REMOTE(memb->user))
+ LocalUser* localuser = IS_LOCAL(memb->user);
+ if (!localuser)
return;
if (memb->user->IsModeSet(botmode) && !dobots)
@@ -169,8 +176,12 @@ class ModuleChanHistory : public Module
for(std::deque<HistoryItem>::iterator i = list->lines.begin(); i != list->lines.end(); ++i)
{
- if (i->ts >= mintime)
- memb->user->Write(i->line);
+ const HistoryItem& item = *i;
+ if (item.ts >= mintime)
+ {
+ ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, item.sourcemask, memb->chan, item.text);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
+ }
}
}
diff --git a/src/modules/m_chanlog.cpp b/src/modules/m_chanlog.cpp
index f618a539c..85e7ca2eb 100644
--- a/src/modules/m_chanlog.cpp
+++ b/src/modules/m_chanlog.cpp
@@ -70,7 +70,8 @@ class ModuleChanLog : public Module
Channel *c = ServerInstance->FindChan(it->second);
if (c)
{
- c->WriteChannelWithServ(ServerInstance->Config->ServerName, "PRIVMSG %s :%s", c->name.c_str(), snotice.c_str());
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->Config->ServerName, c, snotice);
+ c->Write(ServerInstance->GetRFCEvents().privmsg, privmsg);
ServerInstance->PI->SendMessage(c, 0, snotice);
}
}
diff --git a/src/modules/m_cloaking.cpp b/src/modules/m_cloaking.cpp
index c277759d1..b9ff085c3 100644
--- a/src/modules/m_cloaking.cpp
+++ b/src/modules/m_cloaking.cpp
@@ -313,7 +313,13 @@ class ModuleCloaking : public Module
if (u->IsModeSet(cu) && !cu.active)
{
u->SetMode(cu, false);
- u->WriteCommand("MODE", "-" + ConvToStr(cu.GetModeChar()));
+
+ if (!IS_LOCAL(u))
+ return;
+ Modes::ChangeList modechangelist;
+ modechangelist.push_remove(&cu);
+ ClientProtocol::Events::Mode modeevent(ServerInstance->FakeClient, NULL, u, modechangelist);
+ static_cast<LocalUser*>(u)->Send(modeevent);
}
cu.active = false;
}
diff --git a/src/modules/m_conn_waitpong.cpp b/src/modules/m_conn_waitpong.cpp
index b4441c88c..f2e9590c8 100644
--- a/src/modules/m_conn_waitpong.cpp
+++ b/src/modules/m_conn_waitpong.cpp
@@ -46,8 +46,10 @@ class ModuleWaitPong : public Module
ModResult OnUserRegister(LocalUser* user) CXX11_OVERRIDE
{
std::string pingrpl = ServerInstance->GenRandomStr(10);
-
- user->Write("PING :%s", pingrpl.c_str());
+ {
+ ClientProtocol::Messages::Ping pingmsg(pingrpl);
+ user->Send(ServerInstance->GetRFCEvents().ping, pingmsg);
+ }
if(sendsnotice)
user->WriteNotice("*** If you are having problems connecting due to ping timeouts, please type /quote PONG " + pingrpl + " or /raw PONG " + pingrpl + " now.");
diff --git a/src/modules/m_delayjoin.cpp b/src/modules/m_delayjoin.cpp
index f9cd837d7..7c557eb35 100644
--- a/src/modules/m_delayjoin.cpp
+++ b/src/modules/m_delayjoin.cpp
@@ -33,14 +33,50 @@ class DelayJoinMode : public ModeHandler
ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding) CXX11_OVERRIDE;
};
+
+namespace
+{
+
+/** Hook handler for join client protocol events.
+ * This allows us to block join protocol events completely, including all associated messages (e.g. MODE, away-notify AWAY).
+ * This is not the same as OnUserJoin() because that runs only when a real join happens but this runs also when a module
+ * such as hostcycle generates a join.
+ */
+class JoinHook : public ClientProtocol::EventHook
+{
+ const LocalIntExt& unjoined;
+
+ public:
+ JoinHook(Module* mod, const LocalIntExt& unjoinedref)
+ : ClientProtocol::EventHook(mod, "JOIN", 10)
+ , unjoined(unjoinedref)
+ {
+ }
+
+ ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+ {
+ const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+ const User* const u = join.GetMember()->user;
+ if ((unjoined.get(u)) && (u != user))
+ return MOD_RES_DENY;
+ return MOD_RES_PASSTHRU;
+ }
+};
+
+}
+
class ModuleDelayJoin : public Module
{
DelayJoinMode djm;
+ void RevealUser(User* user, Channel* chan);
public:
LocalIntExt unjoined;
+ JoinHook joinhook;
+
ModuleDelayJoin()
: djm(this)
, unjoined("delayjoin", ExtensionItem::EXT_MEMBERSHIP, this)
+ , joinhook(this, unjoined)
{
}
@@ -68,7 +104,7 @@ ModeAction DelayJoinMode::OnModeChange(User* source, User* dest, Channel* channe
* they remain permanently invisible on this channel!
*/
MessageTarget msgtarget(channel, 0);
- MessageDetails msgdetails(MSG_PRIVMSG, "");
+ MessageDetails msgdetails(MSG_PRIVMSG, "", ClientProtocol::TagMap());
const Channel::MemberMap& users = channel->GetUsers();
for (Channel::MemberMap::const_iterator n = users.begin(); n != users.end(); ++n)
{
@@ -111,10 +147,7 @@ static void populate(CUList& except, Membership* memb)
void ModuleDelayJoin::OnUserJoin(Membership* memb, bool sync, bool created, CUList& except)
{
if (memb->chan->IsModeSet(djm))
- {
unjoined.set(memb, 1);
- populate(except, memb);
- }
}
void ModuleDelayJoin::OnUserPart(Membership* memb, std::string &partmessage, CUList& except)
@@ -147,20 +180,20 @@ void ModuleDelayJoin::OnUserMessage(User* user, const MessageTarget& target, con
return;
Channel* channel = target.Get<Channel>();
+ RevealUser(user, channel);
+}
- Membership* memb = channel->GetUser(user);
+void ModuleDelayJoin::RevealUser(User* user, Channel* chan)
+{
+ Membership* memb = chan->GetUser(user);
if (!memb || !unjoined.set(memb, 0))
return;
/* Display the join to everyone else (the user who joined got it earlier) */
- channel->WriteAllExceptSender(user, false, 0, "JOIN %s", channel->name.c_str());
-
- std::string ms = memb->modes;
- for(unsigned int i=0; i < memb->modes.length(); i++)
- ms.append(" ").append(user->nick);
-
- if (ms.length() > 0)
- channel->WriteAllExceptSender(user, false, 0, "MODE %s +%s", channel->name.c_str(), ms.c_str());
+ CUList except_list;
+ except_list.insert(user);
+ ClientProtocol::Events::Join joinevent(memb);
+ chan->Write(joinevent, 0, except_list);
}
/* make the user visible if he receives any mode change */
@@ -182,9 +215,7 @@ ModResult ModuleDelayJoin::OnRawMode(User* user, Channel* channel, ModeHandler*
if (!dest)
return MOD_RES_PASSTHRU;
- Membership* memb = channel->GetUser(dest);
- if (memb && unjoined.set(memb, 0))
- channel->WriteAllExceptSender(dest, false, 0, "JOIN %s", channel->name.c_str());
+ RevealUser(dest, channel);
return MOD_RES_PASSTHRU;
}
diff --git a/src/modules/m_hostcycle.cpp b/src/modules/m_hostcycle.cpp
index 0f7405dcc..a3c81df6e 100644
--- a/src/modules/m_hostcycle.cpp
+++ b/src/modules/m_hostcycle.cpp
@@ -24,13 +24,16 @@
class ModuleHostCycle : public Module
{
Cap::Reference chghostcap;
+ const std::string quitmsghost;
+ const std::string quitmsgident;
/** Send fake quit/join/mode messages for host or ident cycle.
*/
- void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg)
+ void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const std::string& reason)
{
- // GetFullHost() returns the original data at the time this function is called
- const std::string quitline = ":" + user->GetFullHost() + " QUIT :" + quitmsg;
+ // The user has the original ident/host at the time this function is called
+ ClientProtocol::Messages::Quit quitmsg(user, reason);
+ ClientProtocol::Event quitevent(ServerInstance->GetRFCEvents().quit, quitmsg);
already_sent_t silent_id = ServerInstance->Users.NextAlreadySentId();
already_sent_t seen_id = ServerInstance->Users.NextAlreadySentId();
@@ -50,7 +53,7 @@ class ModuleHostCycle : public Module
if (i->second)
{
u->already_sent = seen_id;
- u->Write(quitline);
+ u->Send(quitevent);
}
else
{
@@ -65,17 +68,8 @@ class ModuleHostCycle : public Module
{
Membership* memb = *i;
Channel* c = memb->chan;
- const std::string joinline = ":" + newfullhost + " JOIN " + c->name;
- std::string modeline;
- if (!memb->modes.empty())
- {
- modeline = ":" + (ServerInstance->Config->CycleHostsFromUser ? newfullhost : ServerInstance->Config->ServerName)
- + " MODE " + c->name + " +" + memb->modes;
-
- for (size_t j = 0; j < memb->modes.length(); j++)
- modeline.append(" ").append(user->nick);
- }
+ ClientProtocol::Events::Join joinevent(memb, newfullhost);
const Channel::MemberMap& ulist = c->GetUsers();
for (Channel::MemberMap::const_iterator j = ulist.begin(); j != ulist.end(); ++j)
@@ -90,13 +84,11 @@ class ModuleHostCycle : public Module
if (u->already_sent != seen_id)
{
- u->Write(quitline);
+ u->Send(quitevent);
u->already_sent = seen_id;
}
- u->Write(joinline);
- if (!memb->modes.empty())
- u->Write(modeline);
+ u->Send(joinevent);
}
}
}
@@ -104,17 +96,19 @@ class ModuleHostCycle : public Module
public:
ModuleHostCycle()
: chghostcap(this, "chghost")
+ , quitmsghost("Changing host")
+ , quitmsgident("Changing ident")
{
}
void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE
{
- DoHostCycle(user, newident, user->GetDisplayedHost(), "Changing ident");
+ DoHostCycle(user, newident, user->GetDisplayedHost(), quitmsgident);
}
void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE
{
- DoHostCycle(user, user->ident, newhost, "Changing host");
+ DoHostCycle(user, user->ident, newhost, quitmsghost);
}
Version GetVersion() CXX11_OVERRIDE
diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp
index 92e8a0881..14b1cf8a1 100644
--- a/src/modules/m_ircv3.cpp
+++ b/src/modules/m_ircv3.cpp
@@ -22,24 +22,110 @@
#include "modules/cap.h"
#include "modules/ircv3.h"
+class AwayMessage : public ClientProtocol::Message
+{
+ public:
+ AwayMessage(User* user)
+ : ClientProtocol::Message("AWAY", user)
+ {
+ SetParams(user, user->awaymsg);
+ }
+
+ AwayMessage()
+ : ClientProtocol::Message("AWAY")
+ {
+ }
+
+ void SetParams(User* user, const std::string& awaymsg)
+ {
+ // Going away: 1 parameter which is the away reason
+ // Back from away: no parameter
+ if (!awaymsg.empty())
+ PushParam(awaymsg);
+ }
+};
+
+class JoinHook : public ClientProtocol::EventHook
+{
+ ClientProtocol::Events::Join extendedjoinmsg;
+
+ public:
+ const std::string asterisk;
+ ClientProtocol::EventProvider awayprotoev;
+ AwayMessage awaymsg;
+ Cap::Capability extendedjoincap;
+ Cap::Capability awaycap;
+
+ JoinHook(Module* mod)
+ : ClientProtocol::EventHook(mod, "JOIN")
+ , asterisk(1, '*')
+ , awayprotoev(mod, "AWAY")
+ , extendedjoincap(mod, "extended-join")
+ , awaycap(mod, "away-notify")
+ {
+ }
+
+ void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
+ {
+ const ClientProtocol::Events::Join& join = static_cast<const ClientProtocol::Events::Join&>(ev);
+
+ // An extended join has two extra parameters:
+ // First the account name of the joining user or an asterisk if the user is not logged in.
+ // The second parameter is the realname of the joining user.
+
+ Membership* const memb = join.GetMember();
+ const std::string* account = &asterisk;
+ const AccountExtItem* const accountext = GetAccountExtItem();
+ if (accountext)
+ {
+ const std::string* accountname = accountext->get(memb->user);
+ if (accountname)
+ account = accountname;
+ }
+
+ extendedjoinmsg.ClearParams();
+ extendedjoinmsg.SetSource(join);
+ extendedjoinmsg.PushParamRef(memb->chan->name);
+ extendedjoinmsg.PushParamRef(*account);
+ extendedjoinmsg.PushParamRef(memb->user->GetRealName());
+
+ awaymsg.ClearParams();
+ if ((memb->user->IsAway()) && (awaycap.IsActive()))
+ {
+ awaymsg.SetSource(join);
+ awaymsg.SetParams(memb->user, memb->user->awaymsg);
+ }
+ }
+
+ ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
+ {
+ if (extendedjoincap.get(user))
+ messagelist.front() = &extendedjoinmsg;
+
+ if ((!awaymsg.GetParams().empty()) && (awaycap.get(user)))
+ messagelist.push_back(&awaymsg);
+
+ return MOD_RES_PASSTHRU;
+ }
+};
+
class ModuleIRCv3
: public Module
, public AccountEventListener
, public Away::EventListener
{
Cap::Capability cap_accountnotify;
- Cap::Capability cap_awaynotify;
- Cap::Capability cap_extendedjoin;
+ JoinHook joinhook;
- CUList last_excepts;
+ ClientProtocol::EventProvider accountprotoev;
public:
ModuleIRCv3()
: AccountEventListener(this)
, Away::EventListener(this)
, cap_accountnotify(this, "account-notify")
- , cap_awaynotify(this, "away-notify")
- , cap_extendedjoin(this, "extended-join")
+ , joinhook(this)
+ , accountprotoev(this, "ACCOUNT")
{
}
@@ -47,141 +133,41 @@ class ModuleIRCv3
{
ConfigTag* conf = ServerInstance->Config->ConfValue("ircv3");
cap_accountnotify.SetActive(conf->getBool("accountnotify", true));
- cap_awaynotify.SetActive(conf->getBool("awaynotify", true));
- cap_extendedjoin.SetActive(conf->getBool("extendedjoin", true));
+ joinhook.awaycap.SetActive(conf->getBool("awaynotify", true));
+ joinhook.extendedjoincap.SetActive(conf->getBool("extendedjoin", true));
}
void OnAccountChange(User* user, const std::string& newaccount) CXX11_OVERRIDE
{
- // :nick!user@host ACCOUNT account
- // or
- // :nick!user@host ACCOUNT *
- std::string line = ":" + user->GetFullHost() + " ACCOUNT ";
- if (newaccount.empty())
- line += "*";
- else
- line += newaccount;
-
- IRCv3::WriteNeighborsWithCap(user, line, cap_accountnotify);
- }
-
- void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE
- {
- // Remember who is not going to see the JOIN because of other modules
- if ((cap_awaynotify.IsActive()) && (memb->user->IsAway()))
- last_excepts = excepts;
-
- if (!cap_extendedjoin.IsActive())
- return;
-
- /*
- * Send extended joins to clients who have the extended-join capability.
- * An extended join looks like this:
- *
- * :nick!user@host JOIN #chan account :realname
- *
- * account is the joining user's account if he's logged in, otherwise it's an asterisk (*).
- */
-
- std::string line;
- std::string mode;
-
- const Channel::MemberMap& userlist = memb->chan->GetUsers();
- for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it)
- {
- // Send the extended join line if the current member is local, has the extended-join cap and isn't excepted
- User* member = IS_LOCAL(it->first);
- if ((member) && (cap_extendedjoin.get(member)) && (excepts.find(member) == excepts.end()))
- {
- // Construct the lines we're going to send if we haven't constructed them already
- if (line.empty())
- {
- bool has_account = false;
- line = ":" + memb->user->GetFullHost() + " JOIN " + memb->chan->name + " ";
- const AccountExtItem* accountext = GetAccountExtItem();
- if (accountext)
- {
- std::string* accountname;
- accountname = accountext->get(memb->user);
- if (accountname)
- {
- line += *accountname;
- has_account = true;
- }
- }
-
- if (!has_account)
- line += "*";
-
- line += " :" + memb->user->GetRealName();
-
- // If the joining user received privileges from another module then we must send them as well,
- // since silencing the normal join means the MODE will be silenced as well
- if (!memb->modes.empty())
- {
- const std::string& modefrom = ServerInstance->Config->CycleHostsFromUser ? memb->user->GetFullHost() : ServerInstance->Config->ServerName;
- mode = ":" + modefrom + " MODE " + memb->chan->name + " +" + memb->modes;
-
- for (unsigned int i = 0; i < memb->modes.length(); i++)
- mode += " " + memb->user->nick;
- }
- }
-
- // Write the JOIN and the MODE, if any
- member->Write(line);
- if ((!mode.empty()) && (member != memb->user))
- member->Write(mode);
-
- // Prevent the core from sending the JOIN and MODE to this user
- excepts.insert(it->first);
- }
- }
+ // Logged in: 1 parameter which is the account name
+ // Logged out: 1 parameter which is a "*"
+ ClientProtocol::Message msg("ACCOUNT", user);
+ const std::string& param = (newaccount.empty() ? joinhook.asterisk : newaccount);
+ msg.PushParamRef(param);
+ ClientProtocol::Event accountevent(accountprotoev, msg);
+ IRCv3::WriteNeighborsWithCap(user, accountevent, cap_accountnotify);
}
void OnUserAway(User* user) CXX11_OVERRIDE
{
- if (!cap_awaynotify.IsActive())
+ if (!joinhook.awaycap.IsActive())
return;
// Going away: n!u@h AWAY :reason
- const std::string line = ":" + user->GetFullHost() + " AWAY :" + user->awaymsg;
- IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify);
+ AwayMessage msg(user);
+ ClientProtocol::Event awayevent(joinhook.awayprotoev, msg);
+ IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap);
}
void OnUserBack(User* user) CXX11_OVERRIDE
{
- if (!cap_awaynotify.IsActive())
+ if (!joinhook.awaycap.IsActive())
return;
// Back from away: n!u@h AWAY
- const std::string line = ":" + user->GetFullHost() + " AWAY";
- IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify);
- }
-
- void OnPostJoin(Membership *memb) CXX11_OVERRIDE
- {
- if ((!cap_awaynotify.IsActive()) || (!memb->user->IsAway()))
- return;
-
- std::string line = ":" + memb->user->GetFullHost() + " AWAY :" + memb->user->awaymsg;
-
- const Channel::MemberMap& userlist = memb->chan->GetUsers();
- for (Channel::MemberMap::const_iterator it = userlist.begin(); it != userlist.end(); ++it)
- {
- // Send the away notify line if the current member is local, has the away-notify cap and isn't excepted
- User* member = IS_LOCAL(it->first);
- if ((member) && (cap_awaynotify.get(member)) && (last_excepts.find(member) == last_excepts.end()) && (it->second != memb))
- {
- member->Write(line);
- }
- }
-
- last_excepts.clear();
- }
-
- void Prioritize() CXX11_OVERRIDE
- {
- ServerInstance->Modules->SetPriority(this, I_OnUserJoin, PRIORITY_LAST);
+ AwayMessage msg(user);
+ ClientProtocol::Event awayevent(joinhook.awayprotoev, msg);
+ IRCv3::WriteNeighborsWithCap(user, awayevent, joinhook.awaycap);
}
Version GetVersion() CXX11_OVERRIDE
diff --git a/src/modules/m_ircv3_capnotify.cpp b/src/modules/m_ircv3_capnotify.cpp
index 93c30df12..757b0858f 100644
--- a/src/modules/m_ircv3_capnotify.cpp
+++ b/src/modules/m_ircv3_capnotify.cpp
@@ -46,19 +46,53 @@ class CapNotify : public Cap::Capability
}
};
+class CapNotifyMessage : public Cap::MessageBase
+{
+ public:
+ CapNotifyMessage(bool add, const std::string& capname)
+ : Cap::MessageBase((add ? "NEW" : "DEL"))
+ {
+ PushParamRef(capname);
+ }
+};
+
+class CapNotifyValueMessage : public Cap::MessageBase
+{
+ std::string s;
+ const std::string::size_type pos;
+
+ public:
+ CapNotifyValueMessage(const std::string& capname)
+ : Cap::MessageBase("NEW")
+ , s(capname)
+ , pos(s.size()+1)
+ {
+ s.push_back('=');
+ PushParamRef(s);
+ }
+
+ void SetCapValue(const std::string& capvalue)
+ {
+ s.erase(pos);
+ s.append(capvalue);
+ InvalidateCache();
+ }
+};
+
class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener
{
CapNotify capnotify;
std::string reloadedmod;
std::vector<std::string> reloadedcaps;
+ ClientProtocol::EventProvider protoev;
void Send(const std::string& capname, Cap::Capability* cap, bool add)
{
- std::string msg = (add ? "NEW :" : "DEL :");
- msg.append(capname);
- std::string msgwithval = msg;
- msgwithval.push_back('=');
- std::string::size_type msgpos = msgwithval.size();
+ CapNotifyMessage msg(add, capname);
+ CapNotifyValueMessage msgwithval(capname);
+
+ ClientProtocol::Event event(protoev, msg);
+ ClientProtocol::Event eventwithval(protoev, msgwithval);
const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
@@ -73,13 +107,14 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re
const std::string* capvalue = cap->GetValue(user);
if ((capvalue) && (!capvalue->empty()))
{
- msgwithval.append(*capvalue);
- user->WriteCommand("CAP", msgwithval);
- msgwithval.erase(msgpos);
+ msgwithval.SetUser(user);
+ msgwithval.SetCapValue(*capvalue);
+ user->Send(eventwithval);
continue;
}
}
- user->WriteCommand("CAP", msg);
+ msg.SetUser(user);
+ user->Send(event);
}
}
@@ -88,6 +123,7 @@ class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public Re
: Cap::EventListener(this)
, ReloadModule::EventListener(this)
, capnotify(this)
+ , protoev(this, "CAP_NOTIFY")
{
}
diff --git a/src/modules/m_ircv3_chghost.cpp b/src/modules/m_ircv3_chghost.cpp
index 0a9e055b4..aa53612cb 100644
--- a/src/modules/m_ircv3_chghost.cpp
+++ b/src/modules/m_ircv3_chghost.cpp
@@ -24,17 +24,21 @@
class ModuleIRCv3ChgHost : public Module
{
Cap::Capability cap;
+ ClientProtocol::EventProvider protoevprov;
void DoChgHost(User* user, const std::string& ident, const std::string& host)
{
- std::string line(1, ':');
- line.append(user->GetFullHost()).append(" CHGHOST ").append(ident).append(1, ' ').append(host);
- IRCv3::WriteNeighborsWithCap(user, line, cap);
+ ClientProtocol::Message msg("CHGHOST", user);
+ msg.PushParamRef(ident);
+ msg.PushParamRef(host);
+ ClientProtocol::Event protoev(protoevprov, msg);
+ IRCv3::WriteNeighborsWithCap(user, protoev, cap);
}
public:
ModuleIRCv3ChgHost()
: cap(this, "chghost")
+ , protoevprov(this, "CHGHOST")
{
}
diff --git a/src/modules/m_ircv3_echomessage.cpp b/src/modules/m_ircv3_echomessage.cpp
index 056b02194..702552ea7 100644
--- a/src/modules/m_ircv3_echomessage.cpp
+++ b/src/modules/m_ircv3_echomessage.cpp
@@ -21,8 +21,6 @@
#include "inspircd.h"
#include "modules/cap.h"
-static const char* MessageTypeStringSp[] = { "PRIVMSG ", "NOTICE " };
-
class ModuleIRCv3EchoMessage : public Module
{
Cap::Capability cap;
@@ -38,27 +36,31 @@ class ModuleIRCv3EchoMessage : public Module
if (!cap.get(user))
return;
- std::string msg = MessageTypeStringSp[details.type];
+ // Caps are only set on local users
+ LocalUser* const localuser = static_cast<LocalUser*>(user);
+
+ const std::string& text = details.echooriginal ? details.originaltext : details.text;
if (target.type == MessageTarget::TYPE_USER)
{
User* destuser = target.Get<User>();
- msg.append(destuser->nick);
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, destuser, text, details.type);
+ privmsg.AddTags(details.tags_in);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
else if (target.type == MessageTarget::TYPE_CHANNEL)
{
- if (target.status)
- msg.push_back(target.status);
-
Channel* chan = target.Get<Channel>();
- msg.append(chan->name);
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, chan, text, details.type, target.status);
+ privmsg.AddTags(details.tags_in);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
else
{
const std::string* servername = target.Get<std::string>();
- msg.append(*servername);
+ ClientProtocol::Messages::Privmsg privmsg(ClientProtocol::Messages::Privmsg::nocopy, user, *servername, text, details.type);
+ privmsg.AddTags(details.tags_in);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, privmsg);
}
- msg.append(" :").append(details.echooriginal ? details.originaltext : details.text);
- user->WriteFrom(user, msg);
}
void OnUserMessageBlocked(User* user, const MessageTarget& target, const MessageDetails& details) CXX11_OVERRIDE
diff --git a/src/modules/m_ircv3_invitenotify.cpp b/src/modules/m_ircv3_invitenotify.cpp
index 3783ff33c..bcb6f51d5 100644
--- a/src/modules/m_ircv3_invitenotify.cpp
+++ b/src/modules/m_ircv3_invitenotify.cpp
@@ -32,8 +32,8 @@ class ModuleIRCv3InviteNotify : public Module
void OnUserInvite(User* source, User* dest, Channel* chan, time_t expiry, unsigned int notifyrank, CUList& notifyexcepts) CXX11_OVERRIDE
{
- std::string msg = "INVITE ";
- msg.append(dest->nick).append(1, ' ').append(chan->name);
+ ClientProtocol::Messages::Invite invitemsg(source, dest, chan);
+ ClientProtocol::Event inviteevent(ServerInstance->GetRFCEvents().invite, invitemsg);
const Channel::MemberMap& users = chan->GetUsers();
for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i)
{
@@ -47,8 +47,10 @@ class ModuleIRCv3InviteNotify : public Module
if (memb->getRank() < notifyrank)
continue;
+ // Caps are only set on local users
+ LocalUser* const localuser = static_cast<LocalUser*>(user);
// Send and add the user to the exceptions so they won't get the NOTICE invite announcement message
- user->WriteFrom(source, msg);
+ localuser->Send(inviteevent);
notifyexcepts.insert(user);
}
}
diff --git a/src/modules/m_knock.cpp b/src/modules/m_knock.cpp
index a0a8455a8..1b66e01a4 100644
--- a/src/modules/m_knock.cpp
+++ b/src/modules/m_knock.cpp
@@ -81,7 +81,13 @@ class CommandKnock : public Command
c->WriteNotice(InspIRCd::Format("User %s is KNOCKing on %s (%s)", user->nick.c_str(), c->name.c_str(), parameters[1].c_str()));
if (sendnumeric)
- c->WriteChannelWithServ(ServerInstance->Config->ServerName, "710 %s %s %s :is KNOCKing: %s", c->name.c_str(), c->name.c_str(), user->GetFullHost().c_str(), parameters[1].c_str());
+ {
+ Numeric::Numeric numeric(710);
+ numeric.push(c->name).push(user->GetFullHost()).push("is KNOCKing: " + parameters[1]);
+
+ ClientProtocol::Messages::Numeric numericmsg(numeric, c->name);
+ c->Write(ServerInstance->GetRFCEvents().numeric, numericmsg);
+ }
user->WriteNotice("KNOCKing on " + c->name);
return CMD_SUCCESS;
diff --git a/src/modules/m_ldapoper.cpp b/src/modules/m_ldapoper.cpp
index 094b37744..cde5b00d7 100644
--- a/src/modules/m_ldapoper.cpp
+++ b/src/modules/m_ldapoper.cpp
@@ -48,7 +48,8 @@ class LDAPOperBase : public LDAPInterface
CommandBase::Params params;
params.push_back(opername);
params.push_back(password);
- oper_command->Handle(user, params);
+ ClientProtocol::TagMap tags;
+ oper_command->Handle(user, CommandBase::Params(params, tags));
}
void Fallback()
diff --git a/src/modules/m_passforward.cpp b/src/modules/m_passforward.cpp
index e3f8624fc..08d3533cd 100644
--- a/src/modules/m_passforward.cpp
+++ b/src/modules/m_passforward.cpp
@@ -91,8 +91,8 @@ class ModulePassForward : public Module
}
std::string tmp;
- FormatStr(tmp,forwardmsg, user);
- user->WriteServ(tmp);
+ FormatStr(tmp, forwardmsg, user);
+ ServerInstance->Parser.ProcessBuffer(user, tmp);
tmp.clear();
FormatStr(tmp,forwardcmd, user);
diff --git a/src/modules/m_samode.cpp b/src/modules/m_samode.cpp
index 195b767ab..8f28a7e9c 100644
--- a/src/modules/m_samode.cpp
+++ b/src/modules/m_samode.cpp
@@ -26,6 +26,8 @@
*/
class CommandSamode : public Command
{
+ bool logged;
+
public:
bool active;
CommandSamode(Module* Creator) : Command(Creator,"SAMODE", 2)
@@ -55,24 +57,29 @@ class CommandSamode : public Command
Modes::ChangeList emptychangelist;
ServerInstance->Modes->ProcessSingle(ServerInstance->FakeClient, NULL, ServerInstance->FakeClient, emptychangelist);
+ logged = false;
this->active = true;
- CmdResult result = ServerInstance->Parser.CallHandler("MODE", parameters, user);
+ ServerInstance->Parser.CallHandler("MODE", parameters, user);
this->active = false;
- if (result == CMD_SUCCESS)
+ if (!logged)
{
- // If lastparse is empty and the MODE command handler returned CMD_SUCCESS then
- // the client queried the list of a listmode (e.g. /SAMODE #chan b), which was
- // handled internally by the MODE command handler.
+ // If we haven't logged anything yet then the client queried the list of a listmode
+ // (e.g. /SAMODE #chan b), which was handled internally by the MODE command handler.
//
- // Viewing the modes of a user or a channel can also result in CMD_SUCCESS, but
+ // Viewing the modes of a user or a channel could also result in this, but
// that is not possible with /SAMODE because we require at least 2 parameters.
- const std::string& lastparse = ServerInstance->Modes.GetLastParse();
- ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + (lastparse.empty() ? stdalgo::string::join(parameters) : lastparse));
+ LogUsage(user, stdalgo::string::join(parameters));
}
return CMD_SUCCESS;
}
+
+ void LogUsage(const User* user, const std::string& text)
+ {
+ logged = true;
+ ServerInstance->SNO->WriteGlobalSno('a', user->nick + " used SAMODE: " + text);
+ }
};
class ModuleSaMode : public Module
@@ -96,6 +103,25 @@ class ModuleSaMode : public Module
return MOD_RES_PASSTHRU;
}
+ void OnMode(User* user, User* destuser, Channel* destchan, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE
+ {
+ if (!cmd.active)
+ return;
+
+ std::string logtext = (destuser ? destuser->nick : destchan->name);
+ logtext.push_back(' ');
+ logtext += ClientProtocol::Messages::Mode::ToModeLetters(modes);
+
+ for (Modes::ChangeList::List::const_iterator i = modes.getlist().begin(); i != modes.getlist().end(); ++i)
+ {
+ const Modes::Change& item = *i;
+ if (!item.param.empty())
+ logtext.append(1, ' ').append(item.param);
+ }
+
+ cmd.LogUsage(user, logtext);
+ }
+
void Prioritize() CXX11_OVERRIDE
{
Module *override = ServerInstance->Modules->Find("m_override.so");
diff --git a/src/modules/m_sasl.cpp b/src/modules/m_sasl.cpp
index d37e1c90f..480f8f6db 100644
--- a/src/modules/m_sasl.cpp
+++ b/src/modules/m_sasl.cpp
@@ -145,7 +145,7 @@ static Events::ModuleEventProvider* saslevprov;
static void SendSASL(LocalUser* user, const std::string& agent, char mode, const std::vector<std::string>& parameters)
{
- CommandBase::Params params(parameters.size() + 3);
+ CommandBase::Params params;
params.push_back(user->uuid);
params.push_back(agent);
params.push_back(ConvToStr(mode));
@@ -157,6 +157,8 @@ static void SendSASL(LocalUser* user, const std::string& agent, char mode, const
}
}
+static ClientProtocol::EventProvider* g_protoev;
+
/**
* Tracks SASL authentication state like charybdis does. --nenolod
*/
@@ -223,7 +225,15 @@ class SaslAuthenticator
return this->state;
if (msg[2] == "C")
- this->user->Write("AUTHENTICATE %s", msg[3].c_str());
+ {
+ ClientProtocol::Message authmsg("AUTHENTICATE");
+ authmsg.PushParamRef(msg[3]);
+
+ ClientProtocol::Event authevent(*g_protoev, authmsg);
+ LocalUser* const localuser = IS_LOCAL(user);
+ if (localuser)
+ localuser->Send(authevent);
+ }
else if (msg[2] == "D")
{
this->state = SASL_DONE;
@@ -377,6 +387,7 @@ class ModuleSASL : public Module
CommandAuthenticate auth;
CommandSASL sasl;
Events::ModuleEventProvider sasleventprov;
+ ClientProtocol::EventProvider protoev;
public:
ModuleSASL()
@@ -386,8 +397,10 @@ class ModuleSASL : public Module
, auth(this, authExt, cap)
, sasl(this, authExt)
, sasleventprov(this, "event/sasl")
+ , protoev(this, auth.name)
{
saslevprov = &sasleventprov;
+ g_protoev = &protoev;
}
void init() CXX11_OVERRIDE
diff --git a/src/modules/m_showfile.cpp b/src/modules/m_showfile.cpp
index 565aaf78b..99c545140 100644
--- a/src/modules/m_showfile.cpp
+++ b/src/modules/m_showfile.cpp
@@ -63,13 +63,14 @@ class CommandShowFile : public Command
user->WriteRemoteNumeric(endnumeric, endtext.c_str());
}
- else
+ else if (IS_LOCAL(user))
{
- const char* msgcmd = (method == SF_MSG ? "PRIVMSG" : "NOTICE");
+ LocalUser* const localuser = IS_LOCAL(user);
for (file_cache::const_iterator i = contents.begin(); i != contents.end(); ++i)
{
const std::string& line = *i;
- user->WriteCommand(msgcmd, ":" + line);
+ ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, line, ((method == SF_MSG) ? MSG_PRIVMSG : MSG_NOTICE));
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
}
}
return CMD_SUCCESS;
diff --git a/src/modules/m_spanningtree/main.cpp b/src/modules/m_spanningtree/main.cpp
index 3c9089115..0ff180a83 100644
--- a/src/modules/m_spanningtree/main.cpp
+++ b/src/modules/m_spanningtree/main.cpp
@@ -731,7 +731,7 @@ void ModuleSpanningTree::OnUserBack(User* user)
CommandAway::Builder(user).Broadcast();
}
-void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode)
+void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags)
{
if (processflags & ModeParser::MODE_LOCALONLY)
return;
@@ -743,7 +743,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::
CmdBuilder params(source, "MODE");
params.push(u->uuid);
- params.push(output_mode);
+ params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes));
params.push_raw(Translate::ModeChangeListToParams(modes.getlist()));
params.Broadcast();
}
@@ -752,7 +752,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes::
CmdBuilder params(source, "FMODE");
params.push(c->name);
params.push_int(c->age);
- params.push(output_mode);
+ params.push(ClientProtocol::Messages::Mode::ToModeLetters(modes));
params.push_raw(Translate::ModeChangeListToParams(modes.getlist()));
params.Broadcast();
}
diff --git a/src/modules/m_spanningtree/main.h b/src/modules/m_spanningtree/main.h
index 4a65f1c37..60f819e9c 100644
--- a/src/modules/m_spanningtree/main.h
+++ b/src/modules/m_spanningtree/main.h
@@ -172,7 +172,7 @@ class ModuleSpanningTree
void OnLoadModule(Module* mod) CXX11_OVERRIDE;
void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
ModResult OnAcceptConnection(int newsock, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE;
- void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags, const std::string& output_mode) CXX11_OVERRIDE;
+ void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) CXX11_OVERRIDE;
CullResult cull() CXX11_OVERRIDE;
~ModuleSpanningTree();
Version GetVersion() CXX11_OVERRIDE;
diff --git a/src/modules/m_spanningtree/treesocket2.cpp b/src/modules/m_spanningtree/treesocket2.cpp
index 3bc1fc71a..513fa6dbf 100644
--- a/src/modules/m_spanningtree/treesocket2.cpp
+++ b/src/modules/m_spanningtree/treesocket2.cpp
@@ -343,7 +343,8 @@ void TreeSocket::ProcessConnectedLine(std::string& prefix, std::string& command,
res = scmd->Handle(who, params);
else
{
- res = cmd->Handle(who, params);
+ ClientProtocol::TagMap tags;
+ res = cmd->Handle(who, CommandBase::Params(params, tags));
if (res == CMD_INVALID)
throw ProtocolException("Error in command handler");
}
diff --git a/src/modules/m_sqloper.cpp b/src/modules/m_sqloper.cpp
index 2b298f662..da538caef 100644
--- a/src/modules/m_sqloper.cpp
+++ b/src/modules/m_sqloper.cpp
@@ -143,7 +143,8 @@ class OperQuery : public SQL::Query
return;
// Now handle /OPER.
- oper_command->Handle(user, params);
+ ClientProtocol::TagMap tags;
+ oper_command->Handle(user, CommandBase::Params(params, tags));
}
else
{
diff --git a/src/modules/m_timedbans.cpp b/src/modules/m_timedbans.cpp
index ffb84a44f..058028f61 100644
--- a/src/modules/m_timedbans.cpp
+++ b/src/modules/m_timedbans.cpp
@@ -113,7 +113,6 @@ class CommandTban : public Command
return CMD_FAILURE;
}
- CUList tmp;
T.mask = mask;
T.expire = expire + (IS_REMOTE(user) ? 5 : 0);
T.chan = channel;
@@ -124,7 +123,8 @@ class CommandTban : public Command
PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h');
char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@';
- channel->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, tmp, "NOTICE %s :%s", channel->name.c_str(), addban.c_str());
+ ClientProtocol::Messages::Privmsg notice(ServerInstance->FakeClient, channel, addban, MSG_NOTICE);
+ channel->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar);
ServerInstance->PI->SendChannelNotice(channel, pfxchar, addban);
return CMD_SUCCESS;
}
@@ -210,13 +210,13 @@ class ModuleTimedBans : public Module
std::string mask = i->mask;
Channel* cr = i->chan;
{
- CUList empty;
const std::string expiry = "*** Timed ban on " + cr->name + " expired.";
// If halfop is loaded, send notice to halfops and above, otherwise send to ops and above
PrefixMode* mh = ServerInstance->Modes->FindPrefixMode('h');
char pfxchar = (mh && mh->name == "halfop") ? mh->GetPrefix() : '@';
- cr->WriteAllExcept(ServerInstance->FakeClient, true, pfxchar, empty, "NOTICE %s :%s", cr->name.c_str(), expiry.c_str());
+ ClientProtocol::Messages::Privmsg notice(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, cr, expiry, MSG_NOTICE);
+ cr->Write(ServerInstance->GetRFCEvents().privmsg, notice, pfxchar);
ServerInstance->PI->SendChannelNotice(cr, pfxchar, expiry);
Modes::ChangeList setban;
diff --git a/src/usermanager.cpp b/src/usermanager.cpp
index 02c030a42..7466f385b 100644
--- a/src/usermanager.cpp
+++ b/src/usermanager.cpp
@@ -28,21 +28,23 @@ namespace
{
class WriteCommonQuit : public User::ForEachNeighborHandler
{
- std::string line;
- std::string operline;
+ ClientProtocol::Messages::Quit quitmsg;
+ ClientProtocol::Event quitevent;
+ ClientProtocol::Messages::Quit operquitmsg;
+ ClientProtocol::Event operquitevent;
void Execute(LocalUser* user) CXX11_OVERRIDE
{
- user->Write(user->IsOper() ? operline : line);
+ user->Send(user->IsOper() ? operquitevent : quitevent);
}
public:
WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg)
- : line(":" + user->GetFullHost() + " QUIT :")
- , operline(line)
+ : quitmsg(user, msg)
+ , quitevent(ServerInstance->GetRFCEvents().quit, quitmsg)
+ , operquitmsg(user, opermsg)
+ , operquitevent(ServerInstance->GetRFCEvents().quit, operquitmsg)
{
- line += msg;
- operline += opermsg;
user->ForEachNeighbor(*this, false);
}
};
@@ -177,7 +179,12 @@ void UserManager::QuitUser(User* user, const std::string& quitreason, const std:
user->quitting = true;
ServerInstance->Logs->Log("USERS", LOG_DEBUG, "QuitUser: %s=%s '%s'", user->uuid.c_str(), user->nick.c_str(), quitreason.c_str());
- user->Write("ERROR :Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str());
+ LocalUser* const localuser = IS_LOCAL(user);
+ if (localuser)
+ {
+ ClientProtocol::Messages::Error errormsg(InspIRCd::Format("Closing link: (%s@%s) [%s]", user->ident.c_str(), user->GetRealHost().c_str(), operreason ? operreason->c_str() : quitreason.c_str()));
+ localuser->Send(ServerInstance->GetRFCEvents().error, errormsg);
+ }
std::string reason;
reason.assign(quitreason, 0, ServerInstance->Config->Limits.MaxQuit);
@@ -264,12 +271,13 @@ void UserManager::ServerNoticeAll(const char* text, ...)
{
std::string message;
VAFORMAT(message, text, text);
- message = "NOTICE $" + ServerInstance->Config->ServerName + " :" + message;
+ ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, ServerInstance->Config->ServerName, message, MSG_NOTICE);
+ ClientProtocol::Event msgevent(ServerInstance->GetRFCEvents().privmsg, msg);
for (LocalList::const_iterator i = local_users.begin(); i != local_users.end(); ++i)
{
- User* t = *i;
- t->WriteServ(message);
+ LocalUser* user = *i;
+ user->Send(msgevent);
}
}
@@ -320,8 +328,8 @@ void UserManager::DoBackgroundUserStuff()
this->QuitUser(curr, message);
continue;
}
-
- curr->Write("PING :" + ServerInstance->Config->ServerName);
+ ClientProtocol::Messages::Ping ping;
+ curr->Send(ServerInstance->GetRFCEvents().ping, ping);
curr->lastping = 0;
curr->nping = ServerInstance->Time() + curr->MyClass->GetPingTime();
}
diff --git a/src/users.cpp b/src/users.cpp
index 737a3fa5c..e05ef1853 100644
--- a/src/users.cpp
+++ b/src/users.cpp
@@ -26,6 +26,8 @@
#include "inspircd.h"
#include "xline.h"
+ClientProtocol::MessageList LocalUser::sendmsglist;
+
bool User::IsNoticeMaskSet(unsigned char sm)
{
if (!isalpha(sm))
@@ -87,6 +89,7 @@ User::User(const std::string& uid, Server* srv, UserType type)
LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* servaddr)
: User(ServerInstance->UIDGen.GetUID(), ServerInstance->FakeClient->server, USERTYPE_LOCAL)
, eh(this)
+ , serializer(NULL)
, bytes_in(0)
, bytes_out(0)
, cmds_in(0)
@@ -359,7 +362,16 @@ void User::Oper(OperInfo* info)
this->SetMode(opermh, true);
this->oper = info;
- this->WriteCommand("MODE", "+o");
+
+ LocalUser* localuser = IS_LOCAL(this);
+ if (localuser)
+ {
+ Modes::ChangeList changelist;
+ changelist.push_add(opermh);
+ ClientProtocol::Events::Mode modemsg(ServerInstance->FakeClient, NULL, localuser, changelist);
+ localuser->Send(modemsg);
+ }
+
FOREACH_MOD(OnOper, (this, info->name));
std::string opername;
@@ -384,7 +396,7 @@ void User::Oper(OperInfo* info)
ServerInstance->Users->all_opers.push_back(this);
// Expand permissions from config for faster lookup
- if (IS_LOCAL(this))
+ if (localuser)
oper->init();
FOREACH_MOD(OnPostOper, (this, oper->name, opername));
@@ -573,7 +585,10 @@ void LocalUser::FullConnect()
ServerInstance->Parser.CallHandler(command, parameters, this);
if (ServerInstance->Config->RawLog)
- WriteServ("PRIVMSG %s :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.", nick.c_str());
+ {
+ ClientProtocol::Messages::Privmsg rawlogmsg(ServerInstance->FakeClient, this, "*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.");
+ this->Send(ServerInstance->GetRFCEvents().privmsg, rawlogmsg);
+ }
/*
* We don't set REG_ALL until triggering OnUserConnect, so some module events don't spew out stuff
@@ -651,7 +666,11 @@ bool User::ChangeNick(const std::string& newnick, time_t newts)
}
if (this->registered == REG_ALL)
- this->WriteCommon("NICK %s", newnick.c_str());
+ {
+ ClientProtocol::Messages::Nick nickmsg(this, newnick);
+ ClientProtocol::Event nickevent(ServerInstance->GetRFCEvents().nick, nickmsg);
+ this->WriteCommonRaw(nickevent, true);
+ }
const std::string oldnick = nick;
nick = newnick;
@@ -667,7 +686,10 @@ bool User::ChangeNick(const std::string& newnick, time_t newts)
void LocalUser::OverruleNick()
{
- this->WriteFrom(this, "NICK %s", this->uuid.c_str());
+ {
+ ClientProtocol::Messages::Nick nickmsg(this, this->uuid);
+ this->Send(ServerInstance->GetRFCEvents().nick, nickmsg);
+ }
this->WriteNumeric(ERR_NICKNAMEINUSE, this->nick, "Nickname overruled.");
// Clear the bit before calling ChangeNick() to make it NOT run the OnUserPostNick() hook
@@ -763,35 +785,24 @@ void LocalUser::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_elin
}
}
-static std::string wide_newline("\r\n");
-
-void User::Write(const std::string& text)
-{
-}
-
-void User::Write(const char *text, ...)
-{
-}
-
-void LocalUser::Write(const std::string& text)
+void LocalUser::Write(const ClientProtocol::SerializedMessage& text)
{
if (!SocketEngine::BoundsCheckFd(&eh))
return;
- // The maximum size of an IRC message minus the terminating CR+LF.
- const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2;
- if (text.length() > maxmessage)
+ if (ServerInstance->Config->RawLog)
{
- // This should happen rarely or never. Crop the string at MaxLine and try again.
- std::string try_again(text, 0, maxmessage);
- Write(try_again);
- return;
- }
+ if (text.empty())
+ return;
+
+ std::string::size_type nlpos = text.find_first_of("\r\n", 0, 2);
+ if (nlpos == std::string::npos)
+ nlpos = text.length(); // TODO is this ok, test it
- ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str());
+ ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %.*s", uuid.c_str(), (int) nlpos, text.c_str());
+ }
eh.AddWriteBuf(text);
- eh.AddWriteBuf(wide_newline);
const size_t bytessent = text.length() + 2;
ServerInstance->stats.Sent += bytessent;
@@ -799,53 +810,47 @@ void LocalUser::Write(const std::string& text)
this->cmds_out++;
}
-/** Write()
- */
-void LocalUser::Write(const char *text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->Write(textbuffer);
-}
-
-void User::WriteServ(const std::string& text)
-{
- this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str());
-}
-
-/** WriteServ()
- * Same as Write(), except `text' is prefixed with `:server.name '.
- */
-void User::WriteServ(const char* text, ...)
+void LocalUser::Send(ClientProtocol::Event& protoev)
{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->WriteServ(textbuffer);
-}
+ if (!serializer)
+ return;
-void User::WriteCommand(const char* command, const std::string& text)
-{
- this->WriteServ(command + (this->registered & REG_NICK ? " " + this->nick : " *") + " " + text);
+ // In the most common case a static LocalUser field, sendmsglist, is passed to the event to be
+ // populated. The list is cleared before returning.
+ // To handle re-enters, if sendmsglist is non-empty upon entering the method then a temporary
+ // list is used instead of the static one.
+ if (sendmsglist.empty())
+ {
+ Send(protoev, sendmsglist);
+ sendmsglist.clear();
+ }
+ else
+ {
+ ClientProtocol::MessageList msglist;
+ Send(protoev, msglist);
+ }
}
-namespace
+void LocalUser::Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist)
{
- std::string BuildNumeric(const std::string& source, User* targetuser, unsigned int num, const Command::Params& params)
+ // Modules can personalize the messages sent per user for the event
+ protoev.GetMessagesForUser(this, msglist);
+ for (ClientProtocol::MessageList::const_iterator i = msglist.begin(); i != msglist.end(); ++i)
{
- const char* const target = (targetuser->registered & REG_NICK ? targetuser->nick.c_str() : "*");
- std::string raw = InspIRCd::Format(":%s %03u %s", source.c_str(), num, target);
- if (!params.empty())
- {
- for (std::vector<std::string>::const_iterator i = params.begin(); i != params.end()-1; ++i)
- raw.append(1, ' ').append(*i);
- raw.append(" :").append(params.back());
- }
- return raw;
+ ClientProtocol::Message& curr = **i;
+ ModResult res;
+ FIRST_MOD_RESULT(OnUserWrite, res, (this, curr));
+ if (res != MOD_RES_DENY)
+ Write(serializer->SerializeForUser(this, curr));
}
}
void User::WriteNumeric(const Numeric::Numeric& numeric)
{
+ LocalUser* const localuser = IS_LOCAL(this);
+ if (!localuser)
+ return;
+
ModResult MOD_RESULT;
FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric));
@@ -853,24 +858,8 @@ void User::WriteNumeric(const Numeric::Numeric& numeric)
if (MOD_RESULT == MOD_RES_DENY)
return;
- const std::string& servername = (numeric.GetServer() ? numeric.GetServer()->GetName() : ServerInstance->Config->ServerName);
- this->Write(BuildNumeric(servername, this, numeric.GetNumeric(), numeric.GetParams()));
-}
-
-void User::WriteFrom(User *user, const std::string &text)
-{
- const std::string message = ":" + user->GetFullHost() + " " + text;
- this->Write(message);
-}
-
-
-/* write text from an originating user to originating user */
-
-void User::WriteFrom(User *user, const char* text, ...)
-{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- this->WriteFrom(user, textbuffer);
+ ClientProtocol::Messages::Numeric numericmsg(numeric, localuser);
+ localuser->Send(ServerInstance->GetRFCEvents().numeric, numericmsg);
}
void User::WriteRemoteNotice(const std::string& text)
@@ -887,32 +876,24 @@ namespace
{
class WriteCommonRawHandler : public User::ForEachNeighborHandler
{
- const std::string& msg;
+ ClientProtocol::Event& ev;
void Execute(LocalUser* user) CXX11_OVERRIDE
{
- user->Write(msg);
+ user->Send(ev);
}
public:
- WriteCommonRawHandler(const std::string& message)
- : msg(message)
+ WriteCommonRawHandler(ClientProtocol::Event& protoev)
+ : ev(protoev)
{
}
};
}
-void User::WriteCommon(const char* text, ...)
+void User::WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self)
{
- std::string textbuffer;
- VAFORMAT(textbuffer, text, text);
- textbuffer = ":" + this->GetFullHost() + " " + textbuffer;
- this->WriteCommonRaw(textbuffer, true);
-}
-
-void User::WriteCommonRaw(const std::string &line, bool include_self)
-{
- WriteCommonRawHandler handler(line);
+ WriteCommonRawHandler handler(protoev);
ForEachNeighbor(handler, include_self);
}
@@ -1203,6 +1184,16 @@ void User::PurgeEmptyChannels()
this->UnOper();
}
+void User::WriteNotice(const std::string& text)
+{
+ LocalUser* const localuser = IS_LOCAL(this);
+ if (!localuser)
+ return;
+
+ ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, text, MSG_NOTICE);
+ localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
+}
+
const std::string& FakeUser::GetFullHost()
{
if (!ServerInstance->Config->HideServer.empty())