]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Add a module for hiding mode changes from unprivileged users.
authorPeter Powell <petpow@saberuk.com>
Sun, 9 Sep 2018 15:52:32 +0000 (16:52 +0100)
committerPeter Powell <petpow@saberuk.com>
Sun, 9 Sep 2018 15:52:54 +0000 (16:52 +0100)
Co-authored-by: Attila Molnar <attilamolnar@hush.com>
docs/conf/modules.conf.example
src/modules/m_hidemode.cpp [new file with mode: 0644]

index 9cc40bdd7e4e62d13b8f19d1af5efff032dfebba..bc90c7b147ac6b307ff2b2840592d104787a253c 100644 (file)
 # Only show invite exceptions (+I) to channel members:
 #<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.
diff --git a/src/modules/m_hidemode.cpp b/src/modules/m_hidemode.cpp
new file mode 100644 (file)
index 0000000..d6ae058
--- /dev/null
@@ -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)