]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_silence.cpp
Sync helpop chmodes s and p with docs
[user/henk/code/inspircd.git] / src / modules / m_silence.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2019 iwalkalone <iwalkalone69@gmail.com>
6  *   Copyright (C) 2019 Sadie Powell <sadie@witchery.services>
7  *   Copyright (C) 2019 Robby <robby@chatbelgie.be>
8  *
9  * This file is part of InspIRCd.  InspIRCd is free software: you can
10  * redistribute it and/or modify it under the terms of the GNU General Public
11  * License as published by the Free Software Foundation, version 2.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22
23 #include "inspircd.h"
24 #include "modules/ctctags.h"
25
26 enum
27 {
28         // From ircu?
29         RPL_SILELIST = 271,
30         RPL_ENDOFSILELIST = 272,
31         ERR_SILELISTFULL = 511,
32
33         // InspIRCd-specific.
34         ERR_SILENCE = 952
35 };
36
37 class SilenceEntry
38 {
39  public:
40         enum SilenceFlags
41         {
42                 // Does nothing; for internal use only.
43                 SF_NONE = 0,
44
45                 // Exclude users who match this flags ("x").
46                 SF_EXEMPT = 1,
47
48                 // 2, 4, 8, 16 are reserved for future use.
49
50                 // Matches a NOTICE targeted at a channel ("n").
51                 SF_NOTICE_CHANNEL = 32,
52
53                 // Matches a NOTICE targeted at a user ("N").
54                 SF_NOTICE_USER = 64,
55
56                 // Matches a PRIVMSG targeted at a channel ("p").
57                 SF_PRIVMSG_CHANNEL = 128,
58
59                 // Matches a PRIVMSG targeted at a user ("P").
60                 SF_PRIVMSG_USER = 256,
61
62                 // Matches a TAGMSG targeted at a channel ("t").
63                 SF_TAGMSG_CHANNEL = 512,
64
65                 // Matches a TAGMSG targeted at a user ("T").
66                 SF_TAGMSG_USER = 1024,
67
68                 // Matches a CTCP targeted at a channel ("c").
69                 SF_CTCP_CHANNEL = 2048,
70
71                 // Matches a CTCP targeted at a user ("C").
72                 SF_CTCP_USER = 4096,
73
74                 // Matches an invite to a channel ("i").
75                 SF_INVITE = 8192,
76
77                 // The default if no flags have been specified.
78                 SF_DEFAULT = SF_NOTICE_CHANNEL | SF_NOTICE_USER | SF_PRIVMSG_CHANNEL | SF_PRIVMSG_USER | SF_TAGMSG_CHANNEL |
79                         SF_TAGMSG_USER | SF_CTCP_CHANNEL | SF_CTCP_USER | SF_INVITE
80         };
81
82         // The flags that this mask is silenced for.
83         uint32_t flags;
84
85         // The mask which is silenced (e.g. *!*@example.com).
86         std::string mask;
87
88         SilenceEntry(uint32_t Flags, const std::string& Mask)
89                 : flags(Flags)
90                 , mask(Mask)
91         {
92         }
93
94         bool operator <(const SilenceEntry& other) const
95         {
96                 if (flags & SF_EXEMPT && other.flags & ~SF_EXEMPT)
97                         return true;
98                 if (other.flags & SF_EXEMPT && flags & ~SF_EXEMPT)
99                         return false;
100                 if (flags < other.flags)
101                         return true;
102                 if (other.flags < flags)
103                         return false;
104                 return mask < other.mask;
105         }
106
107         // Converts a flag list to a bitmask.
108         static bool FlagsToBits(const std::string& flags, uint32_t& out)
109         {
110                 out = SF_NONE;
111                 for (std::string::const_iterator flag = flags.begin(); flag != flags.end(); ++flag)
112                 {
113                         switch (*flag)
114                         {
115                                 case 'C':
116                                         out |= SF_CTCP_USER;
117                                         break;
118                                 case 'c':
119                                         out |= SF_CTCP_CHANNEL;
120                                         break;
121                                 case 'd':
122                                         out |= SF_DEFAULT;
123                                         break;
124                                 case 'i':
125                                         out |= SF_INVITE;
126                                         break;
127                                 case 'N':
128                                         out |= SF_NOTICE_USER;
129                                         break;
130                                 case 'n':
131                                         out |= SF_NOTICE_CHANNEL;
132                                         break;
133                                 case 'P':
134                                         out |= SF_PRIVMSG_USER;
135                                         break;
136                                 case 'p':
137                                         out |= SF_PRIVMSG_CHANNEL;
138                                         break;
139                                 case 'T':
140                                         out |= SF_TAGMSG_USER;
141                                         break;
142                                 case 't':
143                                         out |= SF_TAGMSG_CHANNEL;
144                                         break;
145                                 case 'x':
146                                         out |= SF_EXEMPT;
147                                         break;
148                                 default:
149                                         out = SF_NONE;
150                                         return false;
151                         }
152                 }
153                 return true;
154         }
155
156         // Converts a bitmask to a flag list.
157         static std::string BitsToFlags(uint32_t flags)
158         {
159                 std::string out;
160                 if (flags & SF_CTCP_USER)
161                         out.push_back('C');
162                 if (flags & SF_CTCP_CHANNEL)
163                         out.push_back('c');
164                 if (flags & SF_INVITE)
165                         out.push_back('i');
166                 if (flags & SF_NOTICE_USER)
167                         out.push_back('N');
168                 if (flags & SF_NOTICE_CHANNEL)
169                         out.push_back('n');
170                 if (flags & SF_PRIVMSG_USER)
171                         out.push_back('P');
172                 if (flags & SF_PRIVMSG_CHANNEL)
173                         out.push_back('p');
174                 if (flags & SF_TAGMSG_CHANNEL)
175                         out.push_back('T');
176                 if (flags & SF_TAGMSG_USER)
177                         out.push_back('t');
178                 if (flags & SF_EXEMPT)
179                         out.push_back('x');
180                 return out;
181         }
182 };
183
184 typedef insp::flat_set<SilenceEntry> SilenceList;
185
186 class SilenceExtItem : public SimpleExtItem<SilenceList>
187 {
188  public:
189         unsigned int maxsilence;
190
191         SilenceExtItem(Module* Creator)
192                 : SimpleExtItem<SilenceList>("silence_list", ExtensionItem::EXT_USER, Creator)
193         {
194         }
195
196         void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE
197         {
198                 LocalUser* user = IS_LOCAL(static_cast<User*>(container));
199                 if (!user)
200                         return;
201
202                 // Remove the old list and create a new one.
203                 unset(user);
204                 SilenceList* list = new SilenceList();
205
206                 irc::spacesepstream ts(value);
207                 while (!ts.StreamEnd())
208                 {
209                         // Check we have space for another entry.
210                         if (list->size() >= maxsilence)
211                         {
212                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Oversized silence list received for %s: %s",
213                                         user->uuid.c_str(), value.c_str());
214                                 delete list;
215                                 return;
216                         }
217
218                         // Extract the mask and the flags.
219                         std::string mask;
220                         std::string flagstr;
221                         if (!ts.GetToken(mask) || !ts.GetToken(flagstr))
222                         {
223                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed silence list received for %s: %s",
224                                         user->uuid.c_str(), value.c_str());
225                                 delete list;
226                                 return;
227                         }
228
229                         // Try to parse the flags.
230                         uint32_t flags;
231                         if (!SilenceEntry::FlagsToBits(flagstr, flags))
232                         {
233                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Malformed silence flags received for %s: %s",
234                                         user->uuid.c_str(), flagstr.c_str());
235                                 delete list;
236                                 return;
237                         }
238
239                         // Store the silence entry.
240                         list->insert(SilenceEntry(flags, mask));
241                 }
242
243                 // The value was well formed.
244                 set(user, list);
245         }
246
247         std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE
248         {
249                 SilenceList* list = static_cast<SilenceList*>(item);
250                 std::string buf;
251                 for (SilenceList::const_iterator iter = list->begin(); iter != list->end(); ++iter)
252                 {
253                         if (iter != list->begin())
254                                 buf.push_back(' ');
255
256                         buf.append(iter->mask);
257                         buf.push_back(' ');
258                         buf.append(SilenceEntry::BitsToFlags(iter->flags));
259                 }
260                 return buf;
261         }
262 };
263
264 class SilenceMessage : public ClientProtocol::Message
265 {
266  public:
267         SilenceMessage(const std::string& mask, const std::string& flags)
268                 : ClientProtocol::Message("SILENCE")
269         {
270                 PushParam(mask);
271                 PushParam(flags);
272         }
273 };
274
275 class CommandSilence : public SplitCommand
276 {
277  private:
278         ClientProtocol::EventProvider msgprov;
279
280         CmdResult AddSilence(LocalUser* user, const std::string& mask, uint32_t flags)
281         {
282                 SilenceList* list = ext.get(user);
283                 if (list && list->size() > ext.maxsilence)
284                 {
285                         user->WriteNumeric(ERR_SILELISTFULL, mask, SilenceEntry::BitsToFlags(flags), "Your SILENCE list is full");
286                         return CMD_FAILURE;
287                 }
288                 else if (!list)
289                 {
290                         // There is no list; create it.
291                         list = new SilenceList();
292                         ext.set(user, list);
293                 }
294
295                 if (!list->insert(SilenceEntry(flags, mask)).second)
296                 {
297                         user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The SILENCE entry you specified already exists");
298                         return CMD_FAILURE;
299                 }
300
301                 SilenceMessage msg("+" + mask, SilenceEntry::BitsToFlags(flags));
302                 user->Send(msgprov, msg);
303                 return CMD_SUCCESS;
304         }
305
306         CmdResult RemoveSilence(LocalUser* user, const std::string& mask, uint32_t flags)
307         {
308                 SilenceList* list = ext.get(user);
309                 if (list)
310                 {
311                         for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
312                         {
313                                 if (!irc::equals(iter->mask, mask) || iter->flags != flags)
314                                         continue;
315
316                                 list->erase(iter);
317                                 SilenceMessage msg("-" + mask, SilenceEntry::BitsToFlags(flags));
318                                 user->Send(msgprov, msg);
319                                 return CMD_SUCCESS;
320                         }
321                 }
322
323                 user->WriteNumeric(ERR_SILENCE, mask, SilenceEntry::BitsToFlags(flags), "The SILENCE entry you specified could not be found");
324                 return CMD_FAILURE;
325         }
326
327         CmdResult ShowSilenceList(LocalUser* user)
328         {
329                 SilenceList* list = ext.get(user);
330                 if (list)
331                 {
332                         for (SilenceList::const_iterator iter = list->begin(); iter != list->end(); ++iter)
333                         {
334                                 user->WriteNumeric(RPL_SILELIST, iter->mask, SilenceEntry::BitsToFlags(iter->flags));
335                         }
336                 }
337                 user->WriteNumeric(RPL_ENDOFSILELIST, "End of SILENCE list");
338                 return CMD_SUCCESS;
339         }
340
341  public:
342         SilenceExtItem ext;
343
344         CommandSilence(Module* Creator)
345                 : SplitCommand(Creator, "SILENCE")
346                 , msgprov(Creator, "SILENCE")
347                 , ext(Creator)
348         {
349                 allow_empty_last_param = false;
350                 syntax = "[(+|-)<mask> [CcdiNnPpTtx]]";
351         }
352
353         CmdResult HandleLocal(LocalUser* user, const Params& parameters) CXX11_OVERRIDE
354         {
355                 if (parameters.empty())
356                         return ShowSilenceList(user);
357
358                 // If neither add nor remove are specified we default to add.
359                 bool is_remove = parameters[0][0] == '-';
360
361                 // If a prefix mask has been given then strip it and clean it up.
362                 std::string mask = parameters[0];
363                 if (mask[0] == '-' || mask[0] == '+')
364                 {
365                         mask.erase(0, 1);
366                         if (mask.empty())
367                                 mask.assign("*");
368                         ModeParser::CleanMask(mask);
369                 }
370
371                 // If the user specified a flags then use that. Otherwise, default to blocking
372                 // all CTCPs, invites, notices, privmsgs, and invites.
373                 uint32_t flags = SilenceEntry::SF_DEFAULT;
374                 if (parameters.size() > 1)
375                 {
376                         if (!SilenceEntry::FlagsToBits(parameters[1], flags))
377                         {
378                                 user->WriteNumeric(ERR_SILENCE, mask, parameters[1], "You specified one or more invalid SILENCE flags");
379                                 return CMD_FAILURE;
380                         }
381                         else if (flags == SilenceEntry::SF_EXEMPT)
382                         {
383                                 // The user specified "x" with no other flags which does not make sense; add the "d" flag.
384                                 flags |= SilenceEntry::SF_DEFAULT;
385                         }
386                 }
387
388                 return is_remove ? RemoveSilence(user, mask, flags) : AddSilence(user, mask, flags);
389         }
390 };
391
392 class ModuleSilence
393         : public Module
394         , public CTCTags::EventListener
395 {
396  private:
397         bool exemptuline;
398         CommandSilence cmd;
399
400         ModResult BuildChannelExempts(User* source, Channel* channel, SilenceEntry::SilenceFlags flag, CUList& exemptions)
401         {
402                 const Channel::MemberMap& members = channel->GetUsers();
403                 for (Channel::MemberMap::const_iterator member = members.begin(); member != members.end(); ++member)
404                 {
405                         if (!CanReceiveMessage(source, member->first, flag))
406                                 exemptions.insert(member->first);
407                 }
408                 return MOD_RES_PASSTHRU;
409         }
410
411         bool CanReceiveMessage(User* source, User* target, SilenceEntry::SilenceFlags flag)
412         {
413                 // Servers handle their own clients.
414                 if (!IS_LOCAL(target))
415                         return true;
416
417                 if (exemptuline && source->server->IsULine())
418                         return true;
419
420                 SilenceList* list = cmd.ext.get(target);
421                 if (!list)
422                         return true;
423
424                 for (SilenceList::iterator iter = list->begin(); iter != list->end(); ++iter)
425                 {
426                         if (!(iter->flags & flag))
427                                 continue;
428
429                         if (InspIRCd::Match(source->GetFullHost(), iter->mask))
430                                 return iter->flags & SilenceEntry::SF_EXEMPT;
431                 }
432
433                 return true;
434         }
435
436  public:
437         ModuleSilence()
438                 : CTCTags::EventListener(this)
439                 , cmd(this)
440         {
441         }
442
443         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
444         {
445                 ConfigTag* tag = ServerInstance->Config->ConfValue("silence");
446                 exemptuline = tag->getBool("exemptuline", true);
447                 cmd.ext.maxsilence = tag->getUInt("maxentries", 32, 1);
448         }
449
450         void On005Numeric(std::map<std::string, std::string>& tokens) CXX11_OVERRIDE
451         {
452                 tokens["ESILENCE"] = "CcdiNnPpTtx";
453                 tokens["SILENCE"] = ConvToStr(cmd.ext.maxsilence);
454         }
455
456         ModResult OnUserPreInvite(User* source, User* dest, Channel* channel, time_t timeout) CXX11_OVERRIDE
457         {
458                 return CanReceiveMessage(source, dest, SilenceEntry::SF_INVITE) ? MOD_RES_PASSTHRU : MOD_RES_DENY;
459         }
460
461         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
462         {
463                 std::string ctcpname;
464                 bool is_ctcp = details.IsCTCP(ctcpname) && !irc::equals(ctcpname, "ACTION");
465
466                 SilenceEntry::SilenceFlags flag = SilenceEntry::SF_NONE;
467                 switch (target.type)
468                 {
469                         case MessageTarget::TYPE_CHANNEL:
470                         {
471                                 if (is_ctcp)
472                                         flag = SilenceEntry::SF_CTCP_CHANNEL;
473                                 else if (details.type == MSG_NOTICE)
474                                         flag = SilenceEntry::SF_NOTICE_CHANNEL;
475                                 else if (details.type == MSG_PRIVMSG)
476                                         flag = SilenceEntry::SF_PRIVMSG_CHANNEL;
477
478                                 return BuildChannelExempts(user, target.Get<Channel>(), flag, details.exemptions);
479                         }
480                         case MessageTarget::TYPE_USER:
481                         {
482                                 if (is_ctcp)
483                                         flag = SilenceEntry::SF_CTCP_USER;
484                                 else if (details.type == MSG_NOTICE)
485                                         flag = SilenceEntry::SF_NOTICE_USER;
486                                 else if (details.type == MSG_PRIVMSG)
487                                         flag = SilenceEntry::SF_PRIVMSG_USER;
488
489                                 if (!CanReceiveMessage(user, target.Get<User>(), flag))
490                                 {
491                                         details.echo_original = true;
492                                         return MOD_RES_DENY;
493                                 }
494                                 break;
495                         }
496                         case MessageTarget::TYPE_SERVER:
497                                 break;
498                 }
499
500                 return MOD_RES_PASSTHRU;
501         }
502
503         ModResult OnUserPreTagMessage(User* user, const MessageTarget& target, CTCTags::TagMessageDetails& details) CXX11_OVERRIDE
504         {
505                 if (target.type == MessageTarget::TYPE_CHANNEL)
506                         return BuildChannelExempts(user, target.Get<Channel>(), SilenceEntry::SF_TAGMSG_CHANNEL, details.exemptions);
507
508                 if (target.type == MessageTarget::TYPE_USER && !CanReceiveMessage(user, target.Get<User>(), SilenceEntry::SF_TAGMSG_USER))
509                 {
510                         details.echo_original = true;
511                         return MOD_RES_DENY;
512                 }
513
514                 return MOD_RES_PASSTHRU;
515         }
516
517         Version GetVersion() CXX11_OVERRIDE
518         {
519                 return Version("Adds the /SILENCE command which allows users to ignore other users on server-side.", VF_OPTCOMMON | VF_VENDOR);
520         }
521 };
522
523 MODULE_INIT(ModuleSilence)