diff --git a/exec/websocketservice-debug.js b/exec/websocketservice-debug.js deleted file mode 100644 index 5a6051b364e3279c2c0da4de35bc181f133f0569..0000000000000000000000000000000000000000 --- a/exec/websocketservice-debug.js +++ /dev/null @@ -1,767 +0,0 @@ -// Synchronet Service for redirecting incoming WebSocket connections to the Telnet and/or RLogin server -// Mainly to be used in conjunction with fTelnet - -// Command-line syntax: -// websocketservice.js [hostname] [port] - -// Example configuration (in ctrl/services.ini) -// -// [WS] -// Port=1123 -// Options=NO_HOST_LOOKUP -// Command=websocketservice.js -// -// [WSS] -// Port=11235 -// Options=NO_HOST_LOOKUP|TLS -// Command=websocketservice.js - -//include definitions for synchronet -load("nodedefs.js"); -load("sockdefs.js"); -load("sbbsdefs.js"); -load("ftelnethelper.js"); -load("sha1.js"); - -// State flags -const WEBSOCKET_NEED_PACKET_START = 0; -const WEBSOCKET_NEED_PAYLOAD_LENGTH = 1; -const WEBSOCKET_NEED_MASKING_KEY = 2; -const WEBSOCKET_DATA = 3; - -// Frame types -const WEBSOCKET_FRAME_UNKNOWN = 0; -const WEBSOCKET_FRAME_TEXT = 1; -const WEBSOCKET_FRAME_BINARY = 2; - -// Global variables -var FFrameMask = []; -var FFrameMasked = false; -var FFrameOpCode = 0; -var FFramePayloadLength = 0; -var FFramePayloadReceived = 0; -var FFrameType = WEBSOCKET_FRAME_UNKNOWN; -var FServerSocket = null; -var FWebSocketDataQueue = ''; -var FWebSocketHeader = {}; -var FWebSocketState = WEBSOCKET_NEED_PACKET_START; -var FWebSocketSubProtocol = ''; -var ipFile; - -// Main line -try { - // Parse and respond to the WebSocket handshake request - if (ShakeHands()) { - - if (UsingHAProxy() && FWebSocketHeader['X-Forwarded-For'] === undefined) { - throw new Error('BBS is using HAProxy, but no X-Forwarded-For header present.'); - } - - SendToWebSocketClient(StringToBytes("Redirecting to server...\r\n")); - - // Default to localhost on the telnet port - var TargetHostname = GetTelnetInterface(); - var TargetPort = GetTelnetPort(); - - // If fTelnet client sent a port on the querystring, try to use that - var Path = ParsePathHeader(); - if (Path.query.Port) { - RequestedPort = parseInt(Path.query.Port); - - // Confirm the user requested either the telnet or rlogin ports (we don't want to allow them to request any arbitrary port as that would be a gaping security hole) - if ((RequestedPort > 0) && (RequestedPort <= 65535) && ((RequestedPort == GetTelnetPort()) || (RequestedPort == GetRLoginPort()))) { - TargetPort = RequestedPort; - log(LOG_DEBUG, "Using user-requested port " + Path.query.Port); - if (TargetPort == GetRLoginPort()) TargetHostname = GetRLoginInterface(); - } else { - log(LOG_NOTICE, "Client requested to connect to port " + Path.query.Port + ", which was denied"); - } - } else { - // If SysOp gave an alternate hostname/port when installing the service, use that instead - for(var i in argv) { - var port = parseInt(argv[i], 10); - if (argv[i].search(/\D/) > -1 || port < 0 || port > 65535) { - TargetHostname = argv[i]; - } else if (!isNaN(port)) { - TargetPort = port; - } - } - } - - // Connect to the server - FServerSocket = new Socket(); - log(LOG_DEBUG, "Connecting to " + TargetHostname + ":" + TargetPort); - if (FServerSocket.connect(TargetHostname, TargetPort)) { - - ipFile = new File(system.temp_dir + 'sbbs-ws-' + FServerSocket.local_port + '.ip'); - if (ipFile.open('w')) { - ipFile.write(client.ip_address); - ipFile.close(); - } - - // Variables we'll use in the loop - var DoYield = true; - var ClientData = []; - var ServerData = []; - - if (UsingHAProxy()) { - var hapstr = '\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x21'; - if (client.socket.family === PF_INET) { - hapstr += '\x11\x00\x0C'; - } else if (client.socket.family === PF_INET6) { - hapstr += '\x21\x00\x24'; - } - hapstr += inet_pton(FWebSocketHeader['X-Forwarded-For']); - hapstr += inet_pton(FServerSocket.remote_ip_address); - hapstr += client.port.toString(16); - hapstr += TargetPort.toString(16); - FServerSocket.send(hapstr); - } - - // Loop while we're still connected on both ends - while ((client.socket.is_connected) && (FServerSocket.is_connected)) { - // Should we yield or not (default true, but disable if we had line activity) - DoYield = true; - - // Check if the client sent anything - ClientData = GetFromWebSocketClient(); - if (ClientData === null) { - // null ClientData means the client socket is no longer connected, or a close frame was received - break; - } else if (ClientData.length > 0) { - SendToServer(ClientData); - DoYield = false; - } - - // Check if the server sent anything - ServerData = GetFromServer(); - if (ServerData === null) { - // null ServerData means the server socket is no longer connected - break; - } else if (ServerData.length > 0) { - SendToWebSocketClient(ServerData); - DoYield = false; - } - - // Yield if we didn't transfer any data - if (DoYield) { - mswait(10); - yield(); - } - } - if (!client.socket.is_connected) log(LOG_DEBUG, 'Client socket no longer connected'); - if (!FServerSocket.is_connected) log(LOG_DEBUG, 'Server socket no longer connected'); - } else { - // FServerSocket.connect() failed - log(LOG_ERR, "Error " + FServerSocket.error + " connecting to server at " + TargetHostname + ":" + TargetPort); - SendToWebSocketClient(StringToBytes("ERROR: Unable to connect to server\r\n")); - mswait(2500); - } - } else { - log(LOG_DEBUG, "WebSocket handshake failed (likely a port scanner)"); - } -} catch (err) { - log(LOG_ERR, err.toString()); -} finally { - if (FServerSocket != null) { - FServerSocket.close(); - } - if (ipFile instanceof File) ipFile.remove(); -} - -function CalculateWebSocketKey(InLine) { - var Digits = ""; - var Spaces = 0; - - // Loop through the line, looking for digits and spaces - for (var i = 0; i < InLine.length; i++) { - if (InLine.charAt(i) == " ") { - Spaces++; - } else if (!isNaN(InLine.charAt(i))) { - Digits += InLine.charAt(i); - } - } - - return Digits / Spaces; -} - -function GetFromServer() { - if (!FServerSocket.is_connected) return null; - - var Result = []; - var InStr = ''; - - if (FServerSocket.data_waiting) { - InStr = FServerSocket.recv(4096); - for (var i = 0; i < InStr.length; i++) { - Result.push(InStr.charCodeAt(i)); - } - } - - return Result; -} - -function GetFromWebSocketClient() { - if (!client.socket.is_connected) return null; - - switch (FWebSocketHeader['Version']) { - case 0: return GetFromWebSocketClientDraft0(); - case 7: - case 8: - case 13: - return GetFromWebSocketClientVersion7(); - } -} - -function GetFromWebSocketClientDraft0() { - var Result = ''; - var InByte = 0; - var InByte2 = 0; - var InByte3 = 0; - - while (client.socket.data_waiting) { - InByte = client.socket.recvBin(1); - - // Check what the client packet state is - switch (FWebSocketState) { - case WEBSOCKET_NEED_PACKET_START: - // Check for 0x00 to indicate the start of a data packet - if (InByte === 0x00) { - FWebSocketState = WEBSOCKET_DATA; - } - break; - case WEBSOCKET_DATA: - // We're in a data packet, so check for 0xFF, which indicates the data packet is done - if (InByte == 0xFF) { - FWebSocketState = WEBSOCKET_NEED_PACKET_START; - } else { - // Check if the byte needs to be UTF-8 decoded - if (InByte < 128) { - Result += String.fromCharCode(InByte); - } else if ((InByte > 191) && (InByte < 224)) { - // Handle UTF-8 decode - InByte2 = client.socket.recvBin(1); - Result += String.fromCharCode(((InByte & 31) << 6) | (InByte2 & 63)); - } else { - // Handle UTF-8 decode (should never need this, but included anyway) - InByte2 = client.socket.recvBin(1); - InByte3 = client.socket.recvBin(1); - Result += String.fromCharCode(((InByte & 15) << 12) | ((InByte2 & 63) << 6) | (InByte3 & 63)); - } - } - break; - } - } - - return Result; -} - -function GetFromWebSocketClientVersion7() { - var Result = ''; - var InByte = 0; - var InByte2 = 0; - - while (client.socket.data_waiting && (Result.length <= 4096)) { - // Check what the client packet state is - switch (FWebSocketState) { - case WEBSOCKET_NEED_PACKET_START: - // Next byte will give us the opcode, and also tell is if the message is fragmented - FFrameOpCode = client.socket.recvBin(1) & 0x0F; - switch (FFrameOpCode) { - case 0: // Continuation frame, keep same opcode as previous frame - break; - - case 1: // Text frame - case 2: // Binary frame - FFrameType = FFrameOpCode; - break; - - case 8: // Close frame - log(LOG_DEBUG, "Client sent a close frame"); - // TODO Protocol says to respond with a close frame - client.socket.close(); - return null; - - case 9: // Ping frame - log(LOG_DEBUG, "Client setnt a ping frame"); - // TODO Protocol says to respond with a pong frame - break; - - case 10: // Pong frame - log(LOG_DEBUG, "Client sent a pong frame"); - break; - - default: // Reserved or unknown frame - throw new Error('Client sent a reserved/unknown frame: ' + FFrameOpCode); - } - - // If we get here, we want to move on to the next state - FFrameMask = []; - FFrameMasked = false; - FFramePayloadLength = 0; - FFramePayloadReceived = 0; - FWebSocketState = WEBSOCKET_NEED_PAYLOAD_LENGTH; - break; - case WEBSOCKET_NEED_PAYLOAD_LENGTH: - InByte = client.socket.recvBin(1); - - FFrameMasked = ((InByte & 0x80) == 128); - - FFramePayloadLength = (InByte & 0x7F); - if (FFramePayloadLength === 126) { - FFramePayloadLength = client.socket.recvBin(2); - } else if (FFramePayloadLength === 127) { - FFramePayloadLength = client.socket.recvBin(8); - } - - if (FFrameMasked) { - FWebSocketState = WEBSOCKET_NEED_MASKING_KEY; - } else { - FWebSocketState = (FFramePayloadLength > 0 ? WEBSOCKET_DATA : WEBSOCKET_NEED_PACKET_START); // NB: Might not be any data to read, so check for payload length before setting state - } - break; - case WEBSOCKET_NEED_MASKING_KEY: - InByte = client.socket.recvBin(4); - FFrameMask[0] = (InByte >> 24) & 0xFF; - FFrameMask[1] = (InByte >> 16) & 0xFF; - FFrameMask[2] = (InByte >> 8) & 0xFF; - FFrameMask[3] = (InByte >> 0) & 0xFF; - FWebSocketState = (FFramePayloadLength > 0 ? WEBSOCKET_DATA : WEBSOCKET_NEED_PACKET_START); // NB: Might not be any data to read, so check for payload length before setting state - break; - case WEBSOCKET_DATA: - var BytesToRead = FFramePayloadLength - FWebSocketDataQueue.length; - var InStr = client.socket.recv(BytesToRead); - if (InStr) { - if (InStr.length != BytesToRead) { - // Didn't get the whole websocket data packet this read, so queue up the latest read, then return the data we have so far - log(LOG_DEBUG, 'Asked for ' + BytesToRead + ' but got ' + InStr.length + ', queuing for a subsequent read'); - FWebSocketDataQueue += InStr; - return Result; - } - - // Prepend the queued text, if we have any - if (FWebSocketDataQueue.length > 0) { - InStr = FWebSocketDataQueue + InStr; - } - - if (FFrameType == WEBSOCKET_FRAME_TEXT) { - for (var i = 0; i < InStr.length; i++) { - InByte = InStr.charCodeAt(i); - if (FFrameMasked) InByte = UnmaskByte(InByte); - - // Check if the byte needs to be UTF-8 decoded - if ((InByte & 0x80) === 0) { - Result += String.fromCharCode(InByte); - } else if ((InByte & 0xE0) === 0xC0) { - // Handle UTF-8 decode - InByte2 = InStr.charCodeAt(++i); - if (FFrameMasked) InByte2 = UnmaskByte(InByte2); - Result += String.fromCharCode(((InByte & 31) << 6) | (InByte2 & 63)); - } else { - throw new Error('GetFromWebSocketClientVersion7 Byte out of range: ' + InByte); - } - } - } else if (FFrameType === WEBSOCKET_FRAME_BINARY) { - for (var i = 0; i < InStr.length; i++) { - InByte = InStr.charCodeAt(i); - if (FFrameMasked) InByte = UnmaskByte(InByte); - Result += String.fromCharCode(InByte); - } - } else { - throw new Error('GetFromWebSocketClientVersion7 Invalid frame type: ' + FFrameType); - } - - // Finished the data packet, so reset variables - FWebSocketDataQueue = ''; - FWebSocketState = WEBSOCKET_NEED_PACKET_START; - } - break; - } - } - - return Result; -} - -function ParsePathHeader() { - var Result = {}; - Result.query = {}; - - var Path = FWebSocketHeader['Path']; - if (Path) { - if (Path.indexOf('?') === -1) { - // No querystring - Result.path_info = Path; - } else { - // Have querystring, so parse it out - Result.path_info = Path.split('?')[0]; - var KeyValues = Path.split('?')[1].split('&'); - for (var i = 0; i < KeyValues.length; i++) { - var KeyValue = KeyValues[i].split('='); - Result.query[KeyValue[0]] = KeyValue[1]; - } - } - } - - return Result; -} - -function SendToServer(AStr) { - if (!FServerSocket.is_connected) return; - if (AStr.length == 0) return; - - var bytesSent = FServerSocket.send(AStr); - if (bytesSent != AStr.length) { - throw new Error('SendToServer only sent ' + bytesSent + ' of ' + AStr.length + ' bytes'); - } -} - -function SendToWebSocketClient(AData) { - if (!client.socket.is_connected) return; - if (AData.length == 0) return; - - switch (FWebSocketHeader['Version']) { - case 0: - SendToWebSocketClientDraft0(AData); - break; - case 7: - case 8: - case 13: - SendToWebSocketClientVersion7(AData); - break; - } -} - -function SendToWebSocketClientDraft0(AData) { - // Send 0x00 to indicate the start of a data packet - client.socket.sendBin(0x00, 1); - - for (var i = 0; i < AData.length; i++) { - // Check if the byte needs to be UTF-8 encoded - if ((AData[i] & 0xFF) <= 127) { - client.socket.sendBin(AData[i], 1); - } else if ((AData[i] & 0xFF) <= 2047) { - // Handle UTF-8 encode - client.socket.sendBin((AData[i] >> 6) | 192, 1); - client.socket.sendBin((AData[i] & 63) | 128, 1); - } else { - throw new Error('SendToWebSocketClientDraft0 Byte out of range: ' + AData[i]); - } - } - - // Send 0xFF to indicate the end of a data packet - client.socket.sendBin(0xFF, 1); -} - -function SendToWebSocketClientVersion7(AData) { - var ToSend = ''; - - if (FWebSocketSubProtocol === 'binary') { - for (var i = 0; i < AData.length; i++) { - ToSend += String.fromCharCode(AData[i]); - } - client.socket.sendBin(0x82, 1); - } else { - for (var i = 0; i < AData.length; i++) { - // Check if the byte needs to be UTF-8 encoded - if ((AData[i] & 0xFF) <= 127) { - ToSend += String.fromCharCode(AData[i]); - } else if (((AData[i] & 0xFF) >= 128) && ((AData[i] & 0xFF) <= 2047)) { - // Handle UTF-8 encode - ToSend += String.fromCharCode((AData[i] >> 6) | 192); - ToSend += String.fromCharCode((AData[i] & 63) | 128); - } else { - throw new Error('SendToWebSocketClientVersion7 Byte out of range: ' + AData[i]); - } - } - client.socket.sendBin(0x81, 1); - } - - if (ToSend.length <= 125) { - client.socket.sendBin(ToSend.length, 1); - } else if (ToSend.length <= 65535) { - client.socket.sendBin(126, 1); - client.socket.sendBin(ToSend.length, 2); - } else { - // NOTE: client.socket.sendBin(ToSend.length, 8); didn't work, so this - // modification limits the send to 2^32 bytes (probably not an issue) - // Probably should look into a proper fix at some point though - client.socket.sendBin(127, 1); - client.socket.sendBin(0, 4); - client.socket.sendBin(ToSend.length, 4); - } - - var bytesSent = client.socket.send(ToSend); - if (bytesSent != ToSend.length) { - throw new Error('SendToWebSocketClientVersion7 only sent ' + bytesSent + ' of ' + ToSend.length + ' bytes'); - } -} - -function ShakeHands() { - FWebSocketHeader['Version'] = 0; - - try { - // Keep reading header data until we get all the data we want - while (true) { - // Read another line, and abort if we don't get one within 5 seconds - var InLine = client.socket.recvline(512, 5); - if (InLine === null) { - log(LOG_DEBUG, "Timeout exceeded while waiting for complete handshake"); - return false; - } - - log(LOG_DEBUG, "Handshake Line: " + InLine); - - // Check for blank line (indicates we have most of the header, and only the last 8 bytes remain - if (InLine === "") { - switch (FWebSocketHeader['Version']) { - case 0: - return ShakeHandsDraft0(); - case 7: - case 8: - case 13: - return ShakeHandsVersion7(); - default: - // TODO If this version does not - // match a version understood by the server, the server MUST - // abort the websocket handshake described in this section and - // instead send an appropriate HTTP error code (such as 426 - // Upgrade Required), and a |Sec-WebSocket-Version| header - // indicating the version(s) the server is capable of - // understanding. - break; - } - break; - } else if (InLine.search(/^Connection:/i) === 0) { - // Example: "Connection: Upgrade" - FWebSocketHeader['Connection'] = InLine.replace(/Connection:\s?/i, ""); - } else if (InLine.search(/^GET/i) === 0) { - // Example: "GET /demo HTTP/1.1" - var GET = InLine.split(" "); - FWebSocketHeader['Path'] = GET[1]; - } else if (InLine.search(/^Host:/i) === 0) { - // Example: "Host: example.com" - FWebSocketHeader['Host'] = InLine.replace(/Host:\s?/i, ""); - } else if (InLine.search(/^Origin:/i) === 0) { - // Example: "Origin: http://example.com" - FWebSocketHeader['Origin'] = InLine.replace(/Origin:\s?/i, ""); - } else if (InLine.search(/^Sec-WebSocket-Key:/i) === 0) { - // Example: "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" - FWebSocketHeader['Key'] = InLine.replace(/Sec-WebSocket-Key:\s?/i, ""); - } else if (InLine.search(/^Sec-WebSocket-Key1:/i) === 0) { - // Example: "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5" - FWebSocketHeader['Key1'] = CalculateWebSocketKey(InLine.replace(/Sec-WebSocket-Key1:\s?/i, "")); - } else if (InLine.search(/^Sec-WebSocket-Key2:/i) === 0) { - // Example: "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00" - FWebSocketHeader['Key2'] = CalculateWebSocketKey(InLine.replace(/Sec-WebSocket-Key2:\s?/i, "")); - } else if (InLine.search(/^Sec-WebSocket-Origin:/i) === 0) { - // Example: "Sec-WebSocket-Origin: http://example.com" - FWebSocketHeader['Origin'] = InLine.replace(/Sec-WebSocket-Origin:\s?/i, ""); - } else if (InLine.search(/^Sec-WebSocket-Protocol:/i) === 0) { - // Example: "Sec-WebSocket-Protocol: sample" - FWebSocketHeader['SubProtocol'] = InLine.replace(/Sec-WebSocket-Protocol:\s?/i, ""); - } else if (InLine.search(/^Sec-WebSocket-Draft/i) === 0) { - // Example: "Sec-WebSocket-Draft: 2" - try { - FWebSocketHeader['Version'] = parseInt(InLine.replace(/Sec-WebSocket-Draft:\s?/i, "")); - } catch (err) { - FWebSocketHeader['Version'] = 0; - } - } else if (InLine.search(/^Sec-WebSocket-Version/i) === 0) { - // Example: "Sec-WebSocket-Version: 8" - try { - FWebSocketHeader['Version'] = parseInt(InLine.replace(/Sec-WebSocket-Version:\s?/i, "")); - } catch (err) { - FWebSocketHeader['Version'] = 0; - } - } else if (InLine.search(/^Upgrade:/i) === 0) { - // Example: "Upgrade: websocket" - FWebSocketHeader['Upgrade'] = InLine.replace(/Upgrade:\s?/i, ""); - } else if (InLine.search(/^X-Forwarded-For/i) === 0) { - FWebSocketHeader['X-Forwarded-For'] = InLine.replace(/X-Forwarded-For:\s?/i, ""); - } - } - } catch (err) { - log(LOG_DEBUG, "ShakeHands() error: " + err.toString()); - } - - return false; -} - -function ShakeHandsDraft0() { - // Ensure we have all the data we need - if (('Key1' in FWebSocketHeader) && ('Key2' in FWebSocketHeader) && ('Host' in FWebSocketHeader) && ('Origin' in FWebSocketHeader !== "") && ('Path' in FWebSocketHeader)) { - // Combine Key1, Key2, and the last 8 bytes into a string that we will later hash - var ToHash = "" - ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0xFF000000) >> 24); - ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x00FF0000) >> 16); - ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x0000FF00) >> 8); - ToHash += String.fromCharCode((FWebSocketHeader['Key1'] & 0x000000FF) >> 0); - ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0xFF000000) >> 24); - ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x00FF0000) >> 16); - ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x0000FF00) >> 8); - ToHash += String.fromCharCode((FWebSocketHeader['Key2'] & 0x000000FF) >> 0); - for (var i = 0; i < 8; i++) { - ToHash += String.fromCharCode(client.socket.recvBin(1)); - } - - // Hash the string - var Hashed = md5_calc(ToHash, true); - - // Setup the handshake response - var Response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + - "Upgrade: WebSocket\r\n" + - "Connection: Upgrade\r\n" + - "Sec-WebSocket-Origin: " + FWebSocketHeader['Origin'] + "\r\n" + - "Sec-WebSocket-Location: ws://" + FWebSocketHeader['Host'] + FWebSocketHeader['Path'] + "\r\n"; - if ('SubProtocol' in FWebSocketHeader) Response += "Sec-WebSocket-Protocol: " + FWebSocketHeader['SubProtocol'] + "\r\n"; - Response += "\r\n"; - - // Loop through the hash string (which is hex encoded) and append the individual bytes to the response - for (var i = 0; i < Hashed.length; i += 2) { - Response += String.fromCharCode(parseInt(Hashed.charAt(i) + Hashed.charAt(i + 1), 16)); - } - - // Send the response and return - client.socket.send(Response); - return true; - } else { - // We're missing some pice of data, log what we do have - log(LOG_DEBUG, "Missing some piece of handshake data. Here's what we have:"); - for(var x in FWebSocketHeader) { - log(LOG_DEBUG, x + " => " + FWebSocketHeader[x]); - } - return false; - } -} - -function ShakeHandsVersion7() { - // Ensure we have all the data we need - if (('Key' in FWebSocketHeader) && ('Host' in FWebSocketHeader) && ('Origin' in FWebSocketHeader !== "") && ('Path' in FWebSocketHeader)) { - var AcceptGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - // Combine Key and GUID - var ToHash = FWebSocketHeader['Key'] + AcceptGUID; - - // Hash the string - var Hashed = Sha1.hash(ToHash, false); - - // Encode the hash - var ToEncode = ''; - for (var i = 0; i <= 38; i += 2) { - ToEncode += String.fromCharCode(parseInt(Hashed.substr(i, 2), 16)); - } - var Encoded = base64_encode(ToEncode); - - // Setup the handshake response - var Response = "HTTP/1.1 101 Switching Protocols\r\n" + - "Upgrade: websocket\r\n" + - "Connection: Upgrade\r\n" + - "Sec-WebSocket-Accept: " + Encoded + "\r\n"; - if ('SubProtocol' in FWebSocketHeader) { - if (FWebSocketHeader['SubProtocol'].indexOf('binary') >= 0) { - FWebSocketSubProtocol = 'binary'; - } else if (FWebSocketHeader['SubProtocol'].indexOf('plain') >= 0) { - FWebSocketSubProtocol = 'plain'; - } else { - log(LOG_ERR, 'No supported SubProtocols found ("binary" and "plain" are only supported options'); - return false; - } - - log(LOG_DEBUG, 'Selecting "' + FWebSocketSubProtocol + '" SubProtocol'); - Response += "Sec-WebSocket-Protocol: " + FWebSocketSubProtocol + "\r\n"; - } - Response += "\r\n"; - - // Send the response and return - client.socket.send(Response); - return true; - } else { - // We're missing some pice of data, log what we do have - log(LOG_DEBUG, "Missing some piece of handshake data. Here's what we have:"); - for(var x in FWebSocketHeader) { - log(LOG_DEBUG, x + " => " + FWebSocketHeader[x]); - } - return false; - } -} - -// This long function replaces the original one-liner "if (FFrameMasked) InByte ^= FFrameMask[FFramePayloadReceived++ % 4];" -// This is because a sysop running sbbs on a banana pi reported garbled input, and it was determined that the garbled input was -// caused by the above one-liner not working. This function does a bunch of extra checks and will throw if unmasking fails. -// See https://gitlab.synchro.net/main/sbbs/-/issues/782 for more details -function UnmaskByte(maskedByte) { - // Determine which index in the FFrameMask array we need to use to unmask the next byte - var maskIndex = FFramePayloadReceived % 4; - FFramePayloadReceived++; - if (maskIndex === undefined) { - throw new Error('UnmaskByte maskIndex is undefined (should be between 0 and 3)'); - } else if (maskIndex < 0 || maskIndex > 3) { - throw new Error('UnmaskByte maskIndex is ' + maskIndex + ' (should be between 0 and 3)'); - } - - // Read the masking byte from the array - var maskingByte = FFrameMask[maskIndex]; - if (maskingByte === undefined) { - throw new Error('UnmaskByte maskingByte is undefined (should be between 0 and 255)'); - } else if (maskingByte < 0 || maskingByte > 255) { - throw new Error('UnmaskByte maskingByte is ' + maskingByte + ' (should be between 0 and 255)'); - } - - // Check if the masking byte is zero (if it is the maskedByte isn't really masked, and we can return it as-is) - if (maskingByte === 0) { - return maskedByte; - } - - // Unmask the maskedByte and confirm the XOR operation succeeded (ie result doesn't match the original maskedByte) - var result = maskedByte ^ maskingByte; - if (result === maskedByte) { - throw new Error('UnmaskByte result is ' + result + ', expected ' + maskedByte + ' XOR ' + maskingByte + ' = ' + (maskedByte ^ maskingByte)); - } - - return result; -} - -function inet_pton (a) { - // http://kevin.vanzonneveld.net - // + original by: Theriault - // * example 1: inet_pton('::'); - // * returns 1: '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' (binary) - // * example 2: inet_pton('127.0.0.1'); - // * returns 2: '\x7F\x00\x00\x01' (binary) - var r, m, x, i, j, f = String.fromCharCode; - m = a.match(/^(?:\d{1,3}(?:\.|$)){4}/); // IPv4 - if (m) { - m = m[0].split('.'); - m = f(m[0]) + f(m[1]) + f(m[2]) + f(m[3]); - // Return if 4 bytes, otherwise false. - return m.length === 4 ? m : false; - } - r = /^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/; - m = a.match(r); // IPv6 - if (m) { - // Translate each hexadecimal value. - for (j = 1; j < 4; j++) { - // Indice 2 is :: and if no length, continue. - if (j === 2 || m[j].length === 0) { - continue; - } - m[j] = m[j].split(':'); - for (i = 0; i < m[j].length; i++) { - m[j][i] = parseInt(m[j][i], 16); - // Would be NaN if it was blank, return false. - if (isNaN(m[j][i])) { - return false; // Invalid IP. - } - m[j][i] = f(m[j][i] >> 8) + f(m[j][i] & 0xFF); - } - m[j] = m[j].join(''); - } - x = m[1].length + m[3].length; - if (x === 16) { - return m[1] + m[3]; - } else if (x < 16 && m[2].length > 0) { - return m[1] + (new Array(16 - x + 1)).join('\x00') + m[3]; - } - } - return false; // Invalid IP. -}