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