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