]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ircv3_labeledresponse.cpp
Fix the cloaking module on C++98 compilers.
[user/henk/code/inspircd.git] / src / modules / m_ircv3_labeledresponse.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2020 Sadie Powell <sadie@witchery.services>
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 #include "modules/cap.h"
22 #include "modules/ircv3_batch.h"
23
24 class LabeledResponseTag : public ClientProtocol::MessageTagProvider
25 {
26  private:
27         const Cap::Capability& cap;
28
29  public:
30         LocalUser* labeluser;
31         std::string label;
32         const std::string labeltag;
33
34         LabeledResponseTag(Module* mod, const Cap::Capability& capref)
35                 : ClientProtocol::MessageTagProvider(mod)
36                 , cap(capref)
37                 , labeluser(NULL)
38                 , labeltag("label")
39         {
40         }
41
42         ModResult OnProcessTag(User* user, const std::string& tagname, std::string& tagvalue) CXX11_OVERRIDE
43         {
44                 if (!irc::equals(tagname, labeltag))
45                         return MOD_RES_PASSTHRU;
46
47                 // If the tag is empty or too long then we can't accept it.
48                 if (tagvalue.empty() || tagvalue.size() > 64)
49                         return MOD_RES_DENY;
50
51                 // If the user is local then we check whether they have the labeled-response
52                 // cap enabled. If not then we reject the label tag originating from them.
53                 LocalUser* lu = IS_LOCAL(user);
54                 if (lu && !cap.get(lu))
55                         return MOD_RES_DENY;
56
57                 // Remote users have their label tag checked by their local server.
58                 return MOD_RES_ALLOW;
59         }
60
61         bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
62         {
63                 // Messages only have a label when being sent to a user that sent one.
64                 return user == labeluser && tagdata.value == label;
65         }
66 };
67
68 class ModuleIRCv3LabeledResponse : public Module
69 {
70  private:
71         Cap::Capability cap;
72         LabeledResponseTag tag;
73         IRCv3::Batch::API batchmanager;
74         IRCv3::Batch::Batch batch;
75         IRCv3::Batch::CapReference batchcap;
76         ClientProtocol::EventProvider ackmsgprov;
77         ClientProtocol::EventProvider labelmsgprov;
78         insp::aligned_storage<ClientProtocol::Message> firstmsg;
79         size_t msgcount;
80
81         void FlushFirstMsg(LocalUser* user)
82         {
83                 // This isn't a side effect but we treat it like one to avoid the logic in OnUserWrite.
84                 firstmsg->SetSideEffect(true);
85                 user->Send(labelmsgprov, *firstmsg);
86                 firstmsg->~Message();
87         }
88
89  public:
90         ModuleIRCv3LabeledResponse()
91                 : cap(this, "labeled-response")
92                 , tag(this, cap)
93                 , batchmanager(this)
94                 , batch("labeled-response")
95                 , batchcap(this)
96                 , ackmsgprov(this, "ACK")
97                 , labelmsgprov(this, "labeled")
98                 , msgcount(0)
99
100         {
101         }
102
103         ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
104         {
105                 // We only care about the initial unvalidated OnPreCommand call.
106                 if (validated || tag.labeluser)
107                         return MOD_RES_PASSTHRU;
108
109                 // We only care about registered users with the labeled-response and batch caps.
110                 if (user->registered != REG_ALL || !cap.get(user) || !batchcap.get(user))
111                         return MOD_RES_PASSTHRU;
112
113                 const ClientProtocol::TagMap& tagmap = parameters.GetTags();
114                 const ClientProtocol::TagMap::const_iterator labeltag = tagmap.find(tag.labeltag);
115                 if (labeltag == tagmap.end())
116                         return MOD_RES_PASSTHRU;
117
118                 tag.label = labeltag->second.value;
119                 tag.labeluser = user;
120                 return MOD_RES_PASSTHRU;
121         }
122
123         void OnPostCommand(Command* command, const CommandBase::Params& parameters, LocalUser* user, CmdResult result, bool loop) CXX11_OVERRIDE
124         {
125                 // Do nothing if this isn't the last OnPostCommand() run for the command.
126                 //
127                 // If a parameter for the command was originally a list and the command handler chose to be executed
128                 // for each element on the list with synthesized parameters (CommandHandler::LoopCall) then this hook
129                 // too will run for each element on the list plus once after the whole list has been processed.
130                 // loop will only be false for the last run.
131                 if (!loop)
132                         OnCommandBlocked(command->name, parameters, user);
133         }
134
135         void OnCommandBlocked(const std::string& command, const CommandBase::Params& parameters, LocalUser* user) CXX11_OVERRIDE
136         {
137                 // If no label was sent we don't have to do anything.
138                 if (!tag.labeluser)
139                         return;
140
141                 switch (msgcount)
142                 {
143                         case 0:
144                         {
145                                 // There was no response so we send an ACK instead.
146                                 ClientProtocol::Message ackmsg("ACK", ServerInstance->FakeClient);
147                                 ackmsg.AddTag(tag.labeltag, &tag, tag.label);
148                                 ackmsg.SetSideEffect(true);
149                                 tag.labeluser->Send(ackmsgprov, ackmsg);
150                                 break;
151                         }
152
153                         case 1:
154                         {
155                                 // There was one response which was cached; send it now.
156                                 firstmsg->AddTag(tag.labeltag, &tag, tag.label);
157                                 FlushFirstMsg(user);
158                                 break;
159                         }
160
161                         default:
162                         {
163                                 // There was two or more responses; send an end-of-batch.
164                                 if (batchmanager)
165                                 {
166                                         // Set end start as side effect so we'll ignore it otherwise it'd end up added into the batch.
167                                         batch.GetBatchEndMessage().SetSideEffect(true);
168                                         batchmanager->End(batch);
169                                 }
170                                 break;
171                         }
172                 }
173
174                 tag.labeluser = NULL;
175                 msgcount = 0;
176         }
177
178         ModResult OnUserWrite(LocalUser* user, ClientProtocol::Message& msg) CXX11_OVERRIDE
179         {
180                 // The label user is writing a message to another user.
181                 if (user != tag.labeluser)
182                         return MOD_RES_PASSTHRU;
183
184                 // The message is a side effect (e.g. a self-PRIVMSG).
185                 if (msg.IsSideEffect())
186                         return MOD_RES_PASSTHRU;
187
188                 switch (++msgcount)
189                 {
190                         case 1:
191                         {
192                                 // First reply message. We can' send it yet because we don't know if there will be more.
193                                 new(firstmsg) ClientProtocol::Message(msg);
194                                 firstmsg->CopyAll();
195                                 return MOD_RES_DENY;
196                         }
197
198                         case 2:
199                         {
200                                 // Second reply message. This and all subsequent messages need to go into a batch.
201                                 if (batchmanager)
202                                 {
203                                         batchmanager->Start(batch);
204
205                                         // Set batch start as side effect so we'll ignore it otherwise it'd end up added into the batch.
206                                         ClientProtocol::Message& batchstartmsg = batch.GetBatchStartMessage();
207                                         batchstartmsg.SetSideEffect(true);
208                                         batchstartmsg.AddTag(tag.labeltag, &tag, tag.label);
209
210                                         batch.AddToBatch(*firstmsg);
211                                         batch.AddToBatch(msg);
212                                 }
213
214                                 // Flush first message which triggers the batch start message
215                                 FlushFirstMsg(user);
216                                 return MOD_RES_PASSTHRU;
217                         }
218
219                         default:
220                         {
221                                 // Third or later message. Put it in the batch and send directly.
222                                 if (batchmanager)
223                                         batch.AddToBatch(msg);
224                                 return MOD_RES_PASSTHRU;
225                         }
226                 }
227         }
228
229         void Prioritize() CXX11_OVERRIDE
230         {
231                 Module* alias = ServerInstance->Modules->Find("m_alias.so");
232                 ServerInstance->Modules->SetPriority(this, I_OnPreCommand, PRIORITY_BEFORE, alias);
233         }
234
235         Version GetVersion() CXX11_OVERRIDE
236         {
237                 return Version("Provides support for the IRCv3 Labeled Response specification.", VF_VENDOR);
238         }
239 };
240
241 MODULE_INIT(ModuleIRCv3LabeledResponse)