|
|
@ -27,6 +27,16 @@ module.exports.createTcpConnectionHandler = function (state) { |
|
|
|
var str; |
|
|
|
var m; |
|
|
|
|
|
|
|
if (!firstChunk) { |
|
|
|
try { |
|
|
|
conn.end(); |
|
|
|
} catch(e) { |
|
|
|
console.error("[lib/unwrap-tls.js] Error:", e); |
|
|
|
conn.destroy(); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
//conn.pause();
|
|
|
|
conn.unshift(firstChunk); |
|
|
|
|
|
|
@ -40,6 +50,8 @@ module.exports.createTcpConnectionHandler = function (state) { |
|
|
|
function deferData(fn) { |
|
|
|
if (fn) { |
|
|
|
state[fn](servername, conn); |
|
|
|
} else { |
|
|
|
console.error("[SANITY ERROR] '" + fn + "' doesn't have a state handler"); |
|
|
|
} |
|
|
|
/* |
|
|
|
process.nextTick(function () { |
|
|
@ -48,33 +60,78 @@ module.exports.createTcpConnectionHandler = function (state) { |
|
|
|
*/ |
|
|
|
} |
|
|
|
|
|
|
|
function tryTls() { |
|
|
|
var vhost; |
|
|
|
var httpOutcomes = { |
|
|
|
missingServername: function () { |
|
|
|
console.log("[debug] [http] missing servername"); |
|
|
|
// TODO use a more specific error page
|
|
|
|
deferData('handleInsecureHttp'); |
|
|
|
} |
|
|
|
, requiresSetup: function () { |
|
|
|
console.log("[debug] [http] requires setup"); |
|
|
|
// TODO Insecure connections for setup will not work on secure domains (i.e. .app)
|
|
|
|
state.httpSetupServer.emit('connection', conn); |
|
|
|
} |
|
|
|
, isInternal: function () { |
|
|
|
console.log("[debug] [http] is known internally (admin)"); |
|
|
|
if (/well-known/.test(str)) { |
|
|
|
deferData('handleHttp'); |
|
|
|
} else { |
|
|
|
deferData('handleInsecureHttp'); |
|
|
|
} |
|
|
|
} |
|
|
|
, isVhost: function () { |
|
|
|
console.log("[debug] [http] is vhost (normal server)"); |
|
|
|
if (/well-known/.test(str)) { |
|
|
|
deferData('handleHttp'); |
|
|
|
} else { |
|
|
|
deferData('handleInsecureHttp'); |
|
|
|
} |
|
|
|
} |
|
|
|
, assumeExternal: function () { |
|
|
|
console.log("[debug] [http] assume external"); |
|
|
|
var service = 'http'; |
|
|
|
|
|
|
|
if (!Devices.exist(state.deviceLists, servername)) { |
|
|
|
// It would be better to just re-read the host header rather
|
|
|
|
// than creating a whole server object, but this is a "rare"
|
|
|
|
// case and I'm feeling lazy right now.
|
|
|
|
console.log("[debug] [http] no device connected"); |
|
|
|
state.createHttpInvalid(servername).emit('connection', conn); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!state.servernames.length) { |
|
|
|
// TODO make https redirect configurable on a per-domain basis
|
|
|
|
// /^\/\.well-known\/acme-challenge\//.test(str)
|
|
|
|
if (/well-known/.test(str)) { |
|
|
|
// HTTP
|
|
|
|
console.log("[debug] [http] passthru"); |
|
|
|
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport); |
|
|
|
return; |
|
|
|
} else { |
|
|
|
console.log("[debug] [http] redirect to https"); |
|
|
|
deferData('handleInsecureHttp'); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
var tlsOutcomes = { |
|
|
|
missingServername: function () { |
|
|
|
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); } |
|
|
|
deferData('httpsInvalid'); |
|
|
|
} |
|
|
|
, requiresSetup: function () { |
|
|
|
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)"); |
|
|
|
deferData('httpsSetupServer'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (-1 !== state.servernames.indexOf(servername)) { |
|
|
|
, isInternal: function () { |
|
|
|
if (state.debug) { console.log("[Admin]", servername); } |
|
|
|
deferData('httpsTunnel'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (state.config.nowww && /^www\./i.test(servername)) { |
|
|
|
console.log("TODO: use www bare redirect"); |
|
|
|
} |
|
|
|
|
|
|
|
if (!servername) { |
|
|
|
if (state.debug) { console.log("No SNI was given, so there's nothing we can do here"); } |
|
|
|
deferData('httpsInvalid'); |
|
|
|
return; |
|
|
|
, isVhost: function (vhost) { |
|
|
|
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); } |
|
|
|
deferData('httpsVhost'); |
|
|
|
} |
|
|
|
|
|
|
|
function run() { |
|
|
|
var nextDevice = Devices.next(state.deviceLists, servername); |
|
|
|
, assumeExternal: function () { |
|
|
|
var nextDevice = Devices.next(state.deviceLists, servername); |
|
|
|
if (!nextDevice) { |
|
|
|
if (state.debug) { console.log("No devices match the given servername"); } |
|
|
|
deferData('httpsInvalid'); |
|
|
@ -82,27 +139,33 @@ module.exports.createTcpConnectionHandler = function (state) { |
|
|
|
} |
|
|
|
|
|
|
|
if (state.debug) { console.log("pipeWs(servername, service, deviceLists['" + servername + "'], socket)"); } |
|
|
|
deferData(); |
|
|
|
pipeWs(servername, service, nextDevice, conn, serviceport); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function handleConnection(outcomes) { |
|
|
|
var vhost; |
|
|
|
|
|
|
|
// No routing information available
|
|
|
|
if (!servername) { outcomes.missingServername(); return; } |
|
|
|
// Server needs to be set up
|
|
|
|
if (!state.servernames.length) { outcomes.requiresSetup(); return; } |
|
|
|
// This is one of the admin domains
|
|
|
|
if (-1 !== state.servernames.indexOf(servername)) { outcomes.isInternal(); return; } |
|
|
|
|
|
|
|
// TODO don't run an fs check if we already know this is working elsewhere
|
|
|
|
//if (!state.validHosts) { state.validHosts = {}; }
|
|
|
|
if (state.config.vhost) { |
|
|
|
vhost = state.config.vhost.replace(/:hostname/, (servername||'reallydoesntexist')); |
|
|
|
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); } |
|
|
|
//state.httpsVhost(servername, conn);
|
|
|
|
//return;
|
|
|
|
vhost = state.config.vhost.replace(/:hostname/, servername); |
|
|
|
require('fs').readdir(vhost, function (err, nodes) { |
|
|
|
if (state.debug && err) { console.log("VHOST error", err); } |
|
|
|
if (err || !nodes) { run(); return; } |
|
|
|
//if (nodes) { deferData('httpsVhost'); return; }
|
|
|
|
deferData('httpsVhost'); |
|
|
|
if (err || !nodes) { outcomes.assumeExternal(); return; } |
|
|
|
outcomes.isVhost(vhost); |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
run(); |
|
|
|
outcomes.assumeExternal(); |
|
|
|
} |
|
|
|
|
|
|
|
// https://github.com/mscdex/httpolyglot/issues/3#issuecomment-173680155
|
|
|
@ -111,40 +174,19 @@ module.exports.createTcpConnectionHandler = function (state) { |
|
|
|
service = 'https'; |
|
|
|
servername = (sni(firstChunk)||'').toLowerCase().trim(); |
|
|
|
if (state.debug) { console.log("[tcp] tls hello from '" + servername + "'"); } |
|
|
|
tryTls(); |
|
|
|
handleConnection(tlsOutcomes); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (firstChunk[0] > 32 && firstChunk[0] < 127) { |
|
|
|
// (probably) HTTP
|
|
|
|
str = firstChunk.toString(); |
|
|
|
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); |
|
|
|
servername = (m && m[1].toLowerCase() || '').split(':')[0]; |
|
|
|
if (state.debug) { console.log("[tcp] http hostname '" + servername + "'"); } |
|
|
|
|
|
|
|
if (/HTTP\//i.test(str)) { |
|
|
|
if (!state.servernames.length) { |
|
|
|
console.info("[tcp] No admin servername. Entering setup mode."); |
|
|
|
deferData(); |
|
|
|
state.httpSetupServer.emit('connection', conn); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
service = 'http'; |
|
|
|
// TODO make https redirect configurable
|
|
|
|
// /^\/\.well-known\/acme-challenge\//.test(str)
|
|
|
|
if (/well-known/.test(str)) { |
|
|
|
// HTTP
|
|
|
|
if (Devices.exist(state.deviceLists, servername)) { |
|
|
|
deferData(); |
|
|
|
pipeWs(servername, service, Devices.next(state.deviceLists, servername), conn, serviceport); |
|
|
|
return; |
|
|
|
} |
|
|
|
deferData('handleHttp'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// redirect to https
|
|
|
|
deferData('handleInsecureHttp'); |
|
|
|
handleConnection(httpOutcomes); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|