]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Import the anticaps module from inspircd-extras.
authorPeter Powell <petpow@saberuk.com>
Tue, 30 Jan 2018 12:30:54 +0000 (12:30 +0000)
committerPeter Powell <petpow@saberuk.com>
Wed, 7 Feb 2018 12:20:42 +0000 (12:20 +0000)
docs/conf/modules.conf.example
src/modules/m_anticaps.cpp [new file with mode: 0644]

index 24a2ae7b21d9ee2984a82641b0cd7465dfc3eaf1..8bc34f2d3ea1d54f536cf18ef257681df9c7995c 100644 (file)
 # To use, ALLTIME must be in one of your oper class blocks.
 #<module name="alltime">
 
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# Anticaps module: Adds channel mode +B which allows you to punish
+# users that send overly capitalised messages to channels. Unlike the
+# blockcaps module this module is more flexible as it has more options
+# for punishment and allows channels to configure their own punishment
+# policies.
+#<module name="anticaps">
+#
+# You may also configure the characters which anticaps considers to be
+# lower case and upper case. Any characters not listed here are assumed
+# to be punctuation and will be ignored when counting:
+# <anticaps lowercase="abcdefghijklmnopqrstuvwxyz"
+#           uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ">
+
 #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 # Auditorium module: Adds channel mode +u which makes everyone else
 # except you in the channel invisible, used for large meetings etc.
 
 #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 # Block CAPS module: Adds channel mode +B, blocks all-CAPS messages.
+#
+# NOTE: This module is deprecated and will be removed in a future version
+# of InspIRCd. You should use the anticaps module shown above instead.
 #<module name="blockcaps">
 #
 #-#-#-#-#-#-#-#-#-#-#-  BLOCKCAPS CONFIGURATION  -#-#-#-#-#-#-#-#-#-#-#
diff --git a/src/modules/m_anticaps.cpp b/src/modules/m_anticaps.cpp
new file mode 100644 (file)
index 0000000..6b0c192
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ *   Copyright (C) 2017 Peter Powell <petpow@saberuk.com>
+ *
+ * This file is part of InspIRCd.  InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/exemption.h"
+
+enum AntiCapsMethod
+{
+       ACM_BAN,
+       ACM_BLOCK,
+       ACM_MUTE,
+       ACM_KICK,
+       ACM_KICK_BAN
+};
+
+class AntiCapsSettings
+{
+ public:
+       const AntiCapsMethod method;
+       const uint16_t minlen;
+       const uint8_t percent;
+
+       AntiCapsSettings(const AntiCapsMethod& Method, const uint16_t& MinLen, const uint8_t& Percent)
+               : method(Method)
+               , minlen(MinLen)
+               , percent(Percent)
+       {
+       }
+};
+
+class AntiCapsMode : public ParamMode<AntiCapsMode, SimpleExtItem<AntiCapsSettings> >
+{
+ private:
+       bool ParseMethod(irc::sepstream& stream, AntiCapsMethod& method)
+       {
+               std::string methodstr;
+               if (!stream.GetToken(methodstr))
+                       return false;
+
+               if (irc::equals(methodstr, "ban"))
+                       method = ACM_BAN;
+               else if (irc::equals(methodstr, "block"))
+                       method = ACM_BLOCK;
+               else if (irc::equals(methodstr, "mute"))
+                       method = ACM_MUTE;
+               else if (irc::equals(methodstr, "kick"))
+                       method = ACM_KICK;
+               else if (irc::equals(methodstr, "kickban"))
+                       method = ACM_KICK_BAN;
+               else
+                       return false;
+
+               return true;
+       }
+
+       bool ParseMinimumLength(irc::sepstream& stream, uint16_t& minlen)
+       {
+               std::string minlenstr;
+               if (!stream.GetToken(minlenstr))
+                       return false;
+
+               uint16_t result = atoi(minlenstr.c_str());
+               if (result < 1 || result > ServerInstance->Config->Limits.MaxLine)
+                       return false;
+
+               minlen = result;
+               return true;
+       }
+
+       bool ParsePercent(irc::sepstream& stream, uint8_t& percent)
+       {
+               std::string percentstr;
+               if (!stream.GetToken(percentstr))
+                       return false;
+
+               int result = atoi(percentstr.c_str());
+               if (result < 1 || result > 100)
+                       return false;
+
+               percent = result;
+               return true;
+       }
+
+ public:
+       AntiCapsMode(Module* Creator)
+               : ParamMode<AntiCapsMode, SimpleExtItem<AntiCapsSettings> >(Creator, "anticaps", 'B')
+       {
+       }
+
+       ModeAction OnSet(User* source, Channel* channel, std::string& parameter)
+       {
+               irc::sepstream stream(parameter, ':');
+               AntiCapsMethod method;
+               uint16_t minlen;
+               uint8_t percent;
+
+               // Attempt to parse the method.
+               if (!ParseMethod(stream, method) || !ParseMinimumLength(stream, minlen) || !ParsePercent(stream, percent))
+               {
+                       source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, "Invalid anticaps mode parameter. Syntax: <ban|block|mute|kick|kickban>:{minlen}:{percent}."));
+                       return MODEACTION_DENY;
+               }
+
+               ext.set(channel, new AntiCapsSettings(method, minlen, percent));
+               return MODEACTION_ALLOW;
+       }
+
+       void SerializeParam(Channel* chan, const AntiCapsSettings* acs, std::string& out)
+       {
+               switch (acs->method)
+               {
+                       case ACM_BAN:
+                               out.append("ban");
+                               break;
+                       case ACM_BLOCK:
+                               out.append("block");
+                               break;
+                       case ACM_MUTE:
+                               out.append("mute");
+                               break;
+                       case ACM_KICK:
+                               out.append("kick");
+                               break;
+                       case ACM_KICK_BAN:
+                               out.append("kickban");
+                               break;
+                       default:
+                               out.append("unknown~");
+                               out.append(ConvToStr(acs->method));
+                               break;
+               }
+               out.push_back(':');
+               out.append(ConvToStr(acs->minlen));
+               out.push_back(':');
+               out.append(ConvToStr(acs->percent));
+       }
+};
+
+class ModuleAntiCaps : public Module
+{
+ private:
+       CheckExemption::EventProvider exemptionprov;
+       std::bitset<UCHAR_MAX> uppercase;
+       std::bitset<UCHAR_MAX> lowercase;
+       AntiCapsMode mode;
+
+       void CreateBan(Channel* channel, User* user, bool mute)
+       {
+               std::string banmask(mute ? "m:" : "");
+               banmask.append("*!*@");
+               banmask.append(user->GetDisplayedHost());
+
+               Modes::ChangeList changelist;
+               changelist.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), banmask);
+               ServerInstance->Modes->Process(ServerInstance->FakeClient, channel, NULL, changelist);
+       }
+
+       void InformUser(Channel* channel, User* user, const std::string& message)
+       {
+               user->WriteNumeric(ERR_CANNOTSENDTOCHAN, channel, message + " and was blocked.");
+       }
+
+ public:
+       ModuleAntiCaps()
+               : exemptionprov(this)
+               , mode(this)
+       {
+       }
+
+       void ReadConfig(ConfigStatus&) CXX11_OVERRIDE
+       {
+               ConfigTag* tag = ServerInstance->Config->ConfValue("anticaps");
+
+               uppercase.reset();
+               const std::string upper = tag->getString("uppercase", "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+               for (std::string::const_iterator iter = upper.begin(); iter != upper.end(); ++iter)
+                       uppercase.set(static_cast<unsigned char>(*iter));
+
+               lowercase.reset();
+               const std::string lower = tag->getString("lowercase", "abcdefghijklmnopqrstuvwxyz");
+               for (std::string::const_iterator iter = lower.begin(); iter != lower.end(); ++iter)
+                       lowercase.set(static_cast<unsigned char>(*iter));
+       }
+
+       ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
+       {
+               // We only want to operate on messages from local users.
+               if (!IS_LOCAL(user))
+                       return MOD_RES_PASSTHRU;
+
+               // The mode can only be applied to channels.
+               if (target.type != MessageTarget::TYPE_CHANNEL)
+                       return MOD_RES_PASSTHRU;
+
+               // We only act if the channel has the mode set.
+               Channel* channel = target.Get<Channel>();
+               if (!channel->IsModeSet(&mode))
+                       return MOD_RES_PASSTHRU;
+
+               // If the user is exempt from anticaps then we don't need
+               // to do anything else.
+               ModResult result = CheckExemption::Call(exemptionprov, user, channel, "anticaps");
+                       if (result == MOD_RES_ALLOW)
+                       return MOD_RES_PASSTHRU;
+
+               // If the message is a CTCP then we skip it unless it is
+               // an ACTION in which case we skip the prefix and suffix.
+               std::string::const_iterator text_begin = details.text.begin();
+               std::string::const_iterator text_end = details.text.end();
+               if (details.text[0] == '\1')
+               {
+                       // If the CTCP is not an action then skip it.
+                       if (details.text.compare(0, 8, "\1ACTION ", 8))
+                               return MOD_RES_PASSTHRU;
+
+                       // Skip the CTCP message characters.
+                       text_begin += 8;
+                       if (*details.text.rbegin() == '\1')
+                               text_end -= 1;
+               }
+
+               // Retrieve the anticaps config. This should never be
+               // null but its better to be safe than sorry.
+               AntiCapsSettings* config = mode.ext.get(channel);
+               if (!config)
+                       return MOD_RES_PASSTHRU;
+
+               // If the message is shorter than the minimum length then
+               // we don't need to do anything else.
+               size_t length = std::distance(text_begin, text_end);
+               if (length < config->minlen)
+                       return MOD_RES_PASSTHRU;
+
+               // Count the characters to see how many upper case and
+               // ignored (non upper or lower) characters there are.
+               size_t upper = 0;
+               for (std::string::const_iterator iter = text_begin; iter != text_end; ++iter)
+               {
+                       unsigned char chr = static_cast<unsigned char>(*iter);
+                       if (uppercase.test(chr))
+                               upper += 1;
+                       else if (!lowercase.test(chr))
+                               length -= 1;
+               }
+
+               // If the message was entirely symbols then the message
+               // can't contain any upper case letters.
+               if (length == 0)
+                       return MOD_RES_PASSTHRU;
+
+               // Calculate the percentage.
+               double percent = round((upper * 100) / length);
+               if (percent < config->percent)
+                       return MOD_RES_PASSTHRU;
+
+               const std::string message = InspIRCd::Format("Your message exceeded the %d%% upper case character threshold for %s",
+                       config->percent, channel->name.c_str());
+
+               switch (config->method)
+               {
+                       case ACM_BAN:
+                               InformUser(channel, user, message);
+                               CreateBan(channel, user, false);
+                               break;
+
+                       case ACM_BLOCK:
+                               InformUser(channel, user, message);
+                               break;
+
+                       case ACM_MUTE:
+                               InformUser(channel, user, message);
+                               CreateBan(channel, user, true);
+                               break;
+
+                       case ACM_KICK:
+                               channel->KickUser(ServerInstance->FakeClient, user, message);
+                               break;
+
+                       case ACM_KICK_BAN:
+                               CreateBan(channel, user, false);
+                               channel->KickUser(ServerInstance->FakeClient, user, message);
+                               break;
+               }
+               return MOD_RES_DENY;
+       }
+
+       Version GetVersion() CXX11_OVERRIDE
+       {
+               return Version("Provides support for punishing users that send capitalised messages.", VF_COMMON|VF_VENDOR);
+       }
+};
+
+MODULE_INIT(ModuleAntiCaps)