diff options
-rw-r--r-- | docs/conf/inspircd.conf.example | 6 | ||||
-rw-r--r-- | include/hashcomp.h | 5 | ||||
-rw-r--r-- | include/users.h | 3 | ||||
-rw-r--r-- | src/coremods/core_serialize_rfc.cpp | 75 | ||||
-rw-r--r-- | src/hashcomp.cpp | 4 | ||||
-rw-r--r-- | src/users.cpp | 42 |
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; |