]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_silence.cpp
Update my name and email address.
[user/henk/code/inspircd.git] / src / modules / m_silence.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 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/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 SilenceExtItem : public SimpleExtItem<SilenceList>
184 {
185  public:
186         unsigned int maxsilence;
187
188         SilenceExtItem(Module* Creator)
189                 : SimpleExtItem<SilenceList>("silence_list", ExtensionItem::EXT_USER, Creator)
190         {
191         }
192
193         void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE
194         {
195                 LocalUser* user = IS_LOCAL(static_cast<User*>(container));
196                 if (!user)
197                         return;
198
199                 // Remove the old list and create a new one.
200                 unset(user);
201                 SilenceList* list = new SilenceList();
202
203                 irc::spacesepstream ts(value);
204                 while (!ts.StreamEnd())
205                 {
206                         // Check we have space for another entry.
207                         if (list->size() >= maxsilence)
208                         {
209                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Oversized silence list received for %s: %s",
210                                         user->uuid.c_str(), value.c_str());
211                                 delete list;
212                                 return;
213                         }
214
215                         // Extract the mask and the flags.
216                         std::string mask;
217                         std::string flagstr;
218                         if (!ts.GetToken(mask) || !ts.GetToken(flagstr))
219                         {
220                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed silence list received for %s: %s",
221                                         user->uuid.c_str(), value.c_str());
222                                 delete list;
223                                 return;
224                         }
225
226                         // Try to parse the flags.
227                         uint32_t flags;
228                         if (!SilenceEntry::FlagsToBits(flagstr, flags))
229                         {
230                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed silence flags received for %s: %s",
231                                         user->uuid.c_str(), flagstr.c_str());
232                                 delete list;
233                                 return;
234                         }
235
236                         // Store the silence entry.
237                         list->insert(SilenceEntry(flags, mask));
238                 }
239
240                 // The value was well formed.
241                 set(user, list);
242         }
243
244         std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
245         {
246                 SilenceList* list = static_cast<SilenceList*>(item);
247                 std::string buf;
248                 for (SilenceList::const_iterator iter = list->begin(); iter != list->end(); ++iter)
249                 {
250                         if (iter != list->begin())
251                                 buf.push_back(' ');
252
253                         buf.append(iter->mask);
254                         buf.push_back(' ');
255                         buf.append(SilenceEntry::BitsToFlags(iter->flags));
256                 }
257                 return buf;
258         }
259 };
260
261 class SilenceMessage : public ClientProtocol::Message
262 {
263  public:
264         SilenceMessage(const std::string& mask, const std::string& flags)
265                 : ClientProtocol::Message("SILENCE")
266         {
267                 PushParam(mask);
268                 PushParam(flags);
269         }
270 };
271
272 class CommandSilence : public SplitCommand
273 {
274  private:
275         ClientProtocol::EventProvider msgprov;
276
277         CmdResult AddSilence(LocalUser* user, const std::string& mask, uint32_t flags)
278         {
279                 SilenceList* list = ext.get(user);
280                 if (list && list->size() > ext.maxsilence)
281                 {
282                         user->WriteNumeric(ERR_SILELISTFULL, mask, SilenceEntry::BitsToFlags(flags), "Your SILENCE list is full");
283                         return CMD_FAILURE;
284                 }
285                 else if (!list)
286                 {
287                         // There is no list; create it.
288                         list = new SilenceList();
289                         ext.set(user, list);
290                 }
291
292                 if (!list->insert(SilenceEntry(flags, mask)).second)
293                 {
294                         user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The SILENCE entry you specified already exists");
295                         return CMD_FAILURE;
296                 }
297
298                 SilenceMessage msg("+" + mask, SilenceEntry::BitsToFlags(flags));
299                 user->Send(msgprov, msg);
300                 return CMD_SUCCESS;
301         }
302
303         CmdResult RemoveSilence(LocalUser* user, const std::string& mask, uint32_t flags)
304         {
305                 SilenceList* list = ext.get(user);
306                 if (list)
307                 {
308                         for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
309                         {
310                                 if (!irc::equals(iter->mask, mask) || iter->flags != flags)
311                                         continue;
312
313                                 list->erase(iter);
314                                 SilenceMessage msg("-" + mask, SilenceEntry::BitsToFlags(flags));
315                                 user->Send(msgprov, msg);
316                                 return CMD_SUCCESS;
317                         }
318                 }
319
320                 user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The SILENCE entry you specified could not be found");
321                 return CMD_FAILURE;
322         }
323
324         CmdResult ShowSilenceList(LocalUser* user)
325         {
326                 SilenceList* list = ext.get(user);
327                 if (list)
328                 {
329                         for (SilenceList::const_iterator iter = list->begin(); iter != list->end(); ++iter)
330                         {
331                                 user->WriteNumeric(RPL_SILELIST, iter->mask, SilenceEntry::BitsToFlags(iter->flags));
332                         }
333                 }
334                 user->WriteNumeric(RPL_ENDOFSILELIST, "End of SILENCE list");
335                 return CMD_SUCCESS;
336         }
337
338  public:
339         SilenceExtItem ext;
340
341         CommandSilence(Module* Creator)
342                 : SplitCommand(Creator, "SILENCE")
343                 , msgprov(Creator, "SILENCE")
344                 , ext(Creator)
345         {
346                 allow_empty_last_param = false;
347                 syntax = "[(+|-)<mask> [CcdiNnPpTtx]]";
348         }
349
350         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
351         {
352                 if (parameters.empty())
353                         return ShowSilenceList(user);
354
355                 // If neither add nor remove are specified we default to add.
356                 bool is_remove = parameters[0][0] == '-';
357
358                 // If a prefix mask has been given then strip it and clean it up.
359                 std::string mask = parameters[0];
360                 if (mask[0] == '-' || mask[0] == '+')
361                 {
362                         mask.erase(0, 1);
363                         if (mask.empty())
364                                 mask.assign("*");
365                         ModeParser::CleanMask(mask);
366                 }
367
368                 // If the user specified a flags then use that. Otherwise, default to blocking
369                 // all CTCPs, invites, notices, privmsgs, and invites.
370                 uint32_t flags = SilenceEntry::SF_DEFAULT;
371                 if (parameters.size() > 1)
372                 {
373                         if (!SilenceEntry::FlagsToBits(parameters[1], flags))
374                         {
375                                 user->WriteNumeric(ERR_SILENCE, mask, parameters[1], "You specified one or more invalid SILENCE flags");
376                                 return CMD_FAILURE;
377                         }
378                         else if (flags == SilenceEntry::SF_EXEMPT)
379                         {
380                                 // The user specified "x" with no other flags which does not make sense; add the "d" flag.
381                                 flags |= SilenceEntry::SF_DEFAULT;
382                         }
383                 }
384
385                 return is_remove ? RemoveSilence(user, mask, flags) : AddSilence(user, mask, flags);
386         }
387 };
388
389 class ModuleSilence
390         : public Module
391         , public CTCTags::EventListener
392 {
393  private:
394         bool exemptuline;
395         CommandSilence cmd;
396
397         ModResult BuildChannelExempts(User* source, Channel* channel, SilenceEntry::SilenceFlags flag, CUList& exemptions)
398         {
399                 const Channel::MemberMap& members = channel->GetUsers();
400                 for (Channel::MemberMap::const_iterator member = members.begin(); member != members.end(); ++member)
401                 {
402                         if (!CanReceiveMessage(source, member->first, flag))
403                                 exemptions.insert(member->first);
404                 }
405                 return MOD_RES_PASSTHRU;
406         }
407
408         bool CanReceiveMessage(User* source, User* target, SilenceEntry::SilenceFlags flag)
409         {
410                 // Servers handle their own clients.
411                 if (!IS_LOCAL(target))
412                         return true;
413
414                 if (exemptuline && source->server->IsULine())
415                         return true;
416
417                 SilenceList* list = cmd.ext.get(target);
418                 if (!list)
419                         return true;
420
421                 for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
422                 {
423                         if (!(iter->flags & flag))
424                                 continue;
425
426                         if (InspIRCd::Match(source->GetFullHost(), iter->mask))
427                                 return iter->flags & SilenceEntry::SF_EXEMPT;
428                 }
429
430                 return true;
431         }
432
433  public:
434         ModuleSilence()
435                 : CTCTags::EventListener(this)
436                 , cmd(this)
437         {
438         }
439
440         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
441         {
442                 ConfigTag* tag = ServerInstance->Config->ConfValue("silence");
443                 exemptuline = tag->getBool("exemptuline", true);
444                 cmd.ext.maxsilence = tag->getUInt("maxentries", 32, 1);
445         }
446
447         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
448         {
449                 tokens["ESILENCE"] = "CcdiNnPpTtx";
450                 tokens["SILENCE"] = ConvToStr(cmd.ext.maxsilence);
451         }
452
453         ModResult OnUserPreInvite(User* source, User* dest, Channel* channel, time_t timeout) CXX11_OVERRIDE
454         {
455                 return CanReceiveMessage(source, dest, SilenceEntry::SF_INVITE) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
456         }
457
458         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
459         {
460                 std::string ctcpname;
461                 bool is_ctcp = details.IsCTCP(ctcpname) && !irc::equals(ctcpname, "ACTION");
462
463                 SilenceEntry::SilenceFlags flag = SilenceEntry::SF_NONE;
464                 switch (target.type)
465                 {
466                         case MessageTarget::TYPE_CHANNEL:
467                         {
468                                 if (is_ctcp)
469                                         flag = SilenceEntry::SF_CTCP_CHANNEL;
470                                 else if (details.type == MSG_NOTICE)
471                                         flag = SilenceEntry::SF_NOTICE_CHANNEL;
472                                 else if (details.type == MSG_PRIVMSG)
473                                         flag = SilenceEntry::SF_PRIVMSG_CHANNEL;
474
475                                 return BuildChannelExempts(user, target.Get<Channel>(), flag, details.exemptions);
476                                 break;
477                         }
478                         case MessageTarget::TYPE_USER:
479                         {
480                                 if (is_ctcp)
481                                         flag = SilenceEntry::SF_CTCP_USER;
482                                 else if (details.type == MSG_NOTICE)
483                                         flag = SilenceEntry::SF_NOTICE_USER;
484                                 else if (details.type == MSG_PRIVMSG)
485                                         flag = SilenceEntry::SF_PRIVMSG_USER;
486
487                                 if (!CanReceiveMessage(user, target.Get<User>(), flag))
488                                 {
489                                         details.echo_original = true;
490                                         return MOD_RES_DENY;
491                                 }
492                                 break;
493                         }
494                         case MessageTarget::TYPE_SERVER:
495                                 break;
496                 }
497
498                 return MOD_RES_PASSTHRU;
499         }
500
501         ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
502         {
503                 if (target.type == MessageTarget::TYPE_CHANNEL)
504                         return BuildChannelExempts(user, target.Get<Channel>(), SilenceEntry::SF_TAGMSG_CHANNEL, details.exemptions);
505
506                 if (target.type == MessageTarget::TYPE_USER && !CanReceiveMessage(user, target.Get<User>(), SilenceEntry::SF_TAGMSG_USER))
507                 {
508                         details.echo_original = true;
509                         return MOD_RES_DENY;
510                 }
511
512                 return MOD_RES_PASSTHRU;
513         }
514
515         Version GetVersion() CXX11_OVERRIDE
516         {
517                 return Version("Provides support for blocking users with the SILENCE command", VF_OPTCOMMON | VF_VENDOR);
518         }
519 };
520
521 MODULE_INIT(ModuleSilence)