diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/channels.h | 71 | ||||
-rw-r--r-- | include/clientprotocol.h | 726 | ||||
-rw-r--r-- | include/clientprotocolevent.h | 78 | ||||
-rw-r--r-- | include/clientprotocolmsg.h | 677 | ||||
-rw-r--r-- | include/command_parse.h | 2 | ||||
-rw-r--r-- | include/configreader.h | 5 | ||||
-rw-r--r-- | include/ctables.h | 35 | ||||
-rw-r--r-- | include/event.h | 33 | ||||
-rw-r--r-- | include/inspircd.h | 19 | ||||
-rw-r--r-- | include/message.h | 9 | ||||
-rw-r--r-- | include/mode.h | 12 | ||||
-rw-r--r-- | include/modechange.h | 17 | ||||
-rw-r--r-- | include/modules.h | 7 | ||||
-rw-r--r-- | include/modules/cap.h | 19 | ||||
-rw-r--r-- | include/modules/ircv3.h | 64 | ||||
-rw-r--r-- | include/stdalgo.h | 82 | ||||
-rw-r--r-- | include/typedefs.h | 28 | ||||
-rw-r--r-- | include/users.h | 90 |
18 files changed, 1821 insertions, 153 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 |