add https pc services support
This commit is contained in:
@@ -10,6 +10,7 @@ const tls = require('tls');
|
||||
const zlib = require('zlib');
|
||||
const http = require('follow-redirects').http
|
||||
const https = require('follow-redirects').https
|
||||
const httpx = require(classPath + "/HTTPX.js");
|
||||
const net = require('net');
|
||||
const crypto = require('crypto')
|
||||
const CryptoJS = require('crypto-js');
|
||||
@@ -40,7 +41,7 @@ function shutdown(signal = 'SIGTERM') {
|
||||
};
|
||||
}
|
||||
|
||||
function findServiceByPort(port) {
|
||||
function getServiceByPort(port) {
|
||||
var service_name = null;
|
||||
Object.keys(minisrv_config.services).forEach(function(k) {
|
||||
if (service_name) return;
|
||||
@@ -52,20 +53,37 @@ function findServiceByPort(port) {
|
||||
return service_name;
|
||||
}
|
||||
|
||||
|
||||
function getPortByService(service) {
|
||||
if (minisrv_config.services[service]) return minisrv_config.services[service].port;
|
||||
else return null;
|
||||
}
|
||||
|
||||
function getSocketServer(socket) {
|
||||
var server = null;
|
||||
|
||||
if (socket._server) {
|
||||
if (socket._server._connectionKey) server = socket._server;
|
||||
} else if (socket._parent) {
|
||||
if (socket._parent._server) {
|
||||
if (socket._parent._server._connectionKey) server = socket._parent._server;
|
||||
}
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
function getSocketDestinationPort(socket) {
|
||||
return parseInt(socket._server._connectionKey.split(':')[2]);
|
||||
return getServerDestinationPort(getSocketServer(socket));
|
||||
}
|
||||
|
||||
function getServerDestinationPort(server) {
|
||||
return parseInt(server._connectionKey.split(':')[2]);
|
||||
}
|
||||
|
||||
function verifyServicePort(service_name, socket) {
|
||||
if (!minisrv_config.config.enable_port_isolation) return service_name;
|
||||
if (socket._server._connectionKey) {
|
||||
var socketPort = getSocketDestinationPort(socket);
|
||||
var server = getSocketServer(socket);
|
||||
if (server) {
|
||||
var socketPort = getServerDestinationPort(server);
|
||||
if (minisrv_config.services[service_name]) {
|
||||
if (minisrv_config.services[service_name].port == socketPort) {
|
||||
if (minisrv_config.services[service_name].servicevault_dir)
|
||||
@@ -611,6 +629,7 @@ async function processURL(socket, request_headers, pc_services = false) {
|
||||
if (pc_services) {
|
||||
original_service_name = request_headers.service_name; // store PC Services service name
|
||||
service_name = verifyServicePort(request_headers.service_name, socket); // get the actual ServiceVault path
|
||||
|
||||
delete request_headers.service_name;
|
||||
}
|
||||
if (request_headers.request_url.indexOf('?') >= 0) {
|
||||
@@ -1068,7 +1087,7 @@ function headerStringToObj(headers, response = false) {
|
||||
return headers_obj;
|
||||
}
|
||||
|
||||
async function sendToClient(socket, headers_obj, data) {
|
||||
async function sendToClient(socket, headers_obj, data = null) {
|
||||
var headers = "";
|
||||
var content_length = 0;
|
||||
if (typeof (data) === 'undefined' || data === null) data = '';
|
||||
@@ -1929,7 +1948,8 @@ if (minisrv_config.config.ServiceDeps) {
|
||||
var service_ip = minisrv_config.config.service_ip;
|
||||
Object.keys(minisrv_config.services).forEach(function (k) {
|
||||
if (configureService(k, minisrv_config.services[k], true)) {
|
||||
console.log(" * Configured Service:", k, "on Port", minisrv_config.services[k].port, "- Service Host:", minisrv_config.services[k].host, "- Bind Port:", !minisrv_config.services[k].nobind, "- PC Services Mode:", (minisrv_config.services[k].pc_services) ? true : false);
|
||||
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);
|
||||
|
||||
if (minisrv_config.services[k].local_nntp_port) {
|
||||
if (!wtvnewsserver) {
|
||||
@@ -2022,25 +2042,55 @@ 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.listen(v, minisrv_config.config.bind_ip);
|
||||
var service_name = getServiceByPort(v);
|
||||
var service_handler = http;
|
||||
var server_opts = {};
|
||||
var using_tls = (minisrv_config.services[service_name].https_cert && minisrv_config.services[service_name].use_https) ? true : false;
|
||||
|
||||
if (using_tls) {
|
||||
service_handler = httpx;
|
||||
server_opts =
|
||||
{
|
||||
key: fs.readFileSync(wtvshared.parseConfigVars(minisrv_config.services[service_name].https_cert.key)),
|
||||
cert: fs.readFileSync(wtvshared.parseConfigVars(minisrv_config.services[service_name].https_cert.cert)),
|
||||
};
|
||||
}
|
||||
//service_handler.createServer(server_opts, server).listen(v, minisrv_config.config.bind_ip);
|
||||
|
||||
if (using_tls && !minisrv_config.services[service_name].force_https) {
|
||||
service_handler = httpx; // HTTP and HTTPS on the same port
|
||||
}
|
||||
|
||||
service_handler.createServer(server_opts, server).listen(v, minisrv_config.config.bind_ip);
|
||||
initstring_pc += v + ", ";
|
||||
|
||||
server.get('*', (req, res) => {
|
||||
var request_headers = {};
|
||||
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 service_name = findServiceByPort(v);
|
||||
|
||||
var request_headers = {};
|
||||
request_headers['request'] = "GET " + req.originalUrl + " HTTP/1.1";
|
||||
request_headers.request_url = req.originalUrl;
|
||||
Object.keys(req.headers).forEach(function (k) {
|
||||
request_headers[k] = req.headers[k];
|
||||
});
|
||||
request_headers.query = req.query;
|
||||
if (minisrv_config.config.debug_flags.show_headers) console.log(" * Incoming PC Headers on", service_name, "socket ID", req.socket.id, wtvshared.filterRequestLog(request_headers));
|
||||
request_headers.service_name = service_name;
|
||||
req.socket.minisrv_pc_mode = true;
|
||||
req.socket.res = res;
|
||||
req.socket.service_name = service_name;
|
||||
processURL(req.socket, request_headers, true)
|
||||
|
||||
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 (!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}/
|
||||
Content-type: text/html`;
|
||||
sendToClient(req.socket, headers);
|
||||
} else {
|
||||
processURL(req.socket, request_headers, true)
|
||||
}
|
||||
})
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFgzCCA2sCFBKo/bZ2E7HHlOfsS4UrBJl1UgpTMA0GCSqGSIb3DQEBCwUAMH0x
|
||||
CzAJBgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazEXMBUGA1UECgwOWmVmaWUg
|
||||
TmV0d29ya3MxJDAiBgNVBAsMG21pbmlzcnYgZGVmYXVsdCBjZXJ0aWZpY2F0ZTEc
|
||||
MBoGA1UEAwwTbWluaXNydi5wY19zZXJ2aWNlczAgFw0yMjExMzAxNDE3MTVaGA8y
|
||||
MTIyMTEwNjE0MTcxNVowfTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE5ldyBZb3Jr
|
||||
MRcwFQYDVQQKDA5aZWZpZSBOZXR3b3JrczEkMCIGA1UECwwbbWluaXNydiBkZWZh
|
||||
dWx0IGNlcnRpZmljYXRlMRwwGgYDVQQDDBNtaW5pc3J2LnBjX3NlcnZpY2VzMIIC
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyOZ9iOYqr1cZLZKEWT8I5f7A
|
||||
l3bd9wslCPKR9Mj3IwEy8KQjxwqJYl6jPvbNMtIgZo7Y9pFBovJEUIFFd4Ffshrm
|
||||
QZZfDJtw+KvZF3m/GdmgRc147m++Ve6OSOvuSdtAOAiujhmwjHsTif+YTcjsOfAT
|
||||
O9bhThIespeU4qDoYyDD+3A4wxwxCIFN7clSr7TpPS3uUQ7woCsi1nxdU8B5txT2
|
||||
KfXOlyL0uwygxEEUy0XrDdpHFt9sV0LWiy4Ho3wVf3dhv2odSEeh8SK04qWMga4A
|
||||
fXEXWwg+4cuho8p24MuZZRsCrqM1O7Hm8wz9FiBJJF5US1jRLOf9sz/iaEz2Mz7C
|
||||
+skG/3gsQ1cI+c/f4jwafYLwJs9XJ141nAxTHxAg2fjg0+Oqe54rs2v/8mBy6r6L
|
||||
bLBlyNMZ4CFD/OhrE8tnP9WhX/l2DUmsQGq2OaL7i3Tjp2jWEwoY0MVcRYiJgA8L
|
||||
5cbWnIWuR0WrDmiKwk1gLdGhvqLxYkouST68zr8Oy9TX2G2s9hF6IZbNN8/ZzMnm
|
||||
y5KjM+DjSCjxym2F03obVGbzCUaqDUHNY84oddoF7zu8lzP8UVYwjvHOvCCQa2Az
|
||||
30KZjF6YEDYxhY4KtxNDxNJ9EHBxXlyvYRo/XM/YOk0wdoZPGq4tsj4BZrXSfuch
|
||||
AO0Pr8qDOt+134K1t5ECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAToJKBCsCcBWB
|
||||
mNdj320fu5emb7ZYDyK5wpCBFwMLl/kx/wfNUB6E3GZJdvFBr3zAOrUbwrXZ9+2T
|
||||
MIP1S5uaG5eCmtLZyH0AjsJa70gUwyqk3VSHXErtT7tbLHTCu/Lqn8XeKiRqjKGi
|
||||
+sI3L4KFzlDELFT8iqkR+MOC8cawKsO1LV/8T0XrRx7zfflQFheo+Zkkn3/EsjP9
|
||||
vwKX5+3u5jyLgYdUOVRoUHI2L9hp6ubcqqKTwyKg74PDKsUkg+dSt9QL+UO1FPT8
|
||||
hDlvKg2p7w3UawF675AgzyF6tsgCYBs7ThaNYWgjKZ9ALDvTPTypUCsNnSZ0lNaT
|
||||
iU+dzEocMo2WesE5B1KH6WdAKD54VV4XHPPEzM2WNRTfs+JiTf9aNTJ2tatwvB5w
|
||||
6glFkgICZ4TgHCADHE0h0aPmVEdzJ6bBA4tUTl6GqWUTVgj53LsdB3NcaEasg/7A
|
||||
zzgPzsw6bZivrodyr2aXSV8VPIzoy6RjLNh4sujGBN4QU+v3o6X9me32WljSQzzx
|
||||
5F3pg/okFauWjF8I6VPKVPW5ReUoRI7B/RkrHEqpCzpPltVV5cHmP+2V5Q6UJegC
|
||||
FebNB1npEfOb/ozpxm4b3UhjrnmIO4NZZs8lvZy/naC+F4QOnMpSGfBjsdZuBlBv
|
||||
o3nALXySQNoUsUhfkpOG/8O9K3HrSYk=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
44
zefie_wtvp_minisrv/includes/classes/HTTPX.js
Normal file
44
zefie_wtvp_minisrv/includes/classes/HTTPX.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
let net = require('net');
|
||||
let http = require('http');
|
||||
let https = require('https');
|
||||
|
||||
exports.createServer = (opts, handler) => {
|
||||
|
||||
let server = net.createServer(socket => {
|
||||
socket.once('data', buffer => {
|
||||
// Pause the socket
|
||||
socket.pause();
|
||||
|
||||
// Determine if this is an HTTP(s) request
|
||||
let byte = buffer[0];
|
||||
|
||||
let protocol;
|
||||
if (byte === 22) {
|
||||
protocol = 'https';
|
||||
} else if (32 < byte && byte < 127) {
|
||||
protocol = 'http';
|
||||
}
|
||||
|
||||
let proxy = server[protocol];
|
||||
if (proxy) {
|
||||
// Push the buffer back onto the front of the data stream
|
||||
socket.unshift(buffer);
|
||||
|
||||
// Emit the socket to the HTTP(s) server
|
||||
proxy.emit('connection', socket);
|
||||
}
|
||||
|
||||
// As of NodeJS 10.x the socket must be
|
||||
// resumed asynchronously or the socket
|
||||
// connection hangs, potentially crashing
|
||||
// the process. Prior to NodeJS 10.x
|
||||
// the socket may be resumed synchronously.
|
||||
process.nextTick(() => socket.resume());
|
||||
});
|
||||
});
|
||||
|
||||
server.http = http.createServer(handler);
|
||||
server.https = https.createServer(opts, handler);
|
||||
return server;
|
||||
};
|
||||
@@ -62,6 +62,14 @@ class WTVShared {
|
||||
else return out;
|
||||
}
|
||||
|
||||
parseConfigVars(s) {
|
||||
if (s.indexOf("%ServiceDeps%") >= 0) {
|
||||
return this.getServiceDep(s.replace("%ServiceDeps%", ""), true);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
atob(a) {
|
||||
const CryptoJS = require('crypto-js');
|
||||
const enc = CryptoJS.enc.Base64.parse(a);
|
||||
|
||||
@@ -273,8 +273,14 @@
|
||||
"drop_connection_on_wrong_port": false, // If true, resets connection if the PC browser connects to a port that is not PC Services enabled
|
||||
"show_verbose_errors": false, // extra debugging
|
||||
"privileged": true,
|
||||
"allow_https": false, // for future use with LetsEncrypt
|
||||
"force_https": false // for future use with LetsEncrypt
|
||||
"use_https": false,
|
||||
"force_https": false,
|
||||
"https_cert": {
|
||||
/* self-signed, can be replaced with another cert */
|
||||
// "domain": "mycooldomain.com",
|
||||
"cert": "%ServiceDeps%/https/selfsigned_cert.pem",
|
||||
"key": "%ServiceDeps%/https/selfsigned_key.pem"
|
||||
}
|
||||
}
|
||||
},
|
||||
"favorites": {
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
<ItemGroup>
|
||||
<Content Include=".gitignore" />
|
||||
<Content Include="app.js" />
|
||||
<Content Include="includes\classes\HTTPX.js">
|
||||
<SubType>Code</SubType>
|
||||
</Content>
|
||||
<Content Include="includes\classes\WTVAdmin.js" />
|
||||
<Content Include="includes\classes\WTVBGMusic.js" />
|
||||
<Content Include="includes\classes\WTVClientCapabilities.js" />
|
||||
|
||||
Reference in New Issue
Block a user