summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/conf/modules.conf.example6
-rw-r--r--src/modules/m_websocket.cpp52
2 files changed, 56 insertions, 2 deletions
diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example
index 8a22a8c71..72f295cc5 100644
--- a/docs/conf/modules.conf.example
+++ b/docs/conf/modules.conf.example
@@ -2167,6 +2167,12 @@
# WebSocket connections. Compatible with SSL/TLS.
# Requires SHA-1 hash support available in the sha1 module.
#<module name="websocket">
+#
+# If you use the websocket module you MUST specify one or more origins
+# which are allowed to connect to the server. You should set this as
+# strict as possible to prevent malicious webpages from connecting to
+# your server.
+# <wsorigin allow="https://webchat.example.com/*">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# XLine database: Stores all *Lines (G/Z/K/R/any added by other modules)
diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp
index 12102d215..5ac661ccf 100644
--- a/src/modules/m_websocket.cpp
+++ b/src/modules/m_websocket.cpp
@@ -21,6 +21,8 @@
#include "iohook.h"
#include "modules/hash.h"
+typedef std::vector<std::string> OriginList;
+
static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static const char whitespace[] = " \t\r\n";
static dynamic_reference_nocheck<HashProvider>* sha1;
@@ -28,6 +30,8 @@ static dynamic_reference_nocheck<HashProvider>* sha1;
class WebSocketHookProvider : public IOHookProvider
{
public:
+ OriginList allowedorigins;
+
WebSocketHookProvider(Module* mod)
: IOHookProvider(mod, "websocket", IOHookProvider::IOH_UNKNOWN, true)
{
@@ -101,6 +105,7 @@ class WebSocketHook : public IOHookMiddle
State state;
time_t lastpingpong;
+ OriginList& allowedorigins;
static size_t FillHeader(unsigned char* outbuf, size_t sendlength, OpCode opcode)
{
@@ -288,6 +293,27 @@ class WebSocketHook : public IOHookMiddle
if (reqend == std::string::npos)
return 0;
+ bool allowedorigin = false;
+ HTTPHeaderFinder originheader;
+ if (originheader.Find(recvq, "Origin:", 7, reqend))
+ {
+ const std::string origin = originheader.ExtractValue(recvq);
+ for (OriginList::const_iterator iter = allowedorigins.begin(); iter != allowedorigins.end(); ++iter)
+ {
+ if (InspIRCd::Match(origin, *iter, ascii_case_insensitive_map))
+ {
+ allowedorigin = true;
+ break;
+ }
+ }
+ }
+
+ if (!allowedorigin)
+ {
+ FailHandshake(sock, "HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n", "WebSocket: Received HTTP request from a non-whitelisted origin");
+ return -1;
+ }
+
HTTPHeaderFinder keyheader;
if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend))
{
@@ -318,10 +344,11 @@ class WebSocketHook : public IOHookMiddle
}
public:
- WebSocketHook(IOHookProvider* Prov, StreamSocket* sock)
+ WebSocketHook(IOHookProvider* Prov, StreamSocket* sock, OriginList& AllowedOrigins)
: IOHookMiddle(Prov)
, state(STATE_HTTPREQ)
, lastpingpong(0)
+ , allowedorigins(AllowedOrigins)
{
sock->AddIOHook(this);
}
@@ -370,7 +397,7 @@ class WebSocketHook : public IOHookMiddle
void WebSocketHookProvider::OnAccept(StreamSocket* sock, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
{
- new WebSocketHook(this, sock);
+ new WebSocketHook(this, sock, allowedorigins);
}
class ModuleWebSocket : public Module
@@ -386,6 +413,27 @@ class ModuleWebSocket : public Module
sha1 = &hash;
}
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTagList tags = ServerInstance->Config->ConfTags("wsorigin");
+ if (tags.first == tags.second)
+ throw ModuleException("You have loaded the websocket module but not configured any allowed origins!");
+
+ OriginList allowedorigins;
+ for (ConfigIter i = tags.first; i != tags.second; ++i)
+ {
+ ConfigTag* tag = i->second;
+
+ // Ensure that we have the <wsorigin:allow> parameter.
+ const std::string allow = tag->getString("allow");
+ if (allow.empty())
+ throw ModuleException("<wsorigin:allow> is a mandatory field, at " + tag->getTagLocation());
+
+ allowedorigins.push_back(allow);
+ }
+ hookprov->allowedorigins.swap(allowedorigins);
+ }
+
void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
{
if (type != ExtensionItem::EXT_USER)