]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - include/modules/cap.h
Allow converting a Cap::Reference to a Cap::Capability*.
[user/henk/code/inspircd.git] / include / modules / cap.h
index cc1cb8d8cc5c3d6c25770363d590c80af3341caa..664120d39de1014a7e2399954bdf7325a4d30700 100644 (file)
@@ -1,8 +1,8 @@
 /*
  * InspIRCd -- Internet Relay Chat Daemon
  *
- *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
- *   Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ *   Copyright (C) 2018-2019 Sadie Powell <sadie@witchery.services>
+ *   Copyright (C) 2015-2016, 2018 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
 
 #pragma once
 
-class CapEvent : public Event
+#include "event.h"
+
+namespace Cap
 {
- public:
-       enum CapEventType
+       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
        {
-               CAPEVENT_REQ,
-               CAPEVENT_LS,
-               CAPEVENT_LIST,
-               CAPEVENT_CLEAR
+        public:
+               ExtItem(Module* mod);
+               void FromInternal(Extensible* container, const std::string& value) CXX11_OVERRIDE;
+               std::string ToHuman(const Extensible* container, void* item) const CXX11_OVERRIDE;
+               std::string ToInternal(const Extensible* container, void* item) const CXX11_OVERRIDE;
        };
 
-       CapEventType type;
-       std::vector<std::string> wanted;
-       std::vector<std::string> ack;
-       User* user;
-       CapEvent(Module* sender, User* u, CapEventType capevtype) : Event(sender, "cap_request"), type(capevtype), user(u) {}
-};
+       class Capability;
 
-class GenericCap
-{
- public:
-       LocalIntExt ext;
-       const std::string cap;
-       GenericCap(Module* parent, const std::string& Cap)
-               : ext("cap_" + Cap, ExtensionItem::EXT_USER, parent)
-               , cap(Cap)
+       enum Protocol
        {
-       }
+               /** Supports capability negotiation protocol v3.1, or none
+                */
+               CAP_LEGACY,
 
-       void HandleEvent(Event& ev)
+               /** Supports capability negotiation v3.2
+                */
+               CAP_302
+       };
+
+       class EventListener : public Events::ModuleEventListener
        {
-               if (ev.id != "cap_request")
-                       return;
+        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;
 
-               CapEvent *data = static_cast<CapEvent*>(&ev);
-               if (data->type == CapEvent::CAPEVENT_REQ)
+               /** 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")
                {
-                       for (std::vector<std::string>::iterator it = data->wanted.begin(); it != data->wanted.end(); ++it)
+               }
+
+               /** 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()) != 0);
+               }
+
+               /** 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 (it->empty())
-                                       continue;
-                               bool enablecap = ((*it)[0] != '-');
-                               if (((enablecap) && (*it == cap)) || (*it == "-" + cap))
-                               {
-                                       // we can handle this, so ACK it, and remove it from the wanted list
-                                       data->ack.push_back(*it);
-                                       data->wanted.erase(it);
-                                       ext.set(data->user, enablecap ? 1 : 0);
-                                       break;
-                               }
+                               if (activate)
+                                       manager->AddCap(this);
+                               else
+                                       manager->DelCap(this);
                        }
                }
-               else if (data->type == CapEvent::CAPEVENT_LS)
+
+               /** 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)
+               {
+               }
+
+               /** Retrieves the underlying cap. */
+               operator const Cap::Capability*() const
+               {
+                       return ref ? *ref : NULL;
+               }
+
+               /** 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)
                {
-                       data->wanted.push_back(cap);
+                       if (ref)
+                               return ref->get(user);
+                       return false;
                }
-               else if (data->type == CapEvent::CAPEVENT_LIST)
+       };
+
+       class MessageBase : public ClientProtocol::Message
+       {
+        public:
+               MessageBase(const std::string& subcmd)
+                       : ClientProtocol::Message("CAP", ServerInstance->Config->GetServerName())
                {
-                       if (ext.get(data->user))
-                               data->wanted.push_back(cap);
+                       PushParamPlaceholder();
+                       PushParam(subcmd);
                }
-               else if (data->type == CapEvent::CAPEVENT_CLEAR)
+
+               void SetUser(LocalUser* user)
                {
-                       data->ack.push_back("-" + cap);
-                       ext.set(data->user, 0);
+                       if (user->registered & REG_NICK)
+                               ReplaceParamRef(0, user->nick);
+                       else
+                               ReplaceParam(0, "*");
                }
-       }
-};
+       };
+}