2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
5 * Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
6 * Copyright (C) 2018 Sadie Powell <sadie@witchery.services>
7 * Copyright (C) 2018 Attila Molnar <attilamolnar@hush.com>
9 * This file is part of InspIRCd. InspIRCd is free software: you can
10 * redistribute it and/or modify it under the terms of the GNU General Public
11 * License as published by the Free Software Foundation, version 2.
13 * This program is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 typedef insp::flat_map<std::string, unsigned int> RanksToSeeMap;
30 RanksToSeeMap rankstosee;
33 unsigned int GetRequiredRank(const ModeHandler& mh) const
35 RanksToSeeMap::const_iterator it = rankstosee.find(mh.name);
36 if (it != rankstosee.end())
43 RanksToSeeMap newranks;
45 ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode");
46 for (ConfigIter i = tags.first; i != tags.second; ++i)
48 ConfigTag* tag = i->second;
49 const std::string modename = tag->getString("mode");
51 throw ModuleException("<hidemode:mode> is empty at " + tag->getTagLocation());
53 unsigned int rank = tag->getUInt("rank", 0);
55 throw ModuleException("<hidemode:rank> must be greater than 0 at " + tag->getTagLocation());
57 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank);
58 newranks.insert(std::make_pair(modename, rank));
60 rankstosee.swap(newranks);
64 class ModeHook : public ClientProtocol::EventHook
66 typedef insp::flat_map<unsigned int, const ClientProtocol::MessageList*> FilteredModeMap;
68 std::vector<Modes::ChangeList> modechangelists;
69 std::list<ClientProtocol::Messages::Mode> filteredmodelist;
70 std::list<ClientProtocol::MessageList> filteredmsgplists;
71 FilteredModeMap cache;
73 static ModResult HandleResult(const ClientProtocol::MessageList* filteredmessagelist, ClientProtocol::MessageList& messagelist)
75 // Deny if member isn't allowed to see even a single mode change from this mode event
76 if (!filteredmessagelist)
79 // Member is allowed to see at least one mode change, replace list
80 if (filteredmessagelist != &messagelist)
81 messagelist = *filteredmessagelist;
83 return MOD_RES_PASSTHRU;
86 Modes::ChangeList* FilterModeChangeList(const ClientProtocol::Events::Mode& mode, unsigned int rank)
88 Modes::ChangeList* modechangelist = NULL;
89 for (Modes::ChangeList::List::const_iterator i = mode.GetChangeList().getlist().begin(); i != mode.GetChangeList().getlist().end(); ++i)
91 const Modes::Change& curr = *i;
92 if (settings.GetRequiredRank(*curr.mh) <= rank)
94 // No restriction on who can see this mode or there is one but the member's rank is sufficient
96 modechangelist->push(curr);
101 // Member cannot see the current mode change
105 // Create new mode change list or reuse the last one if it's empty
106 if ((modechangelists.empty()) || (!modechangelists.back().empty()))
107 modechangelists.push_back(Modes::ChangeList());
109 // Add all modes to it which we've accepted so far
110 modechangelists.back().push(mode.GetChangeList().getlist().begin(), i);
111 modechangelist = &modechangelists.back();
114 return modechangelist;
117 void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
120 filteredmsgplists.clear();
121 filteredmodelist.clear();
122 modechangelists.clear();
124 // Ensure no reallocations will happen
125 const size_t numprefixmodes = ServerInstance->Modes.GetPrefixModes().size();
126 modechangelists.reserve(numprefixmodes);
129 ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
131 const ClientProtocol::Events::Mode& mode = static_cast<const ClientProtocol::Events::Mode&>(ev);
132 Channel* const chan = mode.GetMessages().front().GetChanTarget();
134 return MOD_RES_PASSTHRU;
136 if (user->HasPrivPermission("channels/auspex"))
137 return MOD_RES_PASSTHRU;
139 Membership* const memb = chan->GetUser(user);
141 return MOD_RES_PASSTHRU;
144 const FilteredModeMap::const_iterator it = cache.find(memb->getRank());
145 if (it != cache.end())
146 return HandleResult(it->second, messagelist);
148 // Message for this rank isn't cached, generate it now
149 const Modes::ChangeList* const filteredchangelist = FilterModeChangeList(mode, memb->getRank());
151 // If no new change list was generated (above method returned NULL) it means the member and everyone else
152 // with the same rank can see everything in the original change list.
153 ClientProtocol::MessageList* finalmsgplist = &messagelist;
154 if (filteredchangelist)
156 if (filteredchangelist->empty())
158 // This rank cannot see any mode changes in the original change list
159 finalmsgplist = NULL;
163 // This rank can see some of the mode changes in the filtered mode change list.
164 // Create and store a new protocol message from it.
165 filteredmsgplists.push_back(ClientProtocol::MessageList());
166 ClientProtocol::Events::Mode::BuildMessages(mode.GetMessages().front().GetSourceUser(), chan, NULL, *filteredchangelist, filteredmodelist, filteredmsgplists.back());
167 finalmsgplist = &filteredmsgplists.back();
171 // Cache the result in all cases so it can be reused for further members with the same rank
172 cache.insert(std::make_pair(memb->getRank(), finalmsgplist));
173 return HandleResult(finalmsgplist, messagelist);
179 ModeHook(Module* mod)
180 : ClientProtocol::EventHook(mod, "MODE", 10)
186 class ModuleHideMode : public Module
197 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
199 modehook.settings.Load();
202 Version GetVersion() CXX11_OVERRIDE
204 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);
208 MODULE_INIT(ModuleHideMode)