2018-09-09 16:52:32 +01:00
|
|
|
/*
|
|
|
|
* 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()
|
|
|
|
{
|
2018-12-18 19:06:56 -06:00
|
|
|
RanksToSeeMap newranks;
|
2018-09-09 16:52:32 +01:00
|
|
|
|
|
|
|
ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode");
|
|
|
|
for (ConfigIter i = tags.first; i != tags.second; ++i)
|
|
|
|
{
|
|
|
|
ConfigTag* tag = i->second;
|
2018-12-18 19:06:56 -06:00
|
|
|
const std::string modename = tag->getString("mode");
|
|
|
|
if (modename.empty())
|
|
|
|
throw ModuleException("<hidemode:mode> is empty at " + tag->getTagLocation());
|
|
|
|
|
|
|
|
unsigned int rank = tag->getUInt("rank", 0);
|
|
|
|
if (!rank)
|
|
|
|
throw ModuleException("<hidemode:rank> must be greater than 0 at " + tag->getTagLocation());
|
|
|
|
|
|
|
|
ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank);
|
|
|
|
newranks.insert(std::make_pair(modename, rank));
|
2018-09-09 16:52:32 +01:00
|
|
|
}
|
2018-12-18 19:06:56 -06:00
|
|
|
rankstosee.swap(newranks);
|
2018-09-09 16:52:32 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2019-10-19 20:35:55 -06:00
|
|
|
if (user->HasPrivPermission("channels/auspex"))
|
|
|
|
return MOD_RES_PASSTHRU;
|
|
|
|
|
2018-09-09 16:52:32 +01:00
|
|
|
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)
|