]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_cap.cpp
Merge tag 'v2.0.25' into master.
[user/henk/code/inspircd.git] / src / modules / m_cap.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2015 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 #include "modules/reload.h"
22 #include "modules/cap.h"
23
24 namespace Cap
25 {
26         class ManagerImpl;
27 }
28
29 static Cap::ManagerImpl* managerimpl;
30
31 class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
32 {
33         /** Stores the cap state of a module being reloaded
34          */
35         struct CapModData
36         {
37                 struct Data
38                 {
39                         std::string name;
40                         std::vector<std::string> users;
41
42                         Data(Capability* cap)
43                                 : name(cap->GetName())
44                         {
45                         }
46                 };
47                 std::vector<Data> caps;
48         };
49
50         typedef insp::flat_map<std::string, Capability*, irc::insensitive_swo> CapMap;
51
52         ExtItem capext;
53         CapMap caps;
54         Events::ModuleEventProvider& evprov;
55
56         static bool CanRequest(LocalUser* user, Ext usercaps, Capability* cap, bool adding)
57         {
58                 const bool hascap = ((usercaps & cap->GetMask()) != 0);
59                 if (hascap == adding)
60                         return true;
61
62                 return cap->OnRequest(user, adding);
63         }
64
65         Capability::Bit AllocateBit() const
66         {
67                 Capability::Bit used = 0;
68                 for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
69                 {
70                         Capability* cap = i->second;
71                         used |= cap->GetMask();
72                 }
73
74                 for (unsigned int i = 0; i < MAX_CAPS; i++)
75                 {
76                         Capability::Bit bit = (1 << i);
77                         if (!(used & bit))
78                                 return bit;
79                 }
80                 throw ModuleException("Too many caps");
81         }
82
83         void OnReloadModuleSave(Module* mod, ReloadModule::CustomData& cd) CXX11_OVERRIDE
84         {
85                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "OnReloadModuleSave()");
86                 if (mod == creator)
87                         return;
88
89                 CapModData* capmoddata = new CapModData;
90                 cd.add(this, capmoddata);
91
92                 for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i)
93                 {
94                         Capability* cap = i->second;
95                         // Only save users of caps that belong to the module being reloaded
96                         if (cap->creator != mod)
97                                 continue;
98
99                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Module being reloaded implements cap %s, saving cap users", cap->GetName().c_str());
100                         capmoddata->caps.push_back(CapModData::Data(cap));
101                         CapModData::Data& capdata = capmoddata->caps.back();
102
103                         // Populate list with uuids of users who are using the cap
104                         const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
105                         for (UserManager::LocalList::const_iterator j = list.begin(); j != list.end(); ++j)
106                         {
107                                 LocalUser* user = *j;
108                                 if (cap->get(user))
109                                         capdata.users.push_back(user->uuid);
110                         }
111                 }
112         }
113
114         void OnReloadModuleRestore(Module* mod, void* data) CXX11_OVERRIDE
115         {
116                 CapModData* capmoddata = static_cast<CapModData*>(data);
117                 for (std::vector<CapModData::Data>::const_iterator i = capmoddata->caps.begin(); i != capmoddata->caps.end(); ++i)
118                 {
119                         const CapModData::Data& capdata = *i;
120                         Capability* cap = ManagerImpl::Find(capdata.name);
121                         if (!cap)
122                         {
123                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s is no longer available after reload", capdata.name.c_str());
124                                 continue;
125                         }
126
127                         // Set back the cap for all users who were using it before the reload
128                         for (std::vector<std::string>::const_iterator j = capdata.users.begin(); j != capdata.users.end(); ++j)
129                         {
130                                 const std::string& uuid = *j;
131                                 User* user = ServerInstance->FindUUID(uuid);
132                                 if (!user)
133                                 {
134                                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone when trying to restore cap %s", uuid.c_str(), capdata.name.c_str());
135                                         continue;
136                                 }
137
138                                 cap->set(user, true);
139                         }
140                 }
141                 delete capmoddata;
142         }
143
144  public:
145         ManagerImpl(Module* mod, Events::ModuleEventProvider& evprovref)
146                 : Cap::Manager(mod)
147                 , ReloadModule::EventListener(mod)
148                 , capext(mod)
149                 , evprov(evprovref)
150         {
151                 managerimpl = this;
152         }
153
154         ~ManagerImpl()
155         {
156                 for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i)
157                 {
158                         Capability* cap = i->second;
159                         cap->Unregister();
160                 }
161         }
162
163         void AddCap(Cap::Capability* cap) CXX11_OVERRIDE
164         {
165                 // No-op if the cap is already registered.
166                 // This allows modules to call SetActive() on a cap without checking if it's active first.
167                 if (cap->IsRegistered())
168                         return;
169
170                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Registering cap %s", cap->GetName().c_str());
171                 cap->bit = AllocateBit();
172                 cap->extitem = &capext;
173                 caps.insert(std::make_pair(cap->GetName(), cap));
174                 ServerInstance->Modules.AddReferent("cap/" + cap->GetName(), cap);
175
176                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, true));
177         }
178
179         void DelCap(Cap::Capability* cap) CXX11_OVERRIDE
180         {
181                 // No-op if the cap is not registered, see AddCap() above
182                 if (!cap->IsRegistered())
183                         return;
184
185                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unregistering cap %s", cap->GetName().c_str());
186
187                 // Fire the event first so modules can still see who is using the cap which is being unregistered
188                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, false));
189
190                 // Turn off the cap for all users
191                 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
192                 for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
193                 {
194                         LocalUser* user = *i;
195                         cap->set(user, false);
196                 }
197
198                 ServerInstance->Modules.DelReferent(cap);
199                 cap->Unregister();
200                 caps.erase(cap->GetName());
201         }
202
203         Capability* Find(const std::string& capname) const CXX11_OVERRIDE
204         {
205                 CapMap::const_iterator it = caps.find(capname);
206                 if (it != caps.end())
207                         return it->second;
208                 return NULL;
209         }
210
211         void NotifyValueChange(Capability* cap) CXX11_OVERRIDE
212         {
213                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s changed value", cap->GetName().c_str());
214                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapValueChange, (cap));
215         }
216
217         Protocol GetProtocol(LocalUser* user) const
218         {
219                 return ((capext.get(user) & CAP_302_BIT) ? CAP_302 : CAP_LEGACY);
220         }
221
222         void Set302Protocol(LocalUser* user)
223         {
224                 capext.set(user, capext.get(user) | CAP_302_BIT);
225         }
226
227         bool HandleReq(LocalUser* user, const std::string& reqlist)
228         {
229                 Ext usercaps = capext.get(user);
230                 irc::spacesepstream ss(reqlist);
231                 for (std::string capname; ss.GetToken(capname); )
232                 {
233                         bool remove = (capname[0] == '-');
234                         if (remove)
235                                 capname.erase(capname.begin());
236
237                         Capability* cap = ManagerImpl::Find(capname);
238                         if ((!cap) || (!CanRequest(user, usercaps, cap, !remove)))
239                                 return false;
240
241                         if (remove)
242                                 usercaps = cap->DelFromMask(usercaps);
243                         else
244                                 usercaps = cap->AddToMask(usercaps);
245                 }
246
247                 capext.set(user, usercaps);
248                 return true;
249         }
250
251         void HandleList(std::string& out, LocalUser* user, bool show_all, bool show_values, bool minus_prefix = false) const
252         {
253                 Ext show_caps = (show_all ? ~0 : capext.get(user));
254
255                 for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
256                 {
257                         Capability* cap = i->second;
258                         if (!(show_caps & cap->GetMask()))
259                                 continue;
260
261                         if ((show_all) && (!cap->OnList(user)))
262                                 continue;
263
264                         if (minus_prefix)
265                                 out.push_back('-');
266                         out.append(cap->GetName());
267
268                         if (show_values)
269                         {
270                                 const std::string* capvalue = cap->GetValue(user);
271                                 if ((capvalue) && (!capvalue->empty()) && (capvalue->find(' ') == std::string::npos))
272                                 {
273                                         out.push_back('=');
274                                         out.append(*capvalue, 0, MAX_VALUE_LENGTH);
275                                 }
276                         }
277                         out.push_back(' ');
278                 }
279         }
280
281         void HandleClear(LocalUser* user, std::string& result)
282         {
283                 HandleList(result, user, false, false, true);
284                 capext.unset(user);
285         }
286 };
287
288 Cap::ExtItem::ExtItem(Module* mod)
289         : LocalIntExt("caps", ExtensionItem::EXT_USER, mod)
290 {
291 }
292
293 std::string Cap::ExtItem::serialize(SerializeFormat format, const Extensible* container, void* item) const
294 {
295         std::string ret;
296         // XXX: Cast away the const because IS_LOCAL() doesn't handle it
297         LocalUser* user = IS_LOCAL(const_cast<User*>(static_cast<const User*>(container)));
298         if ((format == FORMAT_NETWORK) || (!user))
299                 return ret;
300
301         // List requested caps
302         managerimpl->HandleList(ret, user, false, false);
303
304         // Serialize cap protocol version. If building a human-readable string append a new token, otherwise append only a single character indicating the version.
305         Protocol protocol = managerimpl->GetProtocol(user);
306         if (format == FORMAT_USER)
307                 ret.append("capversion=3.");
308         else if (!ret.empty())
309                 ret.erase(ret.length()-1);
310
311         if (protocol == CAP_302)
312                 ret.push_back('2');
313         else
314                 ret.push_back('1');
315
316         return ret;
317 }
318
319 void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, const std::string& value)
320 {
321         if (format == FORMAT_NETWORK)
322                 return;
323
324         LocalUser* user = IS_LOCAL(static_cast<User*>(container));
325         if (!user)
326                 return; // Can't happen
327
328         // Process the cap protocol version which is a single character at the end of the serialized string
329         const char verchar = *value.rbegin();
330         if (verchar == '2')
331                 managerimpl->Set302Protocol(user);
332
333         // Remove the version indicator from the string passed to HandleReq
334         std::string caplist(value, 0, value.size()-1);
335         managerimpl->HandleReq(user, caplist);
336 }
337
338 class CommandCap : public SplitCommand
339 {
340         Events::ModuleEventProvider evprov;
341         Cap::ManagerImpl manager;
342
343         static void DisplayResult(LocalUser* user, std::string& result)
344         {
345                 if (*result.rbegin() == ' ')
346                         result.erase(result.end()-1);
347                 user->WriteCommand("CAP", result);
348         }
349
350  public:
351         LocalIntExt holdext;
352
353         CommandCap(Module* mod)
354                 : SplitCommand(mod, "CAP", 1)
355                 , evprov(mod, "event/cap")
356                 , manager(mod, evprov)
357                 , holdext("cap_hold", ExtensionItem::EXT_USER, mod)
358         {
359                 works_before_reg = true;
360         }
361
362         CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE
363         {
364                 if (user->registered != REG_ALL)
365                         holdext.set(user, 1);
366
367                 std::string subcommand(parameters[0].length(), ' ');
368                 std::transform(parameters[0].begin(), parameters[0].end(), subcommand.begin(), ::toupper);
369
370                 if (subcommand == "REQ")
371                 {
372                         if (parameters.size() < 2)
373                                 return CMD_FAILURE;
374
375                         std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :");
376                         result.append(parameters[1]);
377                         user->WriteCommand("CAP", result);
378                 }
379                 else if (subcommand == "END")
380                 {
381                         holdext.unset(user);
382                 }
383                 else if ((subcommand == "LS") || (subcommand == "LIST"))
384                 {
385                         const bool is_ls = (subcommand.length() == 2);
386                         if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
387                                 manager.Set302Protocol(user);
388
389                         std::string result = subcommand + " :";
390                         // Show values only if supports v3.2 and doing LS
391                         manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY)));
392                         DisplayResult(user, result);
393                 }
394                 else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
395                 {
396                         std::string result = "ACK :";
397                         manager.HandleClear(user, result);
398                         DisplayResult(user, result);
399                 }
400                 else
401                 {
402                         user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, subcommand.empty() ? "*" : subcommand, "Invalid CAP subcommand");
403                         return CMD_FAILURE;
404                 }
405
406                 return CMD_SUCCESS;
407         }
408 };
409
410 class ModuleCap : public Module
411 {
412         CommandCap cmd;
413
414  public:
415         ModuleCap()
416                 : cmd(this)
417         {
418         }
419
420         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
421         {
422                 return (cmd.holdext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU);
423         }
424
425         Version GetVersion() CXX11_OVERRIDE
426         {
427                 return Version("Provides support for CAP capability negotiation", VF_VENDOR);
428         }
429 };
430
431 MODULE_INIT(ModuleCap)