]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_cap.cpp
Sync helpop chmodes s and p with docs
[user/henk/code/inspircd.git] / src / modules / m_cap.cpp
index 09d5e604dd12c3e617c3b88a9b799f503a4cdabb..f035944baf87c24b7d9a13383fb270b8b5e97a6d 100644 (file)
@@ -1,7 +1,8 @@
 /*
  * InspIRCd -- Internet Relay Chat Daemon
  *
- *   Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ *   Copyright (C) 2018-2020 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
 #include "modules/reload.h"
 #include "modules/cap.h"
 
+enum
+{
+       // From IRCv3 capability-negotiation-3.1.
+       ERR_INVALIDCAPCMD = 410
+};
+
 namespace Cap
 {
        class ManagerImpl;
@@ -55,7 +62,8 @@ class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
 
        static bool CanRequest(LocalUser* user, Ext usercaps, Capability* cap, bool adding)
        {
-               if ((usercaps & cap->GetMask()) == adding)
+               const bool hascap = ((usercaps & cap->GetMask()) != 0);
+               if (hascap == adding)
                        return true;
 
                return cap->OnRequest(user, adding);
@@ -70,9 +78,9 @@ class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
                        used |= cap->GetMask();
                }
 
-               for (unsigned int i = 0; i < MAX_CAPS; i++)
+               for (size_t i = 0; i < MAX_CAPS; i++)
                {
-                       Capability::Bit bit = (1 << i);
+                       Capability::Bit bit = (static_cast<Capability::Bit>(1) << i);
                        if (!(used & bit))
                                return bit;
                }
@@ -247,7 +255,7 @@ class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
                return true;
        }
 
-       void HandleList(std::string& out, LocalUser* user, bool show_all, bool show_values, bool minus_prefix = false) const
+       void HandleList(std::vector<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));
 
@@ -260,66 +268,81 @@ class Cap::ManagerImpl : public Cap::Manager, public ReloadModule::EventListener
                        if ((show_all) && (!cap->OnList(user)))
                                continue;
 
+                       std::string token;
                        if (minus_prefix)
-                               out.push_back('-');
-                       out.append(cap->GetName());
+                               token.push_back('-');
+                       token.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);
+                                       token.push_back('=');
+                                       token.append(*capvalue, 0, MAX_VALUE_LENGTH);
                                }
                        }
-                       out.push_back(' ');
+                       out.push_back(token);
                }
        }
 
-       void HandleClear(LocalUser* user, std::string& result)
+       void HandleClear(LocalUser* user, std::vector<std::string>& result)
        {
                HandleList(result, user, false, false, true);
                capext.unset(user);
        }
 };
 
+namespace
+{
+       std::string SerializeCaps(const Extensible* container, void* item, bool human)
+       {
+               // 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 (!user)
+                       return std::string();
+
+               // List requested caps
+               std::vector<std::string> result;
+               managerimpl->HandleList(result, 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.
+               std::string version;
+               if (human)
+                       version.append("capversion=3.");
+               switch (managerimpl->GetProtocol(user))
+               {
+                       case Cap::CAP_302:
+                               version.push_back('2');
+                               break;
+                       default:
+                               version.push_back('1');
+                               break;
+               }
+               result.push_back(version);
+
+               return stdalgo::string::join(result, ' ');
+       }
+}
+
 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 Cap::ExtItem::ToHuman(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;
+       return SerializeCaps(container, item, true);
 }
 
-void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, const std::string& value)
+std::string Cap::ExtItem::ToInternal(const Extensible* container, void* item) const
 {
-       if (format == FORMAT_NETWORK)
-               return;
+       return SerializeCaps(container, item, false);
+}
 
+void Cap::ExtItem::FromInternal(Extensible* container, const std::string& value)
+{
        LocalUser* user = IS_LOCAL(static_cast<User*>(container));
        if (!user)
                return; // Can't happen
@@ -334,16 +357,51 @@ void Cap::ExtItem::unserialize(SerializeFormat format, Extensible* container, co
        managerimpl->HandleReq(user, caplist);
 }
 
+class CapMessage : public Cap::MessageBase
+{
+ public:
+       CapMessage(LocalUser* user, const std::string& subcmd, const std::string& result, bool asterisk)
+               : Cap::MessageBase(subcmd)
+       {
+               SetUser(user);
+               if (asterisk)
+                       PushParam("*");
+               PushParamRef(result);
+       }
+};
+
 class CommandCap : public SplitCommand
 {
+ private:
        Events::ModuleEventProvider evprov;
        Cap::ManagerImpl manager;
+       ClientProtocol::EventProvider protoevprov;
 
-       static void DisplayResult(LocalUser* user, std::string& result)
+       void DisplayResult(LocalUser* user, const std::string& subcmd, std::vector<std::string> result, bool asterisk)
        {
-               if (*result.rbegin() == ' ')
-                       result.erase(result.end()-1);
-               user->WriteCommand("CAP", result);
+               size_t maxline = ServerInstance->Config->Limits.MaxLine - ServerInstance->Config->ServerName.size() - user->nick.length() - subcmd.length() - 11;
+               std::string line;
+               for (std::vector<std::string>::const_iterator iter = result.begin(); iter != result.end(); ++iter)
+               {
+                       if (line.length() + iter->length() < maxline)
+                       {
+                               line.append(*iter);
+                               line.push_back(' ');
+                       }
+                       else
+                       {
+                               DisplaySingleResult(user, subcmd, line, asterisk);
+                               line.clear();
+                       }
+               }
+               DisplaySingleResult(user, subcmd, line, false);
+       }
+
+       void DisplaySingleResult(LocalUser* user, const std::string& subcmd, const std::string& result, bool asterisk)
+       {
+               CapMessage msg(user, subcmd, result, asterisk);
+               ClientProtocol::Event ev(protoevprov, msg);
+               user->Send(ev);
        }
 
  public:
@@ -353,52 +411,58 @@ class CommandCap : public SplitCommand
                : SplitCommand(mod, "CAP", 1)
                , evprov(mod, "event/cap")
                , manager(mod, evprov)
+               , protoevprov(mod, name)
                , holdext("cap_hold", ExtensionItem::EXT_USER, mod)
        {
                works_before_reg = true;
        }
 
-       CmdResult HandleLocal(const std::vector<std::string>& parameters, LocalUser* user) CXX11_OVERRIDE
+       CmdResult HandleLocal(LocalUser* user, const Params& parameters) 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")
+               const std::string& subcommand = parameters[0];
+               if (irc::equals(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);
+                       const std::string replysubcmd = (manager.HandleReq(user, parameters[1]) ? "ACK" : "NAK");
+                       DisplaySingleResult(user, replysubcmd, parameters[1], false);
                }
-               else if (subcommand == "END")
+               else if (irc::equals(subcommand, "END"))
                {
                        holdext.unset(user);
                }
-               else if ((subcommand == "LS") || (subcommand == "LIST"))
+               else if (irc::equals(subcommand, "LS") || irc::equals(subcommand, "LIST"))
                {
+                       Cap::Protocol capversion = Cap::CAP_LEGACY;
                        const bool is_ls = (subcommand.length() == 2);
-                       if ((is_ls) && (parameters.size() > 1) && (parameters[1] == "302"))
-                               manager.Set302Protocol(user);
+                       if ((is_ls) && (parameters.size() > 1))
+                       {
+                               unsigned int version = ConvToNum<unsigned int>(parameters[1]);
+                               if (version >= 302)
+                               {
+                                       capversion = Cap::CAP_302;
+                                       manager.Set302Protocol(user);
+                               }
+                       }
 
-                       std::string result = subcommand + " :";
+                       std::vector<std::string> result;
                        // 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);
+                       manager.HandleList(result, user, is_ls, ((is_ls) && (capversion != Cap::CAP_LEGACY)));
+                       DisplayResult(user, subcommand, result, (capversion != Cap::CAP_LEGACY));
                }
-               else if ((subcommand == "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
+               else if (irc::equals(subcommand, "CLEAR") && (manager.GetProtocol(user) == Cap::CAP_LEGACY))
                {
-                       std::string result = "ACK :";
+                       std::vector<std::string> result;
                        manager.HandleClear(user, result);
-                       DisplayResult(user, result);
+                       DisplayResult(user, "ACK", result, false);
                }
                else
                {
-                       user->WriteNumeric(ERR_INVALIDCAPSUBCOMMAND, "%s :Invalid CAP subcommand", subcommand.c_str());
+                       user->WriteNumeric(ERR_INVALIDCAPCMD, subcommand.empty() ? "*" : subcommand, "Invalid CAP subcommand");
                        return CMD_FAILURE;
                }
 
@@ -406,13 +470,33 @@ class CommandCap : public SplitCommand
        }
 };
 
+class PoisonCap : public Cap::Capability
+{
+ public:
+       PoisonCap(Module* mod)
+               : Cap::Capability(mod, "inspircd.org/poison")
+       {
+       }
+
+       bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE
+       {
+               // Reject the attempt to enable this capability.
+               return false;
+       }
+};
+
 class ModuleCap : public Module
 {
+ private:
        CommandCap cmd;
+       PoisonCap poisoncap;
+       Cap::Capability stdrplcap;
 
  public:
        ModuleCap()
                : cmd(this)
+               , poisoncap(this)
+               , stdrplcap(this, "inspircd.org/standard-replies")
        {
        }
 
@@ -423,7 +507,7 @@ class ModuleCap : public Module
 
        Version GetVersion() CXX11_OVERRIDE
        {
-               return Version("Provides support for CAP capability negotiation", VF_VENDOR);
+               return Version("Implements support for the IRCv3 Client Capability Negotiation extension.", VF_VENDOR);
        }
 };