summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Powell <petpow@saberuk.com>2019-01-23 21:45:50 +0000
committerPeter Powell <petpow@saberuk.com>2019-01-24 14:28:21 +0000
commitcbef0241a04eafe5250b75ebb3f7ef8c32ecb260 (patch)
treed856c8ac6db6cf2514bc5090e6b11f759d3c0290
parenta7fac86ccda9e75c6f426b07faa85081f5857d33 (diff)
Implement support for the extended tag space for client tags.
-rw-r--r--docs/conf/inspircd.conf.example6
-rw-r--r--include/hashcomp.h5
-rw-r--r--include/users.h3
-rw-r--r--src/coremods/core_serialize_rfc.cpp75
-rw-r--r--src/hashcomp.cpp4
-rw-r--r--src/users.cpp42
6 files changed, 86 insertions, 49 deletions
diff --git a/docs/conf/inspircd.conf.example b/docs/conf/inspircd.conf.example
index f65f6f5de..f2db74500 100644
--- a/docs/conf/inspircd.conf.example
+++ b/docs/conf/inspircd.conf.example
@@ -367,11 +367,11 @@
# 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.
- # 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,
diff --git a/include/hashcomp.h b/include/hashcomp.h
index f0e092729..80c02332d 100644
--- a/include/hashcomp.h
+++ b/include/hashcomp.h
@@ -197,7 +197,10 @@ namespace irc
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.
diff --git a/include/users.h b/include/users.h
index eaf400c67..3937f74aa 100644
--- a/include/users.h
+++ b/include/users.h
@@ -681,10 +681,13 @@ class CoreExport User : public Extensible
class CoreExport UserIOHandler : public StreamSocket
{
+ private:
+ size_t checked_until;
public:
LocalUser* const user;
UserIOHandler(LocalUser* me)
: StreamSocket(StreamSocket::SS_USER)
+ , checked_until(0)
, user(me)
{
}
diff --git a/src/coremods/core_serialize_rfc.cpp b/src/coremods/core_serialize_rfc.cpp
index 1ba330146..23a4c2052 100644
--- a/src/coremods/core_serialize_rfc.cpp
+++ b/src/coremods/core_serialize_rfc.cpp
@@ -19,11 +19,20 @@
#include "inspircd.h"
+enum
+{
+ // From ircu.
+ ERR_INPUTTOOLONG = 417
+};
+
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);
@@ -47,15 +56,32 @@ bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtoc
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.
+ std::string token;
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), ';');
@@ -78,7 +104,6 @@ bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtoc
HandleTag(user, token, tagval, parseoutput.tags);
}
-
// 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;
}
+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)
{
- 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();
- 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())
@@ -133,16 +170,14 @@ void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const Clie
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())
diff --git a/src/hashcomp.cpp b/src/hashcomp.cpp
index 8febcbb5f..a51430a4b 100644
--- a/src/hashcomp.cpp
+++ b/src/hashcomp.cpp
@@ -193,8 +193,8 @@ size_t irc::insensitive::operator()(const std::string &s) const
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)
{
}
diff --git a/src/users.cpp b/src/users.cpp
index eb87824fc..506cdf6d8 100644
--- a/src/users.cpp
+++ b/src/users.cpp
@@ -239,23 +239,30 @@ void UserIOHandler::OnDataReady()
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;
- 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)
{
- 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':
@@ -263,25 +270,14 @@ void UserIOHandler::OnDataReady()
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
- 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;