Show Bad Gateway rather than disconnect when south-end has gone missing.
This commit is contained in:
parent
9e06faa581
commit
0a67728239
|
@ -47,7 +47,13 @@ function applyConfig(config) {
|
|||
} else {
|
||||
state.Promise = require('bluebird');
|
||||
}
|
||||
state.tlsOptions = {}; // TODO just close the sockets that would use this early? or use the admin servername
|
||||
state.tlsOptions = {
|
||||
// Handles disconnected devices
|
||||
// TODO allow user to opt-in to wildcard hosting for a better error page?
|
||||
SNICallback: function (servername, cb) {
|
||||
return state.greenlock.tlsOptions.SNICallback(state.config.webminDomain || state.servernames[0], cb);
|
||||
}
|
||||
}; // TODO just close the sockets that would use this early? or use the admin servername
|
||||
state.config = config;
|
||||
state.servernames = config.servernames || [];
|
||||
state.secret = state.config.secret;
|
||||
|
|
|
@ -10,7 +10,7 @@ function noSniCallback(tag) {
|
|||
var err = new Error("[noSniCallback] no handler set for '" + tag + "':'" + servername + "'");
|
||||
console.error(err.message);
|
||||
cb(new Error(err));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.create = function (state) {
|
||||
|
@ -72,6 +72,28 @@ module.exports.create = function (state) {
|
|||
state.tlsInvalidSniServer.on('tlsClientError', function () {
|
||||
console.error('tlsClientError InvalidSniServer');
|
||||
});
|
||||
state.createHttpInvalid = function (servername) {
|
||||
return http.createServer(function (req, res) {
|
||||
if (!servername) {
|
||||
res.statusCode = 422;
|
||||
res.end(
|
||||
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
||||
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
||||
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
||||
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO use req.headers.host instead of servername (since domain fronting is disabled anyway)
|
||||
res.statusCode = 502;
|
||||
res.end(
|
||||
"<h1>Oops!</h1>"
|
||||
+ "<p>It looks like '" + encodeURIComponent(servername) + "' isn't connected right now.</p>"
|
||||
+ "<small>Error: 502 Bad Gateway</small>"
|
||||
);
|
||||
});
|
||||
};
|
||||
state.httpsInvalid = function (servername, socket) {
|
||||
// none of these methods work:
|
||||
// httpsServer.emit('connection', socket); // this didn't work
|
||||
|
@ -82,28 +104,9 @@ module.exports.create = function (state) {
|
|||
//state.tlsInvalidSniServer.emit('connection', wrapSocket(socket));
|
||||
var tlsInvalidSniServer = tls.createServer(state.tlsOptions, function (tlsSocket) {
|
||||
console.log('[tlsInvalid] tls connection');
|
||||
// things get a little messed up here
|
||||
var httpInvalidSniServer = http.createServer(function (req, res) {
|
||||
if (!servername) {
|
||||
res.statusCode = 422;
|
||||
res.end(
|
||||
"3. An inexplicable temporal shift of the quantum realm... that makes me feel uncomfortable.\n\n"
|
||||
+ "[ERROR] No SNI header was sent. I can only think of two possible explanations for this:\n"
|
||||
+ "\t1. You really love Windows XP and you just won't let go of Internet Explorer 6\n"
|
||||
+ "\t2. You're writing a bot and you forgot to set the servername parameter\n"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
res.end(
|
||||
"You came in hot looking for '" + servername + "' and, granted, the IP address for that domain"
|
||||
+ " must be pointing here (or else how could you be here?), nevertheless either it's not registered"
|
||||
+ " in the internal system at all (which Seth says isn't even a thing) or there is no device"
|
||||
+ " connected on the south side of the network which has informed me that it's ready to have traffic"
|
||||
+ " for that domain forwarded to it (sorry I didn't check that deeply to determine which).\n\n"
|
||||
+ "Either way, you're doing strange things that make me feel uncomfortable... Please don't touch me there any more.");
|
||||
});
|
||||
httpInvalidSniServer.emit('connection', tlsSocket);
|
||||
// We create an entire http server object because it's difficult to figure out
|
||||
// how to access the original tlsSocket to get the servername
|
||||
state.createHttpInvalid(servername).emit('connection', tlsSocket);
|
||||
});
|
||||
tlsInvalidSniServer.on('tlsClientError', function () {
|
||||
console.error('tlsClientError InvalidSniServer httpsInvalid');
|
||||
|
|
|
@ -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;
|
||||
|
||||
if (!state.servernames.length) {
|
||||
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
||||
deferData('httpsSetupServer');
|
||||
return;
|
||||
var httpOutcomes = {
|
||||
missingServername: function () {
|
||||
console.log("[debug] [http] missing servername");
|
||||
// TODO use a more specific error page
|
||||
deferData('handleInsecureHttp');
|
||||
}
|
||||
|
||||
if (-1 !== state.servernames.indexOf(servername)) {
|
||||
if (state.debug) { console.log("[Admin]", servername); }
|
||||
deferData('httpsTunnel');
|
||||
return;
|
||||
, 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);
|
||||
}
|
||||
|
||||
if (state.config.nowww && /^www\./i.test(servername)) {
|
||||
console.log("TODO: use www bare redirect");
|
||||
, 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 (!servername) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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');
|
||||
return;
|
||||
}
|
||||
|
||||
function run() {
|
||||
var nextDevice = Devices.next(state.deviceLists, servername);
|
||||
, requiresSetup: function () {
|
||||
console.info("[Setup] https => admin => setup => (needs bogus tls certs to start?)");
|
||||
deferData('httpsSetupServer');
|
||||
}
|
||||
, isInternal: function () {
|
||||
if (state.debug) { console.log("[Admin]", servername); }
|
||||
deferData('httpsTunnel');
|
||||
}
|
||||
, isVhost: function (vhost) {
|
||||
if (state.debug) { console.log("[tcp] [vhost]", state.config.vhost, "=>", vhost); }
|
||||
deferData('httpsVhost');
|
||||
}
|
||||
, 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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue