#include "inspircd.h"
+#include "modules/exemption.h"
-#ifdef _WIN32
-// windows.h defines this
-#undef min
-#endif
+class ChannelSettings
+{
+ public:
+ enum RepeatAction
+ {
+ ACT_KICK,
+ ACT_BLOCK,
+ ACT_BAN
+ };
+
+ RepeatAction Action;
+ unsigned int Backlog;
+ unsigned int Lines;
+ unsigned int Diff;
+ unsigned long Seconds;
-class RepeatMode : public ModeHandler
+ void serialize(std::string& out) const
+ {
+ if (Action == ACT_BAN)
+ out.push_back('*');
+ else if (Action == ACT_BLOCK)
+ out.push_back('~');
+
+ 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 : public ParamMode<RepeatMode, SimpleExtItem<ChannelSettings> >
{
private:
struct RepeatItem
unsigned int MaxSecs;
unsigned int MaxBacklog;
unsigned int MaxDiff;
+ unsigned int MaxMessageSize;
ModuleSettings() : MaxLines(0), MaxSecs(0), MaxBacklog(0), MaxDiff() { }
};
- std::vector<std::vector<unsigned int> > mx;
+ std::vector<unsigned int> mx[2];
ModuleSettings ms;
bool CompareLines(const std::string& message, const std::string& historyline, unsigned int trigger)
{
- if (trigger)
+ if (message == historyline)
+ return true;
+ else if (trigger)
return (Levenshtein(message, historyline) <= trigger);
- else
- return (message == historyline);
+
+ return false;
}
unsigned int Levenshtein(const std::string& s1, const std::string& s2)
unsigned int l1 = s1.size();
unsigned int l2 = s2.size();
- for (unsigned int i = 0; i <= l1; i++)
- mx[i][0] = i;
- for (unsigned int i = 0; i <= l2; i++)
+ for (unsigned int i = 0; i < l2; i++)
mx[0][i] = i;
- for (unsigned int i = 1; i <= l1; i++)
- for (unsigned int j = 1; j <= l2; j++)
- mx[i][j] = std::min(std::min(mx[i - 1][j] + 1, mx[i][j - 1] + 1), mx[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1));
- return (mx[l1][l2]);
- }
-
- public:
- enum RepeatAction
- {
- ACT_KICK,
- ACT_BLOCK,
- ACT_BAN
- };
-
- class ChannelSettings
- {
- public:
- RepeatAction Action;
- unsigned int Backlog;
- unsigned int Lines;
- unsigned int Diff;
- unsigned int Seconds;
-
- std::string serialize()
+ for (unsigned int i = 0; i < l1; i++)
{
- std::string ret = ((Action == ACT_BAN) ? "*" : (Action == ACT_BLOCK ? "~" : "")) + ConvToStr(Lines) + ":" + ConvToStr(Seconds);
- if (Diff)
- {
- ret += ":" + ConvToStr(Diff);
- if (Backlog)
- ret += ":" + ConvToStr(Backlog);
- }
- return ret;
+ mx[1][0] = i + 1;
+ for (unsigned int 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:
SimpleExtItem<MemberInfo> MemberInfoExt;
- SimpleExtItem<ChannelSettings> ChanSet;
RepeatMode(Module* Creator)
- : ModeHandler(Creator, "repeat", 'E', PARAM_SETONLY, MODETYPE_CHANNEL)
- , MemberInfoExt("repeat_memb", Creator)
- , ChanSet("repeat", Creator)
+ : ParamMode<RepeatMode, SimpleExtItem<ChannelSettings> >(Creator, "repeat", 'E')
+ , MemberInfoExt("repeat_memb", ExtensionItem::EXT_MEMBERSHIP, Creator)
{
}
- ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding)
+ void OnUnset(User* source, Channel* chan) CXX11_OVERRIDE
{
- if (!adding)
- {
- if (!channel->IsModeSet(this))
- return MODEACTION_DENY;
-
- // Unset the per-membership extension when the mode is removed
- const UserMembList* users = channel->GetUsers();
- for (UserMembCIter i = users->begin(); i != users->end(); ++i)
- MemberInfoExt.unset(i->second);
-
- ChanSet.unset(channel);
- channel->SetModeParam(this, "");
- return MODEACTION_ALLOW;
- }
-
- if (channel->GetModeParameter(this) == parameter)
- return MODEACTION_DENY;
+ // Unset the per-membership extension when the mode is removed
+ const Channel::MemberMap& users = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator i = users.begin(); i != users.end(); ++i)
+ MemberInfoExt.unset(i->second);
+ }
+ ModeAction OnSet(User* source, Channel* channel, std::string& parameter) CXX11_OVERRIDE
+ {
ChannelSettings settings;
if (!ParseSettings(source, parameter, settings))
{
- source->WriteNotice("*** Invalid syntax. Syntax is {[~*]}[lines]:[time]{:[difference]}{:[backlog]}");
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
+ "Invalid repeat syntax. Syntax is: [~|*]<lines>:<sec>[:<difference>][:<backlog>]"));
return MODEACTION_DENY;
}
if ((settings.Backlog > 0) && (settings.Lines > settings.Backlog))
{
- source->WriteNotice("*** You can't set needed lines higher than backlog");
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
+ "Invalid repeat syntax. You can't set lines higher than backlog."));
return MODEACTION_DENY;
}
LocalUser* localsource = IS_LOCAL(source);
- if ((localsource) && (!ValidateSettings(localsource, settings)))
+ if ((localsource) && (!ValidateSettings(localsource, channel, parameter, settings)))
return MODEACTION_DENY;
- ChanSet.set(channel, settings);
- channel->SetModeParam(this, parameter);
+ ext.set(channel, settings);
return MODEACTION_ALLOW;
}
{
// 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() > mx.size())
- message.erase(mx.size());
+ if (message.size() > ms.MaxMessageSize)
+ message.erase(ms.MaxMessageSize);
MemberInfo* rp = MemberInfoExt.get(memb);
if (!rp)
{
if (++matches >= rs->Lines)
{
- if (rs->Action != ACT_BLOCK)
+ if (rs->Action != ChannelSettings::ACT_BLOCK)
rp->Counter = 0;
return true;
}
void Resize(size_t size)
{
- if (size == mx.size())
+ size_t newsize = size+1;
+ if (newsize <= mx[0].size())
return;
- mx.resize(size);
-
- if (mx.size() > size)
- {
- mx.resize(size);
- for (unsigned int i = 0; i < mx.size(); i++)
- mx[i].resize(size);
- }
- else
- {
- for (unsigned int i = 0; i < mx.size(); i++)
- {
- mx[i].resize(size);
- std::vector<unsigned int>(mx[i]).swap(mx[i]);
- }
- std::vector<std::vector<unsigned int> >(mx).swap(mx);
- }
+ ms.MaxMessageSize = size;
+ mx[0].resize(newsize);
+ mx[1].resize(newsize);
}
void ReadConfig()
{
ConfigTag* conf = ServerInstance->Config->ConfValue("repeat");
- ms.MaxLines = conf->getInt("maxlines", 20);
- ms.MaxBacklog = conf->getInt("maxbacklog", 20);
- ms.MaxSecs = conf->getInt("maxsecs", 0);
+ ms.MaxLines = conf->getUInt("maxlines", 20);
+ ms.MaxBacklog = conf->getUInt("maxbacklog", 20);
+ ms.MaxSecs = conf->getDuration("maxtime", conf->getDuration("maxsecs", 0));
- ms.MaxDiff = conf->getInt("maxdistance", 50);
+ ms.MaxDiff = conf->getUInt("maxdistance", 50);
if (ms.MaxDiff > 100)
ms.MaxDiff = 100;
- unsigned int newsize = conf->getInt("size", 512);
+ unsigned int newsize = conf->getUInt("size", 512);
if (newsize > ServerInstance->Config->Limits.MaxLine)
newsize = ServerInstance->Config->Limits.MaxLine;
Resize(newsize);
return ConvToStr(ms.MaxLines) + ":" + ConvToStr(ms.MaxSecs) + ":" + ConvToStr(ms.MaxDiff) + ":" + ConvToStr(ms.MaxBacklog);
}
+ void SerializeParam(Channel* chan, const ChannelSettings* chset, std::string& out)
+ {
+ chset->serialize(out);
+ }
+
private:
bool ParseSettings(User* source, std::string& parameter, ChannelSettings& settings)
{
if ((item[0] == '*') || (item[0] == '~'))
{
- settings.Action = ((item[0] == '*') ? ACT_BAN : ACT_BLOCK);
+ settings.Action = ((item[0] == '*') ? ChannelSettings::ACT_BAN : ChannelSettings::ACT_BLOCK);
item.erase(item.begin());
}
else
- settings.Action = ACT_KICK;
+ settings.Action = ChannelSettings::ACT_KICK;
+
+ if ((settings.Lines = ConvToNum<unsigned int>(item)) == 0)
+ return false;
- if ((settings.Lines = ConvToInt(item)) == 0)
+ if (!InspIRCd::Duration(item, settings.Seconds))
return false;
- if ((!stream.GetToken(item)) || ((settings.Seconds = InspIRCd::Duration(item)) == 0))
+ if ((!stream.GetToken(item)) || (settings.Seconds == 0))
// Required parameter missing
return false;
if (stream.GetToken(item))
{
// There is a diff parameter, see if it's valid (> 0)
- if ((settings.Diff = ConvToInt(item)) == 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 = ConvToInt(item)) == 0)
+ if ((settings.Backlog = ConvToNum<unsigned int>(item)) == 0)
return false;
// If there are still tokens, then it's invalid because we allow only 4
}
}
- parameter = settings.serialize();
return true;
}
- bool ValidateSettings(LocalUser* source, const ChannelSettings& settings)
+ bool ValidateSettings(LocalUser* source, Channel* channel, const std::string& parameter, const ChannelSettings& settings)
{
- if (settings.Backlog && !ms.MaxBacklog)
+ if (ms.MaxLines && settings.Lines > ms.MaxLines)
{
- source->WriteNotice("*** The server administrator has disabled backlog matching");
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format(
+ "Invalid repeat parameter. The line number you specified is too great. Maximum allowed is %u.", ms.MaxLines)));
return false;
}
- if (settings.Diff)
+ if (ms.MaxSecs && settings.Seconds > ms.MaxSecs)
{
- if (settings.Diff > ms.MaxDiff)
- {
- if (ms.MaxDiff == 0)
- source->WriteNotice("*** The server administrator has disabled matching on edit distance");
- else
- source->WriteNotice("*** The distance you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxDiff));
- return false;
- }
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format(
+ "Invalid repeat parameter. The seconds you specified are too great. Maximum allowed is %u.", ms.MaxSecs)));
+ return false;
+ }
- if (ms.MaxLines && settings.Lines > ms.MaxLines)
- {
- source->WriteNotice("*** The line number you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxLines));
- return false;
- }
+ if (settings.Diff && settings.Diff > ms.MaxDiff)
+ {
+ if (ms.MaxDiff == 0)
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
+ "Invalid repeat parameter. The server administrator has disabled matching on edit distance."));
+ else
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format(
+ "Invalid repeat parameter. The distance you specified is too great. Maximum allowed is %u.", ms.MaxDiff)));
+ return false;
+ }
- if (ms.MaxSecs && settings.Seconds > ms.MaxSecs)
- {
- source->WriteNotice("*** The seconds you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxSecs));
- return false;
- }
+ if (settings.Backlog && settings.Backlog > ms.MaxBacklog)
+ {
+ if (ms.MaxBacklog == 0)
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter,
+ "Invalid repeat parameter. The server administrator has disabled backlog matching."));
+ else
+ source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter, InspIRCd::Format(
+ "Invalid repeat paramter. The backlog you specified is too great. Maximum allowed is %u.", ms.MaxBacklog)));
+ return false;
}
return true;
class RepeatModule : public Module
{
+ CheckExemption::EventProvider exemptionprov;
RepeatMode rm;
public:
- RepeatModule() : rm(this) {}
-
- void init() CXX11_OVERRIDE
+ RepeatModule()
+ : exemptionprov(this)
+ , rm(this)
{
- ServerInstance->Modules->AddService(rm);
- ServerInstance->Modules->AddService(rm.ChanSet);
- ServerInstance->Modules->AddService(rm.MemberInfoExt);
- Implementation eventlist[] = { I_OnUserPreMessage, I_OnRehash };
- ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
- rm.ReadConfig();
}
- void OnRehash(User* user) CXX11_OVERRIDE
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
{
rm.ReadConfig();
}
- ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE
+ ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE
{
- if (target_type != TYPE_CHANNEL || !IS_LOCAL(user))
+ if (target.type != MessageTarget::TYPE_CHANNEL || !IS_LOCAL(user))
return MOD_RES_PASSTHRU;
- Membership* memb = ((Channel*)dest)->GetUser(user);
- if (!memb || !memb->chan->IsModeSet(&rm))
+ Channel* chan = target.Get<Channel>();
+ ChannelSettings* settings = rm.ext.get(chan);
+ if (!settings)
return MOD_RES_PASSTHRU;
- if (ServerInstance->OnCheckExemption(user, memb->chan, "repeat") == MOD_RES_ALLOW)
+ Membership* memb = chan->GetUser(user);
+ if (!memb)
return MOD_RES_PASSTHRU;
- RepeatMode::ChannelSettings* settings = rm.ChanSet.get(memb->chan);
- if (!settings)
+ ModResult res = CheckExemption::Call(exemptionprov, user, chan, "repeat");
+ if (res == MOD_RES_ALLOW)
return MOD_RES_PASSTHRU;
- if (rm.MatchLine(memb, settings, text))
+ if (rm.MatchLine(memb, settings, details.text))
{
- if (settings->Action == RepeatMode::ACT_BLOCK)
+ if (settings->Action == ChannelSettings::ACT_BLOCK)
{
- user->WriteNotice("*** This line is too similiar to one of your last lines.");
+ user->WriteNotice("*** This line is too similar to one of your last lines.");
return MOD_RES_DENY;
}
- if (settings->Action == RepeatMode::ACT_BAN)
+ if (settings->Action == ChannelSettings::ACT_BAN)
{
- std::vector<std::string> parameters;
- parameters.push_back(memb->chan->name);
- parameters.push_back("+b");
- parameters.push_back("*!*@" + user->dhost);
- ServerInstance->Modes->Process(parameters, ServerInstance->FakeClient);
+ Modes::ChangeList changelist;
+ changelist.push_add(ServerInstance->Modes->FindMode('b', MODETYPE_CHANNEL), "*!*@" + user->GetDisplayedHost());
+ ServerInstance->Modes->Process(ServerInstance->FakeClient, chan, NULL, changelist);
}
memb->chan->KickUser(ServerInstance->FakeClient, user, "Repeat flood");
Version GetVersion() CXX11_OVERRIDE
{
- return Version("Provides the +E channel mode - for blocking of similiar messages", VF_COMMON|VF_VENDOR, rm.GetModuleSettings());
+ return Version("Provides the +E channel mode - for blocking of similar messages", VF_COMMON|VF_VENDOR, rm.GetModuleSettings());
}
};