From 889d521e05f2e922683d48c74eb00290e7a2f548 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Wed, 17 Jul 2024 00:06:50 +0100 Subject: Shuffle the modules about a bit. --- modules/joinflood.cpp | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 modules/joinflood.cpp (limited to 'modules/joinflood.cpp') diff --git a/modules/joinflood.cpp b/modules/joinflood.cpp new file mode 100644 index 000000000..73bef9f0e --- /dev/null +++ b/modules/joinflood.cpp @@ -0,0 +1,211 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2021 Dominic Hamon + * Copyright (C) 2016, 2018-2023 Sadie Powell + * Copyright (C) 2012-2014 Attila Molnar + * Copyright (C) 2012 Robby + * Copyright (C) 2009 Daniel De Graaf + * Copyright (C) 2008 Robin Burchell + * Copyright (C) 2007 Dennis Friis + * Copyright (C) 2006-2007 Craig Edwards + * + * 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 . + */ + + +#include "inspircd.h" +#include "extension.h" +#include "modules/server.h" +#include "numerichelper.h" + +// The number of seconds the channel will be closed for. +static unsigned int duration; + +/** Holds settings and state associated with channel mode +j + */ +class joinfloodsettings final +{ +public: + unsigned int secs; + unsigned int joins; + time_t reset; + time_t unlocktime = 0; + unsigned int counter = 0; + + joinfloodsettings(unsigned int b, unsigned int c) + : secs(b) + , joins(c) + { + reset = ServerInstance->Time() + secs; + } + + void addjoin() + { + if (ServerInstance->Time() > reset) + { + counter = 1; + reset = ServerInstance->Time() + secs; + } + else + counter++; + } + + bool shouldlock() const + { + return (counter >= this->joins); + } + + void clear() + { + counter = 0; + } + + bool islocked() + { + if (ServerInstance->Time() > unlocktime) + unlocktime = 0; + + return (unlocktime != 0); + } + + void lock() + { + unlocktime = ServerInstance->Time() + duration; + } + + bool operator==(const joinfloodsettings& other) const + { + return ((this->secs == other.secs) && (this->joins == other.joins)); + } +}; + +/** Handles channel mode +j + */ +class JoinFlood final + : public ParamMode> +{ +public: + JoinFlood(Module* Creator) + : ParamMode>(Creator, "joinflood", 'j') + { + syntax = ":"; + } + + bool OnSet(User* source, Channel* channel, std::string& parameter) override + { + std::string::size_type colon = parameter.find(':'); + if ((colon == std::string::npos) || (parameter.find('-') != std::string::npos)) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return false; + } + + /* Set up the flood parameters for this channel */ + unsigned int njoins = ConvToNum(parameter.substr(0, colon)); + unsigned int nsecs = ConvToNum(parameter.substr(colon+1)); + if ((njoins<1) || (nsecs<1)) + { + source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); + return false; + } + + ext.SetFwd(channel, nsecs, njoins); + return true; + } + + void SerializeParam(Channel* chan, const joinfloodsettings* jfs, std::string& out) + { + out.append(ConvToStr(jfs->joins)).push_back(':'); + out.append(ConvToStr(jfs->secs)); + } +}; + +class ModuleJoinFlood final + : public Module + , public ServerProtocol::LinkEventListener +{ +private: + JoinFlood jf; + time_t ignoreuntil = 0; + unsigned long bootwait; + unsigned long splitwait; + ModeHandler::Rank notifyrank; + +public: + ModuleJoinFlood() + : Module(VF_VENDOR, "Adds channel mode j (joinflood) which helps protect against spammers which mass-join channels.") + , ServerProtocol::LinkEventListener(this) + , jf(this) + { + } + + void ReadConfig(ConfigStatus& status) override + { + const auto& tag = ServerInstance->Config->ConfValue("joinflood"); + duration = static_cast(tag->getDuration("duration", 60, 10, 600)); + bootwait = tag->getDuration("bootwait", 30); + splitwait = tag->getDuration("splitwait", 30); + notifyrank = tag->getNum("notifyrank", 0); + + if (status.initial) + ignoreuntil = ServerInstance->startup_time + bootwait; + } + + void OnServerSplit(const Server* server, bool error) override + { + if (splitwait) + ignoreuntil = std::max(ignoreuntil, ServerInstance->Time() + splitwait); + } + + ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven, bool override) override + { + if (!override && chan) + { + joinfloodsettings* f = jf.ext.Get(chan); + if (f && f->islocked()) + { + user->WriteNumeric(ERR_UNAVAILRESOURCE, chan->name, fmt::format("This channel is temporarily unavailable (+{} is set). Please try again later.", + jf.GetModeChar())); + return MOD_RES_DENY; + } + } + return MOD_RES_PASSTHRU; + } + + void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) override + { + /* We arent interested in JOIN events caused by a network burst */ + if (sync || ignoreuntil > ServerInstance->Time()) + return; + + joinfloodsettings* f = jf.ext.Get(memb->chan); + + /* But all others are OK */ + if ((f) && (!f->islocked())) + { + f->addjoin(); + if (f->shouldlock()) + { + f->clear(); + f->lock(); + + PrefixMode* pm = ServerInstance->Modes.FindNearestPrefixMode(notifyrank); + memb->chan->WriteNotice(fmt::format("This channel has been closed to new users for {} seconds because there have been more than {} joins in {} seconds.", + duration, f->joins, f->secs), pm ? pm->GetPrefix() : 0); + } + } + } +}; + +MODULE_INIT(ModuleJoinFlood) -- cgit v1.3.1-10-gc9f91