]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/coremods/core_serialize_rfc.cpp
Fix a bunch of weird indentation and spacing issues.
[user/henk/code/inspircd.git] / src / coremods / core_serialize_rfc.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2018-2019 Sadie Powell <sadie@witchery.services>
5  *   Copyright (C) 2018 Attila Molnar <attilamolnar@hush.com>
6  *
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.
10  *
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
14  * details.
15  *
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/>.
18  */
19
20
21 #include "inspircd.h"
22
23 enum
24 {
25         // From ircu.
26         ERR_INPUTTOOLONG = 417
27 };
28
29 class RFCSerializer : public ClientProtocol::Serializer
30 {
31
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;
34
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;
37
38         static void SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line);
39
40  public:
41         RFCSerializer(Module* mod)
42                 : ClientProtocol::Serializer(mod, "rfc")
43         {
44         }
45
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;
48 };
49
50 bool RFCSerializer::Parse(LocalUser* user, const std::string& line, ClientProtocol::ParseOutput& parseoutput)
51 {
52         size_t start = line.find_first_not_of(" ");
53         if (start == std::string::npos)
54         {
55                 // Discourage the user from flooding the server.
56                 user->CommandFloodPenalty += 2000;
57                 return false;
58         }
59
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;
64
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());
67
68         // This will always exist because of the check at the start of the function.
69         std::string token;
70         tokens.GetMiddle(token);
71         if (token[0] == '@')
72         {
73                 // Check that the client tags fit within the client tag space.
74                 if (token.length() > MAX_CLIENT_MESSAGE_TAG_LENGTH)
75                 {
76                         user->WriteNumeric(ERR_INPUTTOOLONG, "Input line was too long");
77                         user->CommandFloodPenalty += 2000;
78                         return false;
79                 }
80
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);
85
86                 // Line begins with message tags, parse them.
87                 std::string tagval;
88                 irc::sepstream ss(token.substr(1), ';');
89                 while (ss.GetToken(token))
90                 {
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.
93                         //
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)
97                         {
98                                 // Tag has a value
99                                 tagval.assign(token, p+1, std::string::npos);
100                                 token.erase(p);
101                         }
102                         else
103                                 tagval.clear();
104
105                         HandleTag(user, token, tagval, parseoutput.tags);
106                 }
107
108                 // Try to read the prefix or command name.
109                 if (!tokens.GetMiddle(token))
110                 {
111                         // Discourage the user from flooding the server.
112                         user->CommandFloodPenalty += 2000;
113                         return false;
114                 }
115         }
116
117         if (token[0] == ':')
118         {
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.
123
124                 // Try to read the command name.
125                 if (!tokens.GetMiddle(token))
126                 {
127                         // Discourage the user from flooding the server.
128                         user->CommandFloodPenalty += 2000;
129                         return false;
130                 }
131         }
132
133         parseoutput.cmd.assign(token);
134
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);
139
140         return true;
141 }
142
143 namespace
144 {
145         void CheckTagLength(std::string& line, size_t prevsize, size_t& length, size_t maxlength)
146         {
147                 const std::string::size_type diffsize = line.size() - prevsize;
148                 if (length + diffsize > maxlength)
149                         line.erase(prevsize);
150                 else
151                         length += diffsize;
152         }
153 }
154
155 void RFCSerializer::SerializeTags(const ClientProtocol::TagMap& tags, const ClientProtocol::TagSelection& tagwl, std::string& line)
156 {
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)
160         {
161                 if (!tagwl.IsSelected(tags, i))
162                         continue;
163
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;
168                 if (!val.empty())
169                 {
170                         line.push_back('=');
171                         line.append(val);
172                 }
173
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);
180                 else
181                         CheckTagLength(line, prevsize, server_tag_length, MAX_SERVER_MESSAGE_TAG_LENGTH);
182         }
183
184         if (!line.empty())
185                 line.push_back(' ');
186 }
187
188 ClientProtocol::SerializedMessage RFCSerializer::Serialize(const ClientProtocol::Message& msg, const ClientProtocol::TagSelection& tagwl) const
189 {
190         std::string line;
191         SerializeTags(msg.GetTags(), tagwl, line);
192
193         // Save position for length calculation later
194         const std::string::size_type rfcmsg_begin = line.size();
195
196         if (msg.GetSource())
197         {
198                 line.push_back(':');
199                 line.append(*msg.GetSource());
200                 line.push_back(' ');
201         }
202         line.append(msg.GetCommand());
203
204         const ClientProtocol::Message::ParamList& params = msg.GetParams();
205         if (!params.empty())
206         {
207                 for (ClientProtocol::Message::ParamList::const_iterator i = params.begin(); i != params.end()-1; ++i)
208                 {
209                         const std::string& param = *i;
210                         line.push_back(' ');
211                         line.append(param);
212                 }
213
214                 line.append(" :", 2).append(params.back());
215         }
216
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);
221
222         line.append("\r\n", 2);
223         return line;
224 }
225
226 class ModuleCoreRFCSerializer : public Module
227 {
228         RFCSerializer rfcserializer;
229
230  public:
231         ModuleCoreRFCSerializer()
232                 : rfcserializer(this)
233         {
234         }
235
236         void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
237         {
238                 if (type != ExtensionItem::EXT_USER)
239                         return;
240
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");
244         }
245
246         void OnUserInit(LocalUser* user) CXX11_OVERRIDE
247         {
248                 if (!user->serializer)
249                         user->serializer = &rfcserializer;
250         }
251
252         Version GetVersion() CXX11_OVERRIDE
253         {
254                 return Version("RFC client protocol serializer and unserializer", VF_CORE|VF_VENDOR);
255         }
256 };
257
258 MODULE_INIT(ModuleCoreRFCSerializer)