From 907cec23c2b9f123776e5d50b4a9f9ff6c5267c7 Mon Sep 17 00:00:00 2001 From: zefie Date: Fri, 3 Jan 2025 12:49:50 -0500 Subject: [PATCH] v0.9.55 (#32) * v0.9.55 - CGI Support (eg PHP, Perl, etc) - Slight PC Admin updates - Numerous bug fixes - Security updates --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docker-compose/minisrv/Dockerfile | 2 +- docker-compose/minisrv/Dockerfile-dev | 8 + run_docker_dev.sh | 18 + zefie_wtvp_minisrv/app.js | 594 +++++++++----- .../includes/ServiceVault/SharedROMCache/h.js | 3 +- .../ServiceVault/http_pc/admin/index.js | 155 +--- .../ServiceVault/http_pc/admin/ssid.js | 154 ++++ .../includes/ServiceVault/wtv-admin/ban.js | 28 +- .../wtv-head-waiter/login-stage-two.js | 1 + .../ServiceVault/wtv-mail/addressbook.js | 735 ++++++++++++++++-- .../ServiceVault/wtv-mail/addresslist.js | 15 + .../ServiceVault/wtv-mail/listmail.js | 3 +- .../ServiceVault/wtv-mail/readmail.js | 27 +- .../ServiceVault/wtv-register/register.js | 19 +- .../wtv-tricks/benchmark-finished.js | 18 +- .../ServiceVault/wtv-tricks/tricks.js | 18 +- .../includes/classes/Prototypes.js | 32 + .../includes/classes/WTVAdmin.js | 179 ++++- .../includes/classes/WTVClientSessionData.js | 26 +- .../includes/classes/WTVMail.js | 10 +- .../includes/classes/WTVNewsServer.js | 14 +- .../includes/classes/WTVRegister.js | 4 +- .../includes/classes/WTVShared.js | 117 +-- zefie_wtvp_minisrv/includes/config.json | 2 + zefie_wtvp_minisrv/package-lock.json | 14 +- zefie_wtvp_minisrv/package.json | 3 +- zefie_wtvp_minisrv/user_config.example.json | 5 +- .../zefie_wtvp_minisrv.code-workspace | 9 + 28 files changed, 1637 insertions(+), 576 deletions(-) create mode 100644 docker-compose/minisrv/Dockerfile-dev create mode 100755 run_docker_dev.sh create mode 100644 zefie_wtvp_minisrv/includes/ServiceVault/http_pc/admin/ssid.js create mode 100644 zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addresslist.js create mode 100644 zefie_wtvp_minisrv/includes/classes/Prototypes.js create mode 100644 zefie_wtvp_minisrv/zefie_wtvp_minisrv.code-workspace diff --git a/docker-compose/minisrv/Dockerfile b/docker-compose/minisrv/Dockerfile index a438d8ee..33b0c4df 100644 --- a/docker-compose/minisrv/Dockerfile +++ b/docker-compose/minisrv/Dockerfile @@ -1,4 +1,4 @@ -FROM node:lts-alpine3.18 +FROM node:22-alpine RUN apk add git bash RUN npm install -g npm@latest 2>/dev/null > /dev/null diff --git a/docker-compose/minisrv/Dockerfile-dev b/docker-compose/minisrv/Dockerfile-dev new file mode 100644 index 00000000..7f164401 --- /dev/null +++ b/docker-compose/minisrv/Dockerfile-dev @@ -0,0 +1,8 @@ +FROM node:22-alpine + +RUN apk add git bash php-cgi python3 php-mbstring php-dom php-xml php-mysqli php-tokenizer +RUN npm install -g npm@latest 2>/dev/null > /dev/null + +VOLUME /workspace +WORKDIR /workspace + diff --git a/run_docker_dev.sh b/run_docker_dev.sh new file mode 100755 index 00000000..d0f38735 --- /dev/null +++ b/run_docker_dev.sh @@ -0,0 +1,18 @@ +#!/bin/bash +DIR="$(realpath "$(dirname "${0}")")" +BUILDDIR="${DIR}/docker-compose/minisrv" + +docker build -f "${BUILDDIR}/Dockerfile-dev" -t minisrv-dev:latest "${BUILDDIR}" + +if [ "${1}" == "env" ]; then + shift + docker run --rm -it -p 1600-1699:1600-1699 \ + -v "${DIR}/zefie_wtvp_minisrv:/workspace" \ + minisrv-dev:latest \ + "${@}" +else + docker run --rm -it -p 1600-1699:1600-1699 \ + -v "${DIR}/zefie_wtvp_minisrv:/workspace" \ + minisrv-dev:latest \ + node /workspace/app.js +fi diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index fdab7442..88b16b42 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -1,12 +1,14 @@ 'use strict'; const path = require('path'); var classPath = path.resolve(__dirname + path.sep + "includes" + path.sep + "classes" + path.sep) + path.sep; +require(classPath + "Prototypes.js"); const { WTVShared, clientShowAlert } = require(classPath + "WTVShared.js"); const wtvshared = new WTVShared(); // creates minisrv_config const fs = require('fs'); -const tls = require('tls'); const zlib = require('zlib'); +const {serialize, unserialize} = require('php-serialize'); +const {spawn} = require('child_process'); const http = require('follow-redirects').http const https = require('follow-redirects').https const httpx = require(classPath + "/HTTPX.js"); @@ -24,12 +26,16 @@ const WTVFlashrom = require(classPath + "/WTVFlashrom.js"); const vm = require('vm'); const debug = require('debug')('minisrv_main'); const express = require('express'); + var wtvnewsserver = null; + + + process .on('SIGTERM', shutdown('SIGTERM')) .on('SIGINT', shutdown('SIGINT')) - .on('uncaughtException', (e => { console.log(e); })); + .on('uncaughtException', (e => { console.error(e); })); function shutdown(signal = 'SIGTERM') { @@ -159,50 +165,18 @@ var socket_sessions = new Array(); var ports = []; var pc_ports = []; -// add .reverse() feature to all JavaScript Strings in this application -// works for service vault scripts too. -if (!String.prototype.reverse) { - String.prototype.reverse = function () { - return this.split("").reverse().reverseArray.join(""); - } -} - -// for some reason making this a prototype override breaks newsie - -/* -if (!Array.prototype.movekey) { - Array.prototype.movekey = function (from, to) { - this.splice(to, 0, this.splice(from, 1)[0]); - return this; - }; -} -*/ - function moveArrayKey(array, from, to) { array.splice(to, 0, array.splice(from, 1)[0]); return array; }; -// add .getCaseInsensitiveKey() to all JavaScript Objects in this application { -// works for service vault scripts too. - -if (!Object.prototype.getCaseInsensitiveKey) { - Object.prototype.getCaseInsensitiveKey = function (object_name, key_name) { - var foundKey = Object.keys(object_name).find(key => key.toLowerCase() === key_name.toLowerCase()) || null; - if (foundKey) { - // found a key - return object_name[foundKey]; - } else return null; - } -} - function getServiceString(service, overrides = {}) { return wtvshared.getServiceString(service, overrides); } async function sendRawFile(socket, path) { - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + path + " to handle request (Direct File Mode) [Socket " + socket.id + "]"); + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Found " + path + " to handle request (Direct File Mode) [Socket " + socket.id + "]"); var contypes = wtvmime.getContentType(path); var headers = "200 OK\n" headers += "Content-Type: " + contypes[0] + "\n"; @@ -273,6 +247,7 @@ var runScriptInVM = function (script_data, user_contextObj = {}, privileged = fa "Buffer": Buffer, "String": String, "Object": Object, + "Array": Array, // add any additional context objects provided with function call ...user_contextObj @@ -344,6 +319,128 @@ var runScriptInVM = function (script_data, user_contextObj = {}, privileged = fa return contextObj; // updated context object with whatever global varibles the script set } +async function handleCGI(executable, cgi_file, socket, request_headers, vault, service_name, session_data = null, extra_path = "") +{ + var env = wtvshared.cloneObj(process.env); + env.QUERY_STRING = ""; + var request_data = new Array(); + var split_req = request_headers.request.split(' '); + request_data.method = split_req[0]; + var request_type = (request_headers.request_url.indexOf(":/")) ? request_headers.request_url.split(":/")[0] : 'http'; + if (request_type != "http" && request_type != "https") { + request_type = "wtvp"; + request_data.host = minisrv_config.config.service_ip; + request_data.port = minisrv_config.services[service_name].port; + } else { + request_data.host = request_headers.host; + if (request_data.host.indexOf(':') > 0) { + request_data.port = request_data.host.split(':')[1]; + request_data.host = request_data.host.split(':')[0]; + } else { + if (request_type === "https") request_data.port = 443; + else request_data.port = 80; + } + } + // CGI/1.1 stuff + env.SCRIPT_NAME = cgi_file.replace(vault,""); + env.REQUEST_URI = request_headers.request_url; + Object.keys(request_headers.query).forEach(function (k) { + env.QUERY_STRING += k + "=" + request_headers.query[k] + "&"; + }); + env.QUERY_STRING = env.QUERY_STRING.substr(0, env.QUERY_STRING.length - 1); + env.REQUEST_METHOD = request_data.method; + env.SERVER_PROTOCOL = (split_req.length >= 3) ? request_headers.request.split(' ')[2] : "HTTP/1.0"; + env.GATEWAY_INTERFACE = "CGI/1.1"; + env.REMOTE_PORT = socket.remotePort; + env.SCRIPT_FILENAME = cgi_file; + env.SERVER_ADMIN = minisrv_config.config.service_owner_contact; + env.CONTEXT_DOCUMENT_ROOT = vault; + env.CONTEXT_PREFIX = ""; + env.REQUEST_SCHEME = request_type; + env.DOCUMENT_ROOT = vault; + env.REMOTE_ADDR = socket.remoteAddress; + env.SERVER_PORT = request_data.port; + env.SERVER_ADDR = request_data.host; + env.SERVER_NAME = request_data.host; + env.SERVER_SOFTWARE = "Node "+process.versions.node+" Express via " + z_title;; + env.SERVER_SIGNATURE = z_title; + env.ALL_RAW = request_headers.raw_headers; + var raw_header_split = env.ALL_RAW.split("\r\n"); + raw_header_split.forEach(function (header) { + if (header) { + header = header.split(": "); + if (header[0] == "Request") return; + if (header[1]) { + env["HTTP_"+header[0].toUpperCase().replaceAll("-","_")] = header[1]; + } + } + }); + env.SCRIPT_URI = request_type + "://" + request_data.host; + if (request_data.port != 80 && request_data.port != 443 ) env.SCRIPT_URI += ":" + request_data.port; + env.SCRIPT_URI += env.SCRIPT_NAME; + env.SCRIPT_URL = env.SCRIPT_NAME; + env.PHP_SELF = env.SCRIPT_NAME; + env.REQUEST_TIME_FLOAT = Math.floor(new Date().getTime() / 1000); + env.REQUEST_TIME = parseInt(env.REQUEST_TIME_FLOAT); + + env.REDIRECT_STATUS = true; + if (request_headers['content-type']) env.CONTENT_TYPE = request_headers['content-type']; + else delete env['CONTENT_TYPE']; + if (request_headers['content-length']) env.CONTENT_LENGTH = request_headers['content-length']; + else delete env['CONTENT_LENGTH']; + + var post_data = (request_headers['post_data']) ? request_headers['post_data'] : ""; + env.MINISRV_SESSION_STORE = serialize((session_data) ? session_data.getSessionData() : null); + env.MINISRV_DATA_STORE = serialize((session_data) ? session_data.get() : null); + + if (extra_path) { + env.PATH_INFO = extra_path; + env.PATH_TRANSLATED = extra_path ? vault + extra_path : ""; + } else { + delete env['PATH_INFO']; + delete env['PATH_TRANSLATED']; + } + + + var options = { 'cwd': vault, 'env': env, 'timeout': 120000, windowsHide: true, 'uid': process.getuid(), 'gid': process.getgid(), 'stdio': 'overlapped' }; + if (!minisrv_config.config.debug_flags.quiet) (executable == cgi_file) ? console.debug(" * Executing CGI:", executable) : console.debug(" * Executing CGI:", executable, cgi_file); + var cgi = (executable == cgi_file) ? spawn(cgi_file, options=options) : spawn(executable, [cgi_file], options) + var data = ""; + var error = ""; + + if (request_headers['content-length'] && post_data) { + cgi.stdin.write(post_data); + cgi.stdin.end(); + } + + cgi.stdout.on('data', function (dat) { + data += dat; + }); + cgi.stderr.on('data', function (dat) { + error += dat; + }); + cgi.on('close', function (code) { + if (code == 0) { + var stdout = data.split("\r\n\r\n", 2); + var headers = stdout[0]; + data = stdout[1]; + headers = headerStringToObj(headers, true); + if (!headers.Status) headers.Status = "200 OK"; + headers['Connection'] = 'keep-alive'; + sendToClient(socket, headers, data); + } + }); + cgi.on('error', function (err) { + console.error("CGI exec error", err); + var errpage = wtvshared.doErrorPage(500); + sendToClient(socket, errpage[0], errpage[1]); + }); +} + +async function handlePHP(socket, request_headers, php_file, vault, service_name, session_data = null, extra_path = "") { + handleCGI(minisrv_config.config.php_binpath, php_file, socket, request_headers, vault, service_name, session_data, extra_path); +} + async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name, shared_romcache = null, pc_services = false) { var headers, data = null; var request_is_async = false; @@ -428,6 +525,9 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } } } + if (service_vault_file_path.substr(-6, 6) == "/index") { + service_vault_file_path = getDirectoryIndex(service_vault_file_path.substr(0,service_vault_file_path.length-6)); + } var is_dir = false; var file_exists = false; minisrv_catchall, service_path_split, service_path_request_file = null; @@ -439,8 +539,124 @@ async function processPath(socket, service_vault_file_path, request_headers = ne contextObj.cwd = service_vault_file_path.substr(0, service_vault_file_path.lastIndexOf(path.sep)); } + if (fs.existsSync(service_vault_file_path + ".txt")) { + // raw text format, entire payload expected (headers and content) + service_vault_found = true; + request_is_async = true; + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Found " + service_vault_file_path + ".txt to handle request (Raw TXT Mode) [Socket " + socket.id + "]"); + request_headers.service_file_path = service_vault_file_path + ".txt"; + fs.readFile(service_vault_file_path + ".txt", 'Utf-8', function (err, file_raw) { + if (file_raw.indexOf("\n\n") > 0) { + // split headers and data by newline (unix format) + var file_raw_split = file_raw.split("\n\n"); + headers = file_raw_split[0]; + file_raw_split.shift(); + data = file_raw_split.join("\n"); + } else if (file_raw.indexOf("\r\n\r\n") > 0) { + // split headers and data by carrage return + newline (windows format) + var file_raw_split = file_raw.split("\r\n\r\n"); + headers = file_raw_split[0].replace(/\r/g, ""); + file_raw_split.shift(); + data = file_raw_split.join("\r\n"); + } else { + // couldn't find two line breaks, assume entire file is just headers + headers = file_raw; + data = ''; + } + sendToClient(socket, headers, data); + }); + } else if (fs.existsSync(service_vault_file_path + ".js")) { + // synchronous js scripting, process with vars, must set 'headers' and 'data' appropriately. + // loaded script will have r/w access to any JavaScript vars this function does. + // request headers are in an array named `request_headers`. + // Query arguments in `request_headers.query` + // Can upgrade to asynchronous by setting `request_is_async` to `true` + // In Asynchronous mode, you are expected to call sendToClient(socket,headers,data) by the end of your script + // `socket` is already defined and should be passed-through. + service_vault_found = true; + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Found " + service_vault_file_path + ".js to handle request (JS Interpreter mode) [Socket " + socket.id + "]"); + request_headers.service_file_path = service_vault_file_path + ".js"; + // expose var service_dir for script path to the root of the wtv-service + socket_sessions[socket.id].starttime = Math.floor(new Date().getTime() / 1000); + var script_data = fs.readFileSync(service_vault_file_path + ".js").toString(); - if (file_exists && !is_dir) { + var vmResults = runScriptInVM(script_data, contextObj, privileged, service_vault_file_path + ".js"); + // Here we read back certain data from the ServiceVault Script Context Object + updateFromVM.forEach((item) => { + try { + if (typeof vmResults[item[1]] !== "undefined") eval(item[0] + ' = vmResults["' + item[1] + '"]'); + } catch (e) { + console.error("vm readback error", e, item[0] + ' = vmResults[' + item[1] + ']'); + } + }) + + if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.debug(" * Script requested Asynchronous mode"); + } else if (fs.existsSync(service_vault_file_path + ".php") || (service_vault_file_path.indexOf(".php") == service_vault_file_path.length - 4 && fs.existsSync(service_vault_file_path)) || fs.existsSync(service_vault_file_path + ".php")) { + request_is_async = true; + if (minisrv_config.config.php_enabled && minisrv_config.config.php_binpath) { + if (fs.existsSync(service_vault_file_path + ".php") || fs.existsSync(service_vault_file_path)) { + if (fs.existsSync(service_vault_file_path + ".php")) service_vault_file_path += ".php"; + service_vault_found = true; + handlePHP(socket, request_headers, service_vault_file_path, service_vault_dir + path.sep + service_name, (pc_services) ? pc_service_name : service_name, (pc_services) ? null : ssid_sessions[socket.ssid]) + return; + } else { + var extra_path = (service_vault_file_path.lastIndexOf(".php") == -1) ? "" : service_vault_file_path.substr(service_vault_file_path.lastIndexOf(".php") + 4); + service_vault_file_path = service_vault_file_path.substr(0, service_vault_file_path.indexOf(".php") + 4); + if (fs.existsSync(service_vault_file_path)) { + service_vault_found = true; + handlePHP(socket, request_headers, service_vault_file_path, service_vault_dir + path.sep + service_name, (pc_services) ? pc_service_name : service_name, (pc_services) ? null : ssid_sessions[socket.ssid], extra_path) + return; + } else if (service_vault_dir == vaults_to_scan[vaults_to_scan.length - 1]) { + var errpage = wtvshared.doErrorPage(404); + sendToClient(socket, errpage[0], errpage[1]); + return; + } + } + } else { + // php is not enabled, don't expose source code + var errpage = wtvshared.doErrorPage(403); + sendToClient(socket, errpage[0], errpage[1]); + return; + } + } else if (fs.existsSync(service_vault_file_path + ".cgi") || (service_vault_file_path.indexOf(".cgi") == service_vault_file_path.length - 4 && fs.existsSync(service_vault_file_path)) || fs.existsSync(service_vault_file_path + ".cgi")) { + request_is_async = true; + if (minisrv_config.config.php_enabled && minisrv_config.config.php_binpath) { + if (fs.existsSync(service_vault_file_path + ".cgi") || fs.existsSync(service_vault_file_path)) { + if (fs.existsSync(service_vault_file_path + ".cgi")) service_vault_file_path += ".cgi"; + service_vault_found = true; + handleCGI(service_vault_file_path, service_vault_file_path, socket, request_headers, service_vault_dir + path.sep + service_name, (pc_services) ? pc_service_name : service_name, (pc_services) ? null : ssid_sessions[socket.ssid]) + return; + } else { + var extra_path = (service_vault_file_path.lastIndexOf(".cgi") == -1) ? "" : service_vault_file_path.substr(service_vault_file_path.lastIndexOf(".cgi") + 4); + service_vault_file_path = service_vault_file_path.substr(0, service_vault_file_path.indexOf(".cgi") + 4); + if (fs.existsSync(service_vault_file_path)) { + service_vault_found = true; + handleCGI(service_vault_file_path, service_vault_file_path, socket, request_headers, service_vault_dir + path.sep + service_name, (pc_services) ? pc_service_name : service_name, (pc_services) ? null : ssid_sessions[socket.ssid], extra_path) + return; + } else if (service_vault_dir == vaults_to_scan[vaults_to_scan.length - 1]) { + var errpage = wtvshared.doErrorPage(404); + sendToClient(socket, errpage[0], errpage[1]); + return; + } + } + } else { + // php is not enabled, don't expose source code + var errpage = wtvshared.doErrorPage(403); + sendToClient(socket, errpage[0], errpage[1]); + return; + } + } else if (fs.existsSync(service_vault_file_path + ".html")) { + // Standard HTML with no headers, WTV Style + service_vault_found = true; + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Found " + service_vault_file_path + ".html to handle request (HTML Mode) [Socket " + socket.id + "]"); + request_headers.service_file_path = service_vault_file_path + ".html"; + request_is_async = true; + headers = "200 OK\n" + headers += "Content-Type: text/html" + fs.readFile(service_vault_file_path + ".html", null, function (err, data) { + sendToClient(socket, headers, data); + }); + } else if (file_exists && !is_dir) { // file exists, read it and return it service_vault_found = true; request_is_async = true; @@ -474,14 +690,18 @@ async function processPath(socket, service_vault_file_path, request_headers = ne wtvshared.getLineFromFile(service_vault_file_path, 0, function (status, line) { if (!status) { if (line.match(/minisrv\_service\_file.*true/i)) { + request_is_async = true; var errpage = wtvshared.doErrorPage(403); sendToClient(socket, errpage[0], errpage[1]); + return; } else { sendRawFile(socket, service_vault_file_path); } } else { + request_is_async = true; var errpage = wtvshared.doErrorPage(400); sendToClient(socket, errpage[0], errpage[1]); + return; } }); } @@ -490,14 +710,18 @@ async function processPath(socket, service_vault_file_path, request_headers = ne wtvshared.getLineFromFile(service_vault_file_path, 0, function (status, line) { if (!status) { if (line.match(/^#!minisrv/i)) { + request_is_async = true; var errpage = wtvshared.doErrorPage(403); sendToClient(socket, errpage[0], errpage[1]); + return; } else { sendRawFile(socket, service_vault_file_path); } } else { + request_is_async = true; var errpage = wtvshared.doErrorPage(400); sendToClient(socket, errpage[0], errpage[1]); + return; } }); } @@ -505,71 +729,6 @@ async function processPath(socket, service_vault_file_path, request_headers = ne // not a potential service file, so safe to send sendRawFile(socket, service_vault_file_path); } - - } else if (fs.existsSync(service_vault_file_path + ".txt")) { - // raw text format, entire payload expected (headers and content) - service_vault_found = true; - request_is_async = true; - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + service_vault_file_path + ".txt to handle request (Raw TXT Mode) [Socket " + socket.id + "]"); - request_headers.service_file_path = service_vault_file_path + ".txt"; - fs.readFile(service_vault_file_path + ".txt", 'Utf-8', function (err, file_raw) { - if (file_raw.indexOf("\n\n") > 0) { - // split headers and data by newline (unix format) - var file_raw_split = file_raw.split("\n\n"); - headers = file_raw_split[0]; - file_raw_split.shift(); - data = file_raw_split.join("\n"); - } else if (file_raw.indexOf("\r\n\r\n") > 0) { - // split headers and data by carrage return + newline (windows format) - var file_raw_split = file_raw.split("\r\n\r\n"); - headers = file_raw_split[0].replace(/\r/g, ""); - file_raw_split.shift(); - data = file_raw_split.join("\r\n"); - } else { - // couldn't find two line breaks, assume entire file is just headers - headers = file_raw; - data = ''; - } - sendToClient(socket, headers, data); - }); - } else if (fs.existsSync(service_vault_file_path + ".js")) { - // synchronous js scripting, process with vars, must set 'headers' and 'data' appropriately. - // loaded script will have r/w access to any JavaScript vars this function does. - // request headers are in an array named `request_headers`. - // Query arguments in `request_headers.query` - // Can upgrade to asynchronous by setting `request_is_async` to `true` - // In Asynchronous mode, you are expected to call sendToClient(socket,headers,data) by the end of your script - // `socket` is already defined and should be passed-through. - service_vault_found = true; - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + service_vault_file_path + ".js to handle request (JS Interpreter mode) [Socket " + socket.id + "]"); - request_headers.service_file_path = service_vault_file_path + ".js"; - // expose var service_dir for script path to the root of the wtv-service - socket_sessions[socket.id].starttime = Math.floor(new Date().getTime() / 1000); - var script_data = fs.readFileSync(service_vault_file_path + ".js").toString(); - - var vmResults = runScriptInVM(script_data, contextObj, privileged, service_vault_file_path + ".js"); - // Here we read back certain data from the ServiceVault Script Context Object - updateFromVM.forEach((item) => { - try { - if (typeof vmResults[item[1]] !== "undefined") eval(item[0] + ' = vmResults["' + item[1] + '"]'); - } catch (e) { - console.error("vm readback error", e, item[0] + ' = vmResults[' + item[1] + ']'); - } - }) - - if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.log(" * Script requested Asynchronous mode"); - } - else if (fs.existsSync(service_vault_file_path + ".html")) { - // Standard HTML with no headers, WTV Style - service_vault_found = true; - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + service_vault_file_path + ".html to handle request (HTML Mode) [Socket " + socket.id + "]"); - request_headers.service_file_path = service_vault_file_path + ".html"; - request_is_async = true; - headers = "200 OK\n" - headers += "Content-Type: text/html" - fs.readFile(service_vault_file_path + ".html", null, function (err, data) { - sendToClient(socket, headers, data); - }); } else { // look for a catchallin the current path and all parent paths up until the service root if (minisrv_config.config.catchall_file_name) { @@ -584,7 +743,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne var catchall_file = service_check_dir.join(path.sep) + path.sep + minisrv_catchall_file_name; if (fs.existsSync(catchall_file)) { service_vault_found = true; - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found catchall at " + catchall_file + " to handle request (JS Interpreter Mode) [Socket " + socket.id + "]"); + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Found catchall at " + catchall_file + " to handle request (JS Interpreter Mode) [Socket " + socket.id + "]"); request_headers.service_file_path = catchall_file; var script_data = fs.readFileSync(catchall_file).toString(); @@ -599,7 +758,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } }); - if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.log(" * Script requested Asynchronous mode"); + if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.debug(" * Script requested Asynchronous mode"); break; } else { service_check_dir.pop(); @@ -642,16 +801,25 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } } +function getDirectoryIndex(svpath) { + if (fs.existsSync(svpath + path.sep + "index.js")) return svpath + path.sep + "index"; + else if (fs.existsSync(svpath + path.sep + "index.html")) return svpath + path.sep + "index.html"; + else if (fs.existsSync(svpath + path.sep + "index.htm")) return svpath + path.sep + "index.htm"; + else if (fs.existsSync(svpath + path.sep + "index.txt")) return svpath + path.sep + "index.txt"; + else if (fs.existsSync(svpath + path.sep + "index.php")) return svpath + path.sep + "index.php"; + else if (fs.existsSync(svpath + path.sep + "index.cgi")) return svpath + path.sep + "index.cgi"; + else return svpath; +} + async function processURL(socket, request_headers, pc_services = false) { var shortURL, headers, data, service_name, original_service_name = ""; var allow_double_slash, enable_multi_query, use_external_proxy = false; request_headers.query = {}; if (request_headers.request_url) { - service_name = request_headers.service_name; + service_name = socket.service_name; if (pc_services) { - original_service_name = request_headers.service_name; // store service name - service_name = verifyServicePort(request_headers.service_name, socket); // get the actual ServiceVault path - delete request_headers.service_name; + original_service_name = socket.service_name; // store service name + service_name = verifyServicePort(socket.service_name, socket); // get the actual ServiceVault path } if (request_headers.request_url.indexOf('?') >= 0) { shortURL = request_headers.request_url.split('?')[0]; @@ -762,7 +930,7 @@ async function processURL(socket, request_headers, pc_services = false) { headers += "minisrv-no-mail-count: true\n"; data = ""; sendToClient(socket, headers, data); - console.log(" * Lockdown rejected request for " + shortURL + " on socket ID", socket.id); + console.warn(" * Lockdown rejected request for " + shortURL + " on socket ID", socket.id); return; } @@ -774,7 +942,7 @@ async function processURL(socket, request_headers, pc_services = false) { headers += "minisrv-no-mail-count: true\n"; data = ""; sendToClient(socket, headers, data); - console.log(" * Incomplete login rejected request for " + shortURL + " on socket ID", socket.id); + console.warn(" * Incomplete login rejected request for " + shortURL + " on socket ID", socket.id); return; } } @@ -794,7 +962,7 @@ Location: ${minisrv_config.config.unauthorized_url} minisrv-no-mail-count: true`; data = ""; sendToClient(socket, headers, data); - console.log(" * Rejected login bypass request for " + shortURL + " on socket ID", socket.id); + console.warn(" * Rejected login bypass request for " + shortURL + " on socket ID", socket.id); return; } } @@ -833,7 +1001,7 @@ minisrv-no-mail-count: true`; // detect if client is trying to load wtv-star due to client-perceived error if (getSocketDestinationPort(socket) == getPortByService("wtv-star")) { // is wtv-star - if (minisrv_config.config.debug_flags.debug) console.log(" * client requested", shortURL, "on wtv-star port", getSocketDestinationPort(socket)) + if (minisrv_config.config.debug_flags.debug) console.debug(" * client requested", shortURL, "on wtv-star port", getSocketDestinationPort(socket)) shortURL = "wtv-star:/star"; service_name = "wtv-star"; } else { @@ -850,7 +1018,7 @@ minisrv-no-mail-count: true`; if ((shortURL.indexOf(":/ROMCache/") != -1 || shortURL.indexOf("://ROMCache/") != -1) && minisrv_config.config.enable_shared_romcache) { shared_romcache = wtvshared.fixPathSlashes(minisrv_config.config.SharedROMCache + path.sep + shortURL.split(':/')[1]); } - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); + if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Incoming headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); socket_sessions[socket.id].request_headers = request_headers; processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services); } else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0 || (use_external_proxy == true && shortURL.indexOf(service_name + "://") >= 0) && !pc_services) { @@ -869,10 +1037,9 @@ minisrv-no-mail-count: true`; var urlToPath = wtvshared.fixPathSlashes(service_name + path.sep + shortURL); processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services); } else { - debug('request_headers', request_headers); if (request_headers.request.indexOf("HTTP/1.0") > 0) { // webtv in HTTP/1.0 mode, try to kick it back to WTVP - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming invalid headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); + if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Incoming headers (HTTP/1.0) on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); var errpage = wtvshared.doErrorPage(500, null, null, false, true); headers = errpage[0]; data = '' @@ -880,7 +1047,7 @@ minisrv-no-mail-count: true`; sendToClient(socket, headers, data); } else { // error reading headers (no request_url provided) - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming invalid headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); + if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Incoming headers (INVALID) on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); var errpage = wtvshared.doErrorPage(400, null, null, false, true); headers = errpage[0]; data = '' @@ -891,30 +1058,25 @@ minisrv-no-mail-count: true`; } } -Array.prototype.replace = function(sub, newSub) { - splits = this.split(sub, 2); - return Array.concat(splits[0], newSub, splits[1]) -} - function handleProxy(socket, request_type, request_headers, res, data) { console.log(` * Proxy Request ${request_type.toUpperCase()} ${res.statusCode} for ${request_headers.request}`) // an http response error is not a request error, and will come here under the 'end' event rather than an 'error' event. switch (res.statusCode) { case 404: - res.headers.Response = res.statusCode + " The publisher can’t find the page requested."; + res.headers.Status = res.statusCode + " The publisher can’t find the page requested."; break; case 401: case 403: - res.headers.Response = res.statusCode + " The publisher of that page has not authorized you to use it."; + res.headers.Status = res.statusCode + " The publisher of that page has not authorized you to use it."; break; case 500: - res.headers.Response = res.statusCode + " The publisher of that page can’t be reached."; + res.headers.Status = res.statusCode + " The publisher of that page can’t be reached."; break; default: - res.headers.Response = res.statusCode + " " + res.statusMessage; + res.headers.Status = res.statusCode + " " + res.statusMessage; break; } @@ -977,7 +1139,7 @@ async function doHTTPProxy(socket, request_headers) { var idx = request_headers.request_url.indexOf('/') - 1; var request_type = request_headers.request_url.substring(0, idx); - if (minisrv_config.config.debug_flags.show_headers) console.log(request_type.toUpperCase() + " Proxy: Client Request Headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); + if (minisrv_config.config.debug_flags.show_headers) console.debug(request_type.toUpperCase() + " Proxy: Client Request Headers on socket ID", socket.id, (await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))))); switch (request_type) { case "https": var proxy_agent = https; @@ -1059,7 +1221,7 @@ async function doHTTPProxy(socket, request_headers) { if (minisrv_config.services[request_type].external_proxy_is_http1 && data.length > 0) { handleProxy(socket, request_type, request_headers, res, data); } else { - console.log(" * Unhandled Proxy Request Error:", err); + console.error(" * Unhandled Proxy Request Error:", err); } }); @@ -1074,15 +1236,14 @@ async function doHTTPProxy(socket, request_headers) { var errpage, headers, data = null; if (err.code == "ENOTFOUND" || err.message.indexOf("HostUnreachable") > 0) { errpage = wtvshared.doErrorPage(400, `The publisher ${request_data.host} is unknown.`); + sendToClient(socket, errpage[0], errpage[1]); } else { if (minisrv_config.services[request_type].external_proxy_is_http1 && !data_handled) { handleProxy(socket, request_type, request_headers, res, data); } else { - console.log(" * Unhandled Proxy Request Error:", err); + console.error(" * Unhandled Proxy Request Error:", err); errpage = wtvshared.doErrorPage(400); - headers = errpage[0]; - data = errpage[1]; - sendToClient(socket, headers, data); + sendToClient(socket, errpage[0], errpage[1]); } } @@ -1100,7 +1261,7 @@ async function doHTTPProxy(socket, request_headers) { function stripHeaders(headers_obj, whitelist) { var whitelisted_headers = new Array(); var out_headers = new Array(); - out_headers.Response = headers_obj.Response; + out_headers.Status = headers_obj.Status; if (headers_obj['wtv-connection-close']) out_headers['wtv-connection-close'] = headers_obj['wtv-connection-close']; // compare regardless of case @@ -1130,12 +1291,13 @@ function stripHeaders(headers_obj, whitelist) { function headerStringToObj(headers, response = false) { var inc_headers = 0; var headers_obj = {}; + headers_obj.raw_headers = headers; var headers_obj_pre = headers.split("\n"); headers_obj_pre.forEach(function (d) { if (/^SECURE ON/.test(d) && !response) { headers_obj.secure = true; - } else if (/^([0-9]{3}) $/.test(d.substring(0, 4)) && response) { - headers_obj.Response = d.replace("\r", ""); + } else if (/^([0-9]{3}) $/.test(d.substring(0, 4)) && response && !headers_obj.Status) { + headers_obj.Status = d.replace("\r", ""); } else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4)) && !response) { headers_obj.request = d.replace("\r", ""); var request_url = d.split(' '); @@ -1206,7 +1368,7 @@ async function sendToClient(socket, headers_obj, data = null) { // add Connection header if missing, default to Keep-Alive if (!headers_obj.Connection) { headers_obj.Connection = "Keep-Alive"; - headers_obj = wtvshared.moveObjectElement('Connection', 'Response', headers_obj); + headers_obj = wtvshared.moveObjectKey('Connection', 'Status', headers_obj); } var content_length = 0; @@ -1217,9 +1379,19 @@ async function sendToClient(socket, headers_obj, data = null) { } // fix captialization - if (headers_obj["Content-type"]) { - headers_obj["Content-Type"] = headers_obj["Content-type"]; - delete headers_obj["Content-type"]; + if (headers_obj["raw_headers"]) { + delete headers_obj["raw_headers"]; + } + var contype_key = wtvshared.getCaseInsensitiveKey('content-type', headers_obj); + if (contype_key) { + if (socket.ssid && headers_obj[contype_key].indexOf(";") > 0) { + // WebTV client + headers_obj[contype_key] = headers_obj[contype_key].split(";")[0]; + } + if (contype_key != "Content-type") { + headers_obj["Content-type"] = headers_obj[contype_key]; + delete headers_obj[contype_key]; + } } // Add last modified if not a dynamic script @@ -1260,7 +1432,7 @@ async function sendToClient(socket, headers_obj, data = null) { } // compress if needed - if (compression_type > 0 && content_length > 0 && headers_obj['Response'].substring(0, 3) == "200") { + if (compression_type > 0 && content_length > 0 && headers_obj['Status'].substring(0, 3) == "200") { var uncompressed_content_length = content_length; switch (compression_type) { case 1: @@ -1291,16 +1463,16 @@ async function sendToClient(socket, headers_obj, data = null) { } var compression_ratio = (uncompressed_content_length / compressed_content_length).toFixed(2); var compression_percentage = ((1 - (compressed_content_length / uncompressed_content_length)) * 100).toFixed(1); - if (uncompressed_content_length != compressed_content_length) if (minisrv_config.config.debug_flags.debug) console.log(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_ratio, "~ Saved:", compression_percentage.toString() + "%"); + if (uncompressed_content_length != compressed_content_length) if (minisrv_config.config.debug_flags.debug) console.debug(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_ratio, "~ Saved:", compression_percentage.toString() + "%"); } if (!socket.res) { // encrypt if needed if (socket_sessions[socket.id].secure == true && !socket_sessions[socket.id].do_not_encrypt) { headers_obj["wtv-encrypted"] = 'true'; - headers_obj = wtvshared.moveObjectElement('wtv-encrypted', 'Connection', headers_obj); + headers_obj = wtvshared.moveObjectKey('wtv-encrypted', 'Connection', headers_obj); if (content_length > 0 && socket_sessions[socket.id].wtvsec) { - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Encrypting response to client ...") + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Encrypting response to client ...") var enc_data = socket_sessions[socket.id].wtvsec.Encrypt(1, data); data = enc_data; } @@ -1335,7 +1507,7 @@ async function sendToClient(socket, headers_obj, data = null) { if (ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64) { if (ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket) { headers_obj["wtv-ticket"] = ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64; - headers_obj = wtvshared.moveObjectElement("wtv-ticket", "Connection", headers_obj); + headers_obj = wtvshared.moveObjectKey("wtv-ticket", "Connection", headers_obj); ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket = false; } } @@ -1343,11 +1515,28 @@ async function sendToClient(socket, headers_obj, data = null) { } } + // rearranges headers for WebTV (and zefie's OCD) + headers_obj = wtvshared.moveObjectKey("Status", 0, headers_obj); // move Status to top + headers_obj = wtvshared.moveObjectKey("Connection", 1, headers_obj); // move Connection to second + headers_obj = wtvshared.moveObjectKey("Content-type", -1, headers_obj); // move Content-type to last + headers_obj = wtvshared.moveObjectKey("Content-length", "Content-type", headers_obj); // move Content-length to before Content-type + + // remove x-powered-by header if client is WebTV + var xpower = wtvshared.getCaseInsensitiveKey("x-powered-by", headers_obj); + if (!xpower) { + // add X-Powered-By header if not WebTV and not already set + if (!socket.ssid) headers_obj['X-Powered-By'] = "NodeJS ("+process.version+") Express via " + z_title; + } else { + // delete if webtv + if (socket.ssid) delete headers_obj[xpower]; + } + headers_obj = wtvshared.moveObjectKey("x-powered-by", -2, headers_obj, true) // move x-powered-by before Content-type + if (!socket.res) { // header object to string - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Outgoing headers on socket ID", socket.id, headers_obj); + if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Outgoing headers on socket ID", socket.id, headers_obj); Object.keys(headers_obj).forEach(function (k) { - if (k == "Response") { + if (k == "Status") { headers += headers_obj[k] + end_of_line; } else { if (k.indexOf('_') >= 0) { @@ -1365,17 +1554,13 @@ async function sendToClient(socket, headers_obj, data = null) { } } } - // send to client if (socket.res) { - - var resCode = parseInt(headers_obj.Response.substr(0, 3)); - headers_obj['x-powered-by'] = "Express via " + z_title; + var resCode = parseInt(headers_obj.Status.substr(0, 3)); socket.res.writeHead(resCode, headers_obj); socket.res.end(data); - var log_obj = Object.assign({}, socket.res.getHeaders()); - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Outgoing PC headers on " + socket.service_name + " socket ID", socket.id, log_obj); - if (minisrv_config.config.debug_flags.quiet) console.log(" * Sent response " + headers_obj.Response + " to PC client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)"); + if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Outgoing PC headers on " + socket.service_name + " socket ID", socket.id, headers_obj); + if (minisrv_config.config.debug_flags.quiet) console.debug(" * Sent response " + headers_obj.status + " to PC client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)"); } else { var toClient = null; if (typeof data == 'string') { @@ -1391,7 +1576,7 @@ async function sendToClient(socket, headers_obj, data = null) { } else { sendToSocket(socket, new Buffer.from(concatArrayBuffer(Buffer.from(headers + end_of_line), data))); } - if (minisrv_config.config.debug_flags.quiet) console.log(" * Sent" + verbosity_mod + " " + headers_obj.Response + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)"); + if (minisrv_config.config.debug_flags.quiet) console.debug(" * Sent" + verbosity_mod + " " + headers_obj.status + " to client (Content-Type:", headers_obj['Content-Type'], "~", headers_obj['Content-length'], "bytes)"); } } } @@ -1441,6 +1626,8 @@ async function sendToSocket(socket, data) { } function concatArrayBuffer(buffer1, buffer2) { + if (!buffer1) return buffer2; + if (!buffer2) return buffer1; var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength); tmp.set(new Uint8Array(buffer1), 0); tmp.set(new Uint8Array(buffer2), buffer1.byteLength); @@ -1494,7 +1681,6 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq // its not a POST and it failed the isUnencryptedString test, so we think this is an encrypted blob if (socket_sessions[socket.id].secure != true) { // first time so reroll sessions - // if (minisrv_config.config.debug_flags.debug) 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(minisrv_config); socket_sessions[socket.id].wtvsec.IssueChallenge(); socket_sessions[socket.id].wtvsec.SecureOn(); @@ -1550,27 +1736,20 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq } } - if (!socket.ssid) { - // process as pc service - processURL(socket, headers); - return; - } - - if (!ssid_sessions[socket.ssid] || !socket.ssid) return headers; - if (!ssid_sessions[socket.ssid].getClientAddress()) ssid_sessions[socket.ssid].setClientAddress(socket.remoteAddress); - ssid_sessions[socket.ssid].checkSecurity(); - - if (headers["wtv-capability-flags"] != null) { - if (!ssid_sessions[socket.ssid]) { - ssid_sessions[socket.ssid] = new WTVClientSessionData(minisrv_config, socket.ssid); - ssid_sessions[socket.ssid].SaveIfRegistered(); - } - if (!ssid_sessions[socket.ssid].capabilities) ssid_sessions[socket.ssid].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 if (socket.ssid) { + if (!ssid_sessions[socket.ssid] || !socket.ssid) return headers; + if (!ssid_sessions[socket.ssid].getClientAddress()) ssid_sessions[socket.ssid].setClientAddress(socket.remoteAddress); + if (ssid_sessions[socket.ssid]) ssid_sessions[socket.ssid].checkSecurity(); + + if (headers["wtv-capability-flags"] != null) { + if (!ssid_sessions[socket.ssid]) { + ssid_sessions[socket.ssid] = new WTVClientSessionData(minisrv_config, socket.ssid); + ssid_sessions[socket.ssid].SaveIfRegistered(); + } + if (!ssid_sessions[socket.ssid].capabilities) ssid_sessions[socket.ssid].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 Object.keys(headers).forEach(function (k) { if (k.substr(0, 4) === "wtv-") { if (k === "wtv-incarnation" && socket_sessions[socket.id].wtvsec) { @@ -1598,7 +1777,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq } else { if (ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64 != headers["wtv-ticket"]) { if (!ssid_sessions[socket.ssid].data_store.wtvsec_login.update_ticket) { - if (minisrv_config.config.debug_flags.debug) console.log(" # New ticket from client"); + if (minisrv_config.config.debug_flags.debug) console.debug(" # New ticket from client"); ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64 = headers["wtv-ticket"]; ssid_sessions[socket.ssid].data_store.wtvsec_login.DecodeTicket(ssid_sessions[socket.ssid].data_store.wtvsec_login.ticket_b64); if (headers["wtv-incarnation"]) ssid_sessions[socket.ssid].data_store.wtvsec_login.set_incarnation(headers["wtv-incarnation"]); @@ -1617,7 +1796,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq if ((headers.secure === true || headers.encrypted === true) && !skipSecure) { if (!socket_sessions[socket.id].wtvsec) { - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Starting new WTVSec instance on socket", socket.id); + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Starting new WTVSec instance on socket", socket.id); if (ssid_sessions[socket.ssid].get("wtv-incarnation")) { socket_sessions[socket.id].wtvsec = new WTVSec(minisrv_config, ssid_sessions[socket.ssid].get("wtv-incarnation")); } else { @@ -1629,7 +1808,6 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq } if (socket_sessions[socket.id].secure != true) { // first time so reroll sessions - //if (minisrv_config.config.debug_flags.debug) console.log(" # [ SECURE ON BLOCK (" + socket.id + ") ]"); socket_sessions[socket.id].secure = true; } if (!headers.request_url) { @@ -1663,8 +1841,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq if (!secure_headers) return; delete socket_sessions[socket.id].secure_buffer; - if (minisrv_config.config.debug_flags.debug) console.log(" # Encrypted Request (SECURE ON)", "on", socket.id); - if (minisrv_config.config.debug_flags.show_headers) console.log(secure_headers); + if (minisrv_config.config.debug_flags.debug) console.debug(" # Encrypted Request (SECURE ON)", "on", socket.id); if (!secure_headers.request) { socket_sessions[socket.id].secure = false; var errpage = wtvshared.doErrorPage(400); @@ -1777,7 +1954,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq 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 ]"); + console.debug(" * ", 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)); @@ -1785,7 +1962,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq 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)); + console.debug(" * 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; @@ -1799,9 +1976,9 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq 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 ]"); + if (minisrv_config.config.debug_flags.debug) console.debug(" # 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); + if (minisrv_config.config.debug_flags.debug) console.debug(" # Unencrypted POST Content", "on", socket.id); } socket_sessions[socket.id].expecting_post_data = false; delete socket_sessions[socket.id].headers; @@ -1814,7 +1991,9 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq 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, null, "Received too much data in POST request
Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length); + var errmsg = "Received too much data in POST request
Got " + (socket_sessions[socket.id].post_data.length / 2) + ", expected " + socket_sessions[socket.id].post_data_length; + console.error(errmsg); + var errpage = wtvshared.doErrorPage(400, null, errmsg); headers = errpage[0]; data = errpage[1]; sendToClient(socket, headers, data); @@ -1907,7 +2086,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq async function cleanupSocket(socket) { try { if (socket_sessions[socket.id]) { - if (!minisrv_config.config.debug_flags.quiet) console.log(" * Cleaning up disconnected socket", socket.id); + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * Cleaning up disconnected socket", socket.id); delete socket_sessions[socket.id]; } if (socket.ssid) { @@ -1927,7 +2106,7 @@ async function cleanupSocket(socket) { // set timeout to check ssid_sessions[socket.ssid].data_store.socket_check = setTimeout(function (ssid) { if (ssid_sessions[ssid].currentConnections() === 0) { - if (!minisrv_config.config.debug_flags.quiet) console.log(" * WebTV SSID", wtvshared.filterSSID(ssid), "has not been seen in", (timeout / 1000), "seconds, cleaning up session data for this SSID"); + if (!minisrv_config.config.debug_flags.quiet) console.debug(" * WebTV SSID", wtvshared.filterSSID(ssid), "has not been seen in", (timeout / 1000), "seconds, cleaning up session data for this SSID"); delete ssid_sessions[ssid]; } }, timeout, socket.ssid); @@ -2072,6 +2251,7 @@ if (minisrv_config.config.SessionStore) { var service_ip = minisrv_config.config.service_ip; Object.keys(minisrv_config.services).forEach(function (k) { + if (typeof(minisrv_config.services[k]) === 'function') return; if (configureService(k, minisrv_config.services[k], true)) { var using_tls = (minisrv_config.services[k].pc_services && minisrv_config.services[k].https_cert && minisrv_config.services[k].use_https) ? true : false; console.log(" * Configured Service:", k, "on Port", minisrv_config.services[k].port, "- Service Host:", minisrv_config.services[k].host + ((using_tls) ? " (TLS)" : ""), "- Bind Port:", !minisrv_config.services[k].nobind, "- PC Services Mode:", (minisrv_config.services[k].pc_services) ? true : false); @@ -2174,6 +2354,7 @@ if (!minisrv_config.config.bind_ip) minisrv_config.config.bind_ip = "0.0.0.0"; pc_bind_ports.every(function (v) { try { var server = express(); + server.use(express.raw({ type: '*/*' })) var service_name = getServiceByPort(v); var service_handler = http; var server_opts = {}; @@ -2208,13 +2389,52 @@ pc_bind_ports.every(function (v) { var request_headers = {}; request_headers['request'] = "GET " + req.originalUrl + " HTTP/1.1"; request_headers.request_url = req.originalUrl; + request_headers.raw_headers = "Request: "+request_headers['request']+"\r\n"; Object.keys(req.headers).forEach(function (k) { request_headers[k] = req.headers[k]; + request_headers.raw_headers += k+": "+req.headers[k] + "\r\n"; }); request_headers.query = req.query; - request_headers.service_name = service_name; - if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming " + ((ssl) ? "HTTPS" : "HTTP") + " PC Headers on", service_name, "socket ID", req.socket.id, wtvshared.filterRequestLog(request_headers)); + if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming " + ((ssl) ? "HTTPS" : "HTTP") + " PC GET Headers on", service_name, "socket ID", req.socket.id, wtvshared.filterRequestLog(request_headers)); + if (!ssl && minisrv_config.services[service_name].force_https && minisrv_config.services[service_name].https_cert) { + var headers = `302 Moved +Location: https://${(minisrv_config.services[service_name].https_cert.domain) ? minisrv_config.services[service_name].https_cert.domain : minisrv_config.services[service_name].host}:${minisrv_config.services[service_name].port}${req.originalUrl} +Content-type: text/html`; + sendToClient(req.socket, headers); + } else { + processURL(req.socket, request_headers, true) + } + }) + server.post('*', (req, res) => { + var ssl = (req.socket.ssl) ? true : false; + var service_name = getServiceByPort(v); + req.socket.minisrv_pc_mode = true; + req.socket.res = res; + req.socket.service_name = service_name; + req.socket.id = parseInt(crc16('CCITT-FALSE', Buffer.from(String(req.socket.remoteAddress) + String(req.socket.remotePort), "utf8")).toString(16), 16); + socket_sessions[req.socket.id] = []; + + var request_headers = {}; + request_headers['request'] = "POST " + req.originalUrl + " HTTP/1.1"; + request_headers.request_url = req.originalUrl; + request_headers.raw_headers = "Request: "+request_headers['request']+"\r\n"; + Object.keys(req.headers).forEach(function (k) { + request_headers[k] = req.headers[k]; + request_headers.raw_headers += k+": "+req.headers[k] + "\r\n"; + }); + request_headers.query = req.query; + if (req.body) { + var data = ""; + for (var i=0; i `; data = htmlhead; - if (!request_headers.query.cmd) { - data += `Please select an option to get started: + data += `Please select an option to get started:
- List all SSIDs and their Primary User
+ List all SSIDs and their Primary User

`; - } - else if (request_headers.query.cmd == "list") { - data += `
`; - if (request_headers.query.msg) { - data += decodeURI(request_headers.query.msg) + "
"; - } - data += ``; - accounts = wtva.listRegisteredSSIDs(); - Object.keys(accounts).forEach(function (k) { - data += ``; - }); - data += `
${accounts[k][0]}${(accounts[k][1]['username'] === undefined) ? "Unregistered SSID" : accounts[k][1]['username'] }
`; - } else if (request_headers.query.cmd == "ssid") { - var ssid = request_headers.query.ssid; - if (!ssid) { - redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; - } else { - data += "
"; - if (request_headers.query.msg) { - data += decodeURI(request_headers.query.msg) + "
"; - } - data += ` -` - data += `

Management for SSID: ${ssid}

`; - data += `
` - data += `` - if (wtva.isBanned(ssid)) { - data += `` - data += `` - } else { - data += `` - data += `` - } - data += "

"; - user_info = wtva.getAccountInfoBySSID(ssid.toLowerCase()); - if (user_info.account_users) { - if (user_info.account_users['subscriber']) { - data += ``; - if (Object.keys(user_info.account_users).length > 1) { - data += `` - } - data += "
Primary User:${user_info.account_users['subscriber'].subscriber_username}
Additional Users:`; - Object.keys(user_info.account_users).forEach(function (k) { - if (k == "subscriber") return; - data += user_info.account_users[k].subscriber_username + "
"; - }) - data += `

"; - } else { - data += "The user aborted registration, so this account has no users." - } - } else { - data += "The SSID does not exist in the SessionStore." - } - } - } else if (request_headers.query.cmd == "delete") { - redirectmsg = ""; - var ssid = request_headers.query.ssid; - if (ssid) { - var userAccount = wtva.getAccountBySSID(ssid); - userAccount.unregisterBox(); - redirectmsg = `All data for SSID ${ssid} has been deleted. Please note that this does not include Usenet posts made by this account.`; - } else { - redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; - } - headers = "302 OK\nLocation: /admin/?cmd=list&msg=" + encodeURI(redirectmsg); - } else if (request_headers.query.cmd == "ban") { - redirectmsg = ""; - var ssid = request_headers.query.ssid; - if (ssid) { - var fake_config = wtvshared.getUserConfig(); - if (!fake_config.config) fake_config.config = {}; - if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; - var entry_exists = false; - Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { - if (fake_config.config.ssid_block_list[k] == ssid) { - redirectmsg = "The SSID was already banned."; - } - }); - if (!entry_exists) { - fake_config.config.ssid_block_list.push(ssid); - wtvshared.writeToUserConfig(fake_config); - reloadConfig(); - redirectmsg = "The SSID is now banned."; - } - } else { - redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; - } - headers = "302 OK\nLocation: /admin/?cmd=ssid&ssid=" + encodeURI(ssid) + "&msg=" + encodeURI(redirectmsg); - } else if (request_headers.query.cmd == "unban") { - redirectmsg = "The SSID was not banned, so it could not be unbanned."; - var ssid = request_headers.query.ssid; - if (ssid) { - var config_changed = false; - var fake_config = wtvshared.getUserConfig(); - if (!fake_config.config) fake_config.config = {}; - if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; - if (typeof request_headers.query.ssid === 'string') { - Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { - if (fake_config.config.ssid_block_list[k] == request_headers.query.ssid) { - fake_config.config.ssid_block_list.splice(k, 1); - config_changed = true - } - }); - } else { - Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { - Object.keys(request_headers.query.ssid).forEach(function (j) { - if (fake_config.config.ssid_block_list[k] == request_headers.query.ssid[j]) { - fake_config.config.ssid_block_list.splice(k, 1); - config_changed = true - } - }); - }); - } - if (config_changed) { - wtvshared.writeToUserConfig(fake_config); - minisrv_config = reloadConfig(); - redirectmsg = "The SSID is now unbanned."; - } - else { - redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; - } - } - headers = "302 OK\nLocation: /admin/?cmd=ssid&ssid=" + encodeURI(ssid) + "&msg=" + encodeURI(redirectmsg); - } - - } else { - var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); - headers = errpage[0]; - headers += "\nWWW-Authenticate: Basic"; - data = errpage[1]; - } } else { - var errpage = wtvshared.doErrorPage(403, auth); + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); headers = errpage[0]; + headers += "\nWWW-Authenticate: Basic"; data = errpage[1]; } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} \ No newline at end of file diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/http_pc/admin/ssid.js b/zefie_wtvp_minisrv/includes/ServiceVault/http_pc/admin/ssid.js new file mode 100644 index 00000000..fc1f04fb --- /dev/null +++ b/zefie_wtvp_minisrv/includes/ServiceVault/http_pc/admin/ssid.js @@ -0,0 +1,154 @@ +var minisrv_service_file = true; + +WTVAdmin = require(classPath + "/WTVAdmin.js") +var wtva = new WTVAdmin(minisrv_config, socket, service_name); +var auth = wtva.isAuthorized(); +if (auth === true) { + var password = null; + if (request_headers.authorization) { + var authheader = request_headers.authorization.split(' '); + console.log(request_headers) + + if (authheader[0] == "Basic") { + password = Buffer.from(authheader[1], 'base64').toString(); + password = password.split(':')[1]; + } + } + if (wtva.checkPassword(password)) { + headers = `200 OK +Content-Type: text/html` + + htmlhead = ` + +zefie minisrv v${minisrv_config.version} account administration + + +

+Welcome to the zefie minisrv v${minisrv_config.version} Account Administration +

+`; + data = htmlhead; + if (request_headers.query.cmd == "list") { + data += `
`; + if (request_headers.query.msg) { + data += decodeURI(request_headers.query.msg) + "
"; + } + data += ``; + accounts = wtva.listRegisteredSSIDs(); + Object.keys(accounts).forEach(function (k) { + data += ``; + }); + data += `
${accounts[k][0]}${(accounts[k][1]['username'] === undefined) ? "Unregistered SSID" : accounts[k][1]['username'] }
`; + + } else if (request_headers.query.cmd == "ssid") { + var ssid = request_headers.query.ssid; + if (!ssid) { + redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; + } else { + data += "
"; + if (request_headers.query.msg) { + data += decodeURI(request_headers.query.msg) + "
"; + } + data += ` +` + data += `

Management for SSID: ${ssid}

`; + data += `` + data += `` + if (wtva.isBanned(ssid)) { + data += `` + data += `` + } else { + data += `` + data += `` + } + data += "

Back to SSID List"; + data += "

"; + user_info = wtva.getAccountInfoBySSID(ssid); + if (user_info.account_users) { + if (user_info.account_users['subscriber']) { + data += ``; + if (Object.keys(user_info.account_users).length > 1) { + data += `` + } + data += "
Primary User:${user_info.account_users['subscriber'].subscriber_username}
Additional Users:`; + Object.keys(user_info.account_users).forEach(function (k) { + if (k == "subscriber") return; + data += user_info.account_users[k].subscriber_username + "
"; + }) + data += `

"; + } else { + data += "The user aborted registration, so this account has no users." + } + } else { + data += "The SSID does not exist in the SessionStore." + } + } + } else if (request_headers.query.cmd == "delete") { + redirectmsg = ""; + var ssid = request_headers.query.ssid; + if (ssid) { + var userAccount = wtva.getAccountBySSID(ssid); + userAccount.unregisterBox(); + redirectmsg = `All data for SSID ${ssid} has been deleted. Please note that this does not include Usenet posts made by this account.`; + } else { + redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; + } + headers = "302 OK\nLocation: /admin/?cmd=list&msg=" + encodeURI(redirectmsg); + } else if (request_headers.query.cmd == "ban") { + redirectmsg = ""; + var ssid = request_headers.query.ssid; + if (ssid) { + var result = wtva.banSSID(ssid); + if (result === wtva.SUCCESS) { + reloadConfig(); + redirectmsg = "The SSID is now banned."; + } else if (result == wtva.REASON_EXISTS) { + redirectmsg = "The SSID was already banned."; + } else { + redirectmsg = "Unknown response " + result.toString(); + } + } else { + redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; + } + headers = "302 OK\nLocation: /admin/?cmd=ssid&ssid=" + encodeURI(ssid) + "&msg=" + encodeURI(redirectmsg); + } else if (request_headers.query.cmd == "unban") { + redirectmsg = "The SSID was not banned, so it could not be unbanned."; + var ssid = request_headers.query.ssid; + if (ssid) { + var result = wtv.unbanSSID(ssid); + if (result === wtva.SUCCESS) { + reloadConfig(); + redirectmsg = "The SSID is now unbanned."; + } else if (result == wtva.REASON_EXISTS) { + redirectmsg = "The SSID was not banned."; + } else { + redirectmsg = "Unknown response " + result.toString(); + } + } else { + redirectmsg = `An SSID is required for the ${request_headers.query.cmd} command.`; + } + headers = "302 OK\nLocation: /admin/?cmd=ssid&ssid=" + encodeURI(ssid) + "&msg=" + encodeURI(redirectmsg); + } else { + var errpage = wtvshared.doErrorPage(401, "Missing command."); + headers = errpage[0]; + headers += "\nWWW-Authenticate: Basic"; + data = errpage[1]; + } + } else { + var errpage = wtvshared.doErrorPage(401, "Please enter the administration password, you can leave the username blank."); + headers = errpage[0]; + headers += "\nWWW-Authenticate: Basic"; + data = errpage[1]; + } +} else { + var errpage = wtvshared.doErrorPage(403, auth); + headers = errpage[0]; + data = errpage[1]; +} diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-admin/ban.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-admin/ban.js index 43616e52..4f98dd8c 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-admin/ban.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-admin/ban.js @@ -15,24 +15,7 @@ if (auth === true) { if (wtva.checkPassword(password)) { if (request_headers.query.ssid) { var ssid = request_headers.query.ssid.toLowerCase(); - if (ssid == socket.ssid) { - var nobanself = true; - } else { - var fake_config = wtvshared.getUserConfig(); - if (!fake_config.config) fake_config.config = {}; - if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; - var entry_exists = false; - Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { - if (fake_config.config.ssid_block_list[k] == ssid) { - entry_exists = true; - } - }); - if (!entry_exists) { - fake_config.config.ssid_block_list.push(ssid); - wtvshared.writeToUserConfig(fake_config); - reloadConfig(); - } - } + var result = wtva.banSSID(ssid, socket.ssid); } headers = `200 OK Content-Type: text/html @@ -61,13 +44,16 @@ wtv-expire-all: wtv-admin:/ban`;


` if (request_headers.query.ssid) { - if (nobanself) { + if (result == wtva.REASON_SELF) { data += "Cannot ban yourself." } else { - if (entry_exists) { + if (result == wtva.REASON_EXISTS) { data += "SSID " + request_headers.query.ssid + " is already in the ban list.

"; - } else { + } else if (result === wtva.SUCCESS) { + reloadConfig(); data += "SSID " + request_headers.query.ssid + " added to the ban list.

"; + } else { + data += "Unexpected response "+result.toString()+".

"; } } } diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-head-waiter/login-stage-two.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-head-waiter/login-stage-two.js index 6c3c1aa2..1e97b3af 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-head-waiter/login-stage-two.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-head-waiter/login-stage-two.js @@ -95,6 +95,7 @@ wtv-language-header: en-US,en wtv-noback-all: wtv- wtv-transition-override: off wtv-smartcard-inserted-message: Contacting service +wtv-addresses-url: wtv-mail:/addresslist wtv-ssl-timeout: 240 wtv-login-timeout: 7200 `; diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addressbook.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addressbook.js index 90131838..9052e80b 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addressbook.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addressbook.js @@ -1,38 +1,300 @@ -var minisrv_service_file = true; +var minisrv_service_file = true; +var camefrom = request_headers.query.camefrom; +var action = request_headers.query.action; -if (request_headers.query.action == "editfromheader") { - - function parseAddress() { - var nickname = request_headers.query.nickname + ":" - var address = request_headers.query.address; - return { - nickname: address - }; - } - var addresstoadd = parseAddress() - - if (session_data.getSessionData("address_book")) { - session_data.setSessionData("address_book", session_data.getSessionData("address_book") + addresstoadd); - session_data.saveSessionData(); - } else { - session_data.setSessionData("address_book", addresstoadd); - session_data.saveSessionData(); - } - - - headers = `200 OK -Content-type: text/html -wtv-expire: wtv-mail:/addresslist`; -} else { - var camefrom = request_headers.query.camefrom; - - var CommonBLIMScripts = wtvshared.unpackCompressedB64('eNrFVttuGjEQfQaJfxjtS3ZbKFCpfQhJJIiEmiptI5V8gFkPYOG1kS9QVOXf6/Ui4l1uq0hRH9eeOXPOmVnbNzpVbGWAEzG3ZI630XeyJsVidNdqzqxIDZMC5mi+2YyInyTDGDPC+JBShVonrebfhkJjlYARZ9knF/l8NHDQeAkA9SnANgjc5KsHyPoE8mtGucQPt4dijupB/1qSbRwCxgUk00PO1hgnkAxazTCZ6bFiKOh5sUw/iEemTRyNH6M2lPVWAUdcpkusjTi6jDjkXG7qIw5PIHa7sJKcpdtrYDMwCwSrUQGhVAOBmfehDRsEYo3MiGEp4XwLJK8ORFCwYppr80h5+gqVdgSJ8V/atQYMyzDgPsUCdrJg+slHH4rw7B2JiTxj8ZoowD+YWoPeDbiFyBMLkK8i+FhKdJ/RVTKIyvnPhYocYSeoJoYbzImTJ62JQypt6H/o93q95GjIrlobPu+Dyq2gEoQ0UHDaLFA4mztTS+mWiXmnAxlZOm+t2tvNNOSmexe4cyywm9a1W2Em1zhWMntHz8/49eXALief5BocNS4JjZM87OvesNeJqjSsnr6jv0RQParA3kuFp7WFHT/DzYNcHPfRBWpHjen3D6sfDnM9by4RqPa8vjdvUVEdsP+t4Qy3ev0dXjrfKdNvE/2uQ+1OqFSKNQp3nqR4DdoQZdw14ReV9s0MRPzOt++DvRMa5ApFKazMJRnAPiy/191bRT8RgTz2tBpVVlPlDkmwK38BOaDgNQCrPC+kuJCb/e4OtMTrSMFW86ZbPJPu/gEVxhrd'); +var address_book = null +address_book = session_data.getSessionData("address_book") +if (address_book == null) { + session_data.setSessionData("address_book", []) + address_book = []; +} +if (!camefrom && !action) { headers = `200 OK` - + data = ` + + + +Addresses + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + +
+
+
+ +
+ +
+ +
+ + + +
+ + + + +
+
Mail list +
+
+
+
+ +
+ +
+ +
+ + + +
+ + + + +
+
Add +
+
+
+
+ +
+ +
+ +
+ + + +
+ + + + +
+
Look up +
+
+
+
+ +
+ +
+ +
+ + + +
+ + + + +
+
Help +
+
+
+
+ +
+ +
+ +
+ +
+
+
+ + + + +
+
+ + + + +E-mail addresses for ${session_data.getSessionData("subscriber_username")} + + + +
+ + + + + + + + + + + +
+ +Your address is ${session_data.getSessionData("subscriber_username")}@${minisrv_config.config.service_name} +
+ +
+ +
+
+ +
+Name + +E-mail address + + +
+ +
+
+ +
+ +
`; + for (let i = 0; i < address_book.length; i++) { + data += ` + + + +
+ + +
${address_book[i].name} + +${address_book[i].address} +
+
+
+
` + } + data += ` + +`; +} else { switch (camefrom) { case "messenger": - data = `${CommonBLIMScripts} + headers = `200 OK`; + data = ` Addresses @@ -51,11 +313,11 @@ label="View saved messages"> - -
+ +
-
+
@@ -63,14 +325,14 @@ label="View saved messages"> - -
- - -
-
Mail List -
-
+ +
+ + +
+
Mail List +
+
@@ -79,14 +341,14 @@ label="View saved messages"> - -
- - -
-
Add -
-
+ +
+ + +
+
Add +
+
@@ -94,14 +356,14 @@ label="View saved messages"> - -
- - -
-
Help -
-
+ +
+ + +
+
Help +
+
@@ -117,12 +379,14 @@ label="View saved messages">
-
Buddies for ${session_data.getSessionData("subscriber_username") || "You"} +
Buddies for ${session_data.getSessionData("subscriber_username") || "You" + }
- -
Your address is ${session_data.getSessionData("messenger_email") || "unlinked"}@${session_data.getSessionData("messenger_domain") || "escargot.chat"} - -
+ +
Your address is ${session_data.getSessionData("messenger_email") || "unlinked" + }@${session_data.getSessionData("messenger_domain") || "escargot.chat"} + +
@@ -145,21 +409,20 @@ var gUserHasNoFriends = (Blim.listLength("FL") < 0); var i; var listLength = Blim.listLength("FL"); if (listLength == 0) -{ document.write('
'); -document.write("
You don't have any buddies yet. "); +{document.write(''); +document.write("
You don't have any buddies yet. "); document.write('

To add a buddy or send an instant message, '); -document.write('choose Use MSN Messenger now. '); -document.write('You can also add buddies in your Address book.'); +document.write('press OPTIONS and choose messenger '); } else -{ document.write(""); +{document.write("
"); var isFirst = true; for ( i=0 ; i < listLength; i++) -{ var ID = Blim.listItem("FL", i); +{var ID = Blim.listItem("FL", i); var humanName = Blim.getUmanName(ID); document.write('
"); @@ -206,6 +469,336 @@ document.close(); -` +`; + break; } -} \ No newline at end of file + + switch (action) { + case "edit": + case "editfromheader": + const newaddress = request_headers.query.new_address + headers = `200 OK`; + data = ` + + + + +${(!newaddress) ? "Change" : "Add"} an e-mail address + + + + + + + + + + + + + + + + + + +
+ + +
+ + + +
+ + + +
+
+
+ +
+ +
+ +
+ + + +
+ + + + +
+
Addresses +
+
+`; + if (!newaddress) { + data += `
+ +
+ +
+ +
+ + + +
+ + + + +
+
Discard +
+
`; + } + data += ` +
+
+ +
+ +
+ +
+ +
+
+
+ + + + +
+
+ + + +${(!newaddress) ? "Change" : "Add"} an e-mail address + + + +
+ + + + + + + + + + + +
+ +`; + if (!newaddress) { + data += `To change the e-mail address, make your changes, then choose Done.

+To remove the entry from your address book, choose Discard.`; + } else { + data += "Type a name and electronic mail address to add to your address book, then choose Add."; + } + data += ` +

+
+
+ +
+Name +
+ + +
+
+ +
+Address +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + + + +
+ + +${(!newaddress) ? `` : ""} + +`; + break; + + case "add": + var nameExists = false; + var addrExists = false; + // dumbass protection for making addresses look proper in the list + var address = request_headers.query.address.split("@")[0]; + address += `@${minisrv_config.config.service_name}`; + // sanity checks to make sure the user doesn't have duplicate names/addresses + address_book.forEach(user => { + if (user.name.includes(request_headers.query.nickname)) { + nameExists = true; + } + }); + + address_book.forEach(user => { + if (user.address.includes(address)) { + addrExists = true; + } + }); + + if (addrExists) { + headers = `400 The address ${address} already exists in your address book.`; + } else if (nameExists) { + headers = `400 The name ${request_headers.query.nickname} already exists in your address book. Please choose a different name and try again.`; + } else { + const entry = { + name: request_headers.query.nickname, + address: address + } + address_book.push(entry) + session_data.setSessionData("address_book", address_book.sort(function (a, b) { return a.name < b.name ? -1 : 1; })) + headers = `302 Moved temporarily +wtv-expire-all: wtv-mail:/addressbook +wtv-expire: wtv-mail:/addresslist +Location: wtv-mail:/addressbook`; + } + break; + + case "change": + var address = request_headers.query.address + var nickname = request_headers.query.nickname + if (!address) { + address = address_book[request_headers.query.id].address + } + if (!nickname) { + nickname = address_book[request_headers.query.id].nickname + } + // dumbass protection for making addresses look proper in the list + address = address.split("@")[0]; + address += `@${minisrv_config.config.service_name}`; + var nameExists = false; + var addrExists = false; + if (address_book.length > 1) { + var otheraddrs = address_book.slice(0) + otheraddrs.splice(request_headers.query.id, 1) + // sanity checks to make sure the user doesn't have duplicate names/addresses + otheraddrs.forEach(user => { + if (user.name.includes(nickname)) { + nameExists = true; + } + }); + + otheraddrs.forEach(user => { + if (user.address.includes(address)) { + addrExists = true; + } + }); + } + + if (addrExists) { + headers = `400 The address ${address} already exists in your address book.`; + } else if (nameExists) { + headers = `400 The name ${nickname} already exists in your address book. Please choose a different name and try again.`; + } else { + const entry = { + name: nickname, + address: address + } + address_book[request_headers.query.id] = entry + session_data.setSessionData("address_book", address_book.sort(function (a, b) { return a.name < b.name ? -1 : 1; })) + headers = `302 Moved temporarily +wtv-expire-all: wtv-mail:/addressbook +wtv-expire: wtv-mail:/addresslist +Location: wtv-mail:/addressbook`; + } + break; + + case "discard": + if (address_book.length == 1) { + address_book = [] + } else { + address_book.splice(request_headers.query.id, 1) + } + session_data.setSessionData("address_book", address_book.sort(function (a, b) { return a.name < b.name ? -1 : 1; })) + headers = `302 Moved temporarily +wtv-expire-all: wtv-mail:/addressbook +wtv-expire: wtv-mail:/addresslist +Location: wtv-mail:/addressbook`; + break; + + default: + headers = `302 Moved temporarily +Location: wtv-mail:/addressbook`; + break; + } +} diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addresslist.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addresslist.js new file mode 100644 index 00000000..4977349e --- /dev/null +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/addresslist.js @@ -0,0 +1,15 @@ +var minisrv_service_file = true; + +headers = `200 OK +Content-Type: x-wtv-addresses`; + +const address_book = session_data.getSessionData("address_book") + +if (session_data.getSessionData("address_book") != null) { + data = `` + for (let i = 0; i < address_book.length; i++) { + data += address_book[i].name + '\0' + address_book[i].address + '\0' + } +} else { + session_data.setSessionData("address_book", []) +} diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/listmail.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/listmail.js index 9e78f93d..19668a14 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/listmail.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/listmail.js @@ -143,7 +143,7 @@ label="View saved e-mail messages">
-
@@ -282,6 +282,7 @@ ${username}@${minisrv_config.config.service_name} Object.keys(message_list).forEach(function (k) { var message = message_list[k]; if (typeof message.subject == "object" && message.subject) message.subject = wtvshared.decodeBufferText(message.subject); + message.known_sender = session_data.isAddressInAddressBook(message.from_addr); var message_font_open = ""; var message_font_close = ""; if (message.unread) { diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/readmail.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/readmail.js index 1adc1887..e283cb49 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/readmail.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-mail/readmail.js @@ -42,10 +42,13 @@ Content-type: text/html`; var message_colors = null; - - if (message.body.indexOf("
"); - if (message.body.indexOf("") >= 0) { - message.allow_html = true; + if (message.body) { + message.body = message.body.replace(/\n/g, "

"); + if (message.body.indexOf("") >= 0) { + message.allow_html = true; + } + data += `${(message.allow_html) ? wtvshared.sanitizeSignature(message.body) : wtvshared.htmlEntitize(message.body, true)}` } - data += `${(message.allow_html) ? wtvshared.sanitizeSignature(message.body) : wtvshared.htmlEntitize(message.body, true)} -
-
`; + data += "

"; if (message.signature) { data += wtvshared.sanitizeSignature(message.signature); } @@ -296,8 +300,7 @@ ${(message.subject) ? wtvshared.htmlEntitize(message.subject) : '(No subject)'} `; if (Array.isArray(message.attachments)) { message.attachments.forEach((v, k) => { - if (v) { - console.log("*****************", v['Content-Type']); + if (v) { switch (v['Content-Type']) { case "image/jpeg": data += `

`; diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-register/register.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-register/register.js index 602b196b..240baa13 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-register/register.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-register/register.js @@ -8,26 +8,23 @@ var wtvr = new WTVRegister(minisrv_config); var namerand = Math.floor(Math.random() * 100000); var nickname = (minisrv_config.config.service_name + '_' + namerand) var human_name = nickname; -var hasJS = session_data.hasCap("client-can-do-javascript"); -if (hasJS) { - var form_data = `'; -} else { - var form_data = ``; - if (minisrv_config.config.allow_guests) form_data += ``; -} +var hasJS = session_data.hasCap("client-can-do-javascript") && parseInt(session_data.get('wtv-system-version')) >= 2000; +var form_data = ""; var main_data = `
Welcome to the ${minisrv_config.config.service_name} Mini Service, operated by ${minisrv_config.config.service_owner}. The next screens will lead you through a quick setup process for using this service.

Press the "Continue" button below to begin setup.

`; + if (hasJS) { - form_data += ``; + form_data = ``; } else { + form_data = ``; + if (minisrv_config.config.allow_guests) form_data += ``; form_data += ``; } form_data += `

`; - data = wtvr.getHTMLTemplate("Welcome to " + minisrv_config.config.service_name, main_data, form_data, hasJS); \ No newline at end of file diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/benchmark-finished.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/benchmark-finished.js index 4d16216d..acb05281 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/benchmark-finished.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/benchmark-finished.js @@ -24,10 +24,11 @@ if (isNaN(start_time)) { var image_size = fs.statSync(image_filename).size var image_size_kb = parseFloat(image_size / 1024).toFixed(3); var throughput = parseFloat((image_size / download_time) * 1024).toFixed(0); + var throughput_bps = parseInt(throughput * 8) data += ` -
+
POP Number: @@ -61,18 +62,21 @@ if (isNaN(start_time)) {
Throughput: - ${throughput} bytes/sec + ${throughput} bytes/sec (${throughput_bps} bps) + -
`; } data += ` +

-

-Re-Test + + +
+Re-Test -Back to Tricks - +Back to Tricks +
diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/tricks.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/tricks.js index e3758010..94d4d281 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/tricks.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/tricks.js @@ -21,14 +21,7 @@ tricks = [ ["client:ResetNVAndPowerOff", "Blast NVRAM"], ["wtv-tricks:/charmap", "Character Map"], ["wtv-tricks:/cSetup", "Connect Setup"], - ["wtv-tricks:/benchmark", "Speed Test"], - ["", ""], - ["", ""], - ["", ""], - ["", ""], - ["", ""], - ["", ""], - ["", ""], + ["wtv-tricks:/benchmark", "Speed Test"] ] // add these at the bottom tricks.push((session_data.getSessionData("registered")) ? ["wtv-tricks:/unregister", "Unregister This Box"] : ["wtv-tricks:/register", "Register This Box"]); // reg/unreg @@ -39,24 +32,23 @@ data = `
  -
-`; +
 `; for (i = 0; i < tricks.length; i += 2) { data += ` -
${(tricks[i][0] != "") ? `${tricks[i][1]}` : `  `} +${(tricks[i][0] != "") ? `€ ${tricks[i][1]}` : `  `} ` if (i + 1 < tricks.length) { - data += (tricks[i + 1][0] != "") ? `${tricks[i + 1][1]}` : `  ` + data += (tricks[i + 1][0] != "") ? `€ ${tricks[i + 1][1]}` : `  ` } else { // require even number of tricks data += "  " } } -data += `
+data += `
`; diff --git a/zefie_wtvp_minisrv/includes/classes/Prototypes.js b/zefie_wtvp_minisrv/includes/classes/Prototypes.js new file mode 100644 index 00000000..7a2ab26d --- /dev/null +++ b/zefie_wtvp_minisrv/includes/classes/Prototypes.js @@ -0,0 +1,32 @@ +const prototypes = { + String: { + reverse: function () { + return this.split("").reverse().join(""); + }, + toHexString: function () { + var result = ''; + for (var i = 0; i < this.length; i++) { + result += this.charCodeAt(i).toString(16); + } + return result; + } + }, + Array: { + replace: function(sub, newSub) { + splits = this.split(sub, 2); + return Array.concat(splits[0], newSub, splits[1]) + }, + moveKey: function (from, to) { + this.splice(to, 0, this.splice(from, 1)[0]); + return this; + } + } +}; + +for (const [type, methods] of Object.entries(prototypes)) { + for (const [name, method] of Object.entries(methods)) { + if (!global[type].prototype[name]) { + global[type].prototype[name] = method; + } + } +} diff --git a/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js b/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js index 9d49d49a..43fd2608 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVAdmin.js @@ -6,9 +6,18 @@ class WTVAdmin { wtvr = null; wtvshared = null; wtvclient = null; + pcservices = false; WTVClientSessionData = require("./WTVClientSessionData.js"); service_name = "wtv-admin"; + SUCCESS = 0 + FAIL = 1 + INVALID_OP = 2 + + REASON_NOSELF = 3 + REASON_EXISTS = 4 + REASON_NONEXIST = 5 + constructor(minisrv_config, wtvclient, service_name) { this.minisrv_config = minisrv_config; var { WTVShared } = require("./WTVShared.js"); @@ -16,10 +25,69 @@ class WTVAdmin { this.wtvclient = wtvclient; this.wtvshared = new WTVShared(minisrv_config); this.wtvr = new WTVRegister(minisrv_config); - this.clientAddress = wtvclient.getClientAddress(); + if (this.wtvclient.remoteAddress) { + // is a socket + this.clientAddress = this.wtvclient.remoteAddress; + this.pcservices = true; + } else { + // is wtvclient class + this.clientAddress = this.wtvclient.getClientAddress(); + } this.service_name = service_name; } + banSSID(ssid, admin_ssid = null) { + if (ssid == admin_ssid) { + return this.REASON_NOSELF; + } else { + var fake_config = this.wtvshared.getUserConfig(); + if (!fake_config.config) fake_config.config = {}; + if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; + var entry_exists = false; + var self = this; + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + if (fake_config.config.ssid_block_list[k] == ssid) { + return self.REASON_EXISTS; + } + }); + if (!entry_exists) { + fake_config.config.ssid_block_list.push(ssid); + this.wtvshared.writeToUserConfig(fake_config); + return this.SUCCESS; + } + } + } + + unbanSSID(ssid) { + var config_changed = false; + var fake_config = wtvshared.getUserConfig(); + if (!fake_config.config) fake_config.config = {}; + if (!fake_config.config.ssid_block_list) fake_config.config.ssid_block_list = []; + if (typeof request_headers.query.ssid === 'string') { + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid.toLowerCase()) { + fake_config.config.ssid_block_list.splice(k, 1); + config_changed = true + } + }); + } else { + Object.keys(fake_config.config.ssid_block_list).forEach(function (k) { + Object.keys(request_headers.query.ssid).forEach(function (j) { + if (fake_config.config.ssid_block_list[k].toLowerCase() == request_headers.query.ssid[j].toLowerCase()) { + fake_config.config.ssid_block_list.splice(k, 1); + config_changed = true + } + }); + }); + } + if (config_changed) { + wtvshared.writeToUserConfig(fake_config); + minisrv_config = reloadConfig(); + return this.SUCCESS + } else { + return this.REASON_NONEXIST; + } + } ip2long(ip) { var components; @@ -53,47 +121,87 @@ class WTVAdmin { rejectConnection(reason_is_ssid) { var rejectReason; - if (reason_is_ssid) { - rejectReason = this.wtvclient.ssid + " is not in the whitelist."; - console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that SSID is not in the admin whitelist."); + if (this.pcservices) { + rejectReason = this.clientAddress + " is not in the whitelist for PC Services Admin."; + console.log(" * Request from IP (" + this.clientAddress + ") for PC Services Admin, but that IP is not authorized."); } else { - rejectReason = this.clientAddress + " is not in the whitelist for SSID " + this.wtvclient.ssid + "."; - console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that IP is not authorized for that SSID."); + if (reason_is_ssid) { + rejectReason = this.wtvclient.ssid + " is not in the whitelist."; + console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that SSID is not in the admin whitelist."); + } else { + rejectReason = this.clientAddress + " is not in the whitelist for SSID " + this.wtvclient.ssid + "."; + console.log(" * Request from SSID", this.wtvshared.filterSSID(this.wtvclient.ssid), "(" + this.clientAddress + ") for wtv-admin, but that IP is not authorized for that SSID."); + } } return rejectReason; } checkPassword(password) { - if (this.minisrv_config.services[this.service_name].password) { - return (password == this.minisrv_config.services[this.service_name].password); + if (this.pcservices) { + if (this.minisrv_config.config.pc_admin.password) { + return (password == this.minisrv_config.config.pc_admin.password); + } else { + // no password set + return true; + } } else { - // no password set - return true; + if (this.minisrv_config.services[this.service_name].password) { + return (password == this.minisrv_config.services[this.service_name].password); + } else { + // no password set + return true; + } } - } + } + + listRegisteredSSIDs() { + var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts"); + var self = this; + var out = []; + this.fs.readdirSync(search_dir).forEach(file => { + if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory()) { + var user = self.getAccountInfoBySSID(file); + out.push([file, user]); + } + }); + return out; + } isAuthorized(justchecking = false) { var allowed_ssid = false; var allowed_ip = false; - if (this.minisrv_config.services[this.service_name].authorized_ssids) { - var self = this; - Object.keys(self.minisrv_config.services[this.service_name].authorized_ssids).forEach(function (k) { - if (typeof self.minisrv_config.services[self.service_name].authorized_ssids[k] == "string") { - var ssid = self.minisrv_config.services[self.service_name].authorized_ssids[k] - if (ssid == self.wtvclient.ssid) allowed_ssid = true; - allowed_ip = true; // no ip block defined - } else { - var ssid = k; - if (ssid == self.wtvclient.ssid) { - allowed_ssid = true; - Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) { - if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) { - allowed_ip = true; - } - }); + var use_ssid = (this.wtvclient.ssid && !this.pcservices) ? true : false + if (use_ssid) { + if (this.minisrv_config.services[this.service_name].authorized_ssids) { + var self = this; + Object.keys(self.minisrv_config.services[this.service_name].authorized_ssids).forEach(function (k) { + if (typeof self.minisrv_config.services[self.service_name].authorized_ssids[k] == "string") { + var ssid = self.minisrv_config.services[self.service_name].authorized_ssids[k] + if (ssid == self.wtvclient.ssid) allowed_ssid = true; + allowed_ip = true; // no ip block defined + } else { + var ssid = k; + if (ssid == self.wtvclient.ssid) { + allowed_ssid = true; + Object.keys(self.minisrv_config.services[self.service_name].authorized_ssids[k]).forEach(function (j) { + if (self.isInSubnet(self.clientAddress, self.minisrv_config.services[self.service_name].authorized_ssids[k][j])) { + allowed_ip = true; + } + }); + } } + }); + } + } else { + if (this.pcservices) { + if (this.minisrv_config.config.pc_admin.ip_whitelist) { + var self = this; + Object.keys(this.minisrv_config.config.pc_admin.ip_whitelist).forEach(function (k) { + allowed_ip = self.isInSubnet(self.clientAddress, self.minisrv_config.config.pc_admin.ip_whitelist[k]); + }); } - }); + } + allowed_ssid = true; } if (justchecking) { return (allowed_ssid && allowed_ip) ? true : false; @@ -103,7 +211,7 @@ class WTVAdmin { } getAccountInfo(username, directory = null) { - var search_dir = this.minisrv_config.config.SessionStore + this.path.sep + "accounts"; + var search_dir = this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts"); var account_data = null; var self = this; if (directory) search_dir = directory; @@ -118,7 +226,7 @@ class WTVAdmin { var temp_session_data = JSON.parse(temp_session_data_file); if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) { - account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.minisrv_config.config.SessionStore + this.path.sep + "accounts", "").split(this.path.sep)[1]]; + account_data = [temp_session_data, (search_dir + self.path.sep + file).replace(this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts"), "").split(this.path.sep)[1]]; } } catch (e) { console.error(" # Error parsing Session Data JSON", search_dir + self.path.sep + file, e); @@ -145,7 +253,16 @@ class WTVAdmin { if (userSession.isRegistered(false)) { account_info.ssid = ssid; account_info.account_users = userSession.listPrimaryAccountUsers(); - account_info.username = account_info.account_users['subscriber'].subscriber_username; + if (account_info.account_users) { + if (account_info.account_users['subscriber']) { + account_info.username = account_info.account_users['subscriber'].subscriber_username; + } else { + account_info.username = account_info.account_users[0]; + } + } else { + account_info.username = account_info.account_users[0]; + } + account_info.user_id = 0; return account_info; } diff --git a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js index 50e45306..2b0bf6db 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js @@ -127,6 +127,19 @@ class WTVClientSessionData { return true; } + isAddressInAddressBook(addr) { + const addresses = this.getSessionData("address_book"); + if (addresses) { + for (let i = 0; i < addresses.length; i++) { + console.log(addr.toLowerCase(), addresses[i].address.toLowerCase()) + if (addr.toLowerCase() == addresses[i].address.toLowerCase()) { + return true; + } + } + } + return false; + } + findFreeUserSlot() { if (this.user_id != 0) return false; // subscriber only command var master_directory = this.getUserStoreDirectory(true); @@ -158,10 +171,11 @@ class WTVClientSessionData { var master_directory = this.getUserStoreDirectory(true); var account_data = []; var self = this; + this.debug(this.ssid) this.fs.readdirSync(master_directory).forEach(f => { if (self.fs.lstatSync(master_directory + self.path.sep + f).isDirectory()) { if (f.substr(0, 4) == "user") { - var user_file = master_directory + self.path.sep + f + self.path.sep + f + ".json"; + var user_file = this.path.resolve(master_directory + self.path.sep + f + self.path.sep + f + ".json"); if (self.fs.existsSync(user_file)) { if (f == "user0") { account_data['subscriber'] = JSON.parse(this.fs.readFileSync(user_file)); @@ -193,7 +207,7 @@ class WTVClientSessionData { } getAccountStoreDirectory() { - return this.path.resolve(this.wtvshared.getAbsolutePath() + this.path.sep + this.minisrv_config.config.SessionStore + this.path.sep + "accounts"); + return this.wtvshared.getAbsolutePath(this.minisrv_config.config.SessionStore + this.path.sep + "accounts"); } /** @@ -205,7 +219,7 @@ class WTVClientSessionData { if (user_id == null) user_id = this.user_id; var userstore = this.getAccountStoreDirectory() + this.path.sep + this.ssid + this.path.sep; if (!subscriber) userstore += "user" + user_id + this.path.sep; - return userstore; + return this.wtvshared.getAbsolutePath(userstore); } removeUser(user_id) { @@ -264,7 +278,7 @@ class WTVClientSessionData { if (!store_dir) return false; // unregistered // FileStore store_dir += "FileStore" + this.path.sep; - var store_dir_path = this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep)); + var store_dir_path = this.wtvshared.getAbsolutePath(this.wtvshared.makeSafePath(store_dir, path.replace('/', this.path.sep))); if (this.fs.existsSync(store_dir_path)) return this.fs.readFileSync(store_dir_path); else return false; } @@ -611,8 +625,8 @@ class WTVClientSessionData { } getSessionData(key = null) { - if (typeof (this.data_store) === 'session_store') return null; - else if (key === null) return this.data_store; + if (typeof (this.session_store) === 'session_store') return null; + else if (key === null) return this.session_store; else if (this.session_store[key]) return this.session_store[key]; else return null; } diff --git a/zefie_wtvp_minisrv/includes/classes/WTVMail.js b/zefie_wtvp_minisrv/includes/classes/WTVMail.js index faebcfe4..c4487dc5 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVMail.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVMail.js @@ -376,7 +376,7 @@ class WTVMail { checkUserExists(username, directory = null) { // returns the user's ssid, and user_id and userid in an array if true, false if not - var search_dir = this.minisrv_config.config.SessionStore; + var search_dir = this.path.resolve(this.wtvshared.getAbsolutePath() + this.path.sep + this.minisrv_config.config.SessionStore); var return_val = false; var self = this; if (directory) search_dir = directory; @@ -388,9 +388,11 @@ class WTVMail { try { var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8'); var temp_session_data = JSON.parse(temp_session_data_file); - if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) { - return_val = search_dir.replace(this.minisrv_config.config.SessionStore + self.path.sep + "accounts" + self.path.sep, '').replace("user", '').split(self.path.sep); - return_val.push(temp_session_data.subscriber_name); + if (temp_session_data.subscriber_username) { + if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) { + return_val = search_dir.replace(this.minisrv_config.config.SessionStore + self.path.sep + "accounts" + self.path.sep, '').replace("user", '').split(self.path.sep); + return_val.push(temp_session_data.subscriber_name); + } } } catch (e) { console.error(" # Error parsing Session Data JSON", file, e); diff --git a/zefie_wtvp_minisrv/includes/classes/WTVNewsServer.js b/zefie_wtvp_minisrv/includes/classes/WTVNewsServer.js index 531c2744..3e8f5ccf 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVNewsServer.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVNewsServer.js @@ -228,13 +228,13 @@ class WTVNewsServer { post_data.headers['Injection-Date'] = this.strftime("%a, %-d %b %Y %H:%M:%S %z", Date.parse(Date.now())) // Reorder headers per examples in RFC3977 sect 6.2.1.3, not sure if needed - post_data.headers = this.wtvshared.moveObjectElement('Path', null, post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('From', 'Path', post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('Newsgroups', 'From', post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('Subject', 'Newsgroups', post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('Date', 'Subject', post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('Organization', 'Date', post_data.headers, true); - post_data.headers = this.wtvshared.moveObjectElement('Message-ID', 'Organization', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Path', null, post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('From', 'Path', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Newsgroups', 'From', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Subject', 'Newsgroups', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Date', 'Subject', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Organization', 'Date', post_data.headers, true); + post_data.headers = this.wtvshared.moveObjectKey('Message-ID', 'Organization', post_data.headers, true); // end reordering of headers if (this.articleExists(group, post_data.articleNumber)) return false // should not occur, but just in case diff --git a/zefie_wtvp_minisrv/includes/classes/WTVRegister.js b/zefie_wtvp_minisrv/includes/classes/WTVRegister.js index 7e6ef2c8..2aa1e7be 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVRegister.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVRegister.js @@ -95,9 +95,9 @@ class WTVRegister { } data += ` - +
-
+ ${main_content}

diff --git a/zefie_wtvp_minisrv/includes/classes/WTVShared.js b/zefie_wtvp_minisrv/includes/classes/WTVShared.js index bf58aab7..9b1553c7 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVShared.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVShared.js @@ -349,7 +349,7 @@ class WTVShared { utf8Decode(utf8String) { if (typeof utf8String !== 'string') { - throw new TypeError('parameter ‘utf8String’ is not a string'); + throw new TypeError('parameter �utf8String� is not a string'); } const textDecoder = new TextDecoder('utf-8'); const bytes = new Uint8Array(utf8String.split('').map(c => c.charCodeAt(0))); @@ -480,57 +480,6 @@ class WTVShared { else return this.parseSSID(ssid).manufacturer || null; } - /** - * Moves an object to the desired location in the object (reorder) - * @param {string} currentKey Name of the object Key to move - * @param {string} afterKey Name of the object key to place currentKey after - * @param {object} obj The object to work on - * @param {boolean} caseInsensitive - * @returns {object} The modified object - */ - moveObjectElement(currentKey, afterKey, obj, caseInsensitive = false) { - let keys = Object.keys(obj); - let values = Object.values(obj); - - if (caseInsensitive) { - const lowerCurrentKey = currentKey.toLowerCase(); - const foundKey = keys.find(k => k.toLowerCase() === lowerCurrentKey); - currentKey = foundKey || currentKey; - } - - const afterKeyIndex = typeof afterKey === 'string' ? - keys.findIndex(k => caseInsensitive ? k.toLowerCase() === afterKey.toLowerCase() : k === afterKey) - : -1; - - if (afterKeyIndex === -1) { - // If afterKey is not found or not defined, don't move the element - return obj; - } - - const currentIndex = keys.indexOf(currentKey); - if (currentIndex === -1) { - // If currentKey is not found, don't move the element - return obj; - } - - // Remove the current element from keys and values - const [currentKeyValue] = values.splice(currentIndex, 1); - keys.splice(currentIndex, 1); - - // Insert the current element after the afterKey position - keys.splice(afterKeyIndex + 1, 0, currentKey); - values.splice(afterKeyIndex + 1, 0, currentKeyValue); - - // Reconstruct the object with the new order - const result = {}; - keys.forEach((key, index) => { - result[key] = values[index]; - }); - - return result; - } - - readMiniSrvConfig(user_config = true, notices = true, reload_notice = false) { const log = (msg) => { if (notices || reload_notice) console.log(msg); @@ -608,7 +557,7 @@ class WTVShared { var new_user_config = {}; Object.assign(new_user_config, minisrv_user_config, config); if (this.minisrv_config.config.debug_flags.debug) console.log(" * Writing new user configuration..."); - this.fs.writeFileSync(this.getAbsolutePath("user_config.json", this.parentDirectory), JSON.stringify(new_user_config, null, "\t")); + this.fs.writeFileSync(this.getAbsolutePath("user_config.json", this.appdir), JSON.stringify(new_user_config, null, "\t")); return true; } catch (e) { @@ -827,6 +776,7 @@ class WTVShared { } }); } + delete newobj.raw_headers; return newobj; } @@ -1160,6 +1110,67 @@ class WTVShared { getTemplate(service_name, path, path_only = false) { return this.getServiceDep(service_name + this.path.sep + path, path_only, true); } + + + /** + * Finds a key in an object + * @param {string} key The key to find + * @param {object} obj The object to search + * @param {boolean} case_insensitive Search case insensitive + * @returns + */ + findObjectKeyIndex(key, obj, case_insensitive = false) { + const keys = Object.keys(obj); + if (case_insensitive) { + key = key.toLowerCase(); + return keys.findIndex(k => k.toLowerCase() === key); + } + return keys.indexOf(key); + } + /** + * Moves an object to the desired location in the object (reorder) + * @param {string|int} currentKey Name of the object Key to move or the index to move + * @param {string|int} destKey Name of the object key to place currentKey after or the index to place it at + * @param {object} obj The object to work on + * @param {boolean} case_insensitive Search case insensitive + * @returns {object} The modified object + */ + moveObjectKey(currentKey, destKey, obj, case_insensitive = false) { + let keys = Object.keys(obj); + let values = Object.values(obj); + + const currentIndex = typeof currentKey === 'string' ? this.findObjectKeyIndex(currentKey, obj, case_insensitive) : parseInt(currentKey); + if (currentIndex === -1) return obj; + + var destIndex = typeof destKey === 'string' ? this.findObjectKeyIndex(destKey, obj, case_insensitive) : parseInt(destKey); + + // Bump by one if the destKey is a string (put after the key) + if (typeof destKey === 'string' && destIndex !== -1) destIndex++; + + // If destKey is not found or not defined, don't move the element + if (isNaN(destIndex)) return obj; + + // Remove the current element from keys and values + const [currentKeyValue] = values.splice(currentIndex, 1); + const [currentKeyName] = keys.splice(currentIndex, 1); + + // Insert the current element after the destKey position + keys.splice(destIndex, 0, currentKeyName); + values.splice(destIndex, 0, currentKeyValue); + + // Reconstruct the object with the new order + const result = {}; + keys.forEach((key, index) => { + result[key] = values[index]; + }); + + return result; + } + + getCaseInsensitiveKey(key, obj) { + const foundKey = Object.keys(obj).find(k => k.toLowerCase() === key.toLowerCase()); + return foundKey || null; + } } class clientShowAlert { diff --git a/zefie_wtvp_minisrv/includes/config.json b/zefie_wtvp_minisrv/includes/config.json index a4db1e15..0440259c 100644 --- a/zefie_wtvp_minisrv/includes/config.json +++ b/zefie_wtvp_minisrv/includes/config.json @@ -18,6 +18,8 @@ "UserServiceDeps", "includes/ServiceDeps" ], + "cgi_enabled": false, // Disable CGI by default + "php_enabled": false, // Disable PHP by default "SessionStore": "SessionStore", // Where we store account (session) data. Best left unchanged. "SharedROMCache": "SharedROMCache", // Shared ROMCache (wtv-service:/ROMCache/, where wtv-service is any configured service). Found under service vault. Best left unchanged. "enable_shared_romcache": true, // Disabling this will cause a lot of problems without manual intervention. Best left unchanged. diff --git a/zefie_wtvp_minisrv/package-lock.json b/zefie_wtvp_minisrv/package-lock.json index bbece715..428edfa8 100644 --- a/zefie_wtvp_minisrv/package-lock.json +++ b/zefie_wtvp_minisrv/package-lock.json @@ -1,12 +1,12 @@ { "name": "zefie_wtvp_minisrv", - "version": "0.9.54", + "version": "0.9.55", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zefie_wtvp_minisrv", - "version": "0.9.54", + "version": "0.9.55", "license": "GPL3", "dependencies": { "@mafintosh/vm2": "^3.9.2", @@ -21,6 +21,7 @@ "mime-types": "^2.1.35", "newsie": "1.2.1", "nntp-server-zefie": "^3.1.0", + "php-serialize": "^5.0.1", "proxy-agent": "^6.4.0", "rc4-crypto": "^1.5.0", "sanitize-html": "^2.13.0", @@ -2036,6 +2037,15 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/php-serialize": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/php-serialize/-/php-serialize-5.0.1.tgz", + "integrity": "sha512-+uxULDruX7uwGZmC1HjcvQMg6APyK1wzWXnaRR0Vxb7Vk4YMn5/Chp9tm+ccNqmXOtfZ/oZVbR8GZnPnojltDw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", diff --git a/zefie_wtvp_minisrv/package.json b/zefie_wtvp_minisrv/package.json index ccbbd10d..cf4fd162 100644 --- a/zefie_wtvp_minisrv/package.json +++ b/zefie_wtvp_minisrv/package.json @@ -1,6 +1,6 @@ { "name": "zefie_wtvp_minisrv", - "version": "0.9.54", + "version": "0.9.55", "description": "WebTV Service (WTVP) Emulation Server", "main": "app.js", "homepage": "https://github.com/zefie/zefie_wtvp_minisrv", @@ -38,6 +38,7 @@ "mime-types": "^2.1.35", "newsie": "1.2.1", "nntp-server-zefie": "^3.1.0", + "php-serialize": "^5.0.1", "proxy-agent": "^6.4.0", "rc4-crypto": "^1.5.0", "sanitize-html": "^2.13.0", diff --git a/zefie_wtvp_minisrv/user_config.example.json b/zefie_wtvp_minisrv/user_config.example.json index 1da1ab4e..a1644f94 100644 --- a/zefie_wtvp_minisrv/user_config.example.json +++ b/zefie_wtvp_minisrv/user_config.example.json @@ -15,6 +15,9 @@ "C:/Users/zefie/webtv/ServiceVault2", "/home/zefie/webtv/ServiceVault" ], + "php_enabled": true, // enables PHP CGI support + "php_binpath": "/usr/bin/php-cgi", // path to PHP CGI binary + "cgi_enabled": true, // enables CGI Support "ssid_block_list": [ // list of SSID's to block "8100000000000000", @@ -157,4 +160,4 @@ ] } } -} \ No newline at end of file +} diff --git a/zefie_wtvp_minisrv/zefie_wtvp_minisrv.code-workspace b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.code-workspace new file mode 100644 index 00000000..2c2577af --- /dev/null +++ b/zefie_wtvp_minisrv/zefie_wtvp_minisrv.code-workspace @@ -0,0 +1,9 @@ +{ + "folders": [ + { + "path": ".." + } + ], + "settings": { + } +} \ No newline at end of file