]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_ircv3_ctctags.cpp
5a88e68a02328c90e8b05f76bd63928031ee4c12
[user/henk/code/inspircd.git] / src / modules / m_ircv3_ctctags.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 Peter Powell <petpow@saberuk.com>
5  *   Copyright (C) 2016 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 #include "modules/cap.h"
23 #include "modules/ctctags.h"
24
25 class CommandTagMsg : public Command
26 {
27  private:
28         Cap::Capability& cap;
29         ChanModeReference moderatedmode;
30         ChanModeReference noextmsgmode;
31         Events::ModuleEventProvider tagevprov;
32         ClientProtocol::EventProvider msgevprov;
33
34         bool FirePreEvents(User* source, MessageTarget& msgtarget, CTCTags::TagMessageDetails& msgdetails)
35         {
36                 // Inform modules that a TAGMSG wants to be sent.
37                 ModResult modres;
38                 FIRST_MOD_RESULT_CUSTOM(tagevprov, CTCTags::EventListener, OnUserPreTagMessage, modres, (source, msgtarget, msgdetails));
39                 if (modres == MOD_RES_DENY)
40                 {
41                         // Inform modules that a module blocked the TAGMSG.
42                         FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserTagMessageBlocked, (source, msgtarget, msgdetails));
43                         return false;
44                 }
45
46                 // Inform modules that a TAGMSG is about to be sent.
47                 FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserTagMessage, (source, msgtarget, msgdetails));
48                 return true;
49         }
50
51         CmdResult FirePostEvent(User* source, const MessageTarget& msgtarget, const CTCTags::TagMessageDetails& msgdetails)
52         {
53                 // If the source is local then update its idle time.
54                 LocalUser* lsource = IS_LOCAL(source);
55                 if (lsource)
56                         lsource->idle_lastmsg = ServerInstance->Time();
57
58                 // Inform modules that a TAGMSG was sent.
59                 FOREACH_MOD_CUSTOM(tagevprov, CTCTags::EventListener, OnUserPostTagMessage, (source, msgtarget, msgdetails));
60                 return CMD_SUCCESS;
61         }
62
63         CmdResult HandleChannelTarget(User* source, const Params& parameters, const char* target, PrefixMode* pm)
64         {
65                 Channel* chan = ServerInstance->FindChan(target);
66                 if (!chan)
67                 {
68                         // The target channel does not exist.
69                         source->WriteNumeric(Numerics::NoSuchChannel(parameters[0]));
70                         return CMD_FAILURE;
71                 }
72
73                 if (IS_LOCAL(source))
74                 {
75                         if (chan->IsModeSet(noextmsgmode) && !chan->HasUser(source))
76                         {
77                                 // The noextmsg mode is set and the source is not in the channel.
78                                 source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (no external messages)");
79                                 return CMD_FAILURE;
80                         }
81
82                         bool no_chan_priv = chan->GetPrefixValue(source) < VOICE_VALUE;
83                         if (no_chan_priv && chan->IsModeSet(moderatedmode))
84                         {
85                                 // The moderated mode is set and the source has no status rank.
86                                 source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (+m)");
87                                 return CMD_FAILURE;
88                         }
89
90                         if (no_chan_priv && ServerInstance->Config->RestrictBannedUsers != ServerConfig::BUT_NORMAL && chan->IsBanned(source))
91                         {
92                                 // The source is banned in the channel and restrictbannedusers is enabled.
93                                 if (ServerInstance->Config->RestrictBannedUsers == ServerConfig::BUT_RESTRICT_NOTIFY)
94                                         source->WriteNumeric(ERR_CANNOTSENDTOCHAN, chan->name, "Cannot send to channel (you're banned)");
95                                 return CMD_FAILURE;
96                         }
97                 }
98
99                 // Fire the pre-message events.
100                 MessageTarget msgtarget(chan, pm ? pm->GetPrefix() : 0);
101                 CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
102                 if (!FirePreEvents(source, msgtarget, msgdetails))
103                         return CMD_FAILURE;
104
105                 unsigned int minrank = pm ? pm->GetPrefixRank() : 0;
106                 CTCTags::TagMessage message(source, chan, parameters.GetTags());
107                 const Channel::MemberMap& userlist = chan->GetUsers();
108                 for (Channel::MemberMap::const_iterator iter = userlist.begin(); iter != userlist.end(); ++iter)
109                 {
110                         LocalUser* luser = IS_LOCAL(iter->first);
111
112                         // Don't send to remote users or the user who is the source. 
113                         if (!luser || luser == source)
114                                 continue;
115
116                         // Don't send to unprivileged or exempt users.
117                         if (iter->second->getRank() < minrank || msgdetails.exemptions.count(luser))
118                                 continue;
119
120                         // Send to users if they have the capability.
121                         if (cap.get(luser))
122                                 luser->Send(msgevprov, message);
123                 }
124                 return FirePostEvent(source, msgtarget, msgdetails);
125         }
126
127         CmdResult HandleServerTarget(User* source, const Params& parameters)
128         {
129                 // If the source isn't allowed to mass message users then reject
130                 // the attempt to mass-message users.
131                 if (!source->HasPrivPermission("users/mass-message"))
132                         return CMD_FAILURE;
133
134                 // Extract the server glob match from the target parameter.
135                 std::string servername(parameters[0], 1);
136
137                 // Fire the pre-message events.
138                 MessageTarget msgtarget(&servername);
139                 CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
140                 if (!FirePreEvents(source, msgtarget, msgdetails))
141                         return CMD_FAILURE;
142
143                 // If the current server name matches the server name glob then send
144                 // the message out to the local users.
145                 if (InspIRCd::Match(ServerInstance->Config->ServerName, servername))
146                 {
147                         CTCTags::TagMessage message(source, "$*", parameters.GetTags());
148                         const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
149                         for (UserManager::LocalList::const_iterator iter = list.begin(); iter != list.end(); ++iter)
150                         {
151                                 LocalUser* luser = IS_LOCAL(*iter);
152
153                                 // Don't send to unregistered users or the user who is the source.
154                                 if (luser->registered != REG_ALL || luser == source)
155                                         continue;
156
157                                 // Don't send to exempt users.
158                                 if (msgdetails.exemptions.count(luser))
159                                         continue;
160
161                                 // Send to users if they have the capability.
162                                 if (cap.get(luser))
163                                         luser->Send(msgevprov, message);
164                         }
165                 }
166
167                 // Fire the post-message event.
168                 return FirePostEvent(source, msgtarget, msgdetails);
169         }
170
171         CmdResult HandleUserTarget(User* source, const Params& parameters)
172         {
173                 User* target;
174                 if (IS_LOCAL(source))
175                 {
176                         // Local sources can specify either a nick or a nick@server mask as the target.
177                         const char* targetserver = strchr(parameters[0].c_str(), '@');
178                         if (targetserver)
179                         {
180                                 // The target is a user on a specific server (e.g. jto@tolsun.oulu.fi).
181                                 target = ServerInstance->FindNickOnly(parameters[0].substr(0, targetserver - parameters[0].c_str()));
182                                 if (target && strcasecmp(target->server->GetName().c_str(), targetserver + 1))
183                                         target = NULL;
184                         }
185                         else
186                         {
187                                 // If the source is a local user then we only look up the target by nick.
188                                 target = ServerInstance->FindNickOnly(parameters[0]);
189                         }
190                 }
191                 else
192                 {
193                         // Remote users can only specify a nick or UUID as the target.
194                         target = ServerInstance->FindNick(parameters[0]);
195                 }
196
197                 if (!target || target->registered != REG_ALL)
198                 {
199                         // The target user does not exist or is not fully registered.
200                         source->WriteNumeric(Numerics::NoSuchNick(parameters[0]));
201                         return CMD_FAILURE;
202                 }
203
204                 // Fire the pre-message events.
205                 MessageTarget msgtarget(target);
206                 CTCTags::TagMessageDetails msgdetails(parameters.GetTags());
207                 if (!FirePreEvents(source, msgtarget, msgdetails))
208                         return CMD_FAILURE;
209
210                 LocalUser* const localtarget = IS_LOCAL(target);
211                 if (localtarget && cap.get(localtarget))
212                 {
213                         // Send to the target if they have the capability and are a local user.
214                         CTCTags::TagMessage message(source, localtarget, parameters.GetTags());
215                         localtarget->Send(msgevprov, message);
216                 }
217
218                 // Fire the post-message event.
219                 return FirePostEvent(source, msgtarget, msgdetails);
220         }
221
222  public:
223         CommandTagMsg(Module* Creator, Cap::Capability& Cap)
224                 : Command(Creator, "TAGMSG", 1)
225                 , cap(Cap)
226                 , moderatedmode(Creator, "moderated")
227                 , noextmsgmode(Creator, "noextmsg")
228                 , tagevprov(Creator, "event/tagmsg")
229                 , msgevprov(Creator, "TAGMSG")
230         {
231                 allow_empty_last_param = false;
232         }
233
234         CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE
235         {
236                 if (CommandParser::LoopCall(user, this, parameters, 0))
237                         return CMD_SUCCESS;
238
239                 // Check that the source has the message tags capability.
240                 if (IS_LOCAL(user) && !cap.get(user))
241                         return CMD_FAILURE;
242
243                 // The target is a server glob.
244                 if (parameters[0][0] == '$')
245                         return HandleServerTarget(user, parameters);
246
247                 // If the message begins with a status character then look it up.
248                 const char* target = parameters[0].c_str();
249                 PrefixMode* pmh = ServerInstance->Modes->FindPrefix(target[0]);
250                 if (pmh)
251                         target++;
252
253                 // The target is a channel name.
254                 if (*target == '#')
255                         return HandleChannelTarget(user, parameters, target, pmh);
256
257                 // The target is a nickname.
258                 return HandleUserTarget(user, parameters);
259         }
260
261         RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
262         {
263                 return ROUTE_MESSAGE(parameters[0]);
264         }
265 };
266
267 class C2CTags : public ClientProtocol::MessageTagProvider
268 {
269  private:
270         Cap::Capability& cap;
271
272  public:
273         C2CTags(Module* Creator, Cap::Capability& Cap)
274                 : ClientProtocol::MessageTagProvider(Creator)
275                 , cap(Cap)
276         {
277         }
278
279         ModResult OnProcessTag(User* user, const std::string& tagname, std::string& tagvalue) CXX11_OVERRIDE
280         {
281                 // A client-only tag is prefixed with a plus sign (+) and otherwise conforms
282                 // to the format specified in IRCv3.2 tags.
283                 if (tagname[0] != '+' || tagname.length() < 2)
284                         return MOD_RES_PASSTHRU;
285
286                 // If the user is local then we check whether they have the message-tags cap
287                 // enabled. If not then we reject all client-only tags originating from them.
288                 LocalUser* lu = IS_LOCAL(user);
289                 if (lu && !cap.get(lu))
290                         return MOD_RES_DENY;
291
292                 // Remote users have their client-only tags checked by their local server.
293                 return MOD_RES_ALLOW;
294         }
295
296         bool ShouldSendTag(LocalUser* user, const ClientProtocol::MessageTagData& tagdata) CXX11_OVERRIDE
297         {
298                 return cap.get(user);
299         }
300 };
301
302 class ModuleIRCv3CTCTags
303         : public Module
304         , public CTCTags::EventListener
305 {
306  private:
307         Cap::Capability cap;
308         CommandTagMsg cmd;
309         C2CTags c2ctags;
310
311         ModResult CopyClientTags(const ClientProtocol::TagMap& tags_in, ClientProtocol::TagMap& tags_out)
312         {
313                 for (ClientProtocol::TagMap::const_iterator i = tags_in.begin(); i != tags_in.end(); ++i)
314                 {
315                         const ClientProtocol::MessageTagData& tagdata = i->second;
316                         if (tagdata.tagprov == &c2ctags)
317                                 tags_out.insert(*i);
318                 }
319                 return MOD_RES_PASSTHRU;
320         }
321
322  public:
323         ModuleIRCv3CTCTags()
324                 : CTCTags::EventListener(this)
325                 , cap(this, "message-tags")
326                 , cmd(this, cap)
327                 , c2ctags(this, cap)
328         {
329         }
330
331         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
332         {
333                 return CopyClientTags(details.tags_in, details.tags_out);
334         }
335
336         ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
337         {
338                 return CopyClientTags(details.tags_in, details.tags_out);
339         }
340
341         Version GetVersion() CXX11_OVERRIDE
342         {
343                 return Version("Provides the DRAFT message-tags IRCv3 extension", VF_VENDOR | VF_COMMON);
344         }
345 };
346
347 MODULE_INIT(ModuleIRCv3CTCTags)