diff options
-rw-r--r-- | docs/conf/modules.conf.example | 8 | ||||
-rw-r--r-- | src/modules/m_hidemode.cpp | 198 |
2 files changed, 206 insertions, 0 deletions
diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 9cc40bdd7..bc90c7b14 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -906,6 +906,14 @@ #<hidelist mode="invex" rank="0"> #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# +# Hide mode module: Allows for hiding mode changes from users who do not +# have sufficient channel privileges. +#<module name="hidemode"> +# +# Hide bans (+b) from people who are not voiced: +#<hidemode mode="ban" rank="10000"> + +#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-# # Hide oper module: Allows opers to hide their oper status from non- # opers by setting user mode +H on themselves. # This module is oper-only. diff --git a/src/modules/m_hidemode.cpp b/src/modules/m_hidemode.cpp new file mode 100644 index 000000000..d6ae05801 --- /dev/null +++ b/src/modules/m_hidemode.cpp @@ -0,0 +1,198 @@ +/* + * InspIRCd -- Internet Relay Chat Daemon + * + * Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com> + * + * 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 <http://www.gnu.org/licenses/>. + */ + + +#include "inspircd.h" + +namespace +{ +class Settings +{ + typedef insp::flat_map<std::string, unsigned int> 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() + { + rankstosee.clear(); + + ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode"); + for (ConfigIter i = tags.first; i != tags.second; ++i) + { + ConfigTag* tag = i->second; + std::string modename = tag->getString("mode"); + unsigned int rank = tag->getInt("rank", 0, 0); + if (!modename.empty() && rank) + { + ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank); + rankstosee.insert(std::make_pair(modename, rank)); + } + } + } +}; + +class ModeHook : public ClientProtocol::EventHook +{ + typedef insp::flat_map<unsigned int, const ClientProtocol::MessageList*> FilteredModeMap; + + std::vector<Modes::ChangeList> modechangelists; + std::list<ClientProtocol::Messages::Mode> filteredmodelist; + std::list<ClientProtocol::MessageList> 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<const ClientProtocol::Events::Mode&>(ev); + Channel* const chan = mode.GetMessages().front().GetChanTarget(); + if (!chan) + 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("Provides support for hiding mode changes", VF_VENDOR); + } +}; + +MODULE_INIT(ModuleHideMode) |