2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2020 Sadie Powell <sadie@witchery.services>
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.
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
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/>.
21 #include "modules/cap.h"
22 #include "modules/ircv3_batch.h"
24 class LabeledResponseTag : public ClientProtocol::MessageTagProvider
27 const Cap::Capability& cap;
32 const std::string labeltag;
34 LabeledResponseTag(Module* mod, const Cap::Capability& capref)
35 : ClientProtocol::MessageTagProvider(mod)
42 ModResult OnProcessTag(User* user, const std::string& tagname, std::string& tagvalue) CXX11_OVERRIDE
44 if (!irc::equals(tagname, labeltag))
45 return MOD_RES_PASSTHRU;
47 // If the tag is empty or too long then we can't accept it.
48 if (tagvalue.empty() || tagvalue.size() > 64)
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))
57 // Remote users have their label tag checked by their local server.
61 bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
63 // Messages only have a label when being sent to a user that sent one.
64 return user == labeluser && tagdata.value == label;
68 class ModuleIRCv3LabeledResponse : public Module
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;
81 void FlushFirstMsg(LocalUser* user)
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);
90 ModuleIRCv3LabeledResponse()
91 : cap(this, "labeled-response")
94 , batch("labeled-response")
96 , ackmsgprov(this, "ACK")
97 , labelmsgprov(this, "labeled")
103 ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE
105 // We only care about the initial unvalidated OnPreCommand call.
106 if (validated || tag.labeluser)
107 return MOD_RES_PASSTHRU;
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;
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;
118 tag.label = labeltag->second.value;
119 tag.labeluser = user;
120 return MOD_RES_PASSTHRU;
123 void OnPostCommand(Command* command, const CommandBase::Params& parameters, LocalUser* user, CmdResult result, bool loop) CXX11_OVERRIDE
125 // Do nothing if this isn't the last OnPostCommand() run for the command.
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.
132 OnCommandBlocked(command->name, parameters, user);
135 void OnCommandBlocked(const std::string& command, const CommandBase::Params& parameters, LocalUser* user) CXX11_OVERRIDE
137 // If no label was sent we don't have to do anything.
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);
155 // There was one response which was cached; send it now.
156 firstmsg->AddTag(tag.labeltag, &tag, tag.label);
163 // There was two or more responses; send an end-of-batch.
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);
174 tag.labeluser = NULL;
178 ModResult OnUserWrite(LocalUser* user, ClientProtocol::Message& msg) CXX11_OVERRIDE
180 // The label user is writing a message to another user.
181 if (user != tag.labeluser)
182 return MOD_RES_PASSTHRU;
184 // The message is a side effect (e.g. a self-PRIVMSG).
185 if (msg.IsSideEffect())
186 return MOD_RES_PASSTHRU;
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);
200 // Second reply message. This and all subsequent messages need to go into a batch.
203 batchmanager->Start(batch);
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);
210 batch.AddToBatch(*firstmsg);
211 batch.AddToBatch(msg);
214 // Flush first message which triggers the batch start message
216 return MOD_RES_PASSTHRU;
221 // Third or later message. Put it in the batch and send directly.
223 batch.AddToBatch(msg);
224 return MOD_RES_PASSTHRU;
229 void Prioritize() CXX11_OVERRIDE
231 Module* alias = ServerInstance->Modules->Find("m_alias.so");
232 ServerInstance->Modules->SetPriority(this, I_OnPreCommand, PRIORITY_BEFORE, alias);
235 Version GetVersion() CXX11_OVERRIDE
237 return Version("Provides support for the IRCv3 Labeled Response specification.", VF_VENDOR);
241 MODULE_INIT(ModuleIRCv3LabeledResponse)