/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2017 B00mX0r * Copyright (C) 2013, 2018, 2020-2022 Sadie Powell * Copyright (C) 2012-2014, 2016 Attila Molnar * Copyright (C) 2012, 2019 Robby * Copyright (C) 2012 Justin Crawford * Copyright (C) 2009 Uli Schlachter * Copyright (C) 2009 Daniel De Graaf * Copyright (C) 2008 Thomas Stagner * Copyright (C) 2007 Robin Burchell * Copyright (C) 2007 Dennis Friis * Copyright (C) 2006 Oliver Lupton * Copyright (C) 2005-2006, 2010 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 "modules/isupport.h" #include "numerichelper.h" class CommandRemove final : public Command { private: ChanModeReference nokicksmode; UserModeReference servprotectmode; public: ModeHandler::Rank protectedrank; bool supportnokicks; CommandRemove(Module* Creator) : Command(Creator, "REMOVE", 2, 3) , nokicksmode(Creator, "nokick") , servprotectmode(Creator, "servprotect") { syntax = { " [:]" }; translation = { TR_TEXT, TR_NICK, TR_TEXT }; } CmdResult Handle(User* user, const CommandBase::Params& parameters) override { // Keep compatibility with v3 servers by allowing them to send removes with the old order. bool neworder = !IS_LOCAL(user) && ServerInstance->Channels.IsPrefix(parameters[0][0]); const std::string& channame = parameters[neworder ? 0 : 1]; const std::string& username = parameters[neworder ? 1 : 0]; /* Look up the user we're meant to be removing from the channel */ User* target; if (IS_LOCAL(user)) target = ServerInstance->Users.FindNick(username); else target = ServerInstance->Users.Find(username); /* And the channel we're meant to be removing them from */ Channel* channel = ServerInstance->Channels.Find(channame); /* Fix by brain - someone needs to learn to validate their input! */ if (!channel) { user->WriteNumeric(Numerics::NoSuchChannel(channame)); return CmdResult::FAILURE; } if ((!target) || (target->registered != REG_ALL)) { user->WriteNumeric(Numerics::NoSuchNick(username)); return CmdResult::FAILURE; } if (!channel->HasUser(target)) { user->WriteNotice(InspIRCd::Format("*** User %s is not on channel %s", target->nick.c_str(), channel->name.c_str())); return CmdResult::FAILURE; } if (target->IsModeSet(servprotectmode)) { user->WriteNumeric(ERR_RESTRICTED, channame, "Only a service may remove a service from a channel."); return CmdResult::FAILURE; } /* We support the +Q channel mode via. the m_nokicks module, if the module is loaded and the mode is set then disallow the /remove */ if ((!IS_LOCAL(user)) || (!supportnokicks) || (!channel->IsModeSet(nokicksmode))) { /* We'll let everyone remove their level and below, eg: * ops can remove ops, halfops, voices, and those with no mode (no moders actually are set to 1) * a services target will get a higher level than it's possible for a /remover to get..so they're safe. * Nobody may remove people with >= protectedrank rank. */ ModeHandler::Rank ulevel = channel->GetPrefixValue(user); ModeHandler::Rank tlevel = channel->GetPrefixValue(target); if ((!IS_LOCAL(user)) || ((ulevel > VOICE_VALUE) && (ulevel >= tlevel) && ((protectedrank == 0) || (tlevel < protectedrank)))) { // REMOVE will be sent to the target's server and it will reply with a PART (or do nothing if it doesn't understand the command) if (!IS_LOCAL(target)) { CommandBase::Params p; p.push_back(channel->name); p.push_back(target->uuid); if (parameters.size() > 2) p.push_back(":" + parameters[2]); ServerInstance->PI->SendEncapsulatedData(target->server->GetName(), "REMOVE", p, user); return CmdResult::SUCCESS; } std::string reasonparam; /* If a reason is given, use it */ if(parameters.size() > 2) reasonparam = parameters[2]; else reasonparam = "No reason given"; /* Build up the part reason string. */ std::string reason = "Removed by " + user->nick + ": " + reasonparam; channel->WriteRemoteNotice(InspIRCd::Format("%s removed %s from the channel", user->nick.c_str(), target->nick.c_str())); target->WriteNotice("*** " + user->nick + " removed you from " + channel->name + " with the message: " + reasonparam); channel->PartUser(target, reason); } else { user->WriteNotice(InspIRCd::Format("*** You do not have access to /REMOVE %s from %s", target->nick.c_str(), channel->name.c_str())); return CmdResult::FAILURE; } } else { /* m_nokicks.so was loaded and +Q was set, block! */ user->WriteNumeric(ERR_RESTRICTED, channel->name, InspIRCd::Format("Can't remove user %s from channel (+Q is set)", target->nick.c_str())); return CmdResult::FAILURE; } return CmdResult::SUCCESS; } }; class ModuleRemove final : public Module , public ISupport::EventListener { private: CommandRemove cmd; public: ModuleRemove() : Module(VF_VENDOR | VF_OPTCOMMON, "Adds the /REMOVE command which allows channel operators to force part users from a channel.") , ISupport::EventListener(this) , cmd(this) { } void OnBuildISupport(ISupport::TokenMap& tokens) override { tokens["REMOVE"]; } void ReadConfig(ConfigStatus& status) override { auto tag = ServerInstance->Config->ConfValue("remove"); cmd.supportnokicks = tag->getBool("supportnokicks"); cmd.protectedrank = tag->getUInt("protectedrank", 50000); } }; MODULE_INIT(ModuleRemove)