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