summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/conf/modules.conf.example16
-rw-r--r--include/modules/cap.h316
-rw-r--r--include/modules/ircv3.h45
-rw-r--r--src/modules/m_cap.cpp430
-rw-r--r--src/modules/m_hostcycle.cpp14
-rw-r--r--src/modules/m_ircv3.cpp35
-rw-r--r--src/modules/m_ircv3_capnotify.cpp149
-rw-r--r--src/modules/m_ircv3_chghost.cpp57
-rw-r--r--src/modules/m_namesx.cpp8
-rw-r--r--src/modules/m_sasl.cpp51
-rw-r--r--src/modules/m_starttls.cpp2
-rw-r--r--src/modules/m_uhnames.cpp8
12 files changed, 1088 insertions, 43 deletions
diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example
index 055bdc0b4..52a8f5eeb 100644
--- a/docs/conf/modules.conf.example
+++ b/docs/conf/modules.conf.example
@@ -878,6 +878,9 @@
# hostcycle: If loaded, when a user gets a host or ident set, it will
# cycle them in all their channels. If not loaded it will simply change
# their host/ident without cycling them.
+# This module is compatible with the ircv3_chghost module. Clients
+# supporting the chghost extension will get the chghost message instead
+# of seeing a host cycle.
#<module name="hostcycle">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
@@ -966,6 +969,19 @@
#<ircv3 accountnotify="on" awaynotify="on" extendedjoin="on">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# IRCv3 cap-notify module: Provides the cap-notify IRCv3.2 extension.
+# Required for IRCv3.2 conformance.
+#<module name="ircv3_capnotify">
+
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# IRCv3 chghost module: Provides the chghost IRCv3.2 extension which
+# allows capable clients to learn when the host/ident of another user
+# changes without cycling the user. This module is compatible with the
+# hostcycle module. If both are loaded, clients supporting the chghost
+# extension will get the chghost message and won't see host cycling.
+#<module name="ircv3_chghost">
+
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Join flood module: Adds support for join flood protection +j X:Y.
# Closes the channel for 60 seconds if X users join in Y seconds.
#<module name="joinflood">
diff --git a/include/modules/cap.h b/include/modules/cap.h
new file mode 100644
index 000000000..e6f9340e8
--- /dev/null
+++ b/include/modules/cap.h
@@ -0,0 +1,316 @@
+/*
+ * 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) - 1;
+ static const intptr_t CAP_302_BIT = (intptr_t)1 << MAX_CAPS;
+ static const unsigned int MAX_VALUE_LENGTH = 100;
+
+ typedef intptr_t Ext;
+ class ExtItem : public LocalIntExt
+ {
+ public:
+ ExtItem(Module* mod);
+ std::string serialize(SerializeFormat format, const Extensible* container, void* item) const;
+ void unserialize(SerializeFormat format, Extensible* container, const std::string& value);
+ };
+
+ class Capability;
+
+ enum Protocol
+ {
+ /** Supports capability negotiation protocol v3.1, or none
+ */
+ CAP_LEGACY,
+
+ /** Supports capability negotiation v3.2
+ */
+ CAP_302
+ };
+
+ class EventListener : public Events::ModuleEventListener
+ {
+ public:
+ EventListener(Module* mod)
+ : ModuleEventListener(mod, "event/cap")
+ {
+ }
+
+ /** Called whenever a new client capability becomes available or unavailable
+ * @param cap Capability being added or removed
+ * @param add If true, the capability is being added, otherwise its being removed
+ */
+ virtual void OnCapAddDel(Capability* cap, bool add) = 0;
+
+ /** Called whenever the value of a cap changes.
+ * @param cap Capability whose value changed
+ */
+ virtual void OnCapValueChange(Capability* cap) { }
+ };
+
+ 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;
+
+ /** Notify manager when a value of a cap changed
+ * @param cap Cap whose value changed
+ */
+ virtual void NotifyValueChange(Capability* cap) = 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.
+ *
+ * It is possible to implement special behavior by inheriting from this class and overriding some of its methods.
+ */
+ 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;
+
+ protected:
+ /** Notify the manager that the value of the capability changed.
+ * Must be called if the value of the cap changes for any reason.
+ */
+ void NotifyValueChange()
+ {
+ if (IsRegistered())
+ manager->NotifyValueChange(this);
+ }
+
+ 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); }
+
+ /** Get the CAP negotiation protocol version of a user.
+ * The cap must be registered for this to return anything other than CAP_LEGACY.
+ * @param user User whose negotiation protocol version to query
+ * @return One of the Capability::Protocol enum indicating the highest supported capability negotiation protocol version
+ */
+ Protocol GetProtocol(LocalUser* user) const
+ {
+ return ((IsRegistered() && (extitem->get(user) & CAP_302_BIT)) ? CAP_302 : CAP_LEGACY);
+ }
+
+ /** Called when a user requests to turn this capability on or off.
+ * @param user User requesting to change the state of the cap
+ * @param add True if requesting to turn the cap on, false if requesting to turn it off
+ * @return True to allow the request, false to reject it
+ */
+ virtual bool OnRequest(LocalUser* user, bool add)
+ {
+ return true;
+ }
+
+ /** Called when a user requests a list of all capabilities and this capability is about to be included in the list.
+ * The default behavior always includes the cap in the list.
+ * @param user User querying a list capabilities
+ * @return True to add this cap to the list sent to the user, false to not list it
+ */
+ virtual bool OnList(LocalUser* user)
+ {
+ return true;
+ }
+
+ /** Query the value of this capability for a user
+ * @param user User who will get the value of the capability
+ * @return Value to show to the user. If NULL, the capability has no value (default).
+ */
+ virtual const std::string* GetValue(LocalUser* user) const
+ {
+ return NULL;
+ }
+ };
+
+ /** Reference to a cap. The cap may be provided by another module.
+ */
+ class Reference
+ {
+ dynamic_reference_nocheck<Capability> ref;
+
+ public:
+ /** Constructor, initializes the capability reference
+ * @param mod Module creating this object
+ * @param Name Raw name of the cap as used in the protocol (CAP LS, etc.)
+ */
+ Reference(Module* mod, const std::string& Name)
+ : ref(mod, "cap/" + Name)
+ {
+ }
+
+ /** Check whether a user has the referenced capability turned on.
+ * @param user User to check
+ * @return True if the user is using the referenced capability, false otherwise
+ */
+ bool get(LocalUser* user)
+ {
+ if (ref)
+ return ref->get(user);
+ return false;
+ }
+ };
+}
diff --git a/include/modules/ircv3.h b/include/modules/ircv3.h
new file mode 100644
index 000000000..e03ee16fa
--- /dev/null
+++ b/include/modules/ircv3.h
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+namespace IRCv3
+{
+ class WriteNeighborsWithCap;
+}
+
+class IRCv3::WriteNeighborsWithCap : public User::ForEachNeighborHandler
+{
+ const Cap::Capability& cap;
+ const std::string& msg;
+
+ void Execute(LocalUser* user) CXX11_OVERRIDE
+ {
+ if (cap.get(user))
+ user->Write(msg);
+ }
+
+ public:
+ WriteNeighborsWithCap(User* user, const std::string& message, const Cap::Capability& capability)
+ : cap(capability)
+ , msg(message)
+ {
+ user->ForEachNeighbor(*this, false);
+ }
+};
diff --git a/src/modules/m_cap.cpp b/src/modules/m_cap.cpp
new file mode 100644
index 000000000..3e9c37a34
--- /dev/null
+++ b/src/modules/m_cap.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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/reload.h"
+#include "modules/cap.h"
+
+namespace Cap
+{
+ class ManagerImpl;
+}
+
+static Cap::ManagerImpl* managerimpl;
+
+class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
+{
+ /** Stores the cap state of a module being reloaded
+ */
+ struct CapModData
+ {
+ struct Data
+ {
+ std::string name;
+ std::vector<std::string> users;
+
+ Data(Capability* cap)
+ : name(cap->GetName())
+ {
+ }
+ };
+ std::vector<Data> caps;
+ };
+
+ typedef insp::flat_map<std::string, Capability*, irc::insensitive_swo> CapMap;
+
+ ExtItem capext;
+ CapMap caps;
+ Events::ModuleEventProvider& evprov;
+
+ static bool CanRequest(LocalUser* user, Ext usercaps, Capability* cap, bool adding)
+ {
+ if ((usercaps & cap->GetMask()) == adding)
+ return true;
+
+ return cap->OnRequest(user, adding);
+ }
+
+ 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");
+ }
+
+ void OnReloadModuleSave(Module* mod, ReloadModule::CustomData& cd) CXX11_OVERRIDE
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "OnReloadModuleSave()");
+ if (mod == creator)
+ return;
+
+ CapModData* capmoddata = new CapModData;
+ cd.add(this, capmoddata);
+
+ for (CapMap::iterator i = caps.begin(); i != caps.end(); ++i)
+ {
+ Capability* cap = i->second;
+ // Only save users of caps that belong to the module being reloaded
+ if (cap->creator != mod)
+ continue;
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Module being reloaded implements cap %s, saving cap users", cap->GetName().c_str());
+ capmoddata->caps.push_back(CapModData::Data(cap));
+ CapModData::Data& capdata = capmoddata->caps.back();
+
+ // Populate list with uuids of users who are using the cap
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator j = list.begin(); j != list.end(); ++j)
+ {
+ LocalUser* user = *j;
+ if (cap->get(user))
+ capdata.users.push_back(user->uuid);
+ }
+ }
+ }
+
+ void OnReloadModuleRestore(Module* mod, void* data) CXX11_OVERRIDE
+ {
+ CapModData* capmoddata = static_cast<CapModData*>(data);
+ for (std::vector<CapModData::Data>::const_iterator i = capmoddata->caps.begin(); i != capmoddata->caps.end(); ++i)
+ {
+ const CapModData::Data& capdata = *i;
+ Capability* cap = ManagerImpl::Find(capdata.name);
+ if (!cap)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s is no longer available after reload", capdata.name.c_str());
+ continue;
+ }
+
+ // Set back the cap for all users who were using it before the reload
+ for (std::vector<std::string>::const_iterator j = capdata.users.begin(); j != capdata.users.end(); ++j)
+ {
+ const std::string& uuid = *j;
+ User* user = ServerInstance->FindUUID(uuid);
+ if (!user)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "User %s is gone when trying to restore cap %s", uuid.c_str(), capdata.name.c_str());
+ continue;
+ }
+
+ cap->set(user, true);
+ }
+ }
+ delete capmoddata;
+ }
+
+ public:
+ ManagerImpl(Module* mod, Events::ModuleEventProvider& evprovref)
+ : Cap::Manager(mod)
+ , ReloadModule::EventListener(mod)
+ , capext(mod)
+ , evprov(evprovref)
+ {
+ managerimpl = this;
+ }
+
+ ~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));
+ ServerInstance->Modules.AddReferent("cap/" + cap->GetName(), cap);
+
+ FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, true));
+ }
+
+ 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());
+
+ // Fire the event first so modules can still see who is using the cap which is being unregistered
+ FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapAddDel, (cap, false));
+
+ // 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);
+ }
+
+ ServerInstance->Modules.DelReferent(cap);
+ 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;
+ }
+
+ void NotifyValueChange(Capability* cap) CXX11_OVERRIDE
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Cap %s changed value", cap->GetName().c_str());
+ FOREACH_MOD_CUSTOM(evprov, Cap::EventListener, OnCapValueChange, (cap));
+ }
+
+ Protocol GetProtocol(LocalUser* user) const
+ {
+ return ((capext.get(user) & CAP_302_BIT) ? CAP_302 : CAP_LEGACY);
+ }
+
+ void Set302Protocol(LocalUser* user)
+ {
+ capext.set(user, capext.get(user) | CAP_302_BIT);
+ }
+
+ 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) || (!CanRequest(user, usercaps, cap, !remove)))
+ 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 show_values, 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 ((show_all) && (!cap->OnList(user)))
+ continue;
+
+ if (minus_prefix)
+ out.push_back('-');
+ out.append(cap->GetName());
+
+ if (show_values)
+ {
+ const std::string* capvalue = cap->GetValue(user);
+ if ((capvalue) && (!capvalue->empty()) && (capvalue->find(' ') == std::string::npos))
+ {
+ out.push_back('=');
+ out.append(*capvalue, 0, MAX_VALUE_LENGTH);
+ }
+ }
+ out.push_back(' ');
+ }
+ }
+
+ void HandleClear(LocalUser* user, std::string& result)
+ {
+ HandleList(result, user, false, false, true);
+ capext.unset(user);
+ }
+};
+
+Cap::ExtItem::ExtItem(Module* mod)
+ : LocalIntExt("caps", ExtensionItem::EXT_USER, mod)
+{
+}
+
+std::string Cap::ExtItem::serialize(SerializeFormat format, const Extensible* container, void* item) const
+{
+ std::string ret;
+ // XXX: Cast away the const because IS_LOCAL() doesn't handle it
+ LocalUser* user = IS_LOCAL(const_cast<User*>(static_cast<const User*>(container)));
+ if ((format == FORMAT_NETWORK) || (!user))
+ return ret;
+
+ // List requested caps
+ managerimpl->HandleList(ret, user, false, false);
+
+ // Serialize cap protocol version. If building a human-readable string append a new token, otherwise append only a single character indicating the version.
+ Protocol protocol = managerimpl->GetProtocol(user);
+ if (format == FORMAT_USER)
+ ret.append("capversion=3.");
+ else if (!ret.empty())
+ ret.erase(ret.length()-1);
+
+ if (protocol == CAP_302)
+ ret.push_back('2');
+ else
+ ret.push_back('1');
+
+ return ret;
+}
+
+void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, const std::string& value)
+{
+ if (format == FORMAT_NETWORK)
+ return;
+
+ LocalUser* user = IS_LOCAL(static_cast<User*>(container));
+ if (!user)
+ return; // Can't happen
+
+ // Process the cap protocol version which is a single character at the end of the serialized string
+ const char verchar = *value.rbegin();
+ if (verchar == '2')
+ managerimpl->Set302Protocol(user);
+
+ // Remove the version indicator from the string passed to HandleReq
+ std::string caplist(value, 0, value.size()-1);
+ managerimpl->HandleReq(user, caplist);
+}
+
+class CommandCap : public SplitCommand
+{
+ Events::ModuleEventProvider evprov;
+ 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)
+ , evprov(mod, "event/cap")
+ , manager(mod, evprov)
+ , 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);
+ if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
+ manager.Set302Protocol(user);
+
+ std::string result = subcommand + " :";
+ // Show values only if supports v3.2 and doing LS
+ manager.HandleList(result, user, is_ls, ((is_ls) && (manager.GetProtocol(user) != Cap::CAP_LEGACY)));
+ DisplayResult(user, result);
+ }
+ else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
+ {
+ 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_hostcycle.cpp b/src/modules/m_hostcycle.cpp
index d4def6473..b33c101ef 100644
--- a/src/modules/m_hostcycle.cpp
+++ b/src/modules/m_hostcycle.cpp
@@ -19,12 +19,15 @@
#include "inspircd.h"
+#include "modules/cap.h"
class ModuleHostCycle : public Module
{
+ Cap::Reference chghostcap;
+
/** Send fake quit/join/mode messages for host or ident cycle.
*/
- static void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg)
+ void DoHostCycle(User* user, const std::string& newident, const std::string& newhost, const char* quitmsg)
{
// GetFullHost() returns the original data at the time this function is called
const std::string quitline = ":" + user->GetFullHost() + " QUIT :" + quitmsg;
@@ -40,7 +43,7 @@ class ModuleHostCycle : public Module
for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
{
LocalUser* u = IS_LOCAL(i->first);
- if (u && !u->quitting)
+ if ((u) && (!u->quitting) && (!chghostcap.get(u)))
{
if (i->second)
{
@@ -80,6 +83,8 @@ class ModuleHostCycle : public Module
continue;
if (u->already_sent == silent_id)
continue;
+ if (chghostcap.get(u))
+ continue;
if (u->already_sent != seen_id)
{
@@ -95,6 +100,11 @@ class ModuleHostCycle : public Module
}
public:
+ ModuleHostCycle()
+ : chghostcap(this, "chghost")
+ {
+ }
+
void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE
{
DoHostCycle(user, newident, user->dhost, "Changing ident");
diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp
index caee0d329..5275e9bd5 100644
--- a/src/modules/m_ircv3.cpp
+++ b/src/modules/m_ircv3.cpp
@@ -19,32 +19,13 @@
#include "inspircd.h"
#include "modules/account.h"
#include "modules/cap.h"
-
-class WriteNeighboursWithExt : public User::ForEachNeighborHandler
-{
- const LocalIntExt& ext;
- const std::string& msg;
-
- void Execute(LocalUser* user) CXX11_OVERRIDE
- {
- if (ext.get(user))
- user->Write(msg);
- }
-
- public:
- WriteNeighboursWithExt(User* user, const std::string& message, const LocalIntExt& extension)
- : ext(extension)
- , msg(message)
- {
- user->ForEachNeighbor(*this, false);
- }
-};
+#include "modules/ircv3.h"
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;
@@ -76,7 +57,7 @@ class ModuleIRCv3 : public Module, public AccountEventListener
else
line += newaccount;
- WriteNeighboursWithExt(user, line, cap_accountnotify.ext);
+ IRCv3::WriteNeighborsWithCap(user, line, cap_accountnotify);
}
void OnUserJoin(Membership* memb, bool sync, bool created, CUList& excepts) CXX11_OVERRIDE
@@ -105,7 +86,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())
@@ -162,7 +143,7 @@ class ModuleIRCv3 : public Module, public AccountEventListener
if (!awaymsg.empty())
line += " :" + awaymsg;
- WriteNeighboursWithExt(user, line, cap_awaynotify.ext);
+ IRCv3::WriteNeighborsWithCap(user, line, cap_awaynotify);
}
return MOD_RES_PASSTHRU;
}
@@ -179,7 +160,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_ircv3_capnotify.cpp b/src/modules/m_ircv3_capnotify.cpp
new file mode 100644
index 000000000..b19c2665d
--- /dev/null
+++ b/src/modules/m_ircv3_capnotify.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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"
+#include "modules/reload.h"
+
+class CapNotify : public Cap::Capability
+{
+ bool OnRequest(LocalUser* user, bool add) CXX11_OVERRIDE
+ {
+ // Users using the negotiation protocol v3.2 or newer may not turn off cap-notify
+ if ((!add) && (GetProtocol(user) != Cap::CAP_LEGACY))
+ return false;
+ return true;
+ }
+
+ bool OnList(LocalUser* user) CXX11_OVERRIDE
+ {
+ // If the client supports 3.2 enable cap-notify for them
+ if (GetProtocol(user) != Cap::CAP_LEGACY)
+ set(user, true);
+ return true;
+ }
+
+ public:
+ CapNotify(Module* mod)
+ : Cap::Capability(mod, "cap-notify")
+ {
+ }
+};
+
+class ModuleIRCv3CapNotify : public Module, public Cap::EventListener, public ReloadModule::EventListener
+{
+ CapNotify capnotify;
+ std::string reloadedmod;
+ std::vector<std::string> reloadedcaps;
+
+ void Send(const std::string& capname, Cap::Capability* cap, bool add)
+ {
+ std::string msg = (add ? "ADD :" : "DEL :");
+ msg.append(capname);
+ std::string msgwithval = msg;
+ msgwithval.push_back('=');
+ std::string::size_type msgpos = msgwithval.size();
+
+ const UserManager::LocalList& list = ServerInstance->Users.GetLocalUsers();
+ for (UserManager::LocalList::const_iterator i = list.begin(); i != list.end(); ++i)
+ {
+ LocalUser* user = *i;
+ if (!capnotify.get(user))
+ continue;
+
+ // If the cap is being added and the client supports cap values then show the value, if any
+ if ((add) && (capnotify.GetProtocol(user) != Cap::CAP_LEGACY))
+ {
+ const std::string* capvalue = cap->GetValue(user);
+ if ((capvalue) && (!capvalue->empty()))
+ {
+ msgwithval.append(*capvalue);
+ user->WriteCommand("CAP", msgwithval);
+ msgwithval.erase(msgpos);
+ continue;
+ }
+ }
+ user->WriteCommand("CAP", msg);
+ }
+ }
+
+ public:
+ ModuleIRCv3CapNotify()
+ : Cap::EventListener(this)
+ , ReloadModule::EventListener(this)
+ , capnotify(this)
+ {
+ }
+
+ void OnCapAddDel(Cap::Capability* cap, bool add) CXX11_OVERRIDE
+ {
+ if (cap->creator == this)
+ return;
+
+ if (cap->creator->ModuleSourceFile == reloadedmod)
+ {
+ if (!add)
+ reloadedcaps.push_back(cap->GetName());
+ return;
+ }
+ Send(cap->GetName(), cap, add);
+ }
+
+ void OnCapValueChange(Cap::Capability* cap) CXX11_OVERRIDE
+ {
+ // The value of a cap has changed, send CAP DEL and CAP NEW with the new value
+ Send(cap->GetName(), cap, false);
+ Send(cap->GetName(), cap, true);
+ }
+
+ void OnReloadModuleSave(Module* mod, ReloadModule::CustomData& cd) CXX11_OVERRIDE
+ {
+ if (mod == this)
+ return;
+ reloadedmod = mod->ModuleSourceFile;
+ // Request callback when reload is complete
+ cd.add(this, NULL);
+ }
+
+ void OnReloadModuleRestore(Module* mod, void* data) CXX11_OVERRIDE
+ {
+ // Reloading can change the set of caps provided by a module so assuming that if the reload succeded all
+ // caps that the module previously provided are available or all were lost if the reload failed is wrong.
+ // Instead, we verify the availability of each cap individually.
+ dynamic_reference_nocheck<Cap::Manager> capmanager(this, "capmanager");
+ if (capmanager)
+ {
+ for (std::vector<std::string>::const_iterator i = reloadedcaps.begin(); i != reloadedcaps.end(); ++i)
+ {
+ const std::string& capname = *i;
+ if (!capmanager->Find(capname))
+ Send(capname, NULL, false);
+ }
+ }
+ reloadedmod.clear();
+ reloadedcaps.clear();
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the cap-notify IRCv3.2 extension", VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleIRCv3CapNotify)
diff --git a/src/modules/m_ircv3_chghost.cpp b/src/modules/m_ircv3_chghost.cpp
new file mode 100644
index 000000000..af3503108
--- /dev/null
+++ b/src/modules/m_ircv3_chghost.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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"
+#include "modules/ircv3.h"
+
+class ModuleIRCv3ChgHost : public Module
+{
+ Cap::Capability cap;
+
+ void DoChgHost(User* user, const std::string& ident, const std::string& host)
+ {
+ std::string line(1, ':');
+ line.append(user->GetFullHost()).append(" CHGHOST ").append(ident).append(1, ' ').append(host);
+ IRCv3::WriteNeighborsWithCap(user, line, cap);
+ }
+
+ public:
+ ModuleIRCv3ChgHost()
+ : cap(this, "chghost")
+ {
+ }
+
+ void OnChangeIdent(User* user, const std::string& newident) CXX11_OVERRIDE
+ {
+ DoChgHost(user, newident, user->dhost);
+ }
+
+ void OnChangeHost(User* user, const std::string& newhost) CXX11_OVERRIDE
+ {
+ DoChgHost(user, user->ident, newhost);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides the chghost IRCv3.2 extension", VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleIRCv3ChgHost)
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 a38ed1a1b..20243370d 100644
--- a/src/modules/m_sasl.cpp
+++ b/src/modules/m_sasl.cpp
@@ -24,6 +24,41 @@
#include "modules/sasl.h"
#include "modules/ssl.h"
+class SASLCap : public Cap::Capability
+{
+ std::string mechlist;
+
+ bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE
+ {
+ // Requesting this cap is allowed anytime
+ if (adding)
+ return true;
+
+ // But removing it can only be done when unregistered
+ return (user->registered != REG_ALL);
+ }
+
+ const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE
+ {
+ return &mechlist;
+ }
+
+ public:
+ SASLCap(Module* mod)
+ : Cap::Capability(mod, "sasl")
+ {
+ }
+
+ void SetMechlist(const std::string& newmechlist)
+ {
+ if (mechlist == newmechlist)
+ return;
+
+ mechlist = newmechlist;
+ NotifyValueChange();
+ }
+};
+
enum SaslState { SASL_INIT, SASL_COMM, SASL_DONE };
enum SaslResult { SASL_OK, SASL_FAIL, SASL_ABORT };
@@ -179,8 +214,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 +226,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 +282,7 @@ class CommandSASL : public Command
class ModuleSASL : public Module
{
SimpleExtItem<SaslAuthenticator> authExt;
- GenericCap cap;
+ SASLCap cap;
CommandAuthenticate auth;
CommandSASL sasl;
Events::ModuleEventProvider sasleventprov;
@@ -255,7 +290,7 @@ class ModuleSASL : public Module
public:
ModuleSASL()
: authExt("sasl_auth", ExtensionItem::EXT_USER, this)
- , cap(this, "sasl")
+ , cap(this)
, auth(this, authExt, cap)
, sasl(this, authExt)
, sasleventprov(this, "event/sasl")
@@ -286,6 +321,12 @@ class ModuleSASL : public Module
return MOD_RES_PASSTHRU;
}
+ void OnDecodeMetaData(Extensible* target, const std::string& extname, const std::string& extdata) CXX11_OVERRIDE
+ {
+ if ((target == NULL) && (extname == "saslmechlist"))
+ cap.SetMechlist(extdata);
+ }
+
Version GetVersion() CXX11_OVERRIDE
{
return Version("Provides support for IRC Authentication Layer (aka: SASL) via AUTHENTICATE.", VF_VENDOR);
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;