]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_silence.cpp
3b32097eabb7da9419977609f42011e3bac30409
[user/henk/code/inspircd.git] / src / modules / m_silence.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 Peter Powell <petpow@saberuk.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 #include "modules/ctctags.h"
22
23 enum
24 {
25         // From ircu?
26         RPL_SILELIST = 271,
27         RPL_ENDOFSILELIST = 272,
28         ERR_SILELISTFULL = 511,
29
30         // InspIRCd-specific.
31         ERR_SILENCE = 952
32 };
33
34 class SilenceEntry
35 {
36  public:
37         enum SilenceFlags
38         {
39                 // Does nothing; for internal use only.
40                 SF_NONE = 0,
41
42                 // Exclude users who match this flags ("x").
43                 SF_EXEMPT = 1,
44
45                 // 2, 4, 8, 16 are reserved for future use.
46
47                 // Matches a NOTICE targeted at a channel ("n").
48                 SF_NOTICE_CHANNEL = 32,
49
50                 // Matches a NOTICE targeted at a user ("N").
51                 SF_NOTICE_USER = 64,
52
53                 // Matches a PRIVMSG targeted at a channel ("p").
54                 SF_PRIVMSG_CHANNEL = 128,
55
56                 // Matches a PRIVMSG targeted at a user ("P").
57                 SF_PRIVMSG_USER = 256,
58
59                 // Matches a TAGMSG targeted at a channel ("t").
60                 SF_TAGMSG_CHANNEL = 512,
61
62                 // Matches a TAGMSG targeted at a user ("T").
63                 SF_TAGMSG_USER = 1024,
64
65                 // Matches a CTCP targeted at a channel ("c").
66                 SF_CTCP_CHANNEL = 2048,
67
68                 // Matches a CTCP targeted at a user ("C").
69                 SF_CTCP_USER = 4096,
70
71                 // Matches an invite to a channel ("i").
72                 SF_INVITE = 8192,
73
74                 // The default if no flags have been specified.
75                 SF_DEFAULT = SF_NOTICE_CHANNEL | SF_NOTICE_USER | SF_PRIVMSG_CHANNEL | SF_PRIVMSG_USER | SF_TAGMSG_CHANNEL |
76                         SF_TAGMSG_USER | SF_CTCP_CHANNEL | SF_CTCP_USER | SF_INVITE
77         };
78
79         // The flags that this mask is silenced for.
80         uint32_t flags;
81
82         // The mask which is silenced (e.g. *!*@example.com).
83         std::string mask;
84
85         SilenceEntry(uint32_t Flags, const std::string& Mask)
86                 : flags(Flags)
87                 , mask(Mask)
88         {
89         }
90
91         bool operator <(const SilenceEntry& other) const
92         {
93                 if (flags & SF_EXEMPT && other.flags & ~SF_EXEMPT)
94                         return true;
95                 if (other.flags & SF_EXEMPT && flags & ~SF_EXEMPT)
96                         return false;
97                 if (flags < other.flags)
98                         return true;
99                 if (other.flags < flags)
100                         return false;
101                 return mask < other.mask;
102         }
103
104         // Converts a flag list to a bitmask.
105         static bool FlagsToBits(const std::string& flags, uint32_t& out)
106         {
107                 out = SF_NONE;
108                 for (std::string::const_iterator flag = flags.begin(); flag != flags.end(); ++flag)
109                 {
110                         switch (*flag)
111                         {
112                                 case 'C':
113                                         out |= SF_CTCP_USER;
114                                         break;
115                                 case 'c':
116                                         out |= SF_CTCP_CHANNEL;
117                                         break;
118                                 case 'd':
119                                         out |= SF_DEFAULT;
120                                         break;
121                                 case 'i':
122                                         out |= SF_INVITE;
123                                         break;
124                                 case 'N':
125                                         out |= SF_NOTICE_USER;
126                                         break;
127                                 case 'n':
128                                         out |= SF_NOTICE_CHANNEL;
129                                         break;
130                                 case 'P':
131                                         out |= SF_PRIVMSG_USER;
132                                         break;
133                                 case 'p':
134                                         out |= SF_PRIVMSG_CHANNEL;
135                                         break;
136                                 case 'T':
137                                         out |= SF_TAGMSG_USER;
138                                         break;
139                                 case 't':
140                                         out |= SF_TAGMSG_CHANNEL;
141                                         break;
142                                 case 'x':
143                                         out |= SF_EXEMPT;
144                                         break;
145                                 default:
146                                         out = SF_NONE;
147                                         return false;
148                         }
149                 }
150                 return true;
151         }
152
153         // Converts a bitmask to a flag list.
154         static std::string BitsToFlags(uint32_t flags)
155         {
156                 std::string out;
157                 if (flags & SF_CTCP_USER)
158                         out.push_back('C');
159                 if (flags & SF_CTCP_CHANNEL)
160                         out.push_back('c');
161                 if (flags & SF_INVITE)
162                         out.push_back('i');
163                 if (flags & SF_NOTICE_USER)
164                         out.push_back('N');
165                 if (flags & SF_NOTICE_CHANNEL)
166                         out.push_back('n');
167                 if (flags & SF_PRIVMSG_USER)
168                         out.push_back('P');
169                 if (flags & SF_PRIVMSG_CHANNEL)
170                         out.push_back('p');
171                 if (flags & SF_TAGMSG_CHANNEL)
172                         out.push_back('T');
173                 if (flags & SF_TAGMSG_USER)
174                         out.push_back('t');
175                 if (flags & SF_EXEMPT)
176                         out.push_back('x');
177                 return out;
178         }
179 };
180
181 typedef insp::flat_set<SilenceEntry> SilenceList;
182
183 class SilenceMessage : public ClientProtocol::Message
184 {
185  public:
186         SilenceMessage(const std::string& mask, const std::string& flags)
187                 : ClientProtocol::Message("SILENCE")
188         {
189                 PushParam(mask);
190                 PushParamRef(flags);
191         }
192 };
193
194 class CommandSilence : public SplitCommand
195 {
196  private:
197         ClientProtocol::EventProvider msgprov;
198
199         CmdResult AddSilence(LocalUser* user, const std::string& mask, uint32_t flags)
200         {
201                 SilenceList* list = ext.get(user);
202                 if (list && list->size() > maxsilence)
203                 {
204                         user->WriteNumeric(ERR_SILELISTFULL, mask, SilenceEntry::BitsToFlags(flags), "Your silence list is full");
205                         return CMD_FAILURE;
206                 }
207                 else if (!list)
208                 {
209                         // There is no list; create it.
210                         list = new SilenceList();
211                         ext.set(user, list);
212                 }
213
214                 if (!list->insert(SilenceEntry(flags, mask)).second)
215                 {
216                         user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The silence entry you specified already exists");
217                         return CMD_FAILURE;
218                 }
219
220                 SilenceMessage msg("+" + mask, SilenceEntry::BitsToFlags(flags));
221                 user->Send(msgprov, msg);
222                 return CMD_SUCCESS;
223         }
224
225         CmdResult RemoveSilence(LocalUser* user, const std::string& mask, uint32_t flags)
226         {
227                 SilenceList* list = ext.get(user);
228                 if (list)
229                 {
230                         for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
231                         {
232                                 if (!irc::equals(iter->mask, mask) || iter->flags != flags)
233                                         continue;
234
235                                 list->erase(iter);
236                                 SilenceMessage msg("-" + mask, SilenceEntry::BitsToFlags(flags));
237                                 user->Send(msgprov, msg);
238                                 return CMD_SUCCESS;
239                         }
240                 }
241
242                 user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The silence entry you specified could not be found");
243                 return CMD_FAILURE;
244         }
245
246         CmdResult ShowSilenceList(LocalUser* user)
247         {
248                 SilenceList* list = ext.get(user);
249                 if (list)
250                 {
251                         for (SilenceList::const_iterator iter = list->begin(); iter != list->end(); ++iter)
252                         {
253                                 user->WriteNumeric(RPL_SILELIST, iter->mask, SilenceEntry::BitsToFlags(iter->flags));
254                         }
255                 }
256                 user->WriteNumeric(RPL_ENDOFSILELIST, "End of silence list");
257                 return CMD_SUCCESS;
258         }
259
260  public:
261         SimpleExtItem<SilenceList> ext;
262         unsigned int maxsilence;
263
264         CommandSilence(Module* Creator)
265                 : SplitCommand(Creator, "SILENCE")
266                 , msgprov(Creator, "SILENCE")
267                 , ext("silence_list", ExtensionItem::EXT_USER, Creator)
268         {
269                 allow_empty_last_param = false;
270                 syntax = "[(+|-)<mask> [CcdiNnPpTtx]]";
271         }
272
273         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
274         {
275                 if (parameters.empty())
276                         return ShowSilenceList(user);
277
278                 // If neither add nor remove are specified we default to add.
279                 bool is_remove = parameters[0][0] == '-';
280
281                 // If a prefix mask has been given then strip it and clean it up.
282                 std::string mask = parameters[0];
283                 if (mask[0] == '-' || mask[0] == '+')
284                 {
285                         mask.erase(0);
286                         if (mask.empty())
287                                 mask.assign("*");
288                         ModeParser::CleanMask(mask);
289                 }
290
291                 // If the user specified a flags then use that. Otherwise, default to blocking
292                 // all CTCPs, invites, notices, privmsgs, and invites.
293                 uint32_t flags = SilenceEntry::SF_DEFAULT;
294                 if (parameters.size() > 1)
295                 {
296                         if (!SilenceEntry::FlagsToBits(parameters[1], flags))
297                         {
298                                 user->WriteNumeric(ERR_SILENCE, mask, parameters[1], "You specified one or more invalid SILENCE flags");
299                                 return CMD_FAILURE;
300                         }
301                         else if (flags == SilenceEntry::SF_EXEMPT)
302                         {
303                                 // The user specified "x" with no other flags which does not make sense; add the "d" flag.
304                                 flags |= SilenceEntry::SF_DEFAULT;
305                         }
306                 }
307
308                 return is_remove ? RemoveSilence(user, mask, flags) : AddSilence(user, mask, flags);
309         }
310 };
311
312 class ModuleSilence
313         : public Module
314         , public CTCTags::EventListener
315 {
316  private:
317         bool exemptuline;
318         CommandSilence cmd;
319
320         ModResult BuildChannelExempts(User* source, Channel* channel, SilenceEntry::SilenceFlags flag, CUList& exemptions)
321         {
322                 const Channel::MemberMap& members = channel->GetUsers();
323                 for (Channel::MemberMap::const_iterator member = members.begin(); member != members.end(); ++member)
324                 {
325                         if (!CanReceiveMessage(source, member->first, flag))
326                                 exemptions.insert(member->first);
327                 }
328                 return MOD_RES_PASSTHRU;
329         }
330
331         bool CanReceiveMessage(User* source, User* target, SilenceEntry::SilenceFlags flag)
332         {
333                 // Servers handle their own clients.
334                 if (!IS_LOCAL(target))
335                         return true;
336
337                 if (exemptuline && source->server->IsULine())
338                         return true;
339
340                 SilenceList* list = cmd.ext.get(target);
341                 if (!list)
342                         return true;
343
344                 for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
345                 {
346                         if (!(iter->flags & flag))
347                                 continue;
348
349                         if (InspIRCd::Match(source->GetFullHost(), iter->mask))
350                                 return iter->flags & SilenceEntry::SF_EXEMPT;
351                 }
352
353                 return true;
354         }
355
356  public:
357         ModuleSilence()
358                 : CTCTags::EventListener(this)
359                 , cmd(this)
360         {
361         }
362
363         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
364         {
365                 ConfigTag* tag = ServerInstance->Config->ConfValue("silence");
366                 exemptuline = tag->getBool("exemptuline", true);
367                 cmd.maxsilence = tag->getUInt("maxentries", 32, 1);
368         }
369
370         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
371         {
372                 tokens["ESILENCE"] = "CcdiNnPpTtx";
373                 tokens["SILENCE"] = ConvToStr(cmd.maxsilence);
374         }
375
376         ModResult OnUserPreInvite(User* source, User* dest, Channel* channel, time_t timeout) CXX11_OVERRIDE
377         {
378                 return CanReceiveMessage(source, dest, SilenceEntry::SF_INVITE) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
379         }
380
381         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
382         {
383                 std::string ctcpname;
384                 bool is_ctcp = details.IsCTCP(ctcpname) && !irc::equals(ctcpname, "ACTION");
385
386                 SilenceEntry::SilenceFlags flag = SilenceEntry::SF_NONE;
387                 if (target.type == MessageTarget::TYPE_CHANNEL)
388                 {
389                         if (is_ctcp)
390                                 flag = SilenceEntry::SF_CTCP_CHANNEL;
391                         else if (details.type == MSG_NOTICE)
392                                 flag = SilenceEntry::SF_NOTICE_CHANNEL;
393                         else if (details.type == MSG_PRIVMSG)
394                                 flag = SilenceEntry::SF_PRIVMSG_CHANNEL;
395
396                         return BuildChannelExempts(user, target.Get<Channel>(), flag, details.exemptions);
397                 }
398
399                 if (target.type == MessageTarget::TYPE_USER)
400                 {
401                         if (is_ctcp)
402                                 flag = SilenceEntry::SF_CTCP_USER;
403                         else if (details.type == MSG_NOTICE)
404                                 flag = SilenceEntry::SF_NOTICE_USER;
405                         else if (details.type == MSG_PRIVMSG)
406                                 flag = SilenceEntry::SF_PRIVMSG_USER;
407
408                         if (!CanReceiveMessage(user, target.Get<User>(), flag))
409                         {
410                                 details.echo_original = true;
411                                 return MOD_RES_DENY;
412                         }
413                 }
414
415                 return MOD_RES_PASSTHRU;
416         }
417
418         ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
419         {
420                 if (target.type == MessageTarget::TYPE_CHANNEL)
421                         return BuildChannelExempts(user, target.Get<Channel>(), SilenceEntry::SF_TAGMSG_CHANNEL, details.exemptions);
422
423                 if (target.type == MessageTarget::TYPE_USER && !CanReceiveMessage(user, target.Get<User>(), SilenceEntry::SF_TAGMSG_USER))
424                 {
425                         details.echo_original = true;
426                         return MOD_RES_DENY;
427                 }
428
429                 return MOD_RES_PASSTHRU;
430         }
431
432         Version GetVersion() CXX11_OVERRIDE
433         {
434                 return Version("Provides support for blocking users with the /SILENCE command", VF_OPTCOMMON | VF_VENDOR);
435         }
436 };
437
438 MODULE_INIT(ModuleSilence)