2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
6 * This file is part of InspIRCd. InspIRCd is free software: you can
7 * redistribute it and/or modify it under the terms of the GNU General Public
8 * License as published by the Free Software Foundation, version 2.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ERR_INPUTTOOLONG = 417
28 class RFCSerializer : public ClientProtocol::Serializer
31 /** The maximum size of client-originated message tags in an incoming message including the `@`. */
32 static const std::string::size_type MAX_CLIENT_MESSAGE_TAG_LENGTH = 4095;
34 /** The maximum size of server-originated message tags in an outgoing message including the `@`. */
35 static const std::string::size_type MAX_SERVER_MESSAGE_TAG_LENGTH = 511;
37 static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
40 RFCSerializer(Module* mod)
41 : ClientProtocol::Serializer(mod, "rfc")
45 bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE;
46 ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE;
49 bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
51 size_t start = line.find_first_not_of(" ");
52 if (start == std::string::npos)
54 // Discourage the user from flooding the server.
55 user->CommandFloodPenalty += 2000;
59 // Work out how long the message can actually be.
60 size_t maxline = ServerInstance->Config->Limits.MaxLine - start - 2;
61 if (line[start] == '@')
62 maxline += MAX_CLIENT_MESSAGE_TAG_LENGTH + 1;
64 irc::tokenstream tokens(line, start, maxline);
65 ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), tokens.GetMessage().c_str());
67 // This will always exist because of the check at the start of the function.
69 tokens.GetMiddle(token);
72 // Check that the client tags fit within the client tag space.
73 if (token.length() > MAX_CLIENT_MESSAGE_TAG_LENGTH)
75 user->WriteNumeric(ERR_INPUTTOOLONG, "Input line was too long");
76 user->CommandFloodPenalty += 2000;
80 // Truncate the RFC part of the message if it is too long.
81 size_t maxrfcline = token.length() + ServerInstance->Config->Limits.MaxLine - 1;
82 if (tokens.GetMessage().length() > maxrfcline)
83 tokens.GetMessage().erase(maxrfcline);
85 // Line begins with message tags, parse them.
87 irc::sepstream ss(token.substr(1), ';');
88 while (ss.GetToken(token))
90 // Two or more tags with the same key must not be sent, but if a client violates that we accept
91 // the first occurence of duplicate tags and ignore all later occurences.
93 // Another option is to reject the message entirely but there is no standard way of doing that.
94 const std::string::size_type p = token.find('=');
95 if (p != std::string::npos)
98 tagval.assign(token, p+1, std::string::npos);
104 HandleTag(user, token, tagval, parseoutput.tags);
107 // Try to read the prefix or command name.
108 if (!tokens.GetMiddle(token))
110 // Discourage the user from flooding the server.
111 user->CommandFloodPenalty += 2000;
118 // If this exists then the client sent a prefix as part of their
119 // message. Section 2.3 of RFC 1459 technically says we should only
120 // allow the nick of the client here but in practise everyone just
121 // ignores it so we will copy them.
123 // Try to read the command name.
124 if (!tokens.GetMiddle(token))
126 // Discourage the user from flooding the server.
127 user->CommandFloodPenalty += 2000;
132 parseoutput.cmd.assign(token);
134 // Build the parameter map. We intentionally do not respect the RFC 1459
135 // thirteen parameter limit here.
136 while (tokens.GetTrailing(token))
137 parseoutput.params.push_back(token);
144 void CheckTagLength(std::string& line, size_t prevsize, size_t& length, size_t maxlength)
146 const std::string::size_type diffsize = line.size() - prevsize;
147 if (length + diffsize > maxlength)
148 line.erase(prevsize);
154 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
156 size_t client_tag_length = 0;
157 size_t server_tag_length = 0;
158 for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
160 if (!tagwl.IsSelected(tags, i))
163 const std::string::size_type prevsize = line.size();
164 line.push_back(prevsize ? ';' : '@');
165 line.append(i->first);
166 const std::string& val = i->second.value;
173 // The tags part of the message must not contain more client and server tags than allowed by the
174 // message tags specification. This is complicated by the tag space having separate limits for
175 // both server-originated and client-originated tags. If either of the tag limits is exceeded then
176 // the most recently added tag is removed.
177 if (i->first[0] == '+')
178 CheckTagLength(line, prevsize, client_tag_length, MAX_CLIENT_MESSAGE_TAG_LENGTH);
180 CheckTagLength(line, prevsize, server_tag_length, MAX_SERVER_MESSAGE_TAG_LENGTH);
187 ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
190 SerializeTags(msg.GetTags(), tagwl, line);
192 // Save position for length calculation later
193 const std::string::size_type rfcmsg_begin = line.size();
198 line.append(*msg.GetSource());
201 line.append(msg.GetCommand());
203 const ClientProtocol::Message::ParamList& params = msg.GetParams();
206 for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
208 const std::string& param = *i;
213 line.append(" :", 2).append(params.back());
216 // Truncate if too long
217 std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2;
218 if (line.length() - rfcmsg_begin > maxline)
219 line.erase(rfcmsg_begin + maxline);
221 line.append("\r\n", 2);
225 class ModuleCoreRFCSerializer : public Module
227 RFCSerializer rfcserializer;
230 ModuleCoreRFCSerializer()
231 : rfcserializer(this)
235 void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
237 if (type != ExtensionItem::EXT_USER)
240 LocalUser* const user = IS_LOCAL(static_cast<User*>(item));
241 if ((user) && (user->serializer == &rfcserializer))
242 ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading");
245 void OnUserInit(LocalUser* user) CXX11_OVERRIDE
247 if (!user->serializer)
248 user->serializer = &rfcserializer;
251 Version GetVersion() CXX11_OVERRIDE
253 return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
257 MODULE_INIT(ModuleCoreRFCSerializer)