summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAttila Molnar <attilamolnar@hush.com>2015-12-05 15:29:01 +0100
committerAttila Molnar <attilamolnar@hush.com>2015-12-05 15:29:01 +0100
commit259b1113944a01aeb450265f03fb97a283e8ef15 (patch)
treea8f9bc4bba98bca09a2ddfa4e9bee525d566fca0
parent806f48627913db210be7c9104de5afe2f47bdb33 (diff)
Add rewritten m_cap module
- Caps are now managed by m_cap - Each cap uses one bit in an extension item shared with other caps
-rw-r--r--include/modules/cap.h191
-rw-r--r--src/modules/m_cap.cpp245
-rw-r--r--src/modules/m_ircv3.cpp16
-rw-r--r--src/modules/m_namesx.cpp8
-rw-r--r--src/modules/m_sasl.cpp8
-rw-r--r--src/modules/m_starttls.cpp2
-rw-r--r--src/modules/m_uhnames.cpp8
7 files changed, 457 insertions, 21 deletions
diff --git a/include/modules/cap.h b/include/modules/cap.h
new file mode 100644
index 000000000..9dd44a4aa
--- /dev/null
+++ b/include/modules/cap.h
@@ -0,0 +1,191 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#pragma once
+
+#include "event.h"
+
+namespace Cap
+{
+ static const unsigned int MAX_CAPS = sizeof(intptr_t) * 8;
+ typedef intptr_t Ext;
+ typedef LocalIntExt ExtItem;
+ class Capability;
+
+ class Manager : public DataProvider
+ {
+ public:
+ Manager(Module* mod)
+ : DataProvider(mod, "capmanager")
+ {
+ }
+
+ /** Register a client capability.
+ * Modules should call Capability::SetActive(true) instead of this method.
+ * @param cap Capability to register
+ */
+ virtual void AddCap(Capability* cap) = 0;
+
+ /** Unregister a client capability.
+ * Modules should call Capability::SetActive(false) instead of this method.
+ * @param cap Capability to unregister
+ */
+ virtual void DelCap(Capability* cap) = 0;
+
+ /** Find a capability by name
+ * @param name Capability to find
+ * @return Capability object pointer if found, NULL otherwise
+ */
+ virtual Capability* Find(const std::string& name) const = 0;
+ };
+
+ /** Represents a client capability.
+ *
+ * Capabilities offer extensions to the client to server protocol. They must be negotiated with clients before they have any effect on the protocol.
+ * Each cap must have a unique name that is used during capability negotiation.
+ *
+ * After construction the cap is ready to be used by clients without any further setup, like other InspIRCd services.
+ * The get() method accepts a user as parameter and can be used to check whether that user has negotiated usage of the cap. This is only known for local users.
+ *
+ * The cap module must be loaded for the capability to work. The IsRegistered() method can be used to query whether the cap is actually online or not.
+ * The capability can be deactivated and reactivated with the SetActive() method. Deactivated caps behave as if they don't exist.
+ */
+ class Capability : public ServiceProvider, private dynamic_reference_base::CaptureHook
+ {
+ typedef size_t Bit;
+
+ /** Bit allocated to this cap, undefined if the cap is unregistered
+ */
+ Bit bit;
+
+ /** Extension containing all caps set by a user. NULL if the cap is unregistered.
+ */
+ ExtItem* extitem;
+
+ /** True if the cap is active. Only active caps are registered in the manager.
+ */
+ bool active;
+
+ /** Reference to the cap manager object
+ */
+ dynamic_reference<Manager> manager;
+
+ void OnCapture() CXX11_OVERRIDE
+ {
+ if (active)
+ SetActive(true);
+ }
+
+ void Unregister()
+ {
+ bit = 0;
+ extitem = NULL;
+ }
+
+ Ext AddToMask(Ext mask) const { return (mask | GetMask()); }
+ Ext DelFromMask(Ext mask) const { return (mask & (~GetMask())); }
+ Bit GetMask() const { return bit; }
+
+ friend class ManagerImpl;
+
+ public:
+ /** Constructor, initializes the capability.
+ * Caps are active by default.
+ * @param mod Module providing the cap
+ * @param Name Raw name of the cap as used in the protocol (CAP LS, etc.)
+ */
+ Capability(Module* mod, const std::string& Name)
+ : ServiceProvider(mod, Name, SERVICE_CUSTOM)
+ , active(true)
+ , manager(mod, "capmanager")
+ {
+ Unregister();
+ }
+
+ ~Capability()
+ {
+ SetActive(false);
+ }
+
+ void RegisterService() CXX11_OVERRIDE
+ {
+ manager.SetCaptureHook(this);
+ SetActive(true);
+ }
+
+ /** Check whether a user has the capability turned on.
+ * This method is safe to call if the cap is unregistered and will return false.
+ * @param user User to check
+ * @return True if the user is using this capability, false otherwise
+ */
+ bool get(User* user) const
+ {
+ if (!IsRegistered())
+ return false;
+ Ext caps = extitem->get(user);
+ return (caps & GetMask());
+ }
+
+ /** Turn the capability on/off for a user. If the cap is not registered this method has no effect.
+ * @param user User to turn the cap on/off for
+ * @param val True to turn the cap on, false to turn it off
+ */
+ void set(User* user, bool val)
+ {
+ if (!IsRegistered())
+ return;
+ Ext curr = extitem->get(user);
+ extitem->set(user, (val ? AddToMask(curr) : DelFromMask(curr)));
+ }
+
+ /** Activate or deactivate the capability.
+ * If activating, the cap is marked as active and if the manager is available the cap is registered in the manager.
+ * If deactivating, the cap is marked as inactive and if it is registered, it will be unregistered.
+ * Users who had the cap turned on will have it turned off automatically.
+ * @param activate True to activate the cap, false to deactivate it
+ */
+ void SetActive(bool activate)
+ {
+ active = activate;
+ if (manager)
+ {
+ if (activate)
+ manager->AddCap(this);
+ else
+ manager->DelCap(this);
+ }
+ }
+
+ /** Get the name of the capability that's used in the protocol
+ * @return Name of the capability as used in the protocol
+ */
+ const std::string& GetName() const { return name; }
+
+ /** Check whether the capability is active. The cap must be active and registered to be used by users.
+ * @return True if the cap is active, false if it has been deactivated
+ */
+ bool IsActive() const { return active; }
+
+ /** Check whether the capability is registered
+ * The cap must be active and the manager must be available for a cap to be registered.
+ * @return True if the cap is registered in the manager, false otherwise
+ */
+ bool IsRegistered() const { return (extitem != NULL); }
+ };
+}
diff --git a/src/modules/m_cap.cpp b/src/modules/m_cap.cpp
new file mode 100644
index 000000000..9e857ad28
--- /dev/null
+++ b/src/modules/m_cap.cpp
@@ -0,0 +1,245 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+#include "modules/cap.h"
+
+namespace Cap
+{
+ class ManagerImpl;
+}
+
+class Cap::ManagerImpl : public Cap::Manager
+{
+ typedef insp::flat_map<std::string, Capability*, irc::insensitive_swo> CapMap;
+
+ ExtItem capext;
+ CapMap caps;
+
+ Capability::Bit AllocateBit() const
+ {
+ Capability::Bit used = 0;
+ for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
+ {
+ Capability* cap = i->second;
+ used |= cap->GetMask();
+ }
+
+ for (unsigned int i = 0; i < MAX_CAPS; i++)
+ {
+ Capability::Bit bit = (1 << i);
+ if (!(used & bit))
+ return bit;
+ }
+ throw ModuleException("Too many caps");
+ }
+
+ public:
+ ManagerImpl(Module* mod)
+ : Cap::Manager(mod)
+ , capext("caps", ExtensionItem::EXT_USER, mod)
+ {
+ }
+
+ ~ManagerImpl()
+ {
+ for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i)
+ {
+ Capability* cap = i->second;
+ cap->Unregister();
+ }
+ }
+
+ void AddCap(Cap::Capability* cap) CXX11_OVERRIDE
+ {
+ // No-op if the cap is already registered.
+ // This allows modules to call SetActive() on a cap without checking if it's active first.
+ if (cap->IsRegistered())
+ return;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Registering cap %s", cap->GetName().c_str());
+ cap->bit = AllocateBit();
+ cap->extitem = &capext;
+ caps.insert(std::make_pair(cap->GetName(), cap));
+ }
+
+ void DelCap(Cap::Capability* cap) CXX11_OVERRIDE
+ {
+ // No-op if the cap is not registered, see AddCap() above
+ if (!cap->IsRegistered())
+ return;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Unregistering cap %s", cap->GetName().c_str());
+
+ // Turn off the cap for all users
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ LocalUser* user = *i;
+ cap->set(user, false);
+ }
+
+ cap->Unregister();
+ caps.erase(cap->GetName());
+ }
+
+ Capability* Find(const std::string& capname) const CXX11_OVERRIDE
+ {
+ CapMap::const_iterator it = caps.find(capname);
+ if (it != caps.end())
+ return it->second;
+ return NULL;
+ }
+
+ bool HandleReq(LocalUser* user, const std::string& reqlist)
+ {
+ Ext usercaps = capext.get(user);
+ irc::spacesepstream ss(reqlist);
+ for (std::string capname; ss.GetToken(capname); )
+ {
+ bool remove = (capname[0] == '-');
+ if (remove)
+ capname.erase(capname.begin());
+
+ Capability* cap = ManagerImpl::Find(capname);
+ if (!cap)
+ return false;
+
+ if (remove)
+ usercaps = cap->DelFromMask(usercaps);
+ else
+ usercaps = cap->AddToMask(usercaps);
+ }
+
+ capext.set(user, usercaps);
+ return true;
+ }
+
+ void HandleList(std::string& out, LocalUser* user, bool show_all, bool minus_prefix = false) const
+ {
+ Ext show_caps = (show_all ? ~0 : capext.get(user));
+
+ for (CapMap::const_iterator i = caps.begin(); i != caps.end(); ++i)
+ {
+ Capability* cap = i->second;
+ if (!(show_caps & cap->GetMask()))
+ continue;
+
+ if (minus_prefix)
+ out.push_back('-');
+ out.append(cap->GetName()).push_back(' ');
+ }
+ }
+
+ void HandleClear(LocalUser* user, std::string& result)
+ {
+ HandleList(result, user, false, true);
+ capext.unset(user);
+ }
+};
+
+class CommandCap : public SplitCommand
+{
+ Cap::ManagerImpl manager;
+
+ static void DisplayResult(LocalUser* user, std::string& result)
+ {
+ if (result.size() > 5)
+ result.erase(result.end()-1);
+ user->WriteCommand("CAP", result);
+ }
+
+ public:
+ LocalIntExt holdext;
+
+ CommandCap(Module* mod)
+ : SplitCommand(mod, "CAP", 1)
+ , manager(mod)
+ , holdext("cap_hold", ExtensionItem::EXT_USER, mod)
+ {
+ works_before_reg = true;
+ }
+
+ CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE
+ {
+ if (user->registered != REG_ALL)
+ holdext.set(user, 1);
+
+ std::string subcommand(parameters[0].length(), ' ');
+ std::transform(parameters[0].begin(), parameters[0].end(), subcommand.begin(), ::toupper);
+
+ if (subcommand == "REQ")
+ {
+ if (parameters.size() < 2)
+ return CMD_FAILURE;
+
+ std::string result = (manager.HandleReq(user, parameters[1]) ? "ACK :" : "NAK :");
+ result.append(parameters[1]);
+ user->WriteCommand("CAP", result);
+ }
+ else if (subcommand == "END")
+ {
+ holdext.unset(user);
+ }
+ else if ((subcommand == "LS") || (subcommand == "LIST"))
+ {
+ const bool is_ls = (subcommand.length() == 2);
+
+ std::string result = subcommand + " :";
+ manager.HandleList(result, user, is_ls);
+ DisplayResult(user, result);
+ }
+ else if (subcommand == "CLEAR")
+ {
+ std::string result = "ACK :";
+ manager.HandleClear(user, result);
+ DisplayResult(user, result);
+ }
+ else
+ {
+ user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, "%s :Invalid CAP subcommand", subcommand.c_str());
+ return CMD_FAILURE;
+ }
+
+ return CMD_SUCCESS;
+ }
+};
+
+class ModuleCap : public Module
+{
+ CommandCap cmd;
+
+ public:
+ ModuleCap()
+ : cmd(this)
+ {
+ }
+
+ ModResult OnCheckReady(LocalUser* user) CXX11_OVERRIDE
+ {
+ return (cmd.holdext.get(user) ? MOD_RES_DENY : MOD_RES_PASSTHRU);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides support for CAP capability negotiation", VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleCap)
diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp
index 123d07069..c99d920ba 100644
--- a/src/modules/m_ircv3.cpp
+++ b/src/modules/m_ircv3.cpp
@@ -22,7 +22,7 @@
class WriteNeighborsWithCap : public User::ForEachNeighborHandler
{
- const LocalIntExt& cap;
+ const Cap::Capability& cap;
const std::string& msg;
void Execute(LocalUser* user) CXX11_OVERRIDE
@@ -32,8 +32,8 @@ class WriteNeighborsWithCap : public User::ForEachNeighborHandler
}
public:
- WriteNeighborsWithCap(User* user, const std::string& message, const GenericCap& capability)
- : cap(capability.ext)
+ WriteNeighborsWithCap(User* user, const std::string& message, const Cap::Capability& capability)
+ : cap(capability)
, msg(message)
{
user->ForEachNeighbor(*this, false);
@@ -42,9 +42,9 @@ class WriteNeighborsWithCap : public User::ForEachNeighborHandler
class ModuleIRCv3 : public Module, public AccountEventListener
{
- GenericCap cap_accountnotify;
- GenericCap cap_awaynotify;
- GenericCap cap_extendedjoin;
+ Cap::Capability cap_accountnotify;
+ Cap::Capability cap_awaynotify;
+ Cap::Capability cap_extendedjoin;
CUList last_excepts;
@@ -105,7 +105,7 @@ class ModuleIRCv3 : public Module, public AccountEventListener
{
// Send the extended join line if the current member is local, has the extended-join cap and isn't excepted
User* member = IS_LOCAL(it->first);
- if ((member) && (cap_extendedjoin.ext.get(member)) && (excepts.find(member) == excepts.end()))
+ if ((member) && (cap_extendedjoin.get(member)) && (excepts.find(member) == excepts.end()))
{
// Construct the lines we're going to send if we haven't constructed them already
if (line.empty())
@@ -179,7 +179,7 @@ class ModuleIRCv3 : public Module, public AccountEventListener
{
// Send the away notify line if the current member is local, has the away-notify cap and isn't excepted
User* member = IS_LOCAL(it->first);
- if ((member) && (cap_awaynotify.ext.get(member)) && (last_excepts.find(member) == last_excepts.end()))
+ if ((member) && (cap_awaynotify.get(member)) && (last_excepts.find(member) == last_excepts.end()))
{
member->Write(line);
}
diff --git a/src/modules/m_namesx.cpp b/src/modules/m_namesx.cpp
index c701f16bf..c906322bf 100644
--- a/src/modules/m_namesx.cpp
+++ b/src/modules/m_namesx.cpp
@@ -25,7 +25,7 @@
class ModuleNamesX : public Module
{
- GenericCap cap;
+ Cap::Capability cap;
public:
ModuleNamesX() : cap(this, "multi-prefix")
{
@@ -52,7 +52,7 @@ class ModuleNamesX : public Module
{
if ((parameters.size()) && (!strcasecmp(parameters[0].c_str(),"NAMESX")))
{
- cap.ext.set(user, 1);
+ cap.set(user, true);
return MOD_RES_DENY;
}
}
@@ -61,7 +61,7 @@ class ModuleNamesX : public Module
ModResult OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) CXX11_OVERRIDE
{
- if (cap.ext.get(issuer))
+ if (cap.get(issuer))
prefixes = memb->GetAllPrefixChars();
return MOD_RES_PASSTHRU;
@@ -69,7 +69,7 @@ class ModuleNamesX : public Module
void OnSendWhoLine(User* source, const std::vector<std::string>& params, User* user, Membership* memb, std::string& line) CXX11_OVERRIDE
{
- if ((!memb) || (!cap.ext.get(source)))
+ if ((!memb) || (!cap.get(source)))
return;
// Channel names can contain ":", and ":" as a 'start-of-token' delimiter is
diff --git a/src/modules/m_sasl.cpp b/src/modules/m_sasl.cpp
index 341b3aea7..297abad85 100644
--- a/src/modules/m_sasl.cpp
+++ b/src/modules/m_sasl.cpp
@@ -179,8 +179,8 @@ class CommandAuthenticate : public Command
{
public:
SimpleExtItem<SaslAuthenticator>& authExt;
- GenericCap& cap;
- CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, GenericCap& Cap)
+ Cap::Capability& cap;
+ CommandAuthenticate(Module* Creator, SimpleExtItem<SaslAuthenticator>& ext, Cap::Capability& Cap)
: Command(Creator, "AUTHENTICATE", 1), authExt(ext), cap(Cap)
{
works_before_reg = true;
@@ -191,7 +191,7 @@ class CommandAuthenticate : public Command
/* Only allow AUTHENTICATE on unregistered clients */
if (user->registered != REG_ALL)
{
- if (!cap.ext.get(user))
+ if (!cap.get(user))
return CMD_FAILURE;
SaslAuthenticator *sasl = authExt.get(user);
@@ -247,7 +247,7 @@ class CommandSASL : public Command
class ModuleSASL : public Module
{
SimpleExtItem<SaslAuthenticator> authExt;
- GenericCap cap;
+ Cap::Capability cap;
CommandAuthenticate auth;
CommandSASL sasl;
Events::ModuleEventProvider sasleventprov;
diff --git a/src/modules/m_starttls.cpp b/src/modules/m_starttls.cpp
index b05302fa9..a47480728 100644
--- a/src/modules/m_starttls.cpp
+++ b/src/modules/m_starttls.cpp
@@ -80,7 +80,7 @@ class CommandStartTLS : public SplitCommand
class ModuleStartTLS : public Module
{
CommandStartTLS starttls;
- GenericCap tls;
+ Cap::Capability tls;
dynamic_reference_nocheck<IOHookProvider> ssl;
public:
diff --git a/src/modules/m_uhnames.cpp b/src/modules/m_uhnames.cpp
index 90bac54f5..ce9c517f4 100644
--- a/src/modules/m_uhnames.cpp
+++ b/src/modules/m_uhnames.cpp
@@ -24,9 +24,9 @@
class ModuleUHNames : public Module
{
- public:
- GenericCap cap;
+ Cap::Capability cap;
+ public:
ModuleUHNames() : cap(this, "userhost-in-names")
{
}
@@ -52,7 +52,7 @@ class ModuleUHNames : public Module
{
if ((parameters.size()) && (!strcasecmp(parameters[0].c_str(),"UHNAMES")))
{
- cap.ext.set(user, 1);
+ cap.set(user, true);
return MOD_RES_DENY;
}
}
@@ -61,7 +61,7 @@ class ModuleUHNames : public Module
ModResult OnNamesListItem(User* issuer, Membership* memb, std::string& prefixes, std::string& nick) CXX11_OVERRIDE
{
- if (cap.ext.get(issuer))
+ if (cap.get(issuer))
nick = memb->user->GetFullHost();
return MOD_RES_PASSTHRU;