]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge branch 'master+cap'
authorAttila Molnar <attilamolnar@hush.com>
Sun, 6 Dec 2015 10:00:12 +0000 (11:00 +0100)
committerAttila Molnar <attilamolnar@hush.com>
Sun, 6 Dec 2015 10:00:12 +0000 (11:00 +0100)
12 files changed:
docs/conf/modules.conf.example
include/modules/cap.h [new file with mode: 0644]
include/modules/ircv3.h [new file with mode: 0644]
src/modules/m_cap.cpp [new file with mode: 0644]
src/modules/m_hostcycle.cpp
src/modules/m_ircv3.cpp
src/modules/m_ircv3_capnotify.cpp [new file with mode: 0644]
src/modules/m_ircv3_chghost.cpp [new file with mode: 0644]
src/modules/m_namesx.cpp
src/modules/m_sasl.cpp
src/modules/m_starttls.cpp
src/modules/m_uhnames.cpp

index 055bdc0b4f1de3efd24c463df8d0105a7492078b..52a8f5eebd9f46a79bf8b2dbbd39b5e3c467f77b 100644 (file)
 # 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">
 
 #-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
 # and host cycling.
 #<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.
diff --git a/include/modules/cap.h b/include/modules/cap.h
new file mode 100644 (file)
index 0000000..e6f9340
--- /dev/null
@@ -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 (file)
index 0000000..e03ee16
--- /dev/null
@@ -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 (file)
index 0000000..3e9c37a
--- /dev/null
@@ -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)
index d4def647347e9e3fa0fbf4366cc85390661afa54..b33c101efb83b9eca3e4ed3f477a3adf085cb302 100644 (file)
 
 
 #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");
index caee0d32944e4405c1f4269b7ee16b3dfaf63738..5275e9bd5911b5fc95d670ee5c6c4212ad790352 100644 (file)
 #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 (file)
index 0000000..b19c266
--- /dev/null
@@ -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 (file)
index 0000000..af35031
--- /dev/null
@@ -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)
index c701f16bf1b20cf450e9c75583fa2d9e70ae3575..c906322bf529967fba09f4413bc0e0fef0b8afb3 100644 (file)
@@ -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
index a38ed1a1bed21b1f5196870d055e64dd022c24f0..20243370da7b0da14c47a056789b06d208aabbe4 100644 (file)
 #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);
index b05302fa96c93f83197e4e547e6e67d79c04004a..a47480728c5f5fb1569993b30ad3388b7bf34cc9 100644 (file)
@@ -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:
index 90bac54f58a243635ae15a76ca525e7654a48796..ce9c517f49149dbb3c81ac135561f8ce4b4e8d2e 100644 (file)
@@ -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;