diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 5e4ca72f..a1b24541 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -1,6 +1,7 @@ 'use strict'; const fs = require('fs'); +const tls = require('tls'); const path = require('path'); const zlib = require('zlib'); const http = require('http'); @@ -25,7 +26,7 @@ process .on('uncaughtException', (e => { console.log(e); })); -function shutdown(signal) { +function shutdown(signal = 'SIGTERM') { return (err) => { console.log("Received signal", signal); if (err) console.error(err.stack || err); @@ -431,16 +432,35 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } } +function verifyServicePort(service_name, socket) { + if (socket._server._connectionKey) { + var socketPort = parseInt(socket._server._connectionKey.split(':')[2]) + if (minisrv_config.services[service_name]) { + if (minisrv_config.services[service_name].port == socketPort) { + if (minisrv_config.services[service_name].servicevault_dir) + return minisrv_config.services[service_name].servicevault_dir; + else + return service_name; + } + } + } + return false; +} + async function processURL(socket, request_headers) { - console.log(request_headers) var shortURL, headers, data = ""; var enable_multi_query = false; - socket.minisrv_pc_mode = false; request_headers.query = new Array(); if (request_headers.request_url) { if (request_headers.request_url.indexOf('?') >= 0) { shortURL = request_headers.request_url.split('?')[0]; - service_name = shortURL.split(':')[0]; + service_name = verifyServicePort(shortURL.split(':')[0], socket); + if (!service_name) { + var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode); + socket_sessions[socket.id].close_me = true; + sendToClient(socket, errpage[0], errpage[1]); + return + } if (minisrv_config.services[service_name]) enable_multi_query = minisrv_config.services[service_name].enable_multi_query || false; var qraw = request_headers.request_url.split('?')[1]; if (qraw.length > 0) { @@ -530,14 +550,30 @@ async function processURL(socket, request_headers) { shortURL = shortURL_service_name + ":/" + shortURL_service_path; } else if (shortURL.indexOf(":") == -1 && request_headers.request.indexOf("HTTP/1") > 0 && socket.ssid == null) { if (request_headers.Host) { - if (minisrv_config.config.pc_server_hidden_service_enabled) { - // browsers typically send a Host header - service_name = minisrv_config.config.pc_server_hidden_service; - socket.minisrv_pc_mode = true; - shortURL = service_name + ":" + shortURL; - - // if a directory, request index - if (shortURL.substring(shortURL.length - 1) == "/") shortURL += "index"; + if (minisrv_config.services.pc_services) { + if (!minisrv_config.services.pc_services.disabled) { + // browsers typically send a Host header + socket.minisrv_pc_mode = true; + service_name = verifyServicePort("pc_services", socket); + if (!service_name) { + var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode); + socket_sessions[socket.id].close_me = true; + sendToClient(socket, errpage[0], errpage[1]); + return; + } + // if a directory, request index + if (shortURL.substring(shortURL.length - 1) == "/") shortURL += "index"; + shortURL = service_name + ":" + shortURL; + } else { + // minimal pc mode to send error + socket.minisrv_pc_mode = true; + var errpage = wtvshared.doErrorPage(401, "PC services are disabled on this server", socket.minisrv_pc_mode); + headers = errpage[0]; + data = errpage[1] + socket_sessions[socket.id].close_me = true; + sendToClient(socket, headers, data); + return; + } } else { // minimal pc mode to send error socket.minisrv_pc_mode = true; @@ -550,7 +586,8 @@ async function processURL(socket, request_headers) { } } } - if (!socket.minisrv_pc_mode) { + + if (!socket.minisrv_pc_mode && !socket.ssid) { // skip box auth tests for pc mode // check security @@ -614,8 +651,15 @@ minisrv-no-mail-count: true`; } else { console.log(" * " + reqverb + " for " + request_headers.request_url, 'on', socket.id); } - // assume webtv since there is a :/ in the GET - var service_name = shortURL.split(':/')[0]; + if (!socket.minisrv_pc_mode) { + var service_name = verifyServicePort(shortURL.split(':/')[0], socket); + if (!service_name) { + var errpage = wtvshared.doErrorPage(500, null, socket.minisrv_pc_mode); + socket_sessions[socket.id].close_me = true; + sendToClient(socket, errpage[0], errpage[1]); + return + } + } var urlToPath = wtvshared.fixPathSlashes(service_name + path.sep + shortURL.split(':/')[1]); var shared_romcache = null; if (shortURL.indexOf(":/ROMCache/") != -1 && minisrv_config.config.enable_shared_romcache) { @@ -1430,125 +1474,132 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq socket_sessions[socket.id].headers = headers; } } else { + if (!socket.ssid) { + // incomplete, dont use https on pc services yet + socket = new tls.TLSSocket(socket); + socket.minisrv_pc_mode = true; + console.log(socket) + console.log("pc https?"); + } else { // handle streaming POST - if (socket_sessions[socket.id].expecting_post_data && headers) { - socket_sessions[socket.id].headers = headers; - if (socket_sessions[socket.id].post_data.length < (socket_sessions[socket.id].post_data_length * 2)) { - new_header_obj = null; - var enc_data = CryptoJS.enc.Hex.parse(data_hex); - if (socket_sessions[socket.id].secure) { - // decrypt if encrypted - var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)) - } else { - // just pass it over - var dec_data = enc_data; - } + if (socket_sessions[socket.id].expecting_post_data && headers) { + socket_sessions[socket.id].headers = headers; + if (socket_sessions[socket.id].post_data.length < (socket_sessions[socket.id].post_data_length * 2)) { + new_header_obj = null; + var enc_data = CryptoJS.enc.Hex.parse(data_hex); + if (socket_sessions[socket.id].secure) { + // decrypt if encrypted + var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)) + } else { + // just pass it over + var dec_data = enc_data; + } - socket_sessions[socket.id].post_data += dec_data.toString(CryptoJS.enc.Hex); + socket_sessions[socket.id].post_data += dec_data.toString(CryptoJS.enc.Hex); - var post_string = "POST"; - if (socket_sessions[socket.id].secure == true) post_string = "Encrypted " + post_string; + var post_string = "POST"; + if (socket_sessions[socket.id].secure == true) post_string = "Encrypted " + post_string; - if (minisrv_config.config.post_debug) { - // `post_debug` logging of every chunk - console.log(" * ", Math.floor(new Date().getTime() / 1000), "Receiving", post_string, "data on", socket.id, "[", socket_sessions[socket.id].post_data.length / 2, "of", socket_sessions[socket.id].post_data_length, "bytes ]"); - } else { - // calculate and display percentage of data received - var postPercent = wtvshared.getPercentage(socket_sessions[socket.id].post_data.length, (socket_sessions[socket.id].post_data_length * 2)); - if (minisrv_config.config.post_percentages) { - if (minisrv_config.config.post_percentages.includes(postPercent)) { - if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array(); - if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) { - console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", wtvshared.filterSSID(socket.ssid)); - socket_sessions[socket.id].post_data_percents_shown[postPercent] = true; + if (minisrv_config.config.post_debug) { + // `post_debug` logging of every chunk + console.log(" * ", Math.floor(new Date().getTime() / 1000), "Receiving", post_string, "data on", socket.id, "[", socket_sessions[socket.id].post_data.length / 2, "of", socket_sessions[socket.id].post_data_length, "bytes ]"); + } else { + // calculate and display percentage of data received + var postPercent = wtvshared.getPercentage(socket_sessions[socket.id].post_data.length, (socket_sessions[socket.id].post_data_length * 2)); + if (minisrv_config.config.post_percentages) { + if (minisrv_config.config.post_percentages.includes(postPercent)) { + if (!socket_sessions[socket.id].post_data_percents_shown) socket_sessions[socket.id].post_data_percents_shown = new Array(); + if (!socket_sessions[socket.id].post_data_percents_shown[postPercent]) { + console.log(" * Received", postPercent, "% of", socket_sessions[socket.id].post_data_length, "bytes on", socket.id, "from", wtvshared.filterSSID(socket.ssid)); + socket_sessions[socket.id].post_data_percents_shown[postPercent] = true; + } + if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown; } - if (postPercent == 100) delete socket_sessions[socket.id].post_data_percents_shown; } } } - } - if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { - // got all expected data - if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; - socket.setTimeout(minisrv_config.config.socket_timeout * 1000); - headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); - if (socket_sessions[socket.id].secure == true) { - if (minisrv_config.config.debug_flags.debug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", headers.post_data.sigBytes, "bytes ]"); - } else { - if (minisrv_config.config.debug_flags.debug) console.log(" # Unencrypted POST Content", "on", socket.id); - } - socket_sessions[socket.id].expecting_post_data = false; - delete socket_sessions[socket.id].headers; - delete socket_sessions[socket.id].post_data; - delete socket_sessions[socket.id].post_data_length; - processURL(socket, headers); - return; - } else if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { - socket_sessions[socket.id].expecting_post_data = false; - if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; - socket.setTimeout(minisrv_config.config.socket_timeout * 1000); - // got too much data ? ... should not ever reach this code - var errpage = wtvshared.doErrorPage(400, "Received too much data in POST request
Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length); - headers = errpage[0]; - data = errpage[1]; - sendToClient(socket, headers, data); - return; - } - - } else if (!skipSecure) { - if (!encryptedRequest) { - if (socket_sessions[socket.id].secure != true) { - socket_sessions[socket.id].wtvsec = new WTVSec(minisrv_config); - socket_sessions[socket.id].wtvsec.IssueChallenge(); - socket_sessions[socket.id].wtvsec.SecureOn(); - socket_sessions[socket.id].secure = true; - } - var enc_data = CryptoJS.enc.Hex.parse(data_hex); - if (enc_data.sigBytes > 0) { - if (!socket_sessions[socket.id].wtvsec) { - var errpage = wtvshared.doErrorPage(400); - var headers = errpage[0]; - headers += "wtv-visit: client:relog\n"; - data = errpage[1]; - sendToClient(socket, headers, data); - return; - } - var str_test = enc_data.toString(CryptoJS.enc.Latin1); - if (isUnencryptedString(str_test)) { - var dec_data = enc_data; + if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { + // got all expected data + if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; + socket.setTimeout(minisrv_config.config.socket_timeout * 1000); + headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); + if (socket_sessions[socket.id].secure == true) { + if (minisrv_config.config.debug_flags.debug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", headers.post_data.sigBytes, "bytes ]"); } else { - var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)); + if (minisrv_config.config.debug_flags.debug) console.log(" # Unencrypted POST Content", "on", socket.id); } - if (!socket_sessions[socket.id].secure_buffer) socket_sessions[socket.id].secure_buffer = ""; - socket_sessions[socket.id].secure_buffer += dec_data.toString(CryptoJS.enc.Hex); - var secure_headers = null; - if (headers['request']) { - if (headers['request'] == "GET") { - if (socket_sessions[socket.id].secure_buffer.indexOf("0d0a0d0a") || socket_sessions[socket.id].secure_buffer.indexOf("0a0a")) { - secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); + socket_sessions[socket.id].expecting_post_data = false; + delete socket_sessions[socket.id].headers; + delete socket_sessions[socket.id].post_data; + delete socket_sessions[socket.id].post_data_length; + processURL(socket, headers); + return; + } else if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { + socket_sessions[socket.id].expecting_post_data = false; + if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; + socket.setTimeout(minisrv_config.config.socket_timeout * 1000); + // got too much data ? ... should not ever reach this code + var errpage = wtvshared.doErrorPage(400, "Received too much data in POST request
Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length); + headers = errpage[0]; + data = errpage[1]; + sendToClient(socket, headers, data); + return; + } + } else if (!skipSecure) { + if (!encryptedRequest) { + if (socket_sessions[socket.id].secure != true) { + socket_sessions[socket.id].wtvsec = new WTVSec(minisrv_config); + socket_sessions[socket.id].wtvsec.IssueChallenge(); + socket_sessions[socket.id].wtvsec.SecureOn(); + socket_sessions[socket.id].secure = true; + } + var enc_data = CryptoJS.enc.Hex.parse(data_hex); + if (enc_data.sigBytes > 0) { + if (!socket_sessions[socket.id].wtvsec) { + var errpage = wtvshared.doErrorPage(400); + var headers = errpage[0]; + headers += "wtv-visit: client:relog\n"; + data = errpage[1]; + sendToClient(socket, headers, data); + return; + } + var str_test = enc_data.toString(CryptoJS.enc.Latin1); + if (isUnencryptedString(str_test)) { + var dec_data = enc_data; + } else { + var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)); + } + if (!socket_sessions[socket.id].secure_buffer) socket_sessions[socket.id].secure_buffer = ""; + socket_sessions[socket.id].secure_buffer += dec_data.toString(CryptoJS.enc.Hex); + var secure_headers = null; + if (headers['request']) { + if (headers['request'] == "GET") { + if (socket_sessions[socket.id].secure_buffer.indexOf("0d0a0d0a") || socket_sessions[socket.id].secure_buffer.indexOf("0a0a")) { + secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); + } + } else { + var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); } } else { var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); } - } else { - var secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); - } - if (secure_headers) { - delete socket_sessions[socket.id].secure_buffer; - if (!headers) headers = new Array(); - headers.encrypted = true; - Object.keys(secure_headers).forEach(function (k, v) { - headers[k] = secure_headers[k]; - }); - if (headers['request']) { - if (headers['request'].substring(0, 4) == "POST") { - if (!socket_sessions[socket.id].post_data) { - socket_sessions[socket.id].post_data_length = headers['Content-length'] || headers['Content-Length'] || 0; - socket_sessions[socket.id].post_data = ""; + if (secure_headers) { + delete socket_sessions[socket.id].secure_buffer; + if (!headers) headers = new Array(); + headers.encrypted = true; + Object.keys(secure_headers).forEach(function (k, v) { + headers[k] = secure_headers[k]; + }); + if (headers['request']) { + if (headers['request'].substring(0, 4) == "POST") { + if (!socket_sessions[socket.id].post_data) { + socket_sessions[socket.id].post_data_length = headers['Content-length'] || headers['Content-Length'] || 0; + socket_sessions[socket.id].post_data = ""; + } + processRequest(socket, dec_data.toString(CryptoJS.enc.Hex)); + } else { + processURL(socket, headers); } - processRequest(socket, dec_data.toString(CryptoJS.enc.Hex)); - } else { - processURL(socket, headers); } } } @@ -1806,14 +1857,16 @@ ports.sort(); // de-duplicate ports in case user configured multiple services on same port const bind_ports = [...new Set(ports)] if (!minisrv_config.config.bind_ip) minisrv_config.config.bind_ip = "0.0.0.0"; -bind_ports.forEach(function (v) { +bind_ports.every(function (v) { try { var server = net.createServer(handleSocket); - server.listen(v, minisrv_config.config.bind_ip); + server.listen(v, minisrv_config.config.bind_ip); initstring += v + ", "; + return true; } catch (e) { throw ("Could not bind to port", v, "on", minisrv_config.config.bind_ip, e.toString()); } + return false; }); initstring = initstring.substring(0, initstring.length - 2); diff --git a/zefie_wtvp_minisrv/config.json b/zefie_wtvp_minisrv/config.json index d8835382..ada51c9b 100644 --- a/zefie_wtvp_minisrv/config.json +++ b/zefie_wtvp_minisrv/config.json @@ -1,6 +1,7 @@ { "config": { "service_ip": "127.0.0.1", + "bind_ip": "0.0.0.0", "ServiceVaults": [ "UserServiceVault", "ServiceVault" @@ -26,8 +27,6 @@ "hide_incomplete_features": true, "enable_lzpf_compression": true, "enable_gzip_compression": true, - "pc_server_hidden_service": "http_pc", - "pc_server_hidden_service_enabled": false, "show_detailed_splash": true, "show_diskmap": false, "unauthorized_url": "wtv-1800:/unauthorized?", @@ -129,6 +128,13 @@ "external_proxy_host": "127.0.0.1", "external_proxy_port": 1080, "flags": "0x00000001" + }, + "pc_services": { + "port": 1699, + "servicevault_dir": "http_pc", + "disabled": true, + "allow_https": false, + "force_https": false } } }