summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/conf/modules.conf.example21
-rw-r--r--src/modules/m_ircv3_sts.cpp181
2 files changed, 202 insertions, 0 deletions
diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example
index cf79dafce..b573de903 100644
--- a/docs/conf/modules.conf.example
+++ b/docs/conf/modules.conf.example
@@ -1014,6 +1014,27 @@
#<module name="ircv3_invitenotify">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
+# IRCv3 Strict Transport Security module: Provides the sts IRCv3.2
+# extension which allows clients connecting insecurely to upgrade their
+# connections to TLS.
+#<module name="ircv3_sts">
+#
+# If using the ircv3_sts module you MUST define a STS policy to send
+# to clients using the <sts> tag. This tag takes the following
+# attributes:
+#
+# host - A glob match for the SNI hostname to apply this policy to.
+# duration - The amount of time that the policy lasts for. Defaults to
+# approximately two months by default.
+# port - The port on which TLS connections to the server are being
+# accepted. You MUST have a CA-verified certificate on this
+# port. Self signed certificates are not acceptable.
+# preload - Whether client developers can include your certificate in
+# preload lists.
+#
+# <sts host="*.example.com" duration="60d" port="6697" preload="yes">
+
+#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Join flood module: Adds support for join flood protection +j X:Y.
# Closes the channel for N seconds if X users join in Y seconds.
#<module name="joinflood">
diff --git a/src/modules/m_ircv3_sts.cpp b/src/modules/m_ircv3_sts.cpp
new file mode 100644
index 000000000..ee619903a
--- /dev/null
+++ b/src/modules/m_ircv3_sts.cpp
@@ -0,0 +1,181 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2015 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2017 Peter Powell <petpow@saberuk.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/ssl.h"
+
+class STSCap : public Cap::Capability
+{
+ private:
+ std::string host;
+ std::string plaintextpolicy;
+ std::string securepolicy;
+
+ bool OnList(LocalUser* user) CXX11_OVERRIDE
+ {
+ // Don't send the cap to clients that only support cap-3.1.
+ if (GetProtocol(user) == Cap::CAP_LEGACY)
+ return false;
+
+ // Plaintext listeners have their own policy.
+ SSLIOHook* sslhook = SSLIOHook::IsSSL(&user->eh);
+ if (!sslhook)
+ return true;
+
+ // If no hostname has been provided for the connection, an STS persistence policy SHOULD NOT be advertised.
+ std::string snihost;
+ if (!sslhook->GetServerName(snihost))
+ return false;
+
+ // Before advertising an STS persistence policy over a secure connection, servers SHOULD verify whether the
+ // hostname provided by clients, for example, via TLS Server Name Indication (SNI), has been whitelisted by
+ // administrators in the server configuration.
+ return InspIRCd::Match(snihost, host, ascii_case_insensitive_map);
+ }
+
+ bool OnRequest(LocalUser* user, bool adding) CXX11_OVERRIDE
+ {
+ // Clients MUST NOT request this capability with CAP REQ. Servers MAY reply with a CAP NAK message if a
+ // client requests this capability.
+ return false;
+ }
+
+ const std::string* GetValue(LocalUser* user) const CXX11_OVERRIDE
+ {
+ return SSLIOHook::IsSSL(&user->eh) ? &securepolicy : &plaintextpolicy;
+ }
+
+ public:
+ STSCap(Module* mod)
+ : Cap::Capability(mod, "sts")
+ {
+ }
+
+ ~STSCap()
+ {
+ // TODO: Send duration=0 when STS vanishes.
+ }
+
+ void SetPolicy(const std::string& newhost, unsigned long duration, unsigned int port, bool preload)
+ {
+ // To enforce an STS upgrade policy, servers MUST send this key to insecurely connected clients. Servers
+ // MAY send this key to securely connected clients, but it will be ignored.
+ std::string newplaintextpolicy("port=");
+ newplaintextpolicy.append(ConvToStr(port));
+
+ // To enforce an STS persistence policy, servers MUST send this key to securely connected clients. Servers
+ // MAY send this key to all clients, but insecurely connected clients MUST ignore it.
+ std::string newsecurepolicy("duration=");
+ newsecurepolicy.append(ConvToStr(duration));
+
+ // Servers MAY send this key to all clients, but insecurely connected clients MUST ignore it.
+ if (preload)
+ newsecurepolicy.append(",preload");
+
+ // Apply the new policy.
+ bool changed = false;
+ if (!irc::equals(host, newhost))
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing STS SNI hostname from \"%s\" to \"%s\"", host.c_str(), newhost.c_str());
+ host = newhost;
+ changed = true;
+ }
+
+ if (plaintextpolicy != newplaintextpolicy)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing plaintext STS policy from \"%s\" to \"%s\"", plaintextpolicy.c_str(), newplaintextpolicy.c_str());
+ plaintextpolicy.swap(newplaintextpolicy);
+ changed = true;
+ }
+
+ if (securepolicy != newsecurepolicy)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Changing secure STS policy from \"%s\" to \"%s\"", securepolicy.c_str(), newsecurepolicy.c_str());
+ securepolicy.swap(newsecurepolicy);
+ changed = true;
+ }
+
+ // If the policy has changed then notify all clients via cap-notify.
+ if (changed)
+ NotifyValueChange();
+ }
+};
+
+class ModuleIRCv3STS : public Module
+{
+ private:
+ STSCap cap;
+
+ // The IRCv3 STS specification requires that the server is listening using SSL using a valid certificate.
+ bool HasValidSSLPort(unsigned int port)
+ {
+ for (std::vector<ListenSocket*>::const_iterator iter = ServerInstance->ports.begin(); iter != ServerInstance->ports.end(); ++iter)
+ {
+ ListenSocket* ls = *iter;
+
+ // Is this listener on the right port?
+ unsigned int saport = ls->bind_sa.port();
+ if (saport != port)
+ continue;
+
+ // Is this listener using SSL?
+ if (ls->bind_tag->getString("ssl").empty())
+ continue;
+
+ // TODO: Add a way to check if a listener's TLS cert is CA-verified.
+ return true;
+ }
+ return false;
+ }
+
+ public:
+ ModuleIRCv3STS()
+ : cap(this)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ // TODO: Multiple SNI profiles
+ ConfigTag* tag = ServerInstance->Config->ConfValue("sts");
+ if (tag == ServerInstance->Config->EmptyTag)
+ throw ModuleException("You must define a STS policy!");
+
+ const std::string host = tag->getString("host");
+ if (host.empty())
+ throw ModuleException("<sts:host> must contain a hostname, at " + tag->getTagLocation());
+
+ int port = tag->getInt("port");
+ if (!HasValidSSLPort(port))
+ throw ModuleException("<sts:port> must be a TLS port, at " + tag->getTagLocation());
+
+ unsigned long duration = tag->getDuration("duration", 60*60*24*30*2, 0, LONG_MAX);
+ bool preload = tag->getBool("preload");
+ cap.SetPolicy(host, duration, port, preload);
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Provides IRCv3 Strict Transport Security policy advertisement", VF_OPTCOMMON);
+ }
+};
+
+MODULE_INIT(ModuleIRCv3STS)