inspircd/src/modules/m_hidemode.cpp

205 lines
6.3 KiB
C++
Raw Normal View History

/*
* InspIRCd -- Internet Relay Chat Daemon
*
2020-01-11 22:02:47 +00:00
* Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
* Copyright (C) 2018 linuxdaemon <linuxdaemon.irc@gmail.com>
* Copyright (C) 2018 Sadie Powell <sadie@witchery.services>
* Copyright (C) 2018 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()
{
RanksToSeeMap newranks;
2021-04-01 18:10:11 +01:00
for (const auto& [_, tag] : ServerInstance->Config->ConfTags("hidemode"))
{
const std::string modename = tag->getString("mode");
if (modename.empty())
throw ModuleException("<hidemode:mode> is empty at " + tag->source.str());
unsigned long rank = tag->getUInt("rank", 0);
if (!rank)
throw ModuleException("<hidemode:rank> must be greater than 0 at " + tag->source.str());
ServerInstance->Logs.Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %lu", modename.c_str(), rank);
newranks.emplace(modename, rank);
}
rankstosee.swap(newranks);
}
};
2021-10-01 06:23:26 +01:00
class ModeHook final
: 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()))
2021-03-30 19:44:07 +01:00
modechangelists.emplace_back();
// 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) 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) 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;
2019-10-19 20:35:55 -06:00
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.
2021-03-30 19:44:07 +01:00
filteredmsgplists.emplace_back();
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.emplace(memb->getRank(), finalmsgplist);
return HandleResult(finalmsgplist, messagelist);
}
public:
Settings settings;
ModeHook(Module* mod)
: ClientProtocol::EventHook(mod, "MODE", 10)
{
}
};
}
2021-10-01 04:37:03 +01:00
class ModuleHideMode final
: public Module
{
private:
ModeHook modehook;
public:
ModuleHideMode()
2020-04-11 14:00:48 +01:00
: Module(VF_VENDOR, "Allows mode changes to be hidden from users without a prefix mode ranked equal to or higher than a defined level.")
, modehook(this)
{
}
void ReadConfig(ConfigStatus& status) override
{
modehook.settings.Load();
}
};
MODULE_INIT(ModuleHideMode)