]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_hidemode.cpp
Add a module for hiding mode changes from unprivileged users.
[user/henk/code/inspircd.git] / src / modules / m_hidemode.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21
22 namespace
23 {
24 class Settings
25 {
26         typedef insp::flat_map<std::string, unsigned int> RanksToSeeMap;
27         RanksToSeeMap rankstosee;
28
29  public:
30         unsigned int GetRequiredRank(const ModeHandler& mh) const
31         {
32                 RanksToSeeMap::const_iterator it = rankstosee.find(mh.name);
33                 if (it != rankstosee.end())
34                         return it->second;
35                 return 0;
36         }
37
38         void Load()
39         {
40                 rankstosee.clear();
41
42                 ConfigTagList tags = ServerInstance->Config->ConfTags("hidemode");
43                 for (ConfigIter i = tags.first; i != tags.second; ++i)
44                 {
45                         ConfigTag* tag = i->second;
46                         std::string modename = tag->getString("mode");
47                         unsigned int rank = tag->getInt("rank", 0, 0);
48                         if (!modename.empty() && rank)
49                         {
50                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank);
51                                 rankstosee.insert(std::make_pair(modename, rank));
52                         }
53                 }
54         }
55 };
56
57 class ModeHook : public ClientProtocol::EventHook
58 {
59         typedef insp::flat_map<unsigned int, const ClientProtocol::MessageList*> FilteredModeMap;
60
61         std::vector<Modes::ChangeList> modechangelists;
62         std::list<ClientProtocol::Messages::Mode> filteredmodelist;
63         std::list<ClientProtocol::MessageList> filteredmsgplists;
64         FilteredModeMap cache;
65
66         static ModResult HandleResult(const ClientProtocol::MessageList* filteredmessagelist, ClientProtocol::MessageList& messagelist)
67         {
68                 // Deny if member isn't allowed to see even a single mode change from this mode event
69                 if (!filteredmessagelist)
70                         return MOD_RES_DENY;
71
72                 // Member is allowed to see at least one mode change, replace list
73                 if (filteredmessagelist != &messagelist)
74                         messagelist = *filteredmessagelist;
75
76                 return MOD_RES_PASSTHRU;
77         }
78
79         Modes::ChangeList* FilterModeChangeList(const ClientProtocol::Events::Mode& mode, unsigned int rank)
80         {
81                 Modes::ChangeList* modechangelist = NULL;
82                 for (Modes::ChangeList::List::const_iterator i = mode.GetChangeList().getlist().begin(); i != mode.GetChangeList().getlist().end(); ++i)
83                 {
84                         const Modes::Change& curr = *i;
85                         if (settings.GetRequiredRank(*curr.mh) <= rank)
86                         {
87                                  // No restriction on who can see this mode or there is one but the member's rank is sufficient
88                                 if (modechangelist)
89                                         modechangelist->push(curr);
90
91                                 continue;
92                         }
93
94                         // Member cannot see the current mode change
95
96                         if (!modechangelist)
97                         {
98                                 // Create new mode change list or reuse the last one if it's empty
99                                 if ((modechangelists.empty()) || (!modechangelists.back().empty()))
100                                         modechangelists.push_back(Modes::ChangeList());
101
102                                 // Add all modes to it which we've accepted so far
103                                 modechangelists.back().push(mode.GetChangeList().getlist().begin(), i);
104                                 modechangelist = &modechangelists.back();
105                         }
106                 }
107                 return modechangelist;
108         }
109
110         void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
111         {
112                 cache.clear();
113                 filteredmsgplists.clear();
114                 filteredmodelist.clear();
115                 modechangelists.clear();
116
117                 // Ensure no reallocations will happen
118                 const size_t numprefixmodes = ServerInstance->Modes.GetPrefixModes().size();
119                 modechangelists.reserve(numprefixmodes);
120         }
121
122         ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
123         {
124                 const ClientProtocol::Events::Mode& mode = static_cast<const ClientProtocol::Events::Mode&>(ev);
125                 Channel* const chan = mode.GetMessages().front().GetChanTarget();
126                 if (!chan)
127                         return MOD_RES_PASSTHRU;
128
129                 Membership* const memb = chan->GetUser(user);
130                 if (!memb)
131                         return MOD_RES_PASSTHRU;
132
133                 // Check cache first
134                 const FilteredModeMap::const_iterator it = cache.find(memb->getRank());
135                 if (it != cache.end())
136                         return HandleResult(it->second, messagelist);
137
138                 // Message for this rank isn't cached, generate it now
139                 const Modes::ChangeList* const filteredchangelist = FilterModeChangeList(mode, memb->getRank());
140
141                 // If no new change list was generated (above method returned NULL) it means the member and everyone else
142                 // with the same rank can see everything in the original change list.
143                 ClientProtocol::MessageList* finalmsgplist = &messagelist;
144                 if (filteredchangelist)
145                 {
146                         if (filteredchangelist->empty())
147                         {
148                                 // This rank cannot see any mode changes in the original change list
149                                 finalmsgplist = NULL;
150                         }
151                         else
152                         {
153                                 // This rank can see some of the mode changes in the filtered mode change list.
154                                 // Create and store a new protocol message from it.
155                                 filteredmsgplists.push_back(ClientProtocol::MessageList());
156                                 ClientProtocol::Events::Mode::BuildMessages(mode.GetMessages().front().GetSourceUser(), chan, NULL, *filteredchangelist, filteredmodelist, filteredmsgplists.back());
157                                 finalmsgplist = &filteredmsgplists.back();
158                         }
159                 }
160
161                 // Cache the result in all cases so it can be reused for further members with the same rank
162                 cache.insert(std::make_pair(memb->getRank(), finalmsgplist));
163                 return HandleResult(finalmsgplist, messagelist);
164         }
165
166  public:
167         Settings settings;
168
169         ModeHook(Module* mod)
170                 : ClientProtocol::EventHook(mod, "MODE", 10)
171         {
172         }
173 };
174 }
175
176 class ModuleHideMode : public Module
177 {
178  private:
179         ModeHook modehook;
180
181  public:
182         ModuleHideMode()
183                 : modehook(this)
184         {
185         }
186
187         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
188         {
189                 modehook.settings.Load();
190         }
191
192         Version GetVersion() CXX11_OVERRIDE
193         {
194                 return Version("Provides support for hiding mode changes", VF_VENDOR);
195         }
196 };
197
198 MODULE_INIT(ModuleHideMode)