aboutsummaryrefslogtreecommitdiffstats
path: root/src/modules/m_repeat.cpp
diff options
context:
space:
mode:
authorGravatar Sadie Powell2024-07-17 00:06:50 +0100
committerGravatar Sadie Powell2024-07-17 00:06:50 +0100
commit889d521e05f2e922683d48c74eb00290e7a2f548 (patch)
treeb4c56bd4cdf0881601c7e4d6ff571e491af10bf4 /src/modules/m_repeat.cpp
parentUse std::endian from C++20 in the sha1 module. (diff)
downloadinspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.gz
inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.tar.bz2
inspircd++-889d521e05f2e922683d48c74eb00290e7a2f548.zip
Shuffle the modules about a bit.
Diffstat (limited to 'src/modules/m_repeat.cpp')
-rw-r--r--src/modules/m_repeat.cpp533
1 files changed, 0 insertions, 533 deletions
diff --git a/src/modules/m_repeat.cpp b/src/modules/m_repeat.cpp
deleted file mode 100644
index 40dc36dda..000000000
--- a/src/modules/m_repeat.cpp
+++ /dev/null
@@ -1,533 +0,0 @@
-/*
- * InspIRCd -- Internet Relay Chat Daemon
- *
- * Copyright (C) 2021 Dominic Hamon
- * Copyright (C) 2021 David Schultz <me@zpld.me>
- * Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
- * Copyright (C) 2018 Matt Schatz <genius3000@g3k.solutions>
- * Copyright (C) 2017-2024 Sadie Powell <sadie@witchery.services>
- * Copyright (C) 2013-2014 Attila Molnar <attilamolnar@hush.com>
- * Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
- *
- * 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 "extension.h"
-#include "modules/exemption.h"
-#include "numerichelper.h"
-#include "timeutils.h"
-
-class ChannelSettings final
-{
-public:
- enum RepeatAction
- {
- ACT_BAN,
- ACT_BLOCK,
- ACT_KICK,
- ACT_KICK_BAN,
- ACT_MUTE,
- };
-
- RepeatAction Action;
- unsigned int Backlog;
- unsigned int Lines;
- unsigned int Diff;
- unsigned long Seconds;
-
- void serialize(bool extended, std::string& out) const
- {
- if (extended)
- {
- switch (Action)
- {
- case ACT_BAN:
- out.append("ban");
- break;
- case ACT_BLOCK:
- out.append("block");
- break;
- case ACT_MUTE:
- out.append("mute");
- break;
- case ACT_KICK:
- out.append("kick");
- break;
- case ACT_KICK_BAN:
- out.append("kickban");
- break;
- }
- out.push_back(':');
- }
- else
- {
- switch (Action)
- {
- case ACT_KICK_BAN:
- out.push_back('*');
- break;
-
- case ACT_BLOCK:
- out.push_back('~');
- break;
-
- default:
- break; // No other types are supported in the old mode.
- }
- }
-
- out.append(ConvToStr(Lines)).push_back(':');
- out.append(ConvToStr(Seconds));
- if (Diff)
- {
- out.push_back(':');
- out.append(ConvToStr(Diff));
- if (Backlog)
- {
- out.push_back(':');
- out.append(ConvToStr(Backlog));
- }
- }
- }
-};
-
-class RepeatMode final
- : public ParamMode<RepeatMode, SimpleExtItem<ChannelSettings>>
-{
-private:
- struct RepeatItem final
- {
- time_t ts;
- std::string line;
- RepeatItem(time_t TS, const std::string& Line)
- : ts(TS)
- , line(Line)
- {
- }
- };
-
- typedef std::deque<RepeatItem> RepeatItemList;
-
- struct MemberInfo final
- {
- RepeatItemList ItemList;
- unsigned int Counter = 0;
- };
-
- struct ModuleSettings final
- {
- bool Extended = false;
- unsigned long MaxLines = 0;
- unsigned long MaxSecs = 0;
- unsigned long MaxBacklog = 0;
- unsigned int MaxDiff = 0;
- size_t MaxMessageSize = 0;
- std::string Message;
- };
-
- std::vector<size_t> mx[2];
-
- bool CompareLines(const std::string& message, const std::string& historyline, unsigned long trigger)
- {
- if (message == historyline)
- return true;
- else if (trigger)
- return (Levenshtein(message, historyline) <= trigger);
-
- return false;
- }
-
- size_t Levenshtein(const std::string& s1, const std::string& s2)
- {
- size_t l1 = s1.size();
- size_t l2 = s2.size();
-
- for (size_t i = 0; i < l2; i++)
- mx[0][i] = i;
- for (size_t i = 0; i < l1; i++)
- {
- mx[1][0] = i + 1;
- for (size_t j = 0; j < l2; j++)
- mx[1][j + 1] = std::min(std::min(mx[1][j] + 1, mx[0][j + 1] + 1), mx[0][j] + ((s1[i] == s2[j]) ? 0 : 1));
-
- mx[0].swap(mx[1]);
- }
- return mx[0][l2];
- }
-
-public:
- ModuleSettings ms;
- SimpleExtItem<MemberInfo> MemberInfoExt;
-
- RepeatMode(Module* Creator)
- : ParamMode<RepeatMode, SimpleExtItem<ChannelSettings>>(Creator, "repeat", 'E')
- , MemberInfoExt(Creator, "repeat", ExtensionType::MEMBERSHIP)
- {
- }
-
- void OnUnset(User* source, Channel* chan) override
- {
- // Unset the per-membership extension when the mode is removed
- for (const auto& [_, memb] : chan->GetUsers())
- MemberInfoExt.Unset(memb);
- }
-
- bool OnSet(User* source, Channel* channel, std::string& parameter) override
- {
- ChannelSettings settings;
- if (!ParseSettings(source, parameter, settings))
- {
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter));
- return false;
- }
-
- if ((settings.Backlog > 0) && (settings.Lines > settings.Backlog))
- {
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
- "You can't set lines higher than backlog."));
- return false;
- }
-
- LocalUser* localsource = IS_LOCAL(source);
- if ((localsource) && (!ValidateSettings(localsource, channel, parameter, settings)))
- return false;
-
- ext.Set(channel, settings);
-
- return true;
- }
-
- bool MatchLine(Membership* memb, ChannelSettings* rs, std::string message)
- {
- // If the message is larger than whatever size it's set to,
- // let's pretend it isn't. If the first 512 (def. setting) match, it's probably spam.
- if (message.size() > ms.MaxMessageSize)
- message.erase(ms.MaxMessageSize);
-
- MemberInfo* rp = MemberInfoExt.Get(memb);
- if (!rp)
- {
- rp = new MemberInfo;
- MemberInfoExt.Set(memb, rp);
- }
-
- unsigned int matches = 0;
- if (!rs->Backlog)
- matches = rp->Counter;
-
- RepeatItemList& items = rp->ItemList;
- const unsigned long trigger = (message.size() * rs->Diff / 100);
- const time_t now = ServerInstance->Time();
-
- std::transform(message.begin(), message.end(), message.begin(), ::tolower);
-
- for (std::deque<RepeatItem>::iterator it = items.begin(); it != items.end(); ++it)
- {
- if (it->ts < now)
- {
- items.erase(it, items.end());
- matches = 0;
- break;
- }
-
- if (CompareLines(message, it->line, trigger))
- {
- if (++matches >= rs->Lines)
- {
- if (rs->Action != ChannelSettings::ACT_BLOCK)
- rp->Counter = 0;
- return true;
- }
- }
- else if ((ms.MaxBacklog == 0) || (rs->Backlog == 0))
- {
- matches = 0;
- items.clear();
- break;
- }
- }
-
- unsigned int max_items = (rs->Backlog ? rs->Backlog : 1);
- if (items.size() >= max_items)
- items.pop_back();
-
- items.push_front(RepeatItem(now + rs->Seconds, message));
- rp->Counter = matches;
- return false;
- }
-
- void Resize(size_t size)
- {
- size_t newsize = size+1;
- if (newsize <= mx[0].size())
- return;
- ms.MaxMessageSize = size;
- mx[0].resize(newsize);
- mx[1].resize(newsize);
- }
-
- void SerializeParam(Channel* chan, const ChannelSettings* chset, std::string& out)
- {
- chset->serialize(ms.Extended, out);
- }
-
- void SetSyntax()
- {
- if (ms.Extended)
- syntax = "{ban|block|mute|kick|kickban}:<lines>:<duration>[:<difference>][:<backlog>]";
- else
- syntax = "[~|*]<lines>:<duration>[:<difference>][:<backlog>]";
-
- }
-
-private:
- bool ParseAction(irc::sepstream& stream, ChannelSettings::RepeatAction& action)
- {
- std::string actionstr;
- if (!stream.GetToken(actionstr))
- return false;
-
- if (irc::equals(actionstr, "ban"))
- action = ChannelSettings::ACT_BAN;
- else if (irc::equals(actionstr, "kick"))
- action = ChannelSettings::ACT_KICK;
- else if (irc::equals(actionstr, "block"))
- action = ChannelSettings::ACT_BLOCK;
- else if (irc::equals(actionstr, "mute"))
- action = ChannelSettings::ACT_MUTE;
- else if (irc::equals(actionstr, "kickban"))
- action = ChannelSettings::ACT_KICK_BAN;
- else
- return false;
-
- return true;
- }
-
- bool ParseSettings(User* source, std::string& parameter, ChannelSettings& settings)
- {
- irc::sepstream stream(parameter, ':');
- std::string item;
-
- if (ms.Extended)
- {
- if (!ParseAction(stream, settings.Action) || !stream.GetToken(item))
- return false;
- }
- else
- {
- if (!stream.GetToken(item))
- return false; // Required parameter missing
-
- settings.Action = ChannelSettings::ACT_KICK;
- if ((item[0] == '*') || (item[0] == '~'))
- {
- settings.Action = ((item[0] == '*') ? ChannelSettings::ACT_KICK_BAN : ChannelSettings::ACT_BLOCK);
- item.erase(item.begin());
- }
- }
-
- if ((settings.Lines = ConvToNum<unsigned int>(item)) == 0)
- return false;
-
- if ((!stream.GetToken(item)) || !Duration::TryFrom(item, settings.Seconds) || (settings.Seconds == 0))
- // Required parameter missing
- return false;
-
- // The diff and backlog parameters are optional
- settings.Diff = settings.Backlog = 0;
- if (stream.GetToken(item))
- {
- // There is a diff parameter, see if it's valid (> 0)
- if ((settings.Diff = ConvToNum<unsigned int>(item)) == 0)
- return false;
-
- if (stream.GetToken(item))
- {
- // There is a backlog parameter, see if it's valid
- if ((settings.Backlog = ConvToNum<unsigned int>(item)) == 0)
- return false;
-
- // If there are still tokens, then it's invalid because we allow only 4
- if (stream.GetToken(item))
- return false;
- }
- }
-
- return true;
- }
-
- bool ValidateSettings(LocalUser* source, Channel* channel, const std::string& parameter, const ChannelSettings& settings)
- {
- if (ms.MaxLines && settings.Lines > ms.MaxLines)
- {
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, fmt::format(
- "The line number you specified is too big. Maximum allowed is {}.", ms.MaxLines)));
- return false;
- }
-
- if (ms.MaxSecs && settings.Seconds > ms.MaxSecs)
- {
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, fmt::format(
- "The seconds you specified are too big. Maximum allowed is {}.", ms.MaxSecs)));
- return false;
- }
-
- if (settings.Diff && settings.Diff > ms.MaxDiff)
- {
- if (ms.MaxDiff == 0)
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
- "The server administrator has disabled matching on edit distance."));
- else
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, fmt::format(
- "The distance you specified is too big. Maximum allowed is {}.", ms.MaxDiff)));
- return false;
- }
-
- if (settings.Backlog && settings.Backlog > ms.MaxBacklog)
- {
- if (ms.MaxBacklog == 0)
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
- "The server administrator has disabled backlog matching."));
- else
- source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, fmt::format(
- "The backlog you specified is too big. Maximum allowed is {}.", ms.MaxBacklog)));
- return false;
- }
-
- return true;
- }
-};
-
-class RepeatModule final
- : public Module
-{
-private:
- ChanModeReference banmode;
- CheckExemption::EventProvider exemptionprov;
- RepeatMode rm;
-
- void CreateBan(Channel* channel, User* user, bool mute)
- {
- std::string banmask(mute ? "m:*!" : "*!");
- banmask.append(user->GetBanUser(false));
- banmask.append("@");
- banmask.append(user->GetDisplayedHost());
-
- Modes::ChangeList changelist;
- changelist.push_add(*banmode, banmask);
- ServerInstance->Modes.Process(ServerInstance->FakeClient, channel, nullptr, changelist);
- }
-
-public:
- RepeatModule()
- : Module(VF_VENDOR | VF_COMMON, "Adds channel mode E (repeat) which helps protect against spammers which spam the same message repeatedly.")
- , banmode(this, "ban")
- , exemptionprov(this)
- , rm(this)
- {
- }
-
- void ReadConfig(ConfigStatus& status) override
- {
- const auto& tag = ServerInstance->Config->ConfValue("repeat");
- rm.ms.Message = tag->getString("message", "Repeat flood (trigger is %lines% messages in %duration%)", 1);
- rm.ms.Extended = tag->getBool("extended");
- rm.ms.MaxBacklog = tag->getNum<unsigned long>("maxbacklog", 20);
- rm.ms.MaxDiff = tag->getNum<unsigned int>("maxdistance", 50, 0, 100);
- rm.ms.MaxLines = tag->getNum<unsigned long>("maxlines", 20);
- rm.ms.MaxSecs = tag->getDuration("maxtime", 0);
-
- rm.Resize(tag->getNum<size_t>("size", 512, 1, ServerInstance->Config->Limits.MaxLine));
- }
-
- ModResult OnUserPreMessage(User* user, MessageTarget& target, MessageDetails& details) override
- {
- if (target.type != MessageTarget::TYPE_CHANNEL || !IS_LOCAL(user))
- return MOD_RES_PASSTHRU;
-
- auto* chan = target.Get<Channel>();
- ChannelSettings* settings = rm.ext.Get(chan);
- if (!settings)
- return MOD_RES_PASSTHRU;
-
- Membership* memb = chan->GetUser(user);
- if (!memb)
- return MOD_RES_PASSTHRU;
-
- ModResult res = exemptionprov.Check(user, chan, "repeat");
- if (res == MOD_RES_ALLOW)
- return MOD_RES_PASSTHRU;
-
- if (user->HasPrivPermission("channels/ignore-repeat"))
- return MOD_RES_PASSTHRU;
-
- if (rm.MatchLine(memb, settings, details.text))
- {
- const std::string message = Template::Replace(rm.ms.Message, {
- { "diff", ConvToStr(settings->Diff) },
- { "duration", Duration::ToString(settings->Seconds) },
- { "lines", ConvToStr(settings->Lines) },
- { "seconds", ConvToStr(settings->Seconds) },
- });
-
- switch (settings->Action)
- {
- case ChannelSettings::ACT_BAN:
- memb->WriteNotice(message);
- CreateBan(chan, user, false);
- break;
-
- case ChannelSettings::ACT_BLOCK:
- memb->WriteNotice(message);
- break;
-
- case ChannelSettings::ACT_KICK:
- chan->KickUser(ServerInstance->FakeClient, user, message);
- break;
-
- case ChannelSettings::ACT_KICK_BAN:
- CreateBan(chan, user, false);
- chan->KickUser(ServerInstance->FakeClient, user, message);
- break;
-
- case ChannelSettings::ACT_MUTE:
- memb->WriteNotice(message);
- CreateBan(chan, user, true);
- break;
- }
- return MOD_RES_DENY;
- }
- return MOD_RES_PASSTHRU;
- }
-
- void Prioritize() override
- {
- ServerInstance->Modules.SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST);
- }
-
- void GetLinkData(LinkData& data, std::string& compatdata) override
- {
- data["actions"] = rm.ms.Extended ? "ban block kick kickban mute" : "block kick kickban";
- data["max-lines"] = ConvToStr(rm.ms.MaxLines);
- data["max-secs"] = ConvToStr(rm.ms.MaxSecs);
- data["max-diff"] = ConvToStr(rm.ms.MaxDiff);
- data["max-backlog"] = ConvToStr(rm.ms.MaxBacklog);
-
- compatdata = fmt::format("{}:{}:{}:{}{}", rm.ms.Extended ? "extended:" : "",
- rm.ms.MaxLines, rm.ms.MaxSecs, rm.ms.MaxDiff, rm.ms.MaxBacklog);
- }
-};
-
-MODULE_INIT(RepeatModule)