]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_cap.cpp
e1593e33f50f54f64a870d814346721a2fca68ac
[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/cap.h"
22
23 namespace Cap
24 {
25         class ManagerImpl;
26 }
27
28 class Cap::ManagerImpl : public Cap::Manager
29 {
30         typedef insp::flat_map<std::string, Capability*, irc::insensitive_swo> CapMap;
31
32         ExtItem capext;
33         CapMap caps;
34         Events::ModuleEventProvider& evprov;
35
36         static bool CanRequest(LocalUser* user, Ext usercaps, Capability* cap, bool adding)
37         {
38                 if ((usercaps & cap->GetMask()) == adding)
39                         return true;
40
41                 return cap->OnRequest(user, adding);
42         }
43
44         Capability::Bit AllocateBit() const
45         {
46                 Capability::Bit used = 0;
47                 for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
48                 {
49                         Capability* cap = i->second;
50                         used |= cap->GetMask();
51                 }
52
53                 for (unsigned int i = 0; i < MAX_CAPS; i++)
54                 {
55                         Capability::Bit bit = (1 << i);
56                         if (!(used & bit))
57                                 return bit;
58                 }
59                 throw ModuleException("Too many caps");
60         }
61
62  public:
63         ManagerImpl(Module* mod, Events::ModuleEventProvider& evprovref)
64                 : Cap::Manager(mod)
65                 , capext("caps", ExtensionItem::EXT_USER, mod)
66                 , evprov(evprovref)
67         {
68         }
69
70         ~ManagerImpl()
71         {
72                 for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i)
73                 {
74                         Capability* cap = i->second;
75                         cap->Unregister();
76                 }
77         }
78
79         void AddCap(Cap::Capability* cap) CXX11_OVERRIDE
80         {
81                 // No-op if the cap is already registered.
82                 // This allows modules to call SetActive() on a cap without checking if it's active first.
83                 if (cap->IsRegistered())
84                         return;
85
86                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Registering cap %s", cap->GetName().c_str());
87                 cap->bit = AllocateBit();
88                 cap->extitem = &capext;
89                 caps.insert(std::make_pair(cap->GetName(), cap));
90
91                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, true));
92         }
93
94         void DelCap(Cap::Capability* cap) CXX11_OVERRIDE
95         {
96                 // No-op if the cap is not registered, see AddCap() above
97                 if (!cap->IsRegistered())
98                         return;
99
100                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unregistering cap %s", cap->GetName().c_str());
101
102                 // Fire the event first so modules can still see who is using the cap which is being unregistered
103                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, false));
104
105                 // Turn off the cap for all users
106                 const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
107                 for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
108                 {
109                         LocalUser* user = *i;
110                         cap->set(user, false);
111                 }
112
113                 cap->Unregister();
114                 caps.erase(cap->GetName());
115         }
116
117         Capability* Find(const std::string& capname) const CXX11_OVERRIDE
118         {
119                 CapMap::const_iterator it = caps.find(capname);
120                 if (it != caps.end())
121                         return it->second;
122                 return NULL;
123         }
124
125         void NotifyValueChange(Capability* cap) CXX11_OVERRIDE
126         {
127                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s changed value", cap->GetName().c_str());
128                 FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapValueChange, (cap));
129         }
130
131         Protocol GetProtocol(LocalUser* user) const
132         {
133                 return ((capext.get(user) & CAP_302_BIT) ? CAP_302 : CAP_LEGACY);
134         }
135
136         void Set302Protocol(LocalUser* user)
137         {
138                 capext.set(user, capext.get(user) | CAP_302_BIT);
139         }
140
141         bool HandleReq(LocalUser* user, const std::string& reqlist)
142         {
143                 Ext usercaps = capext.get(user);
144                 irc::spacesepstream ss(reqlist);
145                 for (std::string capname; ss.GetToken(capname); )
146                 {
147                         bool remove = (capname[0] == '-');
148                         if (remove)
149                                 capname.erase(capname.begin());
150
151                         Capability* cap = ManagerImpl::Find(capname);
152                         if ((!cap) || (!CanRequest(user, usercaps, cap, !remove)))
153                                 return false;
154
155                         if (remove)
156                                 usercaps = cap->DelFromMask(usercaps);
157                         else
158                                 usercaps = cap->AddToMask(usercaps);
159                 }
160
161                 capext.set(user, usercaps);
162                 return true;
163         }
164
165         void HandleList(std::string& out, LocalUser* user, bool show_all, bool show_values, bool minus_prefix = false) const
166         {
167                 Ext show_caps = (show_all ? ~0 : capext.get(user));
168
169                 for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
170                 {
171                         Capability* cap = i->second;
172                         if (!(show_caps & cap->GetMask()))
173                                 continue;
174
175                         if ((show_all) && (!cap->OnList(user)))
176                                 continue;
177
178                         if (minus_prefix)
179                                 out.push_back('-');
180                         out.append(cap->GetName());
181
182                         if (show_values)
183                         {
184                                 const std::string* capvalue = cap->GetValue(user);
185                                 if ((capvalue) && (!capvalue->empty()) && (capvalue->find(' ') == std::string::npos))
186                                 {
187                                         out.push_back('=');
188                                         out.append(*capvalue, 0, MAX_VALUE_LENGTH);
189                                 }
190                         }
191                         out.push_back(' ');
192                 }
193         }
194
195         void HandleClear(LocalUser* user, std::string& result)
196         {
197                 HandleList(result, user, false, false, true);
198                 capext.unset(user);
199         }
200 };
201
202 class CommandCap : public SplitCommand
203 {
204         Events::ModuleEventProvider evprov;
205         Cap::ManagerImpl manager;
206
207         static void DisplayResult(LocalUser* user, std::string& result)
208         {
209                 if (result.size() > 5)
210                         result.erase(result.end()-1);
211                 user->WriteCommand("CAP", result);
212         }
213
214  public:
215         LocalIntExt holdext;
216
217         CommandCap(Module* mod)
218                 : SplitCommand(mod, "CAP", 1)
219                 , evprov(mod, "event/cap")
220                 , manager(mod, evprov)
221                 , holdext("cap_hold", ExtensionItem::EXT_USER, mod)
222         {
223                 works_before_reg = true;
224         }
225
226         CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE
227         {
228                 if (user->registered != REG_ALL)
229                         holdext.set(user, 1);
230
231                 std::string subcommand(parameters[0].length(), ' ');
232                 std::transform(parameters[0].begin(), parameters[0].end(), subcommand.begin(), ::toupper);
233
234                 if (subcommand == "REQ")
235                 {
236                         if (parameters.size() < 2)
237                                 return CMD_FAILURE;
238
239                         std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :");
240                         result.append(parameters[1]);
241                         user->WriteCommand("CAP", result);
242                 }
243                 else if (subcommand == "END")
244                 {
245                         holdext.unset(user);
246                 }
247                 else if ((subcommand == "LS") || (subcommand == "LIST"))
248                 {
249                         const bool is_ls = (subcommand.length() == 2);
250                         if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
251                                 manager.Set302Protocol(user);
252
253                         std::string result = subcommand + " :";
254                         // Show values only if supports v3.2 and doing LS
255                         manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY)));
256                         DisplayResult(user, result);
257                 }
258                 else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
259                 {
260                         std::string result = "ACK :";
261                         manager.HandleClear(user, result);
262                         DisplayResult(user, result);
263                 }
264                 else
265                 {
266                         user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, "%s :Invalid CAP subcommand", subcommand.c_str());
267                         return CMD_FAILURE;
268                 }
269
270                 return CMD_SUCCESS;
271         }
272 };
273
274 class ModuleCap : public Module
275 {
276         CommandCap cmd;
277
278  public:
279         ModuleCap()
280                 : cmd(this)
281         {
282         }
283
284         ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
285         {
286                 return (cmd.holdext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU);
287         }
288
289         Version GetVersion() CXX11_OVERRIDE
290         {
291                 return Version("Provides support for CAP capability negotiation", VF_VENDOR);
292         }
293 };
294
295 MODULE_INIT(ModuleCap)