diff --git a/docs/conf/modules.conf.example b/docs/conf/modules.conf.example index 8668673e6..581212609 100644 --- a/docs/conf/modules.conf.example +++ b/docs/conf/modules.conf.example @@ -2462,19 +2462,19 @@ # Requires SHA-1 hash support available in the sha1 module. # # +# defaultmode: The default frame mode if a client does not send a +# WebSocket subprotocol. Potential values are "text" to +# encode messages as UTF-8 text frames, "binary" to send +# messages as raw binary frames, or "reject" to close +# connections which do not request a subprotocol. Defaults +# to "text". # proxyranges: A space-delimited list of glob or CIDR matches to trust # the X-Real-IP or X-Forwarded-For headers from. If enabled # the server will use the IP address specified by those HTTP # headers. You should NOT enable this unless you are using # a HTTP proxy like nginx as it will allow IP spoofing. -# sendastext: Whether to re-encode messages as UTF-8 before sending to -# WebSocket clients. This is recommended as the WebSocket -# protocol requires all text frames to be sent as UTF-8. -# If you do not have this enabled messages will be sent as -# binary frames instead. Clients can override this using a -# WebSocket subprotocol. See the docs page for more info. -# +# # # 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 diff --git a/src/modules/m_websocket.cpp b/src/modules/m_websocket.cpp index 4d72f1bd2..8ac8a50e3 100644 --- a/src/modules/m_websocket.cpp +++ b/src/modules/m_websocket.cpp @@ -35,17 +35,29 @@ static dynamic_reference_nocheck* sha1; struct WebSocketConfig { + enum DefaultMode + { + // Reject connections if a subprotocol is not requested. + DM_REJECT, + + // Use binary frames if a subprotocol is not requested. + DM_BINARY, + + // Use UTF-8 text frames if a subprotocol is not requested. + DM_TEXT + }; + typedef std::vector OriginList; typedef std::vector ProxyRanges; // The HTTP origins that can connect to the server. OriginList allowedorigins; + // The method to use if a subprotocol is not negotiated. + DefaultMode defaultmode; + // 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 @@ -406,8 +418,10 @@ class WebSocketHook : public IOHookMiddle irc::spacesepstream protostream(protocolheader.ExtractValue(recvq)); for (std::string proto; protostream.GetToken(proto); ) { + bool is_binary = stdalgo::string::equalsci(proto, "binary.inspircd.org"); bool is_text = stdalgo::string::equalsci(proto, "text.inspircd.org"); - if (stdalgo::string::equalsci(proto, "binary.inspircd.org") || is_text) + + if (is_binary || is_text) { selectedproto = proto; sendastext = is_text; @@ -416,6 +430,12 @@ class WebSocketHook : public IOHookMiddle } } + if (selectedproto.empty() && config.defaultmode == WebSocketConfig::DM_REJECT) + { + FailHandshake(sock, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n", "WebSocket: Received HTTP request that did not send the Sec-WebSocket-Protocol header"); + return -1; + } + HTTPHeaderFinder keyheader; if (!keyheader.Find(recvq, "Sec-WebSocket-Key:", 18, reqend)) { @@ -454,7 +474,7 @@ class WebSocketHook : public IOHookMiddle , state(STATE_HTTPREQ) , lastpingpong(0) , config(cfg) - , sendastext(cfg.sendastext) + , sendastext(config.defaultmode != WebSocketConfig::DM_BINARY) { sock->AddIOHook(this); } @@ -577,7 +597,16 @@ class ModuleWebSocket : public Module } ConfigTag* tag = ServerInstance->Config->ConfValue("websocket"); - config.sendastext = tag->getBool("sendastext", true); + + const std::string defaultmodestr = tag->getString("defaultmode", tag->getBool("sendastext", true) ? "text" : "binary", 1); + if (stdalgo::string::equalsci(defaultmodestr, "reject")) + config.defaultmode = WebSocketConfig::DM_REJECT; + else if (stdalgo::string::equalsci(defaultmodestr, "binary")) + config.defaultmode = WebSocketConfig::DM_BINARY; + else if (stdalgo::string::equalsci(defaultmodestr, "text")) + config.defaultmode = WebSocketConfig::DM_TEXT; + else + throw ModuleException(defaultmodestr + " is an invalid value for ; acceptable values are 'binary', 'text' and 'reject', at " + tag->getTagLocation()); irc::spacesepstream proxyranges(tag->getString("proxyranges")); for (std::string proxyrange; proxyranges.GetToken(proxyrange); )