X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_websocket.cpp;h=51dada299e77efbd3990918a410ce8c50a828f06;hb=e86b4311c090d9f3d06c5762a978563acfd84039;hp=12102d2151bd818f392ac82162dbb32dfa117c67;hpb=565544fac966b14e046bb3042ab485f79bcf7c9e;p=user%2Fhenk%2Fcode%2Finspircd.git
diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp
index 12102d215..51dada299 100644
--- a/src/modules/m_websocket.cpp
+++ b/src/modules/m_websocket.cpp
@@ -16,11 +16,17 @@
* along with this program. If not, see .
*/
+/// $CompilerFlags: -Ivendor_directory("utfcpp")
+
#include "inspircd.h"
#include "iohook.h"
#include "modules/hash.h"
+#include
+
+typedef std::vector OriginList;
+
static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
static const char whitespace[] = " \t\r\n";
static dynamic_reference_nocheck* sha1;
@@ -28,6 +34,9 @@ static dynamic_reference_nocheck* sha1;
class WebSocketHookProvider : public IOHookProvider
{
public:
+ OriginList allowedorigins;
+ bool sendastext;
+
WebSocketHookProvider(Module* mod)
: IOHookProvider(mod, "websocket", IOHookProvider::IOH_UNKNOWN, true)
{
@@ -101,6 +110,8 @@ class WebSocketHook : public IOHookMiddle
State state;
time_t lastpingpong;
+ OriginList& allowedorigins;
+ bool& sendastext;
static size_t FillHeader(unsigned char* outbuf, size_t sendlength, OpCode opcode)
{
@@ -237,15 +248,29 @@ class WebSocketHook : public IOHookMiddle
return 0;
unsigned char opcode = (unsigned char)GetRecvQ().c_str()[0];
- opcode &= ~WS_FINBIT;
-
- switch (opcode)
+ switch (opcode & ~WS_FINBIT)
{
case OP_CONTINUATION:
case OP_TEXT:
case OP_BINARY:
{
- return HandleAppData(sock, destrecvq, true);
+ std::string appdata;
+ const int result = HandleAppData(sock, appdata, true);
+ if (result != 1)
+ return result;
+
+ // Strip out any CR+LF which may have been erroneously sent.
+ for (std::string::const_iterator iter = appdata.begin(); iter != appdata.end(); ++iter)
+ {
+ if (*iter != '\r' && *iter != '\n')
+ destrecvq.push_back(*iter);
+ }
+
+ // If we are on the final message of this block append a line terminator.
+ if (opcode & WS_FINBIT)
+ destrecvq.append("\r\n");
+
+ return 1;
}
case OP_PING:
@@ -288,6 +313,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 +364,12 @@ class WebSocketHook : public IOHookMiddle
}
public:
- WebSocketHook(IOHookProvider* Prov, StreamSocket* sock)
+ WebSocketHook(IOHookProvider* Prov, StreamSocket* sock, OriginList& AllowedOrigins, bool& SendAsText)
: IOHookMiddle(Prov)
, state(STATE_HTTPREQ)
, lastpingpong(0)
+ , allowedorigins(AllowedOrigins)
+ , sendastext(SendAsText)
{
sock->AddIOHook(this);
}
@@ -334,11 +382,44 @@ class WebSocketHook : public IOHookMiddle
if (state != STATE_ESTABLISHED)
return (mysendq.empty() ? 0 : 1);
- if (!uppersendq.empty())
+ std::string message;
+ for (StreamSocket::SendQueue::const_iterator elem = uppersendq.begin(); elem != uppersendq.end(); ++elem)
{
- StreamSocket::SendQueue::Element elem = PrepareSendQElem(uppersendq.bytes(), OP_BINARY);
- mysendq.push_back(elem);
- mysendq.moveall(uppersendq);
+ for (StreamSocket::SendQueue::Element::const_iterator chr = elem->begin(); chr != elem->end(); ++chr)
+ {
+ if (*chr == '\n')
+ {
+ // We have found an entire message. Send it in its own frame.
+ if (sendastext)
+ {
+ // If we send messages as text then we need to ensure they are valid UTF-8.
+ std::string encoded;
+ utf8::replace_invalid(message.begin(), message.end(), std::back_inserter(encoded));
+
+ mysendq.push_back(PrepareSendQElem(encoded.length(), OP_TEXT));
+ mysendq.push_back(encoded);
+ }
+ else
+ {
+ // Otherwise, send the raw message as a binary frame.
+ mysendq.push_back(PrepareSendQElem(message.length(), OP_BINARY));
+ mysendq.push_back(message);
+ }
+ message.clear();
+ }
+ else if (*chr != '\r')
+ {
+ message.push_back(*chr);
+ }
+ }
+ }
+
+ // Empty the upper send queue and push whatever is left back onto it.
+ uppersendq.clear();
+ if (!message.empty())
+ {
+ uppersendq.push_back(message);
+ return 0;
}
return 1;
@@ -370,7 +451,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, sendastext);
}
class ModuleWebSocket : public Module
@@ -386,6 +467,30 @@ 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 parameter.
+ const std::string allow = tag->getString("allow");
+ if (allow.empty())
+ throw ModuleException(" is a mandatory field, at " + tag->getTagLocation());
+
+ allowedorigins.push_back(allow);
+ }
+
+ ConfigTag* tag = ServerInstance->Config->ConfValue("websocket");
+ hookprov->sendastext = tag->getBool("sendastext", true);
+ hookprov->allowedorigins.swap(allowedorigins);
+ }
+
void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
{
if (type != ExtensionItem::EXT_USER)