/*
* InspIRCd -- Internet Relay Chat Daemon
*
- * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
- * Copyright (C) 2008 Craig Edwards <craigedwards@brainbox.cc>
+ * 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
#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);
+ std::string serialize(SerializeFormat format, const Extensible* container, void* item) const CXX11_OVERRIDE;
+ void unserialize(SerializeFormat format, Extensible* container, const std::string& value) 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, parent), cap(Cap)
+ enum Protocol
+ {
+ /** Supports capability negotiation protocol v3.1, or none
+ */
+ CAP_LEGACY,
+
+ /** Supports capability negotiation v3.2
+ */
+ CAP_302
+ };
+
+ class EventListener : public Events::ModuleEventListener
{
- ServerInstance->Modules->AddService(ext);
- }
+ 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;
- void HandleEvent(Event& ev)
+ /** Called whenever the value of a cap changes.
+ * @param cap Capability whose value changed
+ */
+ virtual void OnCapValueChange(Capability* cap) { }
+ };
+
+ class Manager : public DataProvider
{
- if (ev.id != "cap_request")
- return;
+ 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();
+ }
- CapEvent *data = static_cast<CapEvent*>(&ev);
- if (data->type == CapEvent::CAPEVENT_REQ)
+ ~Capability()
{
- for (std::vector<std::string>::iterator it = data->wanted.begin(); it != data->wanted.end(); ++it)
+ 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)
{
- data->wanted.push_back(cap);
+ return true;
}
- else if (data->type == CapEvent::CAPEVENT_LIST)
+
+ /** 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
{
- if (ext.get(data->user))
- data->wanted.push_back(cap);
+ return NULL;
}
- else if (data->type == CapEvent::CAPEVENT_CLEAR)
+ };
+
+ /** 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)
{
- data->ack.push_back("-" + cap);
- ext.set(data->user, 0);
}
- }
-};
+ /** 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;
+ }
+ };
+
+ class MessageBase : public ClientProtocol::Message
+ {
+ public:
+ MessageBase(const std::string& subcmd)
+ : ClientProtocol::Message("CAP", ServerInstance->Config->ServerName)
+ {
+ PushParamPlaceholder();
+ PushParam(subcmd);
+ }
+
+ void SetUser(LocalUser* user)
+ {
+ if (user->registered & REG_NICK)
+ ReplaceParamRef(0, user->nick);
+ else
+ ReplaceParam(0, "*");
+ }
+ };
+}