diff options
author | Peter Powell <petpow@saberuk.com> | 2018-08-13 20:17:46 +0100 |
---|---|---|
committer | Peter Powell <petpow@saberuk.com> | 2018-08-13 21:51:11 +0100 |
commit | 58a0a7e01422e62de1565a8eb0a1febdc463d04d (patch) | |
tree | 8861789deefe9df3524690de8ccd11e5366f1f2e | |
parent | e2a820cce21342478653a34cf8ce2b593128d035 (diff) |
Implement IRCv3 message tag support.
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
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()) |