2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2018-2019 Sadie Powell <sadie@witchery.services>
5 * Copyright (C) 2018 Attila Molnar <attilamolnar@hush.com>
7 * This file is part of InspIRCd. InspIRCd is free software: you can
8 * redistribute it and/or modify it under the terms of the GNU General Public
9 * License as published by the Free Software Foundation, version 2.
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26 ERR_INPUTTOOLONG = 417
29 class RFCSerializer : public ClientProtocol::Serializer
32 /** The maximum size of client-originated message tags in an incoming message including the `@`. */
33 static const std::string::size_type MAX_CLIENT_MESSAGE_TAG_LENGTH = 4095;
35 /** The maximum size of server-originated message tags in an outgoing message including the `@`. */
36 static const std::string::size_type MAX_SERVER_MESSAGE_TAG_LENGTH = 4095;
38 static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
41 RFCSerializer(Module* mod)
42 : ClientProtocol::Serializer(mod, "rfc")
46 bool Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput) CXX11_OVERRIDE;
47 ClientProtocol::SerializedMessage Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const CXX11_OVERRIDE;
50 bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
52 size_t start = line.find_first_not_of(" ");
53 if (start == std::string::npos)
55 // Discourage the user from flooding the server.
56 user->CommandFloodPenalty += 2000;
60 // Work out how long the message can actually be.
61 size_t maxline = ServerInstance->Config->Limits.MaxLine - start - 2;
62 if (line[start] == '@')
63 maxline += MAX_CLIENT_MESSAGE_TAG_LENGTH + 1;
65 irc::tokenstream tokens(line, start, maxline);
66 ServerInstance->Logs->Log("USERINPUT", LOG_RAWIO, "C[%s] I %s", user->uuid.c_str(), tokens.GetMessage().c_str());
68 // This will always exist because of the check at the start of the function.
70 tokens.GetMiddle(token);
73 // Check that the client tags fit within the client tag space.
74 if (token.length() > MAX_CLIENT_MESSAGE_TAG_LENGTH)
76 user->WriteNumeric(ERR_INPUTTOOLONG, "Input line was too long");
77 user->CommandFloodPenalty += 2000;
81 // Truncate the RFC part of the message if it is too long.
82 size_t maxrfcline = token.length() + ServerInstance->Config->Limits.MaxLine - 1;
83 if (tokens.GetMessage().length() > maxrfcline)
84 tokens.GetMessage().erase(maxrfcline);
86 // Line begins with message tags, parse them.
88 irc::sepstream ss(token.substr(1), ';');
89 while (ss.GetToken(token))
91 // Two or more tags with the same key must not be sent, but if a client violates that we accept
92 // the first occurrence of duplicate tags and ignore all later occurrences.
94 // Another option is to reject the message entirely but there is no standard way of doing that.
95 const std::string::size_type p = token.find('=');
96 if (p != std::string::npos)
99 tagval.assign(token, p+1, std::string::npos);
105 HandleTag(user, token, tagval, parseoutput.tags);
108 // Try to read the prefix or command name.
109 if (!tokens.GetMiddle(token))
111 // Discourage the user from flooding the server.
112 user->CommandFloodPenalty += 2000;
119 // If this exists then the client sent a prefix as part of their
120 // message. Section 2.3 of RFC 1459 technically says we should only
121 // allow the nick of the client here but in practise everyone just
122 // ignores it so we will copy them.
124 // Try to read the command name.
125 if (!tokens.GetMiddle(token))
127 // Discourage the user from flooding the server.
128 user->CommandFloodPenalty += 2000;
133 parseoutput.cmd.assign(token);
135 // Build the parameter map. We intentionally do not respect the RFC 1459
136 // thirteen parameter limit here.
137 while (tokens.GetTrailing(token))
138 parseoutput.params.push_back(token);
145 void CheckTagLength(std::string& line, size_t prevsize, size_t& length, size_t maxlength)
147 const std::string::size_type diffsize = line.size() - prevsize;
148 if (length + diffsize > maxlength)
149 line.erase(prevsize);
155 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
157 size_t client_tag_length = 0;
158 size_t server_tag_length = 0;
159 for (ClientProtocol::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i)
161 if (!tagwl.IsSelected(tags, i))
164 const std::string::size_type prevsize = line.size();
165 line.push_back(prevsize ? ';' : '@');
166 line.append(i->first);
167 const std::string& val = i->second.value;
174 // The tags part of the message must not contain more client and server tags than allowed by the
175 // message tags specification. This is complicated by the tag space having separate limits for
176 // both server-originated and client-originated tags. If either of the tag limits is exceeded then
177 // the most recently added tag is removed.
178 if (i->first[0] == '+')
179 CheckTagLength(line, prevsize, client_tag_length, MAX_CLIENT_MESSAGE_TAG_LENGTH);
181 CheckTagLength(line, prevsize, server_tag_length, MAX_SERVER_MESSAGE_TAG_LENGTH);
188 ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
191 SerializeTags(msg.GetTags(), tagwl, line);
193 // Save position for length calculation later
194 const std::string::size_type rfcmsg_begin = line.size();
199 line.append(*msg.GetSource());
202 line.append(msg.GetCommand());
204 const ClientProtocol::Message::ParamList& params = msg.GetParams();
207 for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
209 const std::string& param = *i;
214 line.append(" :", 2).append(params.back());
217 // Truncate if too long
218 std::string::size_type maxline = ServerInstance->Config->Limits.MaxLine - 2;
219 if (line.length() - rfcmsg_begin > maxline)
220 line.erase(rfcmsg_begin + maxline);
222 line.append("\r\n", 2);
226 class ModuleCoreRFCSerializer : public Module
228 RFCSerializer rfcserializer;
231 ModuleCoreRFCSerializer()
232 : rfcserializer(this)
236 void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
238 if (type != ExtensionItem::EXT_USER)
241 LocalUser* const user = IS_LOCAL(static_cast<User*>(item));
242 if ((user) && (user->serializer == &rfcserializer))
243 ServerInstance->Users.QuitUser(user, "Protocol serializer module unloading");
246 void OnUserInit(LocalUser* user) CXX11_OVERRIDE
248 if (!user->serializer)
249 user->serializer = &rfcserializer;
252 Version GetVersion() CXX11_OVERRIDE
254 return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
258 MODULE_INIT(ModuleCoreRFCSerializer)