summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorPeter Powell <petpow@saberuk.com>2018-09-09 16:52:32 +0100
committerPeter Powell <petpow@saberuk.com>2018-09-09 16:52:54 +0100
commit923d811b0564db891d7cb97c1ea81a5c901ff3a5 (patch)
tree391b13acb70670228b8bb9b234b9f25e66233ffe /src
parent8512726adf455358d2e467dcc79fc9913ccbcf6b (diff)
Add a module for hiding mode changes from unprivileged users.
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
Diffstat (limited to 'src')
-rw-r--r--src/modules/m_hidemode.cpp198
1 files changed, 198 insertions, 0 deletions
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)