diff --git a/user_config_README.md b/user_config_README.md index 79d36bf4..3748eb9e 100644 --- a/user_config_README.md +++ b/user_config_README.md @@ -11,6 +11,14 @@ Some values are available that are not defined in `config.json` by default. I wi ``` You can set the image to be loaded in the top left in place of the WebTV or MSN logo, as well as the main Splash image shown on login. If an absolute path (`wtv-url:/`, `file://` url, or `http(s)://` url) is not passed, the server will search for the specified filename in `wtv-star/images` of any Service Vault. You'll want to keep the filesizes low. +``` + "post_debug": true +``` +If you would like to see debug information about realtime bytes received from a client POST request, set `post_debug` to true. +``` + "post_percentages": [ 0, 25, 50, 100] +``` +If you would like to see progress updates on client POST requests, you can define which percentages to show here. Other examples would be `[ 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ]` for every 10%, or you could set it to `false`, or `null`, to disable progress updates. Note that percentages are not shown when `post_debug` is enabled. ``` "ssid_block_list": [ "8100000000000000", diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-chat/MakeChatPage.js b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/MakeChatPage.js new file mode 100644 index 00000000..b75a4c37 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/MakeChatPage.js @@ -0,0 +1,174 @@ +headers = `200 OK`; + + +if (request_headers.query.nick) headers += ` +wtv-irc-nick: ${request_headers.query.nick} +wtv-user-nick: ${request_headers.query.nick}` +; + +headers += ` +Content-Type: text/html`; + +if (request_headers.query.host && request_headers.query.port && request_headers.query.channel) { + data = ` + + + + +${request_headers.query.channel} + + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + +
+
+ + +
+
+ + +
+
+ + + + +
+
Create +
+
+
+
+ + + + +
+
People +
+
+
+
+ + + + +
+
Whisper +
+
+
+
+
+
+
+ + + + + +
+ +
+ +
+ + + + +
+ +
+
+ + +
+ + +
   + ${request_headers.query.channel} + + + +
+ + +
+ Go to +
+ + +
+
+
+ + + + + + + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + + +
+ + + + + + + +
+
+ +`; +} else { + var errpage = doErrorPage("400 Chat requires host, port and channel arguments. Do not use the # on channels."); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/top_corner_dark.jpg b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/top_corner_dark.jpg new file mode 100644 index 00000000..6e72143b Binary files /dev/null and b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/top_corner_dark.jpg differ diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/widget.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/widget.gif new file mode 100644 index 00000000..3cf94cab Binary files /dev/null and b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/widget.gif differ diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js index e7e66602..63e2d198 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/get-lc2-page.js @@ -123,7 +123,7 @@ hspace=0 vspace=0 fontsize="large">
- + @@ -206,9 +206,6 @@ ${flashrom_message} -
- -
diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js index de414b98..61ee429e 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-complete.js @@ -23,7 +23,7 @@ hspace=0 vspace=0 fontsize="large">
- + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js index e1667a42..3cb3fa17 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-flashrom/lc2-download-failed.js @@ -51,7 +51,7 @@ data = `
- + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js index 608ba163..8ce70907 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-head-waiter/login-stage-two.js @@ -16,17 +16,14 @@ if (socket.ssid !== null) { if (request_headers["wtv-ticket"].length > 8) { wtvsec_login.DecodeTicket(request_headers["wtv-ticket"]); wtvsec_login.ticket_b64 = request_headers["wtv-ticket"]; - //socket_sessions[socket.id].secure = true; } } else { challenge_response = wtvsec_login.challenge_response; var client_challenge_response = request_headers["wtv-challenge-response"] || null; if (challenge_response && client_challenge_response) { - //if (challenge_response.toString(CryptoJS.enc.Base64).substring(0,85) == client_challenge_response.substring(0,85)) { if (challenge_response.toString(CryptoJS.enc.Base64) == client_challenge_response) { console.log(" * wtv-challenge-response success for " + filterSSID(socket.ssid)); wtvsec_login.PrepareTicket(); - //socket_sessions[socket.id].secure = true; } else { console.log(" * wtv-challenge-response FAILED for " + filterSSID(socket.ssid)); if (zdebug) console.log("Response Expected:", challenge_response.toString(CryptoJS.enc.Base64)); @@ -44,13 +41,13 @@ if (gourl) { headers = `200 OK Connection: Keep-Alive wtv-open-isp-disabled: false -wtv-visit: `+ gourl + ` +wtv-visit: ${gourl} Content-type: text/html`; data = ''; } else { var namerand = Math.floor(Math.random() * 100000); - var nickname = minisrv_config.config.service_name+'_Usr_' + namerand; + var nickname = (minisrv_config.config.service_name + '_' + namerand).substring(0, 16); var userid = '1'+ Math.floor(Math.random() * 1000000000000000000); var offline_user_list = CryptoJS.enc.Latin1.parse("\n\t\n").toString(CryptoJS.enc.Base64); data = ''; @@ -63,16 +60,16 @@ wtv-country: US wtv-language-header: en-US,en wtv-visit: client:closeallpanels wtv-expire-all: client:closeallpanels -wtv-offline-user-list: `+ offline_user_list + ` +wtv-offline-user-list: ${offline_user_list} wtv-bypass-proxy: true -wtv-ticket: `+ wtvsec_login.ticket_b64 + ` +wtv-ticket: ${wtvsec_login.ticket_b64} wtv-messagewatch-checktimeoffset: off wtv-input-timeout: 14400 wtv-connection-timeout: 90 wtv-fader-timeout: 900 wtv-ssl-log-url: wtv-log:/log wtv-smartcard-inserted-message: Contacting service -user-id: `+ userid + ` +user-id: ${userid} wtv-transition-override: off wtv-allow-dsc: true wtv-messenger-enable: 0 @@ -80,12 +77,9 @@ wtv-noback-all: wtv- wtv-service: reset `+ getServiceString('all') + ` wtv-boot-url: wtv-1800:/preregister?relogin=true -`; -//wtv-ssl-certs-download-url: wtv-head-waiter:/ssl-cert.der -//wtv-ssl-certs-checksum: 473926DC1B11F635A6B920953FDCDE6A - headers += `wtv-user-name: `+ nickname + ` -wtv-human-name: `+ nickname + ` -wtv-irc-nick: `+ nickname + ` +wtv-user-name: ${nickname} +wtv-human-name: ${nickname} +wtv-irc-nick: ${nickname} wtv-home-url: wtv-home:/home? wtv-domain: wtv.zefie.com wtv-inactive-timeout: 0 @@ -102,6 +96,7 @@ wtv-demo-mode: 0 wtv-wink-deferrer-retries: 3 wtv-offline-mail-enable: false wtv-name-server: 8.8.8.8 +wtv-settings-url: wtv-setup:/get wtv-visit: wtv-home:/splash? Content-Type: text/html`; } \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js b/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js index d6a8cbab..3f8be789 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-home/home.js @@ -23,26 +23,31 @@ function go() { }

Welcome to `+ z_title + `

-

Encryption Status: `+cryptstatus+`

-Connection Speed: &rate; +Encryption Status: ${cryptstatus}
+Connection Speed: &rate; +

` +data += "\n"; if (fs.existsSync(service_vaults[0] + "/" + service_name + "/home.zefie.html")) { data += fs.readFileSync(service_vaults[0] + "/" + service_name + "/home.zefie.html", { 'encoding': 'utf8' }); diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-home/splash.js b/zefie_wtvp_minisrv/ServiceVault/wtv-home/splash.js index 7de72b69..c52ff929 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-home/splash.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-home/splash.js @@ -2,7 +2,7 @@ headers = `200 OK Connection: Keep-Alive wtv-expire-all: wtv- wtv-expire-all: http -Content-type: text/html` +Content-Type: text/html` data = ` @@ -15,7 +15,7 @@ data = `

- +




@@ -23,14 +23,13 @@ data = `

Mini service
-zefie minisrv v`+ minisrv_config.version; +zefie minisrv v${minisrv_config.version}`; if (getGitRevision()) { - data += ` (git ` + getGitRevision().substring(0,8) + `)`; + data += " (git " + getGitRevision().substring(0, 8) + ")"; } data += `
&rate;
- -`; \ No newline at end of file +`; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-log/log.js b/zefie_wtvp_minisrv/ServiceVault/wtv-log/log.js index 8df47106..288c81a6 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-log/log.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-log/log.js @@ -1,19 +1,18 @@ // write posted log data to disk. should be decrypted by this point (if it was encrypted) if the crypto stream didn't break request_is_async = true; +data = ''; +var fullpath = __dirname + "/ServiceLogPost/" + Math.floor(new Date().getTime() / 1000) + "_" + request_headers.query.type; +if (socket.ssid) fullpath += "_" + socket.ssid; +fullpath += ".txt"; + +fullpath = fullpath.replace(/\\/g, "/"); if (request_headers.post_data) { headers = `200 OK Connection: Keep-Alive Content-length: 0`; - data = ''; - var fullpath = __dirname + "/ServiceLogPost/" + Math.floor(new Date().getTime() / 1000) + "_" + request_headers.query.type; - if (socket.ssid) fullpath += "_" + socket.ssid; - fullpath += ".txt"; - - fullpath = fullpath.replace(/\\/g, "/"); - var logdata_outstring = ''; Object.keys(request_headers.query).forEach(function (k) { logdata_outstring += k + "=" + unescape(request_headers.query[k].toString()) + "\r\n"; @@ -35,7 +34,6 @@ Content-length: 0`; Connection: Keep-Alive Content-length: 0`; - data = ''; var logdata_outstring = ''; Object.keys(request_headers.query).forEach(function (k) { logdata_outstring += k + "=" + unescape(request_headers.query[k].toString()) + "\r\n"; diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-setup/get.js b/zefie_wtvp_minisrv/ServiceVault/wtv-setup/get.js new file mode 100644 index 00000000..827bd585 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-setup/get.js @@ -0,0 +1,31 @@ +headers = `200 OK +Content-Type: text/html` + +var settings_obj = new Array(); +settings_obj["from-server"] = 1; +settings_obj["setup-advanced-options"] = 0; +settings_obj["setup-play-bgm"] = 0; +settings_obj["setup-bgm-tempo"] = -1; +settings_obj["setup-bgm-volume"] = 100; +settings_obj["setup-background-color"] = "c6c6c6"; +settings_obj["setup-font-sizes"] = "medium"; +settings_obj["setup-in-stereo"] = 1; +settings_obj["setup-keyboard"] = "alphabetical"; +settings_obj["setup-link-color"] = "2222bb"; +settings_obj["setup-play-songs"] = 1; +settings_obj["setup-play-sounds"] = 1; +settings_obj["setup-text-color"] = 0; +settings_obj["setup-visited-color"] = "8822bb"; +settings_obj["setup-japan-keyboard"] = "roman"; +settings_obj["setup-japan-softkeyboard"] = "norm" +settings_obj["setup-chat-access-level"] = 0; +settings_obj["setup-chat-on-nontrusted-pages"] = 1; +settings_obj["setup-tv-chat-level"] = 2; + +data = ""; + +Object.keys(settings_obj).forEach(function (k, v) { + data += k + "=" + escape(settings_obj[k]) + "&"; +}); + +data = data.substring(0, (data.length - 1)); \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-star/images/HackTVLogoJewel.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/HackTVLogoJewel.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-star/images/HackTVLogoJewel.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/HackTVLogoJewel.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-star/images/MSNLogo.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/MSNLogo.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-star/images/MSNLogo.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/MSNLogo.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-star/images/WebTVLogoJewel.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/WebTVLogoJewel.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-star/images/WebTVLogoJewel.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/WebTVLogoJewel.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-star/images/splash_logo_hacktv.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/splash_logo_hacktv.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-star/images/splash_logo_hacktv.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/splash_logo_hacktv.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-star/images/splash_logo_msn.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/splash_logo_msn.gif similarity index 100% rename from zefie_wtvp_minisrv/ServiceVault/wtv-star/images/splash_logo_msn.gif rename to zefie_wtvp_minisrv/ServiceVault/wtv-star/ROMCache/splash_logo_msn.gif diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js b/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js index df0682fc..032a8dc5 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-update/sync.js @@ -83,7 +83,7 @@ function processGroup(diskmap_primary_group, diskmap_group_data, diskmap_subgrou if (!post_data_fileinfo[post_data_filecount]) post_data_fileinfo[post_data_filecount] = new Array(); if (post_data_line_name == "Last-modified") { - post_data_fileinfo[post_data_filecount][post_data_line_name] = (Date.parse(post_data_line_data) / 1000); + post_data_fileinfo[post_data_filecount][post_data_line_name] = (new Date(new Date(Date.parse(post_data_line_data)).toUTCString()) / 1000); } else if (post_data_line_name == "Content-length") { post_data_fileinfo[post_data_filecount][post_data_line_name] = parseInt(post_data_line_data); } @@ -107,14 +107,17 @@ function processGroup(diskmap_primary_group, diskmap_group_data, diskmap_subgrou if (!fs.existsSync(post_match_file)) post_match_file = null; }); - var post_match_file_lstat = fs.lstatSync(post_match_file); - var post_match_result = post_data_fileinfo.find(el => el.file === diskmap_group_data.files[k].file) || null; + var file_in_postdata = function (post_file) { + return post_file.file === diskmap_group_data.files[k].file + } + var post_match_file_lstat = fs.lstatSync(post_match_file); + var post_match_result = post_data_fileinfo.find(file_in_postdata) || null; var post_match_file_data = new Buffer.from(fs.readFileSync(post_match_file, { encoding: null, flags: 'r' })); - diskmap_group_data.files[k]["Last-modified"] = (post_match_file_lstat.mtime / 1000); + diskmap_group_data.files[k]["Last-modified"] = (new Date(new Date(post_match_file_lstat.mtime).toUTCString()) / 1000); diskmap_group_data.files[k]["Content-length"] = post_match_file_lstat.size; diskmap_group_data.files[k]["wtv-checksum"] = CryptoJS.MD5(CryptoJS.lib.WordArray.create(post_match_file_data)).toString(CryptoJS.enc.Hex).toLowerCase(); if (!diskmap_group_data.files[k].display) diskmap_group_data.files[k].display = diskmap_group_data.display; @@ -122,6 +125,9 @@ function processGroup(diskmap_primary_group, diskmap_group_data, diskmap_subgrou if (post_match_result) { // md5s match, so client doesn't need file if (diskmap_group_data.files[k]['wtv-checksum'].toLowerCase() == post_match_result["wtv-checksum"]) return; + // last modified is equal to or newer than the last update, and file size match, so assume same file and client does not need it + else if ((post_match_result["Last-modified"] >= diskmap_group_data.files[k]["Last-modified"]) && (post_match_result["Content-length"] == diskmap_group_data.files[k]["Content-length"])) return; + // otherwise send to client else wtv_download_list.push(diskmap_group_data.files[k]); } else { wtv_download_list.push(diskmap_group_data.files[k]); @@ -170,12 +176,12 @@ if (request_headers.query.diskmap && request_headers.query.group && request_head var errpage = doErrorPage(404,"The requested DiskMap does not exist."); headers = errpage[0]; data = errpage[1]; - console.log("wtv-update:/sync error", "could not find diskmap"); + if (zdebug) console.log(" # wtv-update:/sync error", "could not find diskmap"); } } else { var errpage = doErrorPage(400); headers = errpage[0]; data = errpage[1]; - if (zdebug) console.log("wtv-update:/sync error", "missing query arguments"); + if (zdebug) console.log(" # wtv-update:/sync error", "missing query arguments"); } diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 238a9099..366abe2d 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -344,8 +344,6 @@ async function doHTTPProxy(socket, request_headers) { if (res.headers.date) headers.Date = res.headers.date; if (res.headers["content-type"]) headers["Content-type"] = res.headers["content-type"]; if (res.headers.cookie) headers.Cookie = res.headers.cookie; - // content-length is best auto-calculated - //if (res.headers["content-length"]) headers["Content-Length"] = res.headers["content-length"]; if (res.headers.vary) headers.Vary = res.headers.vary; if (res.headers.location) headers.Location = res.headers.location; if (data_hex.substring(0, 8) == "0d0a0d0a") data_hex = data_hex.substring(8); @@ -354,9 +352,9 @@ async function doHTTPProxy(socket, request_headers) { }); }).on('error', function (err) { var errpage, headers, data = null; - if (err.code == "ENOTFOUND") { - errpage = doErrorPage(400,`The publisher ${err.hostname} is unknown.`); - } else { + if (err.code == "ENOTFOUND") errpage = doErrorPage(400, `The publisher ${request_data.host} is unknown.`); + else if (err.message.indexOf("HostUnreachable") > 0) errpage = doErrorPage(400, `The publisher ${request_data.host} could not be reached.`); + else { console.log(" * Unhandled Proxy Request Error:", err); errpage = doErrorPage(400); } @@ -412,6 +410,12 @@ async function sendToClient(socket, headers_obj, data) { // string to header object headers_obj = headerStringToObj(headers_obj, true); } + if (!socket_sessions[socket.id]) { + socket.destroy(); + return; + } + var wtv_connection_close = headers_obj["wtv-connection-close"]; + if (typeof(headers_obj["wtv-connection-close"]) != 'undefined') delete headers_obj["wtv-connection-close"]; // add Connection header if missing, default to Keep-Alive if (!headers_obj.Connection) { @@ -501,7 +505,7 @@ async function sendToClient(socket, headers_obj, data) { socket_sessions[socket.id].buffer = null; if (socket_sessions[socket.id].close_me) socket.end(); if (headers_obj["Connection"]) { - if (headers_obj["Connection"].toLowerCase() == "close" && !headers["wtv-connection-close"] == "false") { + if (headers_obj["Connection"].toLowerCase() == "close" && wtv_connection_close == "true") { socket.destroy(); } } @@ -547,27 +551,42 @@ function headersAreStandard(string, verbose = false) { } async function processRequest(socket, data_hex, returnHeadersBeforeSecure = false, encryptedRequest = false) { - var url = ""; - var data = Buffer.from(data_hex,'hex').toString('ascii'); - var headers = new Array(); + // TODO: clean up this function (how much is even used anymore?) + + var headers = null; + if (socket_sessions[socket.id]) { + if (socket_sessions[socket.id].headers) { + headers = socket_sessions[socket.id].headers; + delete socket_sessions[socket.id].headers; + } + } + var data = Buffer.from(data_hex, 'hex').toString('ascii'); if (typeof data === "string") { - if (data.length > 1) { + if ((data.indexOf("\r\n\r\n") != -1 || data.indexOf("\n\n") != -1) && typeof socket_sessions[socket.id].post_data == "undefined") { if (data.indexOf("\r\n\r\n") != -1) { data = data.split("\r\n\r\n")[0]; } else { data = data.split("\n\n")[0]; } if (headersAreStandard(data)) { - headers = headerStringToObj(data); + if (headers != null) { + var new_header_obj = headerStringToObj(data); + Object.keys(new_header_obj).forEach(function (k, v) { + headers[k] = new_header_obj[k]; + }); + new_header_obj = null; + } else { + headers = headerStringToObj(data); + } } else if (!returnHeadersBeforeSecure) { // if its a POST request, assume its a binary blob and not encrypted (dangerous) if (!encryptedRequest) { - // its not a POST and it 1failed the headersAreStandard test, so we think this is an encrypted blob + // its not a POST and it failed the headersAreStandard test, so we think this is an encrypted blob if (socket_sessions[socket.id].secure != true) { // first time so reroll sessions if (zdebug) console.log(" # [ UNEXPECTED BINARY BLOCK ] First sign of encryption, re-creating RC4 sessions for socket id", socket.id); - socket_sessions[socket.id].wtvsec = new WTVSec(1,zdebug); + socket_sessions[socket.id].wtvsec = new WTVSec(1, zdebug); socket_sessions[socket.id].wtvsec.IssueChallenge(); socket_sessions[socket.id].wtvsec.SecureOn(); socket_sessions[socket.id].secure = true; @@ -584,10 +603,13 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)); var secure_headers = await processRequest(socket, dec_data.toString(CryptoJS.enc.Hex), true, true); - headers.encrypted = true; - Object.keys(secure_headers).forEach(function (k, v) { - headers[k] = secure_headers[k]; - }); + if (secure_headers) { + var headers = new Array(); + headers.encrypted = true; + Object.keys(secure_headers).forEach(function (k, v) { + headers[k] = secure_headers[k]; + }); + } } } } @@ -598,7 +620,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals ssid_sessions[socket.ssid] = new ClientSessionData(); } if (!ssid_sessions[socket.ssid].data_store.sockets) ssid_sessions[socket.ssid].data_store.sockets = new Array(); - ssid_sessions[socket.ssid].data_store.sockets.push(socket.id); + ssid_sessions[socket.ssid].data_store.sockets.push(socket.id); } var ip2long = function (ip) { @@ -626,9 +648,9 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals }; var rejectSSIDConnection = function (ssid, blacklist) { - if (blacklist) console.log(" * Request from SSID", filterSSID(ssid), "(" + socket.remoteAddr + "), but that SSID is in the blacklist, rejecting."); + if (blacklist) console.log(" * Request from SSID", filterSSID(ssid), "(" + socket.remoteAddr + "), but that SSID is in the blacklist, rejecting."); else console.log(" * Request from SSID", filterSSID(socket.ssid), "(" + socket.remoteAddress + "), but that SSID is not in the whitelist, rejecting."); - + var errpage = doErrorPage(401, "Access to this service is denied."); headers = errpage[0]; data = errpage[1]; @@ -713,7 +735,6 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } if (returnHeadersBeforeSecure) { - headers = await checkForPostData(socket, headers, data, data_hex); return headers; } @@ -735,13 +756,13 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals socket_sessions[socket.id].secure = true; } if (!headers.request_url) { - + var header_length = 0; if (data_hex.indexOf("0d0a0d0a")) { // \r\n\r\n - var header_length = data.length + 4; + header_length = data.length + 4; } else if (data_hex.indexOf("0a0a")) { // \n\n - var header_length = data.length + 2; + header_length = data.length + 2; } var enc_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2)); if (enc_data.sigBytes > 0) { @@ -752,12 +773,13 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals headers.psuedo_encryption = true; ssid_sessions[socket.ssid].set("box-does-psuedo-encryption", true); socket_sessions[socket.id].secure = false; - var secure_headers = await processRequest(socket, enc_data.toString(CryptoJS.enc.Hex), true); + var secure_headers = await processRequest(socket, enc_data.toString(CryptoJS.enc.Hex), true, true); } else { // SECURE ON and detected encrypted data ssid_sessions[socket.ssid].set("box-does-psuedo-encryption", false); var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)) - var secure_headers = await processRequest(socket, dec_data.toString(CryptoJS.enc.Hex), true); + var secure_headers = await processRequest(socket, dec_data.toString(CryptoJS.enc.Hex), true, true); + if (!secure_headers) return; if (zdebug) console.log(" # Encrypted Request (SECURE ON)", "on", socket.id); if (zshowheaders) console.log(secure_headers); if (!secure_headers.request) { @@ -775,57 +797,162 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals }); } } - } else { - headers = await checkForPostData(socket, headers, data, data_hex); } - if (!headers.request_url) { - // still no url, likely lost encryption stream, tell client to relog -/* - socket_sessions[socket.id].secure = false; - headers = `300 OK -Connection: Keep-Alive -Expires: Wed, 09 Oct 1991 22:00:00 GMT -wtv-expire-all: wtv-head-waiter: -wtv-expire-all: wtv-1800: -Location: client:relog -wtv-visit: client:relog -Content-type: text/html`; - data = ''; - */ - socket_sessions[socket.id].secure = false - socket_sessions[socket.id].close_me = true; - delete socket_sessions[socket.id].wtvsec; - sendToClient(socket, headers, data); + + // handle POST + if (headers['request']) { + if (headers['request'].substring(0, 4) == "POST") { + if (typeof socket_sessions[socket.id].post_data == "undefined") { + if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown; + socket_sessions[socket.id].post_data_length = headers['Content-length'] || headers['Content-Length'] || 0; + socket_sessions[socket.id].post_data_length = parseInt(socket_sessions[socket.id].post_data_length); + socket_sessions[socket.id].post_data = ""; + socket_sessions[socket.id].headers = headers; + var post_string = "POST"; + if (socket_sessions[socket.id].secure == true) { + post_string = "Encrypted " + post_string; + } else { + // if the request is not encrypted, the client may have just sent the data with the primary headers, so lets look for that. + if (data_hex.indexOf("0d0a0d0a") != -1) socket_sessions[socket.id].post_data = data_hex.substring(data_hex.indexOf("0d0a0d0a") + 8); + if (data_hex.indexOf("0a0a") != -1) socket_sessions[socket.id].post_data = data_hex.substring(data_hex.indexOf("0a0a") + 4); + } + if (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { + // got all expected data + console.log(" * Incoming", post_string, "request on", socket.id, "from", filterSSID(socket.ssid), "to", headers['request_url'], "(got all expected", socket_sessions[socket.id].post_data_length, "bytes of data from client already)"); + headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); + processURL(socket, headers); + } else { + // expecting more data (see below) + console.log(" * Incoming", post_string, "request on", socket.id, "from", filterSSID(socket.ssid), "to", headers['request_url'], "(expecting", socket_sessions[socket.id].post_data_length, "bytes of data from client...)"); + } + if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { + // got too much data ? ... should not ever reach this code + var errpage = 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; + } + return; + } + } else { + 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 { - processURL(socket, headers); + socket_sessions[socket.id].headers = headers; } } else { - // socket error, terminate it. - socket.destroy(); - } - } -} + // handle streaming POST + if (typeof socket_sessions[socket.id].post_data != "undefined" && 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; + } -async function checkForPostData(socket, headers, data, data_hex) { - if (headers.request) { - if (headers.request.substring(0, 4) == "POST") { - if (data_hex.indexOf("0d0a0d0a") != -1) { - // \r\n\r\n - var header_length = data.length + 4; - } else if (data_hex.indexOf("0a0a") != -1) { - // \n\n - var header_length = data.length + 2; - } - var post_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2)); - if (socket_sessions[socket.id].secure == true) { - if (zdebug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", post_data.sigBytes, "bytes ]"); - } else { - if (zdebug) console.log(" # Unencrypted POST Content", "on", socket.id); - } - headers.post_data = post_data; + 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; + + 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 getPercentage = function(partialValue, totalValue) { + return Math.floor((100 * partialValue) / totalValue); + } + var postPercent = 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", 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 (socket_sessions[socket.id].post_data.length == (socket_sessions[socket.id].post_data_length * 2)) { + // got all expected data + headers.post_data = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].post_data); + if (socket_sessions[socket.id].secure == true) { + if (zdebug) console.log(" # Encrypted POST Content (SECURE ON)", "on", socket.id, "[", headers.post_data.sigBytes, "bytes ]"); + } else { + if (zdebug) console.log(" # Unencrypted POST Content", "on", socket.id); + } + 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; + } + if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { + // got too much data ? ... should not ever reach this code + var errpage = 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 (!returnHeadersBeforeSecure) { + if (!encryptedRequest) { + if (socket_sessions[socket.id].secure != true) { + socket_sessions[socket.id].wtvsec = new WTVSec(1, zdebug); + 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 = 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 (headersAreStandard(str_test)) { + var dec_data = enc_data; + } else { + var dec_data = CryptoJS.lib.WordArray.create(socket_sessions[socket.id].wtvsec.Decrypt(0, enc_data)); + } + var secure_headers = await processRequest(socket, dec_data.toString(CryptoJS.enc.Hex), false, true); + if (secure_headers) { + 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" && !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)); + } + } + } + } + } } - } - return headers; + } } async function cleanupSocket(socket) { @@ -844,6 +971,10 @@ async function cleanupSocket(socket) { // if last socket for SSID disconnected, destroy login session if (!zquiet) console.log(" * Last socket from WebTV SSID", filterSSID(socket.ssid),"disconnected, cleaning up primary WTVSec instance for this SSID"); ssid_sessions[socket.ssid].delete("wtvsec_login"); + + // clean up possible minibrowser session data + if (ssid_sessions[socket.ssid].get("wtv-needs-upgrade")) ssid_sessions[socket.ssid].delete("wtv-needs-upgrade"); + if (ssid_sessions[socket.ssid].get("wtv-used-8675309")) ssid_sessions[socket.ssid].delete("wtv-used-8675309"); } } socket.end(); @@ -856,38 +987,32 @@ async function cleanupSocket(socket) { async function handleSocket(socket) { // create unique socket id with client address and port socket.id = parseInt(crc16('CCITT-FALSE', Buffer.from(String(socket.remoteAddress) + String(socket.remotePort), "utf8")).toString(16), 16); - socket_sessions[socket.id] = []; + socket_sessions[socket.id] = []; socket.setEncoding('hex'); //set data encoding (Text: 'ascii', 'utf8' ~ Binary: 'hex', 'base64' (do not trust 'binary' encoding)) - // NOTE: As it stands we use a 'timeout' to start processing data when we have not recieved any data - // from the client in X time (defined in config, in milliseconds). The problem with this is in the case of - // a modem retrain during a request. - - // TODO: Properly know when client is done sending data, by parsing headers. - // Caveat of this is that sometimes the Content-length header does not exist, or will be encrypted. - socket.on('data', function (data_hex) { - socket.setTimeout(minisrv_config.config.socket_timeout); // the timeout mentioned above - - // Store all received data into a buffer. Kind of misleading as its not a true JS Buffer - // but instead a CryptoJS WordArray - if (socket_sessions[socket.id].buffer) { - socket_sessions[socket.id].buffer.concat(CryptoJS.enc.Hex.parse(data_hex)); + if (!socket_sessions[socket.id].secure) { + // buffer unencrypted data until we see the classic double-newline, or get blank + if (!socket_sessions[socket.id].header_buffer) socket_sessions[socket.id].header_buffer = ""; + socket_sessions[socket.id].header_buffer += data_hex; + var header_buffer_text = CryptoJS.enc.Hex.parse(socket_sessions[socket.id].header_buffer).toString(CryptoJS.enc.Latin1); + if (header_buffer_text.indexOf("\r\n\r\n") != -1 || header_buffer_text.indexOf("\n\n") != -1 || header_buffer_text == "") { + data_hex = socket_sessions[socket.id].header_buffer; + delete socket_sessions[socket.id].header_buffer; + processRequest(this, data_hex); + } } else { - socket_sessions[socket.id].buffer = CryptoJS.enc.Hex.parse(data_hex); + // stream encrypted requests through the processor + processRequest(this, data_hex); } }); - socket.on('timeout', async function () { - // start the async chain - if (socket_sessions[socket.id].buffer) { - // process the request if the buffer exists - processRequest(this, socket_sessions[socket.id].buffer.toString(CryptoJS.enc.Hex)); - } + socket.on('timeout', function () { + cleanupSocket(socket); }); socket.on('error', (err) => { - socket.end(); + cleanupSocket(socket); }); socket.on('end', function () { @@ -942,12 +1067,20 @@ function getGitRevision() { } // SERVER START - -var z_title = "zefie's wtv minisrv v" + require('./package.json').version; +var git_commit = getGitRevision() +if (git_commit) { + var z_title = "zefie's wtv minisrv v" + require('./package.json').version + " (git " + git_commit.substring(0,8) + ")"; +} else { + var z_title = "zefie's wtv minisrv v" + require('./package.json').version; +} console.log("**** Welcome to " + z_title + " ****"); console.log(" *** Reading global configuration..."); try { var minisrv_config = JSON.parse(fs.readFileSync(__dirname + "/config.json")); + if (git_commit) { + minisrv_config.config.git_commit = git_commit; + delete this.git_commit; + } } catch (e) { throw ("ERROR: Could not read config.json", e); } @@ -1016,8 +1149,8 @@ Object.keys(minisrv_config.services).forEach(function (k) { if (minisrv_config.config.hide_ssid_in_logs) console.log(" * Masking SSIDs in console logs for security"); else console.log(" * Full SSIDs will be shown in console logs"); -if (minisrv_config.config.service_logo.indexOf(':') == -1) minisrv_config.config.service_logo = "wtv-star:/images/" + minisrv_config.config.service_logo; -if (minisrv_config.config.service_splash_logo.indexOf(':') == -1) minisrv_config.config.service_splash_logo = "wtv-star:/images/" + minisrv_config.config.service_splash_logo; +if (minisrv_config.config.service_logo.indexOf(':') == -1) minisrv_config.config.service_logo = "wtv-star:/ROMCache/" + minisrv_config.config.service_logo; +if (minisrv_config.config.service_splash_logo.indexOf(':') == -1) minisrv_config.config.service_splash_logo = "wtv-star:/ROMCache/" + minisrv_config.config.service_splash_logo; minisrv_config.version = require('./package.json').version; diff --git a/zefie_wtvp_minisrv/config.json b/zefie_wtvp_minisrv/config.json index 34804678..71468cfc 100644 --- a/zefie_wtvp_minisrv/config.json +++ b/zefie_wtvp_minisrv/config.json @@ -9,7 +9,7 @@ "service_logo": "MSNLogo.gif", "service_splash_logo": "splash_logo_msn.gif", "hide_ssid_in_logs": true, - "socket_timeout": 350, + "post_percentages": [ 0, 25, 50, 100], "verbosity": 2 }, "services": { @@ -55,6 +55,16 @@ "flags": "0x04", "connections": 3 }, + "wtv-setup": { + "port": 1613, + "flags": "0x00000010", + "connections": 3 + }, + "wtv-chat": { + "port": 1630, + "connections": 3, + "flags": "0x00000010" + }, "http": { "port": 1650, "connections": 3, diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index b8b8d4b1..14ecdc61 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -1,6 +1,6 @@ { "name": "zefie_wtvp_minisrv", - "version": "0.9.4", + "version": "0.9.5", "description": "WebTV Service (WTVP) Emulation Server", "main": "app.js", "homepage": "https://github.com/zefie/zefie_wtvp_minisrv", diff --git a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj index f64c45c9..02be7160 100644 --- a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj +++ b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj @@ -32,6 +32,7 @@ + Code @@ -174,9 +175,11 @@ - - - + + Code + + + @@ -211,13 +214,15 @@ + + - +