summaryrefslogtreecommitdiff
path: root/src/modules/m_hidemode.cpp
blob: 66e70fb892352f2b090ff6b2e1d1ec8c60c83d31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
/*
 * InspIRCd -- Internet Relay Chat Daemon
 *
 *   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;

		ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode");
		for (ConfigIter i = tags.first; i != tags.second; ++i)
		{
			ConfigTag* tag = i->second;
			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));
		}
		rankstosee.swap(newranks);
	}
};

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;

		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.
				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("Allows mode changes to be hidden from users without a prefix mode ranked equal to or higher than a defined level.", VF_VENDOR);
	}
};

MODULE_INIT(ModuleHideMode)