]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_websocket.cpp
Update copyright headers.
[user/henk/code/inspircd.git] / src / modules / m_websocket.cpp
index 12102d2151bd818f392ac82162dbb32dfa117c67..863362a07b3e71221fdfde8b8fdc3060b2b4f166 100644 (file)
@@ -1,7 +1,9 @@
 /*
  * InspIRCd -- Internet Relay Chat Daemon
  *
- *   Copyright (C) 2016 Attila Molnar <attilamolnar@hush.com>
+ *   Copyright (C) 2019 iwalkalone <iwalkalone69@gmail.com>
+ *   Copyright (C) 2017-2019 Sadie Powell <sadie@witchery.services>
+ *   Copyright (C) 2016-2017 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
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+/// $CompilerFlags: -Ivendor_directory("utfcpp")
+
 
 #include "inspircd.h"
 #include "iohook.h"
 #include "modules/hash.h"
 
+#include <utf8.h>
+
 static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 static const char whitespace[] = " \t\r\n";
 static dynamic_reference_nocheck<HashProvider>* sha1;
 
+struct WebSocketConfig
+{
+       typedef std::vector<std::string> OriginList;
+       typedef std::vector<std::string> ProxyRanges;
+
+       // The HTTP origins that can connect to the server.
+       OriginList allowedorigins;
+
+       // The IP ranges which send trustworthy X-Real-IP or X-Forwarded-For headers.
+       ProxyRanges proxyranges;
+
+       // Whether to send as UTF-8 text instead of binary data.
+       bool sendastext;
+};
+
 class WebSocketHookProvider : public IOHookProvider
 {
  public:
+       WebSocketConfig config;
        WebSocketHookProvider(Module* mod)
                : IOHookProvider(mod, "websocket", IOHookProvider::IOH_UNKNOWN, true)
        {
@@ -101,6 +123,7 @@ class WebSocketHook : public IOHookMiddle
 
        State state;
        time_t lastpingpong;
+       WebSocketConfig& config;
 
        static size_t FillHeader(unsigned char* outbuf, size_t sendlength, OpCode opcode)
        {
@@ -237,15 +260,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 +325,57 @@ 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 (WebSocketConfig::OriginList::const_iterator iter = config.allowedorigins.begin(); iter != config.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;
+               }
+
+               if (!config.proxyranges.empty() && sock->type == StreamSocket::SS_USER)
+               {
+                       LocalUser* luser = static_cast<UserIOHandler*>(sock)->user;
+                       irc::sockets::sockaddrs realsa(luser->client_sa);
+
+                       HTTPHeaderFinder proxyheader;
+                       if (proxyheader.Find(recvq, "X-Real-IP:", 10, reqend)
+                               && irc::sockets::aptosa(proxyheader.ExtractValue(recvq), realsa.port(), realsa))
+                       {
+                               // Nothing to do here.
+                       }
+                       else if (proxyheader.Find(recvq, "X-Forwarded-For:", 16, reqend)
+                               && irc::sockets::aptosa(proxyheader.ExtractValue(recvq), realsa.port(), realsa))
+                       {
+                               // Nothing to do here.
+                       }
+
+                       for (WebSocketConfig::ProxyRanges::const_iterator iter = config.proxyranges.begin(); iter != config.proxyranges.end(); ++iter)
+                       {
+                               if (InspIRCd::MatchCIDR(luser->GetIPString(), *iter, ascii_case_insensitive_map))
+                               {
+                                       // Give the user their real IP address.
+                                       if (realsa != luser->client_sa)
+                                               luser->SetClientIP(realsa);
+                                       break;
+                               }
+                       }
+               }
+
+
                HTTPHeaderFinder keyheader;
                if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend))
                {
@@ -318,10 +406,11 @@ class WebSocketHook : public IOHookMiddle
        }
 
  public:
-       WebSocketHook(IOHookProvider* Prov, StreamSocket* sock)
+       WebSocketHook(IOHookProvider* Prov, StreamSocket* sock, WebSocketConfig& cfg)
                : IOHookMiddle(Prov)
                , state(STATE_HTTPREQ)
                , lastpingpong(0)
+               , config(cfg)
        {
                sock->AddIOHook(this);
        }
@@ -334,11 +423,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 (config.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 +492,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, config);
 }
 
 class ModuleWebSocket : public Module
@@ -386,6 +508,36 @@ 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!");
+
+               WebSocketConfig config;
+               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());
+
+                       config.allowedorigins.push_back(allow);
+               }
+
+               ConfigTag* tag = ServerInstance->Config->ConfValue("websocket");
+               config.sendastext = tag->getBool("sendastext", true);
+
+               irc::spacesepstream proxyranges(tag->getString("proxyranges"));
+               for (std::string proxyrange; proxyranges.GetToken(proxyrange); )
+                       config.proxyranges.push_back(proxyrange);
+
+               // Everything is okay; apply the new config.
+               hookprov->config = config;
+       }
+
        void OnCleanup(ExtensionItem::ExtensibleType type, Extensible* item) CXX11_OVERRIDE
        {
                if (type != ExtensionItem::EXT_USER)