/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2019 Matt Schatz * Copyright (C) 2018 linuxdaemon * Copyright (C) 2018 Sadie Powell * Copyright (C) 2018 Attila Molnar * * 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" namespace { class Settings { typedef insp::flat_map RanksToSeeMap; RanksToSeeMap rankstosee; public: unsigned int GetRequiredRank(const ModeHandler& mh) const { RanksToSeeMap::const_iterator it = rankstosee.find(mh.name); if (it != rankstosee.end()) return it->second; return 0; } void Load() { RanksToSeeMap newranks; ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode"); for (ConfigIter i = tags.first; i != tags.second; ++i) { ConfigTag* tag = i->second; const std::string modename = tag->getString("mode"); if (modename.empty()) throw ModuleException(" is empty at " + tag->getTagLocation()); unsigned int rank = tag->getUInt("rank", 0); if (!rank) throw ModuleException(" must be greater than 0 at " + tag->getTagLocation()); ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank); newranks.insert(std::make_pair(modename, rank)); } rankstosee.swap(newranks); } }; class ModeHook : public ClientProtocol::EventHook { typedef insp::flat_map FilteredModeMap; std::vector modechangelists; std::list filteredmodelist; std::list filteredmsgplists; FilteredModeMap cache; static ModResult HandleResult(const ClientProtocol::MessageList* filteredmessagelist, ClientProtocol::MessageList& messagelist) { // Deny if member isn't allowed to see even a single mode change from this mode event if (!filteredmessagelist) return MOD_RES_DENY; // Member is allowed to see at least one mode change, replace list if (filteredmessagelist != &messagelist) messagelist = *filteredmessagelist; return MOD_RES_PASSTHRU; } Modes::ChangeList* FilterModeChangeList(const ClientProtocol::Events::Mode& mode, unsigned int rank) { Modes::ChangeList* modechangelist = NULL; for (Modes::ChangeList::List::const_iterator i = mode.GetChangeList().getlist().begin(); i != mode.GetChangeList().getlist().end(); ++i) { const Modes::Change& curr = *i; if (settings.GetRequiredRank(*curr.mh) <= rank) { // No restriction on who can see this mode or there is one but the member's rank is sufficient if (modechangelist) modechangelist->push(curr); continue; } // Member cannot see the current mode change if (!modechangelist) { // Create new mode change list or reuse the last one if it's empty if ((modechangelists.empty()) || (!modechangelists.back().empty())) modechangelists.push_back(Modes::ChangeList()); // Add all modes to it which we've accepted so far modechangelists.back().push(mode.GetChangeList().getlist().begin(), i); modechangelist = &modechangelists.back(); } } return modechangelist; } void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE { cache.clear(); filteredmsgplists.clear(); filteredmodelist.clear(); modechangelists.clear(); // Ensure no reallocations will happen const size_t numprefixmodes = ServerInstance->Modes.GetPrefixModes().size(); modechangelists.reserve(numprefixmodes); } ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE { const ClientProtocol::Events::Mode& mode = static_cast(ev); Channel* const chan = mode.GetMessages().front().GetChanTarget(); if (!chan) return MOD_RES_PASSTHRU; if (user->HasPrivPermission("channels/auspex")) return MOD_RES_PASSTHRU; Membership* const memb = chan->GetUser(user); if (!memb) return MOD_RES_PASSTHRU; // Check cache first const FilteredModeMap::const_iterator it = cache.find(memb->getRank()); if (it != cache.end()) return HandleResult(it->second, messagelist); // Message for this rank isn't cached, generate it now const Modes::ChangeList* const filteredchangelist = FilterModeChangeList(mode, memb->getRank()); // If no new change list was generated (above method returned NULL) it means the member and everyone else // with the same rank can see everything in the original change list. ClientProtocol::MessageList* finalmsgplist = &messagelist; if (filteredchangelist) { if (filteredchangelist->empty()) { // This rank cannot see any mode changes in the original change list finalmsgplist = NULL; } else { // This rank can see some of the mode changes in the filtered mode change list. // Create and store a new protocol message from it. filteredmsgplists.push_back(ClientProtocol::MessageList()); ClientProtocol::Events::Mode::BuildMessages(mode.GetMessages().front().GetSourceUser(), chan, NULL, *filteredchangelist, filteredmodelist, filteredmsgplists.back()); finalmsgplist = &filteredmsgplists.back(); } } // Cache the result in all cases so it can be reused for further members with the same rank cache.insert(std::make_pair(memb->getRank(), finalmsgplist)); return HandleResult(finalmsgplist, messagelist); } public: Settings settings; ModeHook(Module* mod) : ClientProtocol::EventHook(mod, "MODE", 10) { } }; } class ModuleHideMode : public Module { private: ModeHook modehook; public: ModuleHideMode() : modehook(this) { } void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE { modehook.settings.Load(); } Version GetVersion() CXX11_OVERRIDE { return Version("Allows mode changes to be hidden from users without a prefix mode ranked equal to or higher than a defined level.", VF_VENDOR); } }; MODULE_INIT(ModuleHideMode)