X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Fm_websocket.cpp;h=1cba6bca6ad5ca06202b2a6c740003b8478e4ec5;hb=061a2e1aed3727b785976ea167cf2084d3e8f0f9;hp=12102d2151bd818f392ac82162dbb32dfa117c67;hpb=124c17e14134a4999afc1a5e981ab7c75b3694b9;p=user%2Fhenk%2Fcode%2Finspircd.git diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index 12102d215..1cba6bca6 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -1,7 +1,9 @@ /* * InspIRCd -- Internet Relay Chat Daemon * - * Copyright (C) 2016 Attila Molnar + * Copyright (C) 2019 iwalkalone + * Copyright (C) 2017-2019 Sadie Powell + * Copyright (C) 2016-2017 Attila Molnar * * 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 @@ -16,18 +18,38 @@ * along with this program. If not, see . */ +/// $CompilerFlags: -Ivendor_directory("utfcpp") + #include "inspircd.h" #include "iohook.h" #include "modules/hash.h" +#include + static const char MagicGUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; static const char whitespace[] = " \t\r\n"; static dynamic_reference_nocheck* sha1; +struct WebSocketConfig +{ + typedef std::vector OriginList; + typedef std::vector 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,61 @@ 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(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); + + // Error if changing their IP gets them banned. + if (luser->quitting) + return -1; + break; + } + } + } + + HTTPHeaderFinder keyheader; if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend)) { @@ -318,10 +410,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 +427,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 +496,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 +512,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 parameter. + const std::string allow = tag->getString("allow"); + if (allow.empty()) + throw ModuleException(" 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) @@ -398,7 +554,7 @@ class ModuleWebSocket : public Module Version GetVersion() CXX11_OVERRIDE { - return Version("Provides RFC 6455 WebSocket support", VF_VENDOR); + return Version("Allows WebSocket clients to connect to the IRC server.", VF_VENDOR); } };