From e591d255b74632a59d7478a69b89a8a27a9dee78 Mon Sep 17 00:00:00 2001 From: zefie Date: Sat, 24 Jul 2021 09:41:00 -0400 Subject: [PATCH] v0.9.6 - update: app.js: add really long timeout for closing missed sockets - update: app.js: fix for minibrowser connectivity - update: app.js: enhance security - update: wtv-home:/home: fix wtv-needs-upgrade -> wtv-need-upgrade - update: wtv-head-waiter:/login-stage-two: do not send wtv-settings:/get to minibrowser - update: wtv-1800:/preregister: Disconnect and clean up all previous sockets for the connecting SSID when hitting preregister. Also re-create wtvsec_login. - update: add initial wtv-capability-flags decoding, as well as wtv-tricks:/info demonstration - update: rename classes - minor update: quirky 'Special Thanks' in each custom class. - minor update: notice about Initial Shared Key and multiple minisrvs - update: wtv-music:/demo/index: update wtv-star image paths - update: app.js: fix unencrypted post - update: app.js: improve buffering and cleanup in attempt to fix occasional 'double-up' bug - update: info.js: remove debug dump of capabilities - Update: add test.js, syntax-testing script for `npm test` - Update: wtv-chat:/home experimental nick change page thanks to MattMan (chat still giving issues on real boxes, works in Viewer) - Update: README.md: Add ways to support the project --- README.md | 6 + .../ServiceVault/wtv-1800/preregister.js | 21 ++ .../ServiceVault/wtv-chat/home.html | 123 ++++++++++++ .../ServiceVault/wtv-chat/images/htv_chat.gif | Bin 0 -> 3744 bytes .../ServiceVault/wtv-chat/images/htv_chat.jpg | Bin 0 -> 2051 bytes .../wtv-head-waiter/login-stage-two.js | 7 +- .../ServiceVault/wtv-home/home.js | 9 +- .../ServiceVault/wtv-music/demo/index.html | 10 +- .../wtv-tricks/images/About_bg.jpg | Bin 0 -> 2240 bytes .../ServiceVault/wtv-tricks/info.js | 148 ++++++++++++++ zefie_wtvp_minisrv/WTVClientCapabilities.js | 150 ++++++++++++++ ...ession_data.js => WTVClientSessionData.js} | 13 +- zefie_wtvp_minisrv/{wtvsec.js => WTVSec.js} | 16 +- zefie_wtvp_minisrv/app.js | 185 +++++++++++++----- zefie_wtvp_minisrv/package.json | 5 +- zefie_wtvp_minisrv/test.js | 33 ++++ zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj | 8 +- 17 files changed, 661 insertions(+), 73 deletions(-) create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-chat/home.html create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/htv_chat.gif create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/htv_chat.jpg create mode 100644 zefie_wtvp_minisrv/ServiceVault/wtv-tricks/images/About_bg.jpg create mode 100644 zefie_wtvp_minisrv/WTVClientCapabilities.js rename zefie_wtvp_minisrv/{session_data.js => WTVClientSessionData.js} (66%) rename zefie_wtvp_minisrv/{wtvsec.js => WTVSec.js} (95%) create mode 100644 zefie_wtvp_minisrv/test.js diff --git a/README.md b/README.md index 5e88c245..c5e0ea95 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,9 @@ This open source server is in alpha status. Use at your own risk. - Test with a WebTV Viewer or connect with a real box - To connect with a real box, you will need to open ports in your firewall and have a way to connect your WebTV (and preferably reroute 10.0.0.1 to the server) - See [ServiceVault.md](ServiceVault.md) for a brief introduction to how the service files work + +### How to Support the Project +- [Report Bugs](https://github.com/zefie/zefie_wtvp_minisrv/issues) +- [Add a Feature and send a Pull Request](https://github.com/zefie/zefie_wtvp_minisrv/pulls) +- Write and submit better documentation than I created (see Pull Request above) +- [Support financially on Patreon](https://www.patreon.com/zefie) \ No newline at end of file diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js b/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js index 2885e20f..ebb0d9a6 100644 --- a/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-1800/preregister.js @@ -6,6 +6,27 @@ if (request_headers["wtv-ticket"]) { gourl = "wtv-head-waiter:/login-stage-two?"; } +if (socket.ssid) { + if (ssid_sessions[socket.ssid].data_store) { + if (ssid_sessions[socket.ssid].data_store.sockets) { + var i = 0; + ssid_sessions[socket.ssid].data_store.sockets.forEach(function (k) { + if (typeof k != "undefined") { + if (k != socket) { + k.destroy(); + ssid_sessions[socket.ssid].data_store.sockets.delete(k); + i++; + } + } + }); + if (i > 0 && zdebug) console.log(" # Closed", i, "previous sockets for", socket.ssid); + } + } + if (ssid_sessions[socket.ssid].data_store.wtvsec_login) { + delete ssid_sessions[socket.ssid].data_store.wtvsec_login; + } +} + headers = `200 OK Connection: Keep-Alive wtv-expire-all: wtv- diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-chat/home.html b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/home.html new file mode 100644 index 00000000..2f14e323 --- /dev/null +++ b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/home.html @@ -0,0 +1,123 @@ + + + +Chat Home + + + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + +
+
+ + + + + + + +
+
+
+
+ + + + + + + +
+ +
+ +
+ + + + +
+ +
+
+ + +
+ + +
   + + + + +
+ + +
+Chat Home +
+ + +
+
+
+ + + + + + + +
+
+ +yo yo yo yo + +enter your nick... + + +
+ + + + +
+
+ + +
+
+ + + +
+ + + + + + + +
+
+ + diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/htv_chat.gif b/zefie_wtvp_minisrv/ServiceVault/wtv-chat/images/htv_chat.gif new file mode 100644 index 0000000000000000000000000000000000000000..7b19a7d967c79f8935b16c821df5fc30f76b917b GIT binary patch literal 3744 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
- +
@@ -35,7 +35,7 @@ WebTV Music Index
- +
@@ -48,7 +48,7 @@ WebTV Music Index
- +
@@ -74,7 +74,7 @@ WebTV Music Index
- +
@@ -87,7 +87,7 @@ WebTV Music Index
- +
diff --git a/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/images/About_bg.jpg b/zefie_wtvp_minisrv/ServiceVault/wtv-tricks/images/About_bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dbfb1d9e29c022e7313d58e03c2ff9f232921d98 GIT binary patch literal 2240 zcmbW0dstKV8OP7bNdj^!ffEdI)yOr8pj;9_CN@wGBoQb%AyA^T5I~ZsTm=MmPY?<< zWhfUB1CN6c?hhge-d1a=q7|evQo0M73paVSw2I&Cp~L7BPJzDo|e8VJvG10 zgCR&uTJP^07HWJ7*#sx2%Tk~e6$C-B@d;$Rm=3|9rKzLwHdGART3XpyS=icG+t^WT z?P*Lp&B1}@?dQh+JXzm=+GeE`1p1JtSvJS4W za9Bxmq`SJgGg)j7*Uvv-W8lZ3SXek;AQZ<)V&guImnJ2r>`0X>l-YZ7a`X1)7ZeqL zUQ+tSA$4U{_0gJRwZ}EuCS7w&Yg>Cq@9Dn&GiT48zwqtlD}z_BeRq9GKRz)z^~3Zn z!~K~Dvp+uk>Cxl4=PwpszIy%ho44;^2zigQO!O-r3g96Si3rim!~+xZfDj6iwAPz! z7c4SM%(Qp%DKtkzDjItRRyh0ODjF$)J5&VASCCZt88{|@Nj|3$P6w9EruMg%e% zAS(n0S3@Y!N6=4|U6&0V?!vRyK8}rM!gvMaaa3h3^`>UtQRhH|NWuu6^S*!ABn3c43pu!&d zxc&M8LrYFTTjB$1W?a@JgGJ@MG~@bSE7BK7DLCdki_WwUSt|u=uE*$v6{mv=+g!-q zy%L3;igk+XNbW*gYM=9lZbz<)6ozwZ8$9G;=i4V)6kgJovTrz(yVlxm zihro7ScskU_q25QK-g(9>l6M$;%Kj#uKcK~SyZ`MDP=#*Zl%WS`&@;pYxpSoRY!@a zFiuCKMkV4Tzj-X6Tt0}{S%-u&{BH!!#~NOwRh9g^sTBZVi1N8Aj(VohXqi3~3iV2EqjK9CFGiW(YXnb{kDU9GF|}(fsXtw?T3J1cNVh-C?J4re!TIxPauQxci5|Vtw1#uy zv;OVgR{H#qhSW6}8tttZQdEO0UVYQ~di_q~`ldahfLzqE;z;ni{gEV zcl*wiJ!2P+WMFv(yWicDmfZY4AW^b%G;cjO_UPXB_&Kv9HvJu;e$n+5O`|urlNC!9 z$_pPyHac?;jkrZdjP0K|AxV{$yQ!J&G+d955{huvAQnZgBHY0frhX}HqpgSBe@wH^ zW>0tKE>IJ)wxafDRyW771_AA}$e##&3HC4P| zpZ=P?mE|;6yQ6Akb${sf@H%m3eKRVG_R^}EumW>Mp=Zj_UG!q{gdS-ceY!cxF$hyc zWNw=}?td(!^xlPyQE^<|>KglXr)RXeBX+-$6E_t2gGu*|7eb%eS+vm(Z+C`Z?vxq#z@ z&ruOS5gdvEaA^1##(<|zIwx4PT~sDaw~&i{7R2Z8^w+JLIT`L2hr-C=+Hqz08m=1? z@d2NBFerpE6sPq82kh~MNOXtFC3f%=w0<&LF*n^pnz{9`YV)MUCG<~^n2jKrC&Ew& z$clxbeJUx7*+6P0*KOgImdqT>Z(9hH(}tMZ+Jm4qLCwM#01CANYauStA*(P{Y%2;^ zGGVz^&0A&$(;b^QBA#`#PFrhf0OhhV!503KJ(I;n5paAg0yLJxEV=dw3C(M|xU@GO zm?vbZM3^h&2^K;E%Yc3**mN2paAt{p_((?py?G>5v@9}n5RFC@3YJ2J>qUG*P1QwD z(Ae<)q^8=%dKQQSAeEG_X2Nv4>b`2zR=zQ%NkN+F&wr>|gn5}t9MsIe@d=CT zF|CN%K-8+Gi_RAPc)EHrV6dIV5TUYWX)rJj!oXK0X#elwLa7kU0;%N_BE$PQ=(kL+ MYt}7x`M`MVUsHZ + + +${minisrv_config.config.service_name} Info + + + + + + +
+
+
+ +

${minisrv_config.config.service_name} Info

+ +
+ + + + + + + + + + + + + + +
+
Connected to: + + Mini Service +
Service: + + ${z_title} +
Client: + + &vers; (Build ${wtv_system_version} [${client_label}]) +
Boot: + + &wtv-bootvers; (Build ${wtv_client_bootrom_version} [${boot_client_label}]) +
+ Silicon serial ID: + + ${wtv_client_serial_number} +
Connected at: + + &rate; +
Client IP number: + + ${socket.remoteAddress} +
+
ROM type: + + ${wtv_client_rom_type} +
Modem f/w (when available): + + &modem; +`; + if (ssid_sessions[socket.ssid].get("wtv-need-upgrade")) { + data += `
Mini-browser: + + Yes +`; + } + data += ` +
Chip version: + + ${wtv_system_chipversion_str} (TODO) +
SysConfig: + + 0x${wtv_system_sysconfig_hex.toUpperCase()} +
+ + + + +
+
Client capabilities: + + +
+ +`; + + + // start loop + + Object.keys(capabilities_table).forEach(function (k) { + data += ` +
${capabilities_table[k][1]} + + `; + if (client_caps[capabilities_table[k][0]]) data += "True\n"; + else data += "False\n"; + }); + +// end loop + +data += ` +
+ +
+
+
+${wtv_system_sysconfig_str}
+
+
+ + `; +} else { + var errpage = doErrorPage(400); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/WTVClientCapabilities.js b/zefie_wtvp_minisrv/WTVClientCapabilities.js new file mode 100644 index 00000000..587586cb --- /dev/null +++ b/zefie_wtvp_minisrv/WTVClientCapabilities.js @@ -0,0 +1,150 @@ +class WTVClientCapabilities { + + /***********************************\ + |* Special Thanks to: *| + |* Outatyme *| + |* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *| + |* For the binary information *| + |* about capability flags *| + \***********************************/ + + capabilities = null; + capabilities_table = null; + + + constructor(wtv_capability_flags = null) { + // [ flag_name, friendly_flag_name ] + // so far we assume the reversed bit order = the order on wtv-tricks:/info (production service) + // also speculation that `client-has-relogin-function` is forced true on the service side + // (this script does not do that, also note that LC2 MiniBrowser does not support client:relog) + // None of this is 100% for certain yet (except the bitfield stuff), do not trust as verbatim, more testing needed + + var capabilities_table = [ + ["client-can-do-muzac", "Can Do Muzac"], + ["client-can-do-chat", "Can Chat"], + ["client-can-do-openISP", "Can do OpenISP"], + ["client-can-receive-compressed-data", "Can receive compressed data"], + ["client-can-display-spotads1", "Can show Spotads1"], + ["client-can-print", "Can Print"], + ["client-can-do-macromedia-flash1", "Can do Macromedia Flash1"], + ["client-can-do-javascript", "Can do JavaScript"], + ["client-can-do-videoflash", "Can do VideoFlash"], + ["client-can-do-videoads", "Can do VideoAds"], + ["client-has-disk", "Has Disk"], + ["client-supports-classical-service", "Supports Classical"], + ["client-open-isp-settings-valid", "OISP settings valid"], + ["client-can-tell-valid-open-isp", "Can tell OISP settings valid"], + ["client-has-tuner", "Has Tuner"], + ["client-can-data-download", "Can data download"], + ["client-supports-approx-content-len", "Supports approximate content length"], + ["client-has-built-in-printer-port", "Has built-in printer port"], + ["client-has-tv-experience", "Has TV experience"], + ["client-can-handle-proxy-bypass", "Can handle proxy bypass"], + ["client-can-handle-download-v2", "Can handle Download protocol 2"], + ["client-has-relogin-function", "Has Relogin function"], + ["client-can-display-spotads2", "Can display spotads2"], + ["client-can-display-30-sec-video-ads", "Can display 30 second video ads"], + ["client-supports-etude-service", "Supports Etude"], + ["client-can-do-av-capture", "Can do AV capture"], + ["client-can-do-disconnected-email", "Can do disconnected email"], + ["client-can-do-macromedia-flash2", "Can do Macromedia Flash2"], + ["client-has-memory-size-bit1-set", "Memory size bit1 set"], + ["client-has-memory-size-bit2-set", "Memory size bit2 set"], + ["client-has-memory-size-bit3-set", "Memory size bit3 set"], + ["client-can-do-rmf", "Can do RMF"], + ["client-can-do-png", "Can do PNG"], + ["client-does-broadband-data-dowload", "Supports broadband download"], + ["client-has-softmodem", "Has Softmodem"], + ["client-can-do-preparsed-epg", "Can do pre-parsed EPG"], + ["client-supports-funk-e-service", "Supports Funk-e"], + ["client-wants-dial-script", "Wants dial script"], + ["client-upgrade-visits-not-needed", "Upgrade visits not needed"], + ["client-uses-flexible-videoad-paths", "Uses flexible videoad paths"], + ["client-non-production-build", "Non-production build"], + ["client-can-download-printer-drivers", "Can download printer drivers"], + ["client-supports-hiphop-service", "Supports HipHop"], + ["client-can-use-messenger", "Can use MSN Messenger"], + ["client-uses-third-party-billing", "Uses 3rd-party billing"], + ["client-can-do-offlineads", "Can do offline ads"], + ["client-has-no-dialin-support", "Has no dialin support"], + ["client-has-ssl-support-for-wtvp", "Has SSL support for WTVP"], + ["client-can-do-audio-capture", "Can do audio capture"], + ["client-can-do-metered-pricing", "Can do Metered Pricing"], + ["client-negotiates-user-agent", "Can Negotiate User-Agent"], + ["client-can-do-element-logging", "Can do Unsupported Element Logging"], + ["client-supports-jazz-security", "Supports Jazz security"], + ["client-supports-MSN-service", "Supports MSN service"], + ["client-supports-notify-port-header", "Supports notify port header"], + ["client-supports-messenger-update-light", "Supports MSN Messenger update light"], + ["client-supports-MSN-chat", "Supports MSN Chat"], + ["client-supports-MSN-chat-findu", "Supports MSN Chat FindU"], + ["client-supports-MSN-messenger-CVR", "Supports MSN Messenger CVR"], + ["client-supports-MSN-messenger-MSNP8", "Supports MSN Messenger MSNP8"], + ["client-supports-MSN-chat-R9C", "Supports MSN Chat R9C"] + ]; + + this.capabilities_table = capabilities_table; + + var capabilities = new Array(); + + // might want to pass without a flag to get the table + if (wtv_capability_flags != null) { + + // define function to convert hex string to binary string (0s & 1s) + var hex2bin = function (hex) { + var binary = ""; + var remainingSize = hex.length; + for (var p = 0; p < hex.length / 8; p++) { + //In case remaining hex length (or initial) is not multiple of 8 + var blockSize = remainingSize < 8 ? remainingSize : 8; + + binary += parseInt(hex.substr(p * 8, blockSize), 16).toString(2); + + remainingSize -= blockSize; + } + return binary; + } + + // Add .reverse() to strings for ease of processing + if (!String.prototype.reverse) { + String.prototype.reverse = function () { + var splitString = this.split(""); + var reverseArray = splitString.reverse(); + var joinArray = reverseArray.join(""); + return joinArray; + } + } + + // convert wtv_capability_flags to binary string, reverse the string, and split into array containing each character; + var bitfield = hex2bin(wtv_capability_flags).reverse().split(""); + + // only add to the capabilities array if the result is true + var add = function (flag_name, flag) { + if (flag) capabilities[flag_name] = flag; + } + + // process bitfield and set capabilities + Object.keys(bitfield).forEach(function (k) { + // Convert binary to boolean, 0 to false, 1 to true + var bitfield_result = (bitfield[k] == "1") + + // set flags based on position of bit + add(capabilities_table[k][0], bitfield_result); + }); + + this.capabilities = capabilities; + return capabilities; + } + } + + get(key = null) { + if (typeof (this.capabilities) === 'undefined') return null; + else if (key === null) return this.capabilities; + else if (this.capabilities[key]) return this.capabilities[key]; + else return null; + } + +} + + +module.exports = WTVClientCapabilities; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/session_data.js b/zefie_wtvp_minisrv/WTVClientSessionData.js similarity index 66% rename from zefie_wtvp_minisrv/session_data.js rename to zefie_wtvp_minisrv/WTVClientSessionData.js index f6e14679..7bec66c7 100644 --- a/zefie_wtvp_minisrv/session_data.js +++ b/zefie_wtvp_minisrv/WTVClientSessionData.js @@ -1,4 +1,13 @@ -class ClientSessionData { +class WTVClientSessionData { + + /***********************************\ + |* Special Thanks to: *| + |* No one *| + |* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *| + |* There is literally nothing *| + |* special about this class *| + \***********************************/ + data_store = null; constructor() { @@ -25,4 +34,4 @@ class ClientSessionData { } -module.exports = ClientSessionData; \ No newline at end of file +module.exports = WTVClientSessionData; \ No newline at end of file diff --git a/zefie_wtvp_minisrv/wtvsec.js b/zefie_wtvp_minisrv/WTVSec.js similarity index 95% rename from zefie_wtvp_minisrv/wtvsec.js rename to zefie_wtvp_minisrv/WTVSec.js index 147bc03d..99206228 100644 --- a/zefie_wtvp_minisrv/wtvsec.js +++ b/zefie_wtvp_minisrv/WTVSec.js @@ -2,9 +2,21 @@ const CryptoJS = require('crypto-js'); const endianness = require('endianness'); var crypto = require('crypto'); +/***********************************\ +|* Special Thanks to: *| +|* eMac (Eric MacDonald) *| +|* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *| +|* For the encryption/decryption *| +|* information and process *| +\***********************************/ + class WTVSec { - //initial_shared_key = CryptoJS.lib.WordArray.random(8); - initial_shared_key_b64 = "CC5rWmRUE0o="; // You can change this but it doesn't mean much for security. Just make sure its static. 8 bytes base64 encoded. + // Initial Shared Key, in Base64 Format + // You can change this but it doesn't mean much for security. Just make sure its static. 8 bytes base64 encoded. + // If you intend to link multiple minisrv's together, they must all share the same Initial Shared Key. + + initial_shared_key_b64 = "CC5rWmRUE0o="; + initial_shared_key = null; current_shared_key = null; challenge_key = null; diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 366abe2d..f0e09753 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -1,6 +1,7 @@ 'use strict'; const fs = require('fs'); +const path = require('path'); const http = require('http'); const https = require('https'); const strftime = require('strftime'); // used externally by service scripts @@ -8,8 +9,9 @@ const net = require('net'); const CryptoJS = require('crypto-js'); const mime = require('mime-types'); const { crc16 } = require('easy-crc'); -var WTVSec = require('./wtvsec.js'); -var ClientSessionData = require('./session_data.js'); +var WTVSec = require('./WTVSec.js'); +var WTVClientCapabilities = require('./WTVClientCapabilities.js'); +var WTVClientSessionData = require('./WTVClientSessionData.js'); // Where we store our session information var ssid_sessions = new Array(); @@ -19,11 +21,13 @@ var ports = []; // add .reverse() feature to all JavaScript Strings in this application // works for service vault scripts too. -String.prototype.reverse = function () { - var splitString = this.split(""); - var reverseArray = splitString.reverse(); - var joinArray = reverseArray.join(""); - return joinArray; +if (!String.prototype.reverse) { + String.prototype.reverse = function () { + var splitString = this.split(""); + var reverseArray = splitString.reverse(); + var joinArray = reverseArray.join(""); + return joinArray; + } } function getServiceString(service) { @@ -89,7 +93,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne try { service_vaults.forEach(function (service_vault_dir) { if (service_vault_found) return; - service_vault_file_path = service_vault_dir + "/" + service_path.replace(/\\/g, "/"); + service_vault_file_path = makeSafePath(service_vault_dir,service_path); if (fs.existsSync(service_vault_file_path)) { @@ -139,7 +143,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne service_vault_found = true; if (!zquiet) console.log(" * Found " + service_vault_file_path + ".js to handle request (JS Interpreter mode) [Socket " + socket.id + "]"); // expose var service_dir for script path to the root of the wtv-service - var service_dir = service_vault_dir.replace(/\\/g, "/") + "/" + service_name; + var service_dir = service_vault_dir + path.sep + service_name; socket_sessions[socket.id].starttime = Math.floor(new Date().getTime() / 1000); var jscript_eval = fs.readFileSync(service_vault_file_path + ".js").toString(); eval(jscript_eval); @@ -166,7 +170,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } if (!request_is_async) { if (!service_vault_found) { - console.log(" * Could not find a Service Vault for", service_path); + console.log(" * Could not find a Service Vault for " + service_name + ":/" + service_path.replace(service_name + path.sep, "")); var errpage = doErrorPage(404); headers = errpage[0]; data = errpage[1]; @@ -209,6 +213,12 @@ function filterSSID(obj) { } } +function makeSafePath(base, target) { + target.replace(/[\|\&\;\$\%\@\"\<\>\+\,\\]/g, ""); + var targetPath = path.posix.normalize(target) + return base + path.sep + targetPath; +} + async function processURL(socket, request_headers) { if (request_headers === null) { return; @@ -251,7 +261,7 @@ async function processURL(socket, request_headers) { } // assume webtv since there is a :/ in the GET var service_name = shortURL.split(':/')[0]; - var urlToPath = service_name + "/" + shortURL.split(':/')[1]; + var urlToPath = service_name + path.sep + shortURL.split(':/')[1]; if (zshowheaders) console.log(" * Incoming headers on socket ID", socket.id, (await filterSSID(request_headers))); processPath(socket, urlToPath, request_headers, service_name); } else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0) { @@ -502,7 +512,16 @@ async function sendToClient(socket, headers_obj, data) { } if (zquiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.http_response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-Length'], "bytes)"); } - socket_sessions[socket.id].buffer = null; + + if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; + if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer; + if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer; + if (socket_sessions[socket.id].buffer) delete socket_sessions[socket.id].buffer; + if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers; + if (socket_sessions[socket.id].post_data) delete socket_sessions[socket.id].post_data; + if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length; + if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown; + if (socket_sessions[socket.id].close_me) socket.end(); if (headers_obj["Connection"]) { if (headers_obj["Connection"].toLowerCase() == "close" && wtv_connection_close == "true") { @@ -550,11 +569,11 @@ function headersAreStandard(string, verbose = false) { return /^([A-Za-z0-9\+\/\=\-\.\,\ \"\;\:\?\&\r\n\(\)\%\<\>\_]{8,})$/.test(string); } -async function processRequest(socket, data_hex, returnHeadersBeforeSecure = false, encryptedRequest = false) { +async function processRequest(socket, data_hex, skipSecure = false, encryptedRequest = false) { - // TODO: clean up this function (how much is even used anymore?) + // This function sucks and needs to be rewritten - var headers = null; + var headers = new Array(); if (socket_sessions[socket.id]) { if (socket_sessions[socket.id].headers) { headers = socket_sessions[socket.id].headers; @@ -570,7 +589,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals data = data.split("\n\n")[0]; } if (headersAreStandard(data)) { - if (headers != null) { + if (headers.length != 0) { var new_header_obj = headerStringToObj(data); Object.keys(new_header_obj).forEach(function (k, v) { headers[k] = new_header_obj[k]; @@ -579,7 +598,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } else { headers = headerStringToObj(data); } - } else if (!returnHeadersBeforeSecure) { + } else if (!skipSecure) { // if its a POST request, assume its a binary blob and not encrypted (dangerous) if (!encryptedRequest) { // its not a POST and it failed the headersAreStandard test, so we think this is an encrypted blob @@ -614,13 +633,15 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } } + if (!headers) return; + if (headers["wtv-client-serial-number"] != null) { socket.ssid = headers["wtv-client-serial-number"]; if (!ssid_sessions[socket.ssid]) { - ssid_sessions[socket.ssid] = new ClientSessionData(); + ssid_sessions[socket.ssid] = new WTVClientSessionData(); } - 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); + if (!ssid_sessions[socket.ssid].data_store.sockets) ssid_sessions[socket.ssid].data_store.sockets = new Set(); + ssid_sessions[socket.ssid].data_store.sockets.add(socket); } var ip2long = function (ip) { @@ -702,6 +723,15 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } } + // Passed Security + + if (headers["wtv-capability-flags"] != null) { + if (!ssid_sessions[socket.ssid]) { + ssid_sessions[socket.ssid] = new WTVClientSessionData(); + } + if (!ssid_sessions[socket.ssid].data_store.capabilities) ssid_sessions[socket.ssid].data_store.capabilities = new WTVClientCapabilities(headers["wtv-capability-flags"]); + } + // log all client wtv- headers to the SessionData for that SSID // this way we can pull up client info such as wtv-client-rom-type or wtv-system-sysconfig @@ -734,11 +764,8 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } } - if (returnHeadersBeforeSecure) { - return headers; - } - if (headers.secure === true || headers.encrypted === true) { + if ((headers.secure === true || headers.encrypted === true) && !skipSecure) { if (!socket_sessions[socket.id].wtvsec) { if (!zquiet) console.log(" * Starting new WTVSec instance on socket", socket.id); if (ssid_sessions[socket.ssid].get("wtv-incarnation")) { @@ -766,7 +793,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } var enc_data = CryptoJS.enc.Hex.parse(data_hex.substring(header_length * 2)); if (enc_data.sigBytes > 0) { - if (headersAreStandard(enc_data.toString(CryptoJS.enc.Latin1), (!returnHeadersBeforeSecure && !encryptedRequest))) { + if (headersAreStandard(enc_data.toString(CryptoJS.enc.Latin1), (!skipSecure && !encryptedRequest))) { // some builds (like our targeted 3833), send SECURE ON but then unencrypted headers if (zdebug) console.log(" # Psuedo-encrypted Request (SECURE ON)", "on", socket.id); // don't actually encrypt output @@ -778,8 +805,23 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals // 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, true); + 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 { + secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); + } + } else { + secure_headers = await processRequest(socket, socket_sessions[socket.id].secure_buffer, true, true); + } if (!secure_headers) return; + + delete socket_sessions[socket.id].secure_buffer; if (zdebug) console.log(" # Encrypted Request (SECURE ON)", "on", socket.id); if (zshowheaders) console.log(secure_headers); if (!secure_headers.request) { @@ -792,11 +834,28 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } } // Merge new headers into existing headers object - Object.keys(secure_headers).forEach(function (k, v) { + Object.keys(secure_headers).forEach(function (k) { headers[k] = secure_headers[k]; }); + } else { + socket_sessions[socket.id].headers = headers; + return; } } + } else if (skipSecure) { + if (headers) { + if (headers['request']) { + if (headers['request'].substring(0, 4) == "POST") { + if (socket_sessions[socket.id].secure_buffer) delete socket_sessions[socket.id].secure_buffer; + } else { + return headers; + } + } else { + return headers; + } + } else { + return; + } } // handle POST @@ -818,11 +877,14 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } 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; 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); + if (socket_sessions[socket.id].headers) delete socket_sessions[socket.id].headers; processURL(socket, headers); } else { // expecting more data (see below) + socket_sessions[socket.id].expecting_post_data = true; 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)) { @@ -841,7 +903,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals delete socket_sessions[socket.id].post_data_length; processURL(socket, headers); return; - } + } } else { socket_sessions[socket.id].headers = headers; } @@ -870,7 +932,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals 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) { + 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)); @@ -888,6 +950,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } 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; 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 ]"); @@ -901,6 +964,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals return; } if (socket_sessions[socket.id].post_data.length > (socket_sessions[socket.id].post_data_length * 2)) { + if (socket_sessions[socket.id].expecting_post_data) delete socket_sessions[socket.id].expecting_post_data; // 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]; @@ -909,7 +973,7 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals return; } - } else if (!returnHeadersBeforeSecure) { + } else if (!skipSecure) { if (!encryptedRequest) { if (socket_sessions[socket.id].secure != true) { socket_sessions[socket.id].wtvsec = new WTVSec(1, zdebug); @@ -933,23 +997,41 @@ async function processRequest(socket, data_hex, returnHeadersBeforeSecure = fals } 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 (!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); + } 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" && !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 (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)); } - } + } } - } + } } } } @@ -962,12 +1044,9 @@ async function cleanupSocket(socket) { delete socket_sessions[socket.id]; } if (socket.ssid) { - var socket_array_index = ssid_sessions[socket.ssid].data_store.sockets.findIndex(element => element == socket.id); - if (socket_array_index != -1) { - ssid_sessions[socket.ssid].data_store.sockets.splice(socket_array_index,1); - } + ssid_sessions[socket.ssid].data_store.sockets.delete(socket); - if (ssid_sessions[socket.ssid].data_store.sockets.length === 0 && ssid_sessions[socket.ssid].data_store.wtvsec_login) { + if (ssid_sessions[socket.ssid].data_store.sockets.size === 0 && ssid_sessions[socket.ssid].data_store.wtvsec_login) { // 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"); @@ -989,20 +1068,20 @@ async function handleSocket(socket) { socket.id = parseInt(crc16('CCITT-FALSE', Buffer.from(String(socket.remoteAddress) + String(socket.remotePort), "utf8")).toString(16), 16); socket_sessions[socket.id] = []; socket.setEncoding('hex'); //set data encoding (Text: 'ascii', 'utf8' ~ Binary: 'hex', 'base64' (do not trust 'binary' encoding)) - + socket.setTimeout(600000); socket.on('data', function (data_hex) { - if (!socket_sessions[socket.id].secure) { + if (!socket_sessions[socket.id].secure && !socket_sessions[socket.id].expecting_post_data) { // 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 == "") { + if (socket_sessions[socket.id].header_buffer.indexOf("0d0a0d0a") != -1 || socket_sessions[socket.id].header_buffer.indexOf("0a0a") != -1) { data_hex = socket_sessions[socket.id].header_buffer; delete socket_sessions[socket.id].header_buffer; processRequest(this, data_hex); } } else { // stream encrypted requests through the processor + if (socket_sessions[socket.id].header_buffer) delete socket_sessions[socket.id].header_buffer; processRequest(this, data_hex); } }); @@ -1041,25 +1120,24 @@ function integrateConfig(main, user) { return main; } -function returnAbsolsutePath(path) { - if (path.substring(0, 1) != "/" && path.substring(1, 1) != ":") { +function returnAbsolsutePath(check_path) { + if (check_path.substring(0, 1) != path.sep && check_path.substring(1, 1) != ":") { // non-absolute path, so use current directory as base - path = (__dirname + "/" + path).replace(/\\/g, "/"); + check_path = (__dirname + path.sep + check_path); } else { // already absolute path - path = path.replace(/\\/g, "/"); } - return path; + return check_path; } function getGitRevision() { try { - const rev = fs.readFileSync(__dirname.replace(/\\/g, "/") + '/../.git/HEAD').toString().trim(); + const rev = fs.readFileSync(__dirname + path.sep + ".." + path.sep + ".git" + path.sep + "HEAD").toString().trim(); if (rev.indexOf(':') === -1) { return rev; } else { - return fs.readFileSync(__dirname.replace(/\\/g, "/") + '/../.git/' + rev.substring(5)).toString().trim(); + return fs.readFileSync(__dirname + path.sep + ".." + path.sep + ".git" + path.sep + rev.substring(5)).toString().trim(); } } catch (e) { return null; @@ -1214,4 +1292,3 @@ initstring = initstring.substring(0, initstring.length - 2); console.log(" * Started server on ports " + initstring + "...") var listening_ip_string = (minisrv_config.config.bind_ip != "0.0.0.0") ? "IP: " + minisrv_config.config.bind_ip : "all interfaces"; console.log(" * Listening on", listening_ip_string,"~","Service IP:", service_ip); - diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index 14ecdc61..5286c4aa 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -1,10 +1,13 @@ { "name": "zefie_wtvp_minisrv", - "version": "0.9.5", + "version": "0.9.6", "description": "WebTV Service (WTVP) Emulation Server", "main": "app.js", "homepage": "https://github.com/zefie/zefie_wtvp_minisrv", "license": "GPL3", + "scripts": { + "test": "node test.js" + }, "author": { "name": "zefie", "email": "zefie@zefie.net", diff --git a/zefie_wtvp_minisrv/test.js b/zefie_wtvp_minisrv/test.js new file mode 100644 index 00000000..49c0d926 --- /dev/null +++ b/zefie_wtvp_minisrv/test.js @@ -0,0 +1,33 @@ +const { promisify } = require('util'); +const { resolve } = require('path'); +const fs = require('fs'); +const readdir = promisify(fs.readdir); +const stat = promisify(fs.stat); +const { exec } = require("child_process"); +var path = require('path'); + +async function getFiles(dir) { + const subdirs = await readdir(dir); + const files = await Promise.all(subdirs.map(async (subdir) => { + const res = resolve(dir, subdir); + return (await stat(res)).isDirectory() ? getFiles(res) : res; + })); + return files.reduce((a, f) => a.concat(f), []); +} + +getFiles(__dirname) + .then(files => { + files.forEach(function (file) { + if (path.extname(file) == ".js" && file.indexOf("node_modules") == -1) { + console.log(" * Checking syntax of", file.replace(__dirname + path.sep, "." + path.sep)); + exec("node --check \"" + file + "\"", (error, stdout, stderr) => { + if (stderr.length > 0) { + console.log(`${stderr}`); + return; + } + }); + } + }); + }) + .catch(e => console.error(e)); + diff --git a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj index 02be7160..2c248647 100644 --- a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj +++ b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.njsproj @@ -202,12 +202,16 @@ - + + Code - + + Code + + Code