]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_hidemode.cpp
Make more modules rehash atomically (#1535)
[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                 RanksToSeeMap newranks;
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                         const std::string modename = tag->getString("mode");
47                         if (modename.empty())
48                                 throw ModuleException("<hidemode:mode> is empty at " + tag->getTagLocation());
49
50                         unsigned int rank = tag->getUInt("rank", 0);
51                         if (!rank)
52                                 throw ModuleException("<hidemode:rank> must be greater than 0 at " + tag->getTagLocation());
53
54                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Hiding the %s mode from users below rank %u", modename.c_str(), rank);
55                         newranks.insert(std::make_pair(modename, rank));
56                 }
57                 rankstosee.swap(newranks);
58         }
59 };
60
61 class ModeHook : public ClientProtocol::EventHook
62 {
63         typedef insp::flat_map<unsigned int, const ClientProtocol::MessageList*> FilteredModeMap;
64
65         std::vector<Modes::ChangeList> modechangelists;
66         std::list<ClientProtocol::Messages::Mode> filteredmodelist;
67         std::list<ClientProtocol::MessageList> filteredmsgplists;
68         FilteredModeMap cache;
69
70         static ModResult HandleResult(const ClientProtocol::MessageList* filteredmessagelist, ClientProtocol::MessageList& messagelist)
71         {
72                 // Deny if member isn't allowed to see even a single mode change from this mode event
73                 if (!filteredmessagelist)
74                         return MOD_RES_DENY;
75
76                 // Member is allowed to see at least one mode change, replace list
77                 if (filteredmessagelist != &messagelist)
78                         messagelist = *filteredmessagelist;
79
80                 return MOD_RES_PASSTHRU;
81         }
82
83         Modes::ChangeList* FilterModeChangeList(const ClientProtocol::Events::Mode& mode, unsigned int rank)
84         {
85                 Modes::ChangeList* modechangelist = NULL;
86                 for (Modes::ChangeList::List::const_iterator i = mode.GetChangeList().getlist().begin(); i != mode.GetChangeList().getlist().end(); ++i)
87                 {
88                         const Modes::Change& curr = *i;
89                         if (settings.GetRequiredRank(*curr.mh) <= rank)
90                         {
91                                  // No restriction on who can see this mode or there is one but the member's rank is sufficient
92                                 if (modechangelist)
93                                         modechangelist->push(curr);
94
95                                 continue;
96                         }
97
98                         // Member cannot see the current mode change
99
100                         if (!modechangelist)
101                         {
102                                 // Create new mode change list or reuse the last one if it's empty
103                                 if ((modechangelists.empty()) || (!modechangelists.back().empty()))
104                                         modechangelists.push_back(Modes::ChangeList());
105
106                                 // Add all modes to it which we've accepted so far
107                                 modechangelists.back().push(mode.GetChangeList().getlist().begin(), i);
108                                 modechangelist = &modechangelists.back();
109                         }
110                 }
111                 return modechangelist;
112         }
113
114         void OnEventInit(const ClientProtocol::Event& ev) CXX11_OVERRIDE
115         {
116                 cache.clear();
117                 filteredmsgplists.clear();
118                 filteredmodelist.clear();
119                 modechangelists.clear();
120
121                 // Ensure no reallocations will happen
122                 const size_t numprefixmodes = ServerInstance->Modes.GetPrefixModes().size();
123                 modechangelists.reserve(numprefixmodes);
124         }
125
126         ModResult OnPreEventSend(LocalUser* user, const ClientProtocol::Event& ev, ClientProtocol::MessageList& messagelist) CXX11_OVERRIDE
127         {
128                 const ClientProtocol::Events::Mode& mode = static_cast<const ClientProtocol::Events::Mode&>(ev);
129                 Channel* const chan = mode.GetMessages().front().GetChanTarget();
130                 if (!chan)
131                         return MOD_RES_PASSTHRU;
132
133                 Membership* const memb = chan->GetUser(user);
134                 if (!memb)
135                         return MOD_RES_PASSTHRU;
136
137                 // Check cache first
138                 const FilteredModeMap::const_iterator it = cache.find(memb->getRank());
139                 if (it != cache.end())
140                         return HandleResult(it->second, messagelist);
141
142                 // Message for this rank isn't cached, generate it now
143                 const Modes::ChangeList* const filteredchangelist = FilterModeChangeList(mode, memb->getRank());
144
145                 // If no new change list was generated (above method returned NULL) it means the member and everyone else
146                 // with the same rank can see everything in the original change list.
147                 ClientProtocol::MessageList* finalmsgplist = &messagelist;
148                 if (filteredchangelist)
149                 {
150                         if (filteredchangelist->empty())
151                         {
152                                 // This rank cannot see any mode changes in the original change list
153                                 finalmsgplist = NULL;
154                         }
155                         else
156                         {
157                                 // This rank can see some of the mode changes in the filtered mode change list.
158                                 // Create and store a new protocol message from it.
159                                 filteredmsgplists.push_back(ClientProtocol::MessageList());
160                                 ClientProtocol::Events::Mode::BuildMessages(mode.GetMessages().front().GetSourceUser(), chan, NULL, *filteredchangelist, filteredmodelist, filteredmsgplists.back());
161                                 finalmsgplist = &filteredmsgplists.back();
162                         }
163                 }
164
165                 // Cache the result in all cases so it can be reused for further members with the same rank
166                 cache.insert(std::make_pair(memb->getRank(), finalmsgplist));
167                 return HandleResult(finalmsgplist, messagelist);
168         }
169
170  public:
171         Settings settings;
172
173         ModeHook(Module* mod)
174                 : ClientProtocol::EventHook(mod, "MODE", 10)
175         {
176         }
177 };
178 }
179
180 class ModuleHideMode : public Module
181 {
182  private:
183         ModeHook modehook;
184
185  public:
186         ModuleHideMode()
187                 : modehook(this)
188         {
189         }
190
191         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
192         {
193                 modehook.settings.Load();
194         }
195
196         Version GetVersion() CXX11_OVERRIDE
197         {
198                 return Version("Provides support for hiding mode changes", VF_VENDOR);
199         }
200 };
201
202 MODULE_INIT(ModuleHideMode)