diff --git a/lib/goldilocks.js b/lib/goldilocks.js index 013a4cb..78905a2 100644 --- a/lib/goldilocks.js +++ b/lib/goldilocks.js @@ -21,108 +21,6 @@ module.exports.create = function (deps, config) { var tls = require('tls'); var domainMatches = require('./match-domain').match; - var tcpRouter = { - _map: { } - , _create: function (address, port) { - // port provides hinting for http, smtp, etc - return function (conn, firstChunk, opts) { - console.log('[tcpRouter] ' + address + ':' + port + ' ' + (opts.servername || '')); - - var m; - var str; - var hostname; - var newHeads; - - // TODO test per-module - // Maybe HTTP - if (firstChunk[0] > 32 && firstChunk[0] < 127) { - str = firstChunk.toString(); - m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); - hostname = (m && m[1].toLowerCase() || '').split(':')[0]; - console.log('[tcpRouter] hostname', hostname); - if (/HTTP\//i.test(str)) { - //conn.__service = 'http'; - } - } - - if (!hostname) { - // TODO allow tcp tunneling - // TODO we need some way of tagging tcp as either terminated tls or insecure - conn.write( - "HTTP/1.1 404 Not Found\r\n" - + "Date: Fri, 31 Dec 1999 23:59:59 GMT\r\n" - + "Content-Type: text/html\r\n" - + "Content-Length: " + 9 + "\r\n" - + "\r\n" - + "Not Found" - ); - conn.end(); - return; - } - - - // Poor-man's http proxy - // XXX SECURITY XXX: should strip existing X-Forwarded headers - newHeads = - [ "X-Forwarded-Proto: " + (opts.encrypted ? 'https' : 'http') - , "X-Forwarded-For: " + (opts.remoteAddress || conn.remoteAddress) - , "X-Forwarded-Host: " + hostname - ]; - - if (!opts.encrypted) { - // a exists-only header that a bad client could not remove - newHeads.push("X-Not-Encrypted: yes"); - } - if (opts.servername) { - newHeads.push("X-Forwarded-Sni: " + opts.servername); - if (opts.servername !== hostname) { - // an exists-only header that a bad client could not remove - newHeads.push("X-Two-Servernames: yes"); - } - } - - firstChunk = firstChunk.toString('utf8'); - // JSON.stringify("Host: example.com\r\nNext: Header".replace(/(Host: [^\r\n]*)/i, "$1" + "\r\n" + "X: XYZ")) - firstChunk = firstChunk.replace(/(Host: [^\r\n]*)/i, "$1" + "\r\n" + newHeads.join("\r\n")); - - process.nextTick(function () { - conn.unshift(Buffer.from(firstChunk, 'utf8')); - }); - - // - // hard-coded routes for the admin interface - if ( - /\blocalhost\.admin\./.test(hostname) || /\badmin\.localhost\./.test(hostname) - || /\blocalhost\.alpha\./.test(hostname) || /\balpha\.localhost\./.test(hostname) - ) { - if (!modules.admin) { - modules.admin = require('./modules/admin.js').create(deps, config); - } - modules.admin.emit('connection', conn); - return; - } - - // TODO static file handiling and such or whatever - if (!modules.http) { - modules.http = require('./modules/http.js').create(deps, config); - } - opts.hostname = hostname; - conn.__opts = opts; - - modules.http.emit('connection', conn); - }; - } - , get: function getTcpRouter(address, port) { - address = address || '0.0.0.0'; - - var id = address + ':' + port; - if (!tcpRouter._map[id]) { - tcpRouter._map[id] = tcpRouter._create(address, port); - } - - return tcpRouter._map[id]; - } - }; var tlsRouter = { proxy: function (socket, opts, mod) { var newConn = deps.net.createConnection({ @@ -231,25 +129,36 @@ module.exports.create = function (deps, config) { // TLS byte 1 is handshake and byte 6 is client hello if (0x16 === firstChunk[0]/* && 0x01 === firstChunk[5]*/) { - console.log('tryTls'); opts.servername = (parseSni(firstChunk)||'').toLowerCase() || 'localhost.invalid'; tlsRouter.processSocket(conn, firstChunk, opts); return; } - console.log('tryTcp'); - - if (opts.hyperPeek) { - // even though we've already peeked, this logic is just as well to let be - // since it works properly either way, unlike the tls socket - conn.once('data', function (chunk) { - console.log('hyperPeek re-peek data', chunk.toString('utf8')); - tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, chunk, opts); + // This doesn't work with TLS, but now that we know this isn't a TLS connection we can + // unshift the first chunk back onto the connection for future use. The unshift should + // happen after any listeners are attached to it but before any new data comes in. + if (!opts.hyperPeek) { + process.nextTick(function () { + conn.unshift(firstChunk); }); - return; } - tcpRouter.get(opts.localAddress || conn.localAddress, conn.localPort)(conn, firstChunk, opts); + // Connection is not TLS, check for HTTP next. + if (firstChunk[0] > 32 && firstChunk[0] < 127) { + var firstStr = firstChunk.toString(); + if (/HTTP\//i.test(firstStr)) { + if (!modules.http) { + modules.http = require('./modules/http.js').create(deps, config); + } + + conn.__opts = opts; + modules.http.emit('connection', conn); + return; + } + } + + console.warn('failed to identify protocol from first chunk', firstChunk); + conn.close(); } function netHandler(conn, opts) { opts = opts || {}; diff --git a/lib/modules/admin.js b/lib/modules/admin.js index acde6a2..7f5a81a 100644 --- a/lib/modules/admin.js +++ b/lib/modules/admin.js @@ -60,7 +60,5 @@ module.exports.create = function (deps, conf) { }); /* device, addresses, cwd, http */ - var app = require('../app.js')(deps, conf, opts); - var http = require('http'); - return http.createServer(app); + return require('../app.js')(deps, conf, opts); }; diff --git a/lib/modules/http.js b/lib/modules/http.js index 1b8207e..307cda6 100644 --- a/lib/modules/http.js +++ b/lib/modules/http.js @@ -2,8 +2,16 @@ module.exports.create = function (deps, conf) { var app = require('express')(); + var adminApp = require('./admin').create(deps, conf); var domainMatches = require('../match-domain').match; + var adminDomains = [ + /\blocalhost\.admin\./ + , /\blocalhost\.alpha\./ + , /\badmin\.localhost\./ + , /\balpha\.localhost\./ + ]; + // We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect // any unencrypted requests to the same port they came from unless it came in on // the default HTTP port, in which case there wont be a port specified in the host. @@ -17,6 +25,18 @@ module.exports.create = function (deps, conf) { redirecter(req, res, next); } + function handleAdmin(req, res, next) { + var admin = adminDomains.some(function (re) { + return re.test(req.headers.host); + }); + + if (admin) { + adminApp(req, res); + } else { + next(); + } + } + function respond404(req, res) { res.writeHead(404); res.end('Not Found'); @@ -36,6 +56,14 @@ module.exports.create = function (deps, conf) { , toProxy: true }); + // We want to override the default value for some headers with the extra information we + // have available to us in the opts object attached to the connection. + proxy.on('proxyReq', function (proxyReq, req) { + var conn = req.connection; + var opts = conn.__opts; + proxyReq.setHeader('X-Forwarded-For', opts.remoteAddress || conn.remoteAddress); + }); + return function (req, res, next) { var hostname = req.headers.host.split(':')[0]; var relevant = mod.domains.some(function (pattern) { @@ -51,6 +79,7 @@ module.exports.create = function (deps, conf) { } app.use(redirectHttps); + app.use(handleAdmin); (conf.http.modules || []).forEach(function (mod) { if (mod.name === 'proxy') {