telebit-relay.js/lib/unwrap-tls.js

160 lines
5.3 KiB
JavaScript
Raw Normal View History

'use strict';
var sni = require('sni');
2018-06-01 06:41:32 +00:00
var pipeWs = require('./pipe-ws.js');
2018-06-01 06:41:32 +00:00
module.exports.createTcpConnectionHandler = function (state) {
var Devices = state.Devices;
2018-05-31 06:12:37 +00:00
return function onTcpConnection(conn, serviceport) {
2018-05-31 06:18:02 +00:00
serviceport = serviceport || conn.localPort;
// this works when I put it here, but I don't know if it's tls yet here
// httpsServer.emit('connection', socket);
//tls3000.emit('connection', socket);
//var tlsSocket = new tls.TLSSocket(socket, { secureContext: tls.createSecureContext(tlsOpts) });
//tlsSocket.on('data', function (chunk) {
// console.log('dummy', chunk.byteLength);
//});
//return;
conn.once('data', function (firstChunk) {
2018-06-19 23:43:28 +00:00
var service = 'tcp';
var servername;
var str;
var m;
2018-05-26 21:21:03 +00:00
conn.pause();
conn.unshift(firstChunk);
// BUG XXX: this assumes that the packet won't be chunked smaller
// than the 'hello' or the point of the 'Host' header.
// This is fairly reasonable, but there are edge cases where
// it does not hold (such as manual debugging with telnet)
// and so it should be fixed at some point in the future
// defer after return (instead of being in many places)
2018-05-26 21:21:03 +00:00
function deferData(fn) {
if (fn) {
2018-06-19 23:43:28 +00:00
state[fn](servername, conn);
2018-05-26 21:21:03 +00:00
}
process.nextTick(function () {
conn.resume();
});
}
function tryTls() {
var vhost;
2018-06-01 06:41:32 +00:00
if (!state.servernames.length) {
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
2018-05-26 21:21:03 +00:00
deferData('httpsSetupServer');
2018-05-23 11:12:39 +00:00
return;
}
2018-06-01 06:41:32 +00:00
if (-1 !== state.servernames.indexOf(servername)) {
if (state.debug) { console.log("[Admin]", servername); }
2018-05-26 21:21:03 +00:00
deferData('httpsTunnel');
return;
}
2018-06-01 06:41:32 +00:00
if (state.config.nowww && /^www\./i.test(servername)) {
console.log("TODO: use www bare redirect");
}
2018-06-14 09:59:19 +00:00
if (!servername) {
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); }
deferData('httpsInvalid');
return;
}
2018-06-14 09:59:19 +00:00
function run() {
2018-06-01 06:41:32 +00:00
var nextDevice = Devices.next(state.deviceLists, servername);
if (!nextDevice) {
2018-06-01 06:41:32 +00:00
if (state.debug) { console.log("No devices match the given servername"); }
2018-05-26 21:21:03 +00:00
deferData('httpsInvalid');
return;
}
2018-06-19 23:43:28 +00:00
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); }
2018-05-26 21:21:03 +00:00
deferData();
2018-06-19 23:43:28 +00:00
pipeWs(servername, service, nextDevice, conn, serviceport);
}
2018-06-01 06:41:32 +00:00
// TODO don't run an fs check if we already know this is working elsewhere
//if (!state.validHosts) { state.validHosts = {}; }
if (state.config.vhost) {
2018-06-14 09:59:19 +00:00
vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist'));
2018-06-01 06:41:32 +00:00
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
2018-06-19 23:43:28 +00:00
//state.httpsVhost(servername, conn);
//return;
require('fs').readdir(vhost, function (err, nodes) {
2018-06-01 06:41:32 +00:00
if (state.debug && err) { console.log("VHOST error", err); }
2018-06-19 23:43:28 +00:00
if (err || !nodes) { run(); return; }
2018-06-14 09:59:19 +00:00
//if (nodes) { deferData('httpsVhost'); return; }
deferData('httpsVhost');
});
return;
}
run();
}
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
if (22 === firstChunk[0]) {
// TLS
service = 'https';
2018-06-14 09:59:19 +00:00
servername = (sni(firstChunk)||'').toLowerCase().trim();
2018-06-01 06:41:32 +00:00
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); }
tryTls();
return;
}
if (firstChunk[0] > 32 && firstChunk[0] < 127) {
str = firstChunk.toString();
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im);
servername = (m && m[1].toLowerCase() || '').split(':')[0];
2018-06-01 06:41:32 +00:00
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); }
2018-05-23 11:12:39 +00:00
if (/HTTP\//i.test(str)) {
2018-06-01 06:41:32 +00:00
if (!state.servernames.length) {
console.info("[tcp] No admin servername. Entering setup mode.");
2018-05-26 21:21:03 +00:00
deferData();
2018-06-01 06:41:32 +00:00
state.httpSetupServer.emit('connection', conn);
2018-05-23 11:12:39 +00:00
return;
}
service = 'http';
2018-05-23 11:12:39 +00:00
// TODO make https redirect configurable
// /^\/\.well-known\/acme-challenge\//.test(str)
if (/well-known/.test(str)) {
// HTTP
2018-06-01 06:41:32 +00:00
if (Devices.exist(state.deviceLists, servername)) {
2018-05-26 21:21:03 +00:00
deferData();
2018-06-19 23:43:28 +00:00
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport);
return;
}
2018-05-26 21:21:03 +00:00
deferData('handleHttp');
return;
}
2018-05-26 21:21:03 +00:00
// redirect to https
deferData('handleInsecureHttp');
return;
}
}
console.error("Got unexpected connection", str);
conn.write(JSON.stringify({ error: {
message: "not sure what you were trying to do there..."
, code: 'E_INVALID_PROTOCOL' }
}));
conn.end();
});
conn.on('error', function (err) {
console.error('[error] tcp socket raw TODO forward and close');
console.error(err);
});
};
};