/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2018-2023, 2025 Sadie Powell * Copyright (C) 2018 A_D * Copyright (C) 2012-2015 Attila Molnar * Copyright (C) 2012 Robby * Copyright (C) 2009 Uli Schlachter * Copyright (C) 2009 Daniel De Graaf * Copyright (C) 2007 Dennis Friis * Copyright (C) 2006 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/invite.h" #include "numerichelper.h" #include "timeutils.h" class KickRejoinData final { struct KickedUser final { std::string uuid; time_t expire; KickedUser(User* user, unsigned int Delay) : uuid(user->uuid) , expire(ServerInstance->Time() + Delay) { } }; typedef std::vector KickedList; mutable KickedList kicked; public: const unsigned int delay; KickRejoinData(unsigned int Delay) : delay(Delay) { } bool canjoin(LocalUser* user) const { for (KickedList::iterator i = kicked.begin(); i != kicked.end(); ) { KickedUser& rec = *i; if (rec.expire > ServerInstance->Time()) { if (rec.uuid == user->uuid) return false; ++i; } else { // Expired record, remove. stdalgo::vector::swaperase(kicked, i); if (kicked.empty()) break; } } return true; } void add(User* user) { // One user can be in the list multiple times if the user gets kicked, force joins // (skipping OnUserPreJoin) and gets kicked again, but that's okay because canjoin() // works correctly in this case as well kicked.emplace_back(user, delay); } }; /** Handles channel mode +J */ class KickRejoin final : public ParamMode> { public: const unsigned int max = 60; KickRejoin(Module* Creator) : ParamMode>(Creator, "kicknorejoin", 'J') { syntax = ""; } bool OnSet(User* source, Channel* channel, std::string& parameter) override { unsigned int v = ConvToNum(parameter); if (v <= 0) { source->WriteNumeric(Numerics::InvalidModeParameter(channel, this, parameter)); return false; } if (IS_LOCAL(source) && v > max) v = max; ext.Set(channel, v); return true; } void SerializeParam(Channel* chan, const KickRejoinData* krd, std::string& out) { out.append(ConvToStr(krd->delay)); } }; class ModuleKickNoRejoin final : public Module { KickRejoin kr; Invite::API invapi; public: ModuleKickNoRejoin() : Module(VF_VENDOR | VF_COMMON, "Adds channel mode J (kicknorejoin) which prevents users from rejoining after being kicked from a channel.") , kr(this) , invapi(this) { } ModResult OnUserPreJoin(LocalUser* user, Channel* chan, const std::string& cname, std::string& privs, const std::string& keygiven, bool override) override { if (!override && chan) { const KickRejoinData* data = kr.ext.Get(chan); if ((data) && !invapi->IsInvited(user, chan) && (!data->canjoin(user))) { user->WriteNumeric(ERR_UNAVAILRESOURCE, chan->name, FMT::format("You must wait for {} after being kicked to rejoin (+{} is set)", Duration::ToLongString(data->delay), kr.GetModeChar())); return MOD_RES_DENY; } } return MOD_RES_PASSTHRU; } void OnUserKick(User* source, Membership* memb, const std::string& reason, CUList& excepts) override { if ((!IS_LOCAL(memb->user)) || (source == memb->user)) return; KickRejoinData* data = kr.ext.Get(memb->chan); if (data) { data->add(memb->user); } } void GetLinkData(LinkData& data) override { data["max-time"] = ConvToStr(kr.max); } }; MODULE_INIT(ModuleKickNoRejoin)