]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Implement support for the extended tag space for client tags.
authorPeter Powell <petpow@saberuk.com>
Wed, 23 Jan 2019 21:45:50 +0000 (21:45 +0000)
committerPeter Powell <petpow@saberuk.com>
Thu, 24 Jan 2019 14:28:21 +0000 (14:28 +0000)
docs/conf/inspircd.conf.example
include/hashcomp.h
include/users.h
src/coremods/core_serialize_rfc.cpp
src/hashcomp.cpp
src/users.cpp

index f65f6f5dea3803f3d55cd3f4a5f4870982940c33..f2db745002dee9f85aa6af9d3c005392da5f9b25 100644 (file)
 
          # softsendq: amount of data in a client's send queue before the server
          # begins delaying their commands in order to allow the sendq to drain
 
          # softsendq: amount of data in a client's send queue before the server
          # begins delaying their commands in order to allow the sendq to drain
-         softsendq="8192"
+         softsendq="10240"
 
          # recvq: amount of data allowed in a client's queue before they are dropped.
 
          # recvq: amount of data allowed in a client's queue before they are dropped.
-         # Entering "8K" is equivalent to "8192", see above.
-         recvq="8K"
+         # Entering "10K" is equivalent to "10240", see above.
+         recvq="10K"
 
          # threshold: This specifies the amount of command penalty a user is allowed to have
          # before being quit or fakelagged due to flood. Normal commands have a penalty of 1,
 
          # threshold: This specifies the amount of command penalty a user is allowed to have
          # before being quit or fakelagged due to flood. Normal commands have a penalty of 1,
index f0e092729d9618cec08e4b01972280721af3a022..80c02332d04dfb6e7936bc7cb4f46e1a69e75080 100644 (file)
@@ -197,7 +197,10 @@ namespace irc
 
         public:
                /** Create a tokenstream and fill it with the provided data. */
 
         public:
                /** Create a tokenstream and fill it with the provided data. */
-               tokenstream(const std::string& msg, size_t start = 0);
+               tokenstream(const std::string& msg, size_t start = 0, size_t end = std::string::npos);
+
+               /** Retrieves the underlying message. */
+               std::string& GetMessage() { return message; }
 
                /** Retrieve the next \<middle> token in the token stream.
                 * @param token The next token available, or an empty string if none remain.
 
                /** Retrieve the next \<middle> token in the token stream.
                 * @param token The next token available, or an empty string if none remain.
index eaf400c67da33081ef203349644065fe21132d20..3937f74aae4cc651e5702370238f970a5f69988f 100644 (file)
@@ -681,10 +681,13 @@ class CoreExport User : public Extensible
 
 class CoreExport UserIOHandler : public StreamSocket
 {
 
 class CoreExport UserIOHandler : public StreamSocket
 {
+ private:
+        size_t checked_until;
  public:
        LocalUser* const user;
        UserIOHandler(LocalUser* me)
                : StreamSocket(StreamSocket::SS_USER)
  public:
        LocalUser* const user;
        UserIOHandler(LocalUser* me)
                : StreamSocket(StreamSocket::SS_USER)
+               , checked_until(0)
                , user(me)
        {
        }
                , user(me)
        {
        }
index 1ba330146bb35aadfbacf94bfcca0bed378bb91c..23a4c205276ce61c188304e00bbd9ed06fda4ec3 100644 (file)
 
 #include "inspircd.h"
 
 
 #include "inspircd.h"
 
+enum
+{
+       // From ircu.
+       ERR_INPUTTOOLONG = 417
+};
+
 class RFCSerializer : public ClientProtocol::Serializer
 {
 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;
+
+       /** The maximum size of client-originated message tags in an incoming message including the `@`. */
+       static const std::string::size_type MAX_CLIENT_MESSAGE_TAG_LENGTH = 4095;
+
+       /** The maximum size of server-originated message tags in an outgoing message including the `@`. */
+       static const std::string::size_type MAX_SERVER_MESSAGE_TAG_LENGTH = 511;
 
        static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
 
 
        static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
 
@@ -47,15 +56,32 @@ bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtoc
                return false;
        }
 
                return false;
        }
 
-       ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), line.c_str());
+       // Work out how long the message can actually be.
+       size_t maxline = ServerInstance->Config->Limits.MaxLine - start - 2;
+       if (line[start] == '@')
+               maxline += MAX_CLIENT_MESSAGE_TAG_LENGTH + 1; 
 
 
-       irc::tokenstream tokens(line, start);
-       std::string token;
+       irc::tokenstream tokens(line, start, maxline);
+       ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), tokens.GetMessage().c_str());
 
        // This will always exist because of the check at the start of the function.
 
        // This will always exist because of the check at the start of the function.
+       std::string token;
        tokens.GetMiddle(token);
        if (token[0] == '@')
        {
        tokens.GetMiddle(token);
        if (token[0] == '@')
        {
+               // Check that the client tags fit within the client tag space.
+               if (token.length() > MAX_CLIENT_MESSAGE_TAG_LENGTH)
+               {
+                       user->WriteNumeric(ERR_INPUTTOOLONG, "Input line was too long");
+                       user->CommandFloodPenalty += 2000;
+                       return false;
+               }
+
+               // Truncate the RFC part of the message if it is too long.
+               size_t maxrfcline = token.length() + ServerInstance->Config->Limits.MaxLine - 1;
+               if (tokens.GetMessage().length() > maxrfcline)
+                       tokens.GetMessage().erase(maxrfcline);
+
                // Line begins with message tags, parse them.
                std::string tagval;
                irc::sepstream ss(token.substr(1), ';');
                // Line begins with message tags, parse them.
                std::string tagval;
                irc::sepstream ss(token.substr(1), ';');
@@ -78,7 +104,6 @@ bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtoc
                        HandleTag(user, token, tagval, parseoutput.tags);
                }
 
                        HandleTag(user, token, tagval, parseoutput.tags);
                }
 
-
                // Try to read the prefix or command name.
                if (!tokens.GetMiddle(token))
                {
                // Try to read the prefix or command name.
                if (!tokens.GetMiddle(token))
                {
@@ -114,17 +139,29 @@ bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtoc
        return true;
 }
 
        return true;
 }
 
+namespace
+{
+       void CheckTagLength(std::string& line, size_t prevsize, size_t& length, size_t maxlength)
+       {
+               const std::string::size_type diffsize = line.size() - prevsize;
+               if (length + diffsize > maxlength)
+                       line.erase(prevsize);
+               else
+                       length += diffsize;
+       }
+}
+
 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
 {
 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
 {
-       char prefix = '@'; // First tag name is prefixed with a '@'
+       size_t client_tag_length = 0;
+       size_t server_tag_length = 0;
        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();
        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.push_back(prevsize ? ';' : '@');
                line.append(i->first);
                const std::string& val = i->second.value;
                if (!val.empty())
                line.append(i->first);
                const std::string& val = i->second.value;
                if (!val.empty())
@@ -133,16 +170,14 @@ void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const Clie
                        line.append(val);
                }
 
                        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;
-               }
+               // The tags part of the message must not contain more client and server tags than allowed by the
+               // message tags specification. This is complicated by the tag space having separate limits for
+               // both server-originated and client-originated tags. If either of the tag limits is exceeded then
+               // the most recently added tag is removed.
+               if (i->first[0] == '+')
+                       CheckTagLength(line, prevsize, client_tag_length, MAX_CLIENT_MESSAGE_TAG_LENGTH);
+               else
+                       CheckTagLength(line, prevsize, server_tag_length, MAX_SERVER_MESSAGE_TAG_LENGTH);
        }
 
        if (!line.empty())
        }
 
        if (!line.empty())
index 8febcbb5f6b08784a3d5d92a0595cec4e80a340c..a51430a4b9efc20f34c6a4f836823b9c21aacfb5 100644 (file)
@@ -193,8 +193,8 @@ size_t irc::insensitive::operator()(const std::string &s) const
        return t;
 }
 
        return t;
 }
 
-irc::tokenstream::tokenstream(const std::string& msg, size_t start)
-       : message(msg, start)
+irc::tokenstream::tokenstream(const std::string& msg, size_t start, size_t end)
+       : message(msg, start, end)
        , position(0)
 {
 }
        , position(0)
 {
 }
index eb87824fcf1f22d7ba543f49edc6d61931c1d9f7..506cdf6d816b0c1fda04fa6f4530c709aa993625 100644 (file)
@@ -239,23 +239,30 @@ void UserIOHandler::OnDataReady()
        if (!user->HasPrivPermission("users/flood/no-fakelag"))
                penaltymax = user->MyClass->GetPenaltyThreshold() * 1000;
 
        if (!user->HasPrivPermission("users/flood/no-fakelag"))
                penaltymax = user->MyClass->GetPenaltyThreshold() * 1000;
 
-       // The maximum size of an IRC message minus the terminating CR+LF.
-       const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2;
+       // The cleaned message sent by the user or empty if not found yet.
        std::string line;
        std::string line;
-       line.reserve(maxmessage);
 
 
-       bool eol_found;
+       // The position of the most \n character or npos if not found yet.
+       std::string::size_type eolpos;
+
+       // The position within the recvq of the current character.
        std::string::size_type qpos;
 
        while (user->CommandFloodPenalty < penaltymax && getSendQSize() < sendqmax)
        {
        std::string::size_type qpos;
 
        while (user->CommandFloodPenalty < penaltymax && getSendQSize() < sendqmax)
        {
-               qpos = 0;
-               eol_found = false;
+               // Check the newly received data for an EOL.
+               eolpos = recvq.find('\n', checked_until);
+               if (eolpos == std::string::npos)
+               {
+                       checked_until = recvq.length();
+                       return;
+               }
 
 
-               const size_t qlen = recvq.length();
-               while (qpos < qlen)
+               // We've found a line! Clean it up and move it to the line buffer.
+               line.reserve(eolpos);
+               for (qpos = 0; qpos < eolpos; ++qpos)
                {
                {
-                       char c = recvq[qpos++];
+                       char c = recvq[qpos];
                        switch (c)
                        {
                                case '\0':
                        switch (c)
                        {
                                case '\0':
@@ -263,25 +270,14 @@ void UserIOHandler::OnDataReady()
                                        break;
                                case '\r':
                                        continue;
                                        break;
                                case '\r':
                                        continue;
-                               case '\n':
-                                       eol_found = true;
-                                       break;
                        }
 
                        }
 
-                       if (eol_found)
-                               break;
-
-                       if (line.length() < maxmessage)
-                               line.push_back(c);
+                       line.push_back(c);
                }
 
                }
 
-               // if we return here, we haven't found a newline and make no modifications to recvq
-               // so we can wait for more data
-               if (!eol_found)
-                       return;
-
                // just found a newline. Terminate the string, and pull it out of recvq
                // just found a newline. Terminate the string, and pull it out of recvq
-               recvq.erase(0, qpos);
+               recvq.erase(0, eolpos + 1);
+               checked_until = 0;
 
                // TODO should this be moved to when it was inserted in recvq?
                ServerInstance->stats.Recv += qpos;
 
                // TODO should this be moved to when it was inserted in recvq?
                ServerInstance->stats.Recv += qpos;