Merge branch 'v1.0' of git.daplie.com:Daplie/Goldilocks.js into v1.0
This commit is contained in:
commit
5c7f2321cc
|
@ -69,26 +69,34 @@ function readConfigAndRun(args) {
|
||||||
|
|
||||||
var recase = require('recase').create({});
|
var recase = require('recase').create({});
|
||||||
config = recase.camelCopy(config);
|
config = recase.camelCopy(config);
|
||||||
|
config.debug = config.debug || args.debug;
|
||||||
|
|
||||||
if (!config.dns) {
|
if (!config.dns) {
|
||||||
config.dns = { modules: { name: 'proxy', port: 3053 } };
|
config.dns = { modules: [{ name: 'proxy', port: 3053 }] };
|
||||||
}
|
}
|
||||||
|
// Use Object.assign to add any properties needed but not defined in the mdns config.
|
||||||
|
// It will first copy the defaults into an empty object, then copy any real config over that.
|
||||||
|
var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
|
||||||
|
config.mdns = Object.assign({}, mdnsDefaults, config.mdns || {});
|
||||||
|
|
||||||
if (!config.tcp) {
|
if (!config.tcp) {
|
||||||
config.tcp = {};
|
config.tcp = {};
|
||||||
}
|
}
|
||||||
if (!config.http) {
|
if (!config.http) {
|
||||||
config.http = { modules: { name: 'proxy', port: 3000 } };
|
config.http = { modules: [{ name: 'proxy', domains: ['*'], port: 3000 }] };
|
||||||
}
|
}
|
||||||
if (!config.tls) {
|
if (!config.tls) {
|
||||||
console.log("TODO: tls: { modules: { name: 'acme', email: 'foo@bar.com', domains: [ '*' ] } }");
|
config.tls = {};
|
||||||
config.tls = {
|
}
|
||||||
agreeTos: args.agreeTos || args.agree || args['agree-tos']
|
if (!config.tls.acme && (args.email || args.agreeTos)) {
|
||||||
, servernames: (args.servernames||'').split(',').filter(Boolean).map(function (str) { return str.toLowerCase(); })
|
config.tls.acme = {};
|
||||||
};
|
}
|
||||||
|
if (typeof args.agreeTos === 'string') {
|
||||||
|
config.tls.acme.approvedDomains = args.agreeTos.split(',');
|
||||||
}
|
}
|
||||||
if (args.email) {
|
if (args.email) {
|
||||||
config.email = args.email;
|
config.email = args.email;
|
||||||
config.tls.email = args.email;
|
config.tls.acme.email = args.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybe this should not go in config... but be ephemeral in some way?
|
// maybe this should not go in config... but be ephemeral in some way?
|
||||||
|
@ -190,20 +198,20 @@ function readConfigAndRun(args) {
|
||||||
|
|
||||||
function readEnv(args) {
|
function readEnv(args) {
|
||||||
// TODO
|
// TODO
|
||||||
|
try {
|
||||||
|
if (process.env.GOLDILOCKS_HOME) {
|
||||||
|
process.chdir(process.env.GOLDILOCKS_HOME);
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
|
||||||
var env = {
|
var env = {
|
||||||
tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
|
tunnel: process.env.GOLDILOCKS_TUNNEL_TOKEN || process.env.GOLDILOCKS_TUNNEL && true
|
||||||
, email: process.env.GOLDILOCKS_EMAIL
|
, email: process.env.GOLDILOCKS_EMAIL
|
||||||
, cwd: process.env.GOLDILOCKS_HOME
|
, cwd: process.env.GOLDILOCKS_HOME || process.cwd()
|
||||||
, debug: process.env.GOLDILOCKS_DEBUG && true
|
, debug: process.env.GOLDILOCKS_DEBUG && true
|
||||||
};
|
};
|
||||||
args.cwd = args.cwd || env.cwd;
|
|
||||||
Object.keys(env).forEach(function (key) {
|
|
||||||
if ('undefined' === typeof args[key]) {
|
|
||||||
args[key] = env[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
readConfigAndRun(args);
|
readConfigAndRun(Object.assign({}, env, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
var program = require('commander');
|
var program = require('commander');
|
||||||
|
@ -217,5 +225,4 @@ program
|
||||||
.option('--debug', "Enable debug output")
|
.option('--debug', "Enable debug output")
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
||||||
program.cwd = process.cwd();
|
|
||||||
readEnv(program);
|
readEnv(program);
|
||||||
|
|
|
@ -10,17 +10,55 @@ tcp:
|
||||||
address: '127.0.0.1:8022'
|
address: '127.0.0.1:8022'
|
||||||
|
|
||||||
tls:
|
tls:
|
||||||
|
acme:
|
||||||
|
email: 'joe.shmoe@example.com'
|
||||||
|
server: 'https://acme-staging.api.letsencrypt.org/directory'
|
||||||
|
challenge_type: 'http-01'
|
||||||
|
approved_domains:
|
||||||
|
- localhost.baz.daplie.me
|
||||||
|
- localhost.beta.daplie.me
|
||||||
|
domains:
|
||||||
|
- names:
|
||||||
|
- localhost.gamma.daplie.me
|
||||||
|
modules:
|
||||||
|
- name: proxy
|
||||||
|
address: '127.0.0.1:6443'
|
||||||
|
- names:
|
||||||
|
- beta.localhost.daplie.me
|
||||||
|
- baz.localhost.daplie.me
|
||||||
|
modules:
|
||||||
|
- name: acme
|
||||||
|
email: 'owner@example.com'
|
||||||
|
challenge_type: 'tls-sni-01'
|
||||||
|
# default server is 'https://acme-v01.api.letsencrypt.org/directory'
|
||||||
modules:
|
modules:
|
||||||
- name: proxy
|
- name: proxy
|
||||||
domains:
|
domains:
|
||||||
- localhost.bar.daplie.me
|
- localhost.bar.daplie.me
|
||||||
- localhost.foo.daplie.me
|
- localhost.foo.daplie.me
|
||||||
address: '127.0.0.1:5443'
|
address: '127.0.0.1:5443'
|
||||||
|
- name: acme
|
||||||
|
email: 'guest@example.com'
|
||||||
|
challenge_type: 'http-01'
|
||||||
|
domains:
|
||||||
|
- foo.localhost.daplie.me
|
||||||
|
- gamma.localhost.daplie.me
|
||||||
|
|
||||||
|
|
||||||
http:
|
http:
|
||||||
trust_proxy: true
|
trust_proxy: true
|
||||||
allow_insecure: false
|
allow_insecure: false
|
||||||
primary_domain: localhost.foo.daplie.me
|
primary_domain: localhost.foo.daplie.me
|
||||||
|
domains:
|
||||||
|
- names:
|
||||||
|
- localhost.baz.daplie.me
|
||||||
|
modules:
|
||||||
|
- name: redirect
|
||||||
|
from: /nowhere/in/particular
|
||||||
|
to: /just/an/example
|
||||||
|
- name: proxy
|
||||||
|
port: 3001
|
||||||
|
|
||||||
modules:
|
modules:
|
||||||
- name: redirect
|
- name: redirect
|
||||||
domains:
|
domains:
|
||||||
|
@ -31,8 +69,15 @@ http:
|
||||||
- name: proxy
|
- name: proxy
|
||||||
domains:
|
domains:
|
||||||
- localhost.daplie.me
|
- localhost.daplie.me
|
||||||
address: '127.0.0.1:4000'
|
host: locahost
|
||||||
|
port: 4000
|
||||||
- name: static
|
- name: static
|
||||||
domains:
|
domains:
|
||||||
- '*.localhost.daplie.me'
|
- '*.localhost.daplie.me'
|
||||||
root: '/srv/www/:hostname'
|
root: '/srv/www/:hostname'
|
||||||
|
|
||||||
|
mdns:
|
||||||
|
disabled: false
|
||||||
|
port: 5353
|
||||||
|
broadcast: '224.0.0.251'
|
||||||
|
ttl: 300
|
||||||
|
|
|
@ -74,46 +74,36 @@ module.exports.create = function (deps, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function dnsListener(msg) {
|
function dnsListener(msg) {
|
||||||
var dgram = require('dgram');
|
if (!Array.isArray(config.dns.modules)) {
|
||||||
var socket = dgram.createSocket('udp4');
|
return;
|
||||||
socket.send(msg, config.dns.proxy.port, config.dns.proxy.address || '127.0.0.1');
|
}
|
||||||
|
var socket = require('dgram').createSocket('udp4');
|
||||||
|
config.dns.modules.forEach(function (mod) {
|
||||||
|
if (mod.name !== 'proxy') {
|
||||||
|
console.warn('found bad DNS module', mod);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var dest = require('./domain-utils').separatePort(mod.address || '');
|
||||||
|
dest.port = dest.port || mod.port;
|
||||||
|
dest.host = dest.host || mod.host || 'localhost';
|
||||||
|
socket.send(msg, dest.port, dest.host);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTcpForwarder(mod) {
|
function createTcpForwarder(mod) {
|
||||||
var destination = mod.address.split(':');
|
var dest = require('./domain-utils').separatePort(mod.address || '');
|
||||||
var connected = false;
|
dest.port = dest.port || mod.port;
|
||||||
|
dest.host = dest.host || mod.host || 'localhost';
|
||||||
|
|
||||||
return function (conn) {
|
return function (conn) {
|
||||||
var newConn = deps.net.createConnection({
|
var newConnOpts = {};
|
||||||
port: destination[1]
|
['remote', 'local'].forEach(function (end) {
|
||||||
, host: destination[0] || '127.0.0.1'
|
['Family', 'Address', 'Port'].forEach(function (name) {
|
||||||
|
newConnOpts[end+name] = conn[end+name];
|
||||||
, remoteFamily: conn.remoteFamily
|
});
|
||||||
, remoteAddress: conn.remoteAddress
|
|
||||||
, remotePort: conn.remotePort
|
|
||||||
}, function () {
|
|
||||||
connected = true;
|
|
||||||
|
|
||||||
newConn.pipe(conn);
|
|
||||||
conn.pipe(newConn);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Not sure how to effectively report this to the user or client, but we need to listen
|
|
||||||
// for the event to prevent it from crashing us.
|
|
||||||
newConn.on('error', function (err) {
|
|
||||||
if (connected) {
|
|
||||||
console.error('TCP forward remote error', err);
|
|
||||||
conn.end();
|
|
||||||
} else {
|
|
||||||
console.log('TCP forward connection error', err);
|
|
||||||
require('./proxy-err-resp').sendBadGateway(conn, err, config.debug);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
conn.on('error', function (err) {
|
|
||||||
console.error('TCP forward client error', err);
|
|
||||||
newConn.end();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deps.proxy(conn, Object.assign({}, dest, newConnOpts));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -249,5 +239,9 @@ module.exports.create = function (deps, config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.mdns.disabled) {
|
||||||
|
require('./mdns').start(deps, config);
|
||||||
|
}
|
||||||
|
|
||||||
return PromiseA.all(listenPromises);
|
return PromiseA.all(listenPromises);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird');
|
||||||
|
var fs = PromiseA.promisifyAll(require('fs'));
|
||||||
|
var idFilename = require('path').join(__dirname, '..', 'var', 'mdns-id');
|
||||||
|
var queryName = '_cloud._tcp.local';
|
||||||
|
|
||||||
|
var randomId = {
|
||||||
|
get: function () {
|
||||||
|
return fs.readFileAsync(idFilename)
|
||||||
|
.catch(function (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
return PromiseA.reject(err);
|
||||||
|
}
|
||||||
|
var id = require('crypto').randomBytes(5).toString('hex');
|
||||||
|
return randomId.set(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
, set: function (value) {
|
||||||
|
return fs.writeFileAsync(idFilename, value)
|
||||||
|
.then(function () {
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function createResponse(name, packet, ttl) {
|
||||||
|
var rpacket = {
|
||||||
|
header: {
|
||||||
|
id: packet.header.id
|
||||||
|
, qr: 1
|
||||||
|
, opcode: 0
|
||||||
|
, aa: 1
|
||||||
|
, tc: 0
|
||||||
|
, rd: 0
|
||||||
|
, ra: 0
|
||||||
|
, res1: 0
|
||||||
|
, res2: 0
|
||||||
|
, res3: 0
|
||||||
|
, rcode: 0
|
||||||
|
, }
|
||||||
|
, question: packet.question
|
||||||
|
, answer: []
|
||||||
|
, authority: []
|
||||||
|
, additional: []
|
||||||
|
, edns_options: []
|
||||||
|
};
|
||||||
|
|
||||||
|
rpacket.answer.push({
|
||||||
|
name: queryName
|
||||||
|
, typeName: 'PTR'
|
||||||
|
, ttl: ttl
|
||||||
|
, className: 'IN'
|
||||||
|
, data: name + '.' + queryName
|
||||||
|
});
|
||||||
|
|
||||||
|
var ifaces = require('./local-ip').find();
|
||||||
|
Object.keys(ifaces).forEach(function (iname) {
|
||||||
|
var iface = ifaces[iname];
|
||||||
|
|
||||||
|
iface.ipv4.forEach(function (addr) {
|
||||||
|
rpacket.additional.push({
|
||||||
|
name: name + '.local'
|
||||||
|
, typeName: 'A'
|
||||||
|
, ttl: ttl
|
||||||
|
, className: 'IN'
|
||||||
|
, address: addr.address
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
iface.ipv6.forEach(function (addr) {
|
||||||
|
rpacket.additional.push({
|
||||||
|
name: name + '.local'
|
||||||
|
, typeName: 'AAAA'
|
||||||
|
, ttl: ttl
|
||||||
|
, className: 'IN'
|
||||||
|
, address: addr.address
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
rpacket.additional.push({
|
||||||
|
name: name + '.' + queryName
|
||||||
|
, typeName: 'SRV'
|
||||||
|
, ttl: ttl
|
||||||
|
, className: 'IN'
|
||||||
|
, priority: 1
|
||||||
|
, weight: 0
|
||||||
|
, port: 443
|
||||||
|
, target: name + ".local"
|
||||||
|
});
|
||||||
|
rpacket.additional.push({
|
||||||
|
name: name + '._device-info._tcp.local'
|
||||||
|
, typeName: 'TXT'
|
||||||
|
, ttl: ttl
|
||||||
|
, className: 'IN'
|
||||||
|
, data: ["model=CloudHome1,1", "dappsvers=1"]
|
||||||
|
});
|
||||||
|
|
||||||
|
return require('dns-suite').DNSPacket.write(rpacket);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.start = function (deps, config) {
|
||||||
|
var socket = require('dgram').createSocket({ type: 'udp4', reuseAddr: true });
|
||||||
|
var dns = require('dns-suite');
|
||||||
|
|
||||||
|
socket.on('message', function (message, rinfo) {
|
||||||
|
// console.log('Received %d bytes from %s:%d', message.length, rinfo.address, rinfo.port);
|
||||||
|
|
||||||
|
var packet;
|
||||||
|
try {
|
||||||
|
packet = dns.DNSPacket.parse(message);
|
||||||
|
}
|
||||||
|
catch (er) {
|
||||||
|
// `dns-suite` actually errors on a lot of the packets floating around in our network,
|
||||||
|
// so don't bother logging any errors. (We still use `dns-suite` because unlike `dns-js`
|
||||||
|
// it can successfully craft the one packet we want to send.)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only respond to queries.
|
||||||
|
if (packet.header.qr !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Only respond if they were asking for cloud devices.
|
||||||
|
if (packet.question.length !== 1 || packet.question[0].name !== queryName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
randomId.get().then(function (name) {
|
||||||
|
var resp = createResponse(name, packet, config.mdns.ttl);
|
||||||
|
socket.send(resp, config.mdns.port, config.mdns.broadcast);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.bind(config.mdns.port, function () {
|
||||||
|
var addr = this.address();
|
||||||
|
console.log('bound on UDP %s:%d for mDNS', addr.address, addr.port);
|
||||||
|
|
||||||
|
socket.setBroadcast(true);
|
||||||
|
socket.addMembership(config.mdns.broadcast);
|
||||||
|
});
|
||||||
|
};
|
|
@ -59,6 +59,6 @@ module.exports.create = function (deps, conf) {
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
/* device, addresses, cwd, http */
|
var app = require('../app.js')(deps, conf, opts);
|
||||||
return require('../app.js')(deps, conf, opts);
|
return require('http').createServer(app);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports.create = function (deps, conf, greenlockMiddleware) {
|
module.exports.create = function (deps, conf, greenlockMiddleware) {
|
||||||
var express = require('express');
|
var PromiseA = require('bluebird');
|
||||||
var app = express();
|
var statAsync = PromiseA.promisify(require('fs').stat);
|
||||||
var adminApp = require('./admin').create(deps, conf);
|
|
||||||
var domainMatches = require('../domain-utils').match;
|
var domainMatches = require('../domain-utils').match;
|
||||||
var separatePort = require('../domain-utils').separatePort;
|
var separatePort = require('../domain-utils').separatePort;
|
||||||
var proxyRoutes = [];
|
|
||||||
|
|
||||||
var adminDomains = [
|
var adminDomains = [
|
||||||
/\blocalhost\.admin\./
|
/\blocalhost\.admin\./
|
||||||
|
@ -15,232 +13,387 @@ module.exports.create = function (deps, conf, greenlockMiddleware) {
|
||||||
, /\balpha\.localhost\./
|
, /\balpha\.localhost\./
|
||||||
];
|
];
|
||||||
|
|
||||||
function moduleMatchesHost(req, mod) {
|
function parseHeaders(conn, opts) {
|
||||||
var host = separatePort(req.headers.host).host;
|
// There should already be a `firstChunk` on the opts, but because we might sometimes
|
||||||
|
// need more than that to get all the headers it's easier to always read the data off
|
||||||
|
// the connection and put it back later if we need to.
|
||||||
|
opts.firstChunk = Buffer.alloc(0);
|
||||||
|
|
||||||
return mod.domains.some(function (pattern) {
|
// First we make sure we have all of the headers.
|
||||||
|
return new PromiseA(function (resolve, reject) {
|
||||||
|
if (opts.firstChunk.includes('\r\n\r\n')) {
|
||||||
|
resolve(opts.firstChunk.toString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var errored = false;
|
||||||
|
function handleErr(err) {
|
||||||
|
errored = true;
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
conn.once('error', handleErr);
|
||||||
|
|
||||||
|
function handleChunk(chunk) {
|
||||||
|
if (!errored) {
|
||||||
|
opts.firstChunk = Buffer.concat([opts.firstChunk, chunk]);
|
||||||
|
if (opts.firstChunk.includes('\r\n\r\n')) {
|
||||||
|
resolve(opts.firstChunk.toString());
|
||||||
|
conn.removeListener('error', handleErr);
|
||||||
|
} else {
|
||||||
|
conn.once('data', handleChunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.once('data', handleChunk);
|
||||||
|
}).then(function (firstStr) {
|
||||||
|
var headerSection = firstStr.split('\r\n\r\n')[0];
|
||||||
|
var lines = headerSection.split('\r\n');
|
||||||
|
var result = {};
|
||||||
|
|
||||||
|
lines.slice(1).forEach(function (line) {
|
||||||
|
var match = /(.*)\s*:\s*(.*)/.exec(line);
|
||||||
|
if (match) {
|
||||||
|
result[match[1].toLowerCase()] = match[2];
|
||||||
|
} else {
|
||||||
|
console.error('HTTP header line does not match pattern', line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var match = /^([a-zA-Z]+)\s+(\S+)\s+HTTP/.exec(lines[0]);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error('first line of "HTTP" does not match pattern: '+lines[0]);
|
||||||
|
}
|
||||||
|
result.method = match[1].toUpperCase();
|
||||||
|
result.url = match[2];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hostMatchesDomains(req, domains) {
|
||||||
|
var host = separatePort((req.headers || req).host).host;
|
||||||
|
|
||||||
|
return domains.some(function (pattern) {
|
||||||
return domainMatches(pattern, host);
|
return domainMatches(pattern, host);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyHost(fullHost) {
|
function determinePrimaryHost() {
|
||||||
var host = separatePort(fullHost).host;
|
var result;
|
||||||
|
if (Array.isArray(conf.http.domains)) {
|
||||||
if (host === 'localhost') {
|
conf.http.domains.some(function (dom) {
|
||||||
return fullHost.replace(host, 'localhost.daplie.me');
|
return dom.names.some(function (domain) {
|
||||||
}
|
if (domain[0] !== '*') {
|
||||||
|
result = domain;
|
||||||
// Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
|
return true;
|
||||||
// but since those still won't be valid domains that won't really be a problem.
|
}
|
||||||
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || /^\[[0-9a-fA-F:]+\]$/.test(host)) {
|
|
||||||
if (!conf.http.primaryDomain) {
|
|
||||||
(conf.http.modules || []).some(function (mod) {
|
|
||||||
return mod.domains.some(function (domain) {
|
|
||||||
if (domain[0] !== '*') {
|
|
||||||
conf.http.primaryDomain = domain;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
return fullHost.replace(host, conf.http.primaryDomain || host);
|
}
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullHost;
|
if (Array.isArray(conf.http.modules)) {
|
||||||
|
conf.http.modules.some(function (mod) {
|
||||||
|
return mod.domains.some(function (domain) {
|
||||||
|
if (domain[0] !== '*') {
|
||||||
|
result = domain;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We handle both HTTPS and HTTP traffic on the same ports, and we want to redirect
|
// 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
|
// 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.
|
// the default HTTP port, in which case there wont be a port specified in the host.
|
||||||
var redirecters = {};
|
var redirecters = {};
|
||||||
function redirectHttps(req, res, next) {
|
var ipv4Re = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||||
if (conf.http.allowInsecure) {
|
var ipv6Re = /^\[[0-9a-fA-F:]+\]$/;
|
||||||
next();
|
function redirectHttps(req, res) {
|
||||||
return;
|
var host = separatePort(req.headers.host);
|
||||||
}
|
|
||||||
|
|
||||||
var port = separatePort(req.headers.host).port;
|
if (!redirecters[host.port]) {
|
||||||
if (!redirecters[port]) {
|
redirecters[host.port] = require('redirect-https')({ port: host.port });
|
||||||
redirecters[port] = require('redirect-https')({
|
|
||||||
port: port
|
|
||||||
, trustProxy: conf.http.trustProxy
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// localhost and IP addresses cannot have real SSL certs (and don't contain any useful
|
// localhost and IP addresses cannot have real SSL certs (and don't contain any useful
|
||||||
// info for redirection either), so we direct some hosts to either localhost.daplie.me
|
// info for redirection either), so we direct some hosts to either localhost.daplie.me
|
||||||
// or the "primary domain" ie the first manually specified domain.
|
// or the "primary domain" ie the first manually specified domain.
|
||||||
req.headers.host = verifyHost(req.headers.host);
|
if (host.host === 'localhost') {
|
||||||
|
req.headers.host = 'localhost.daplie.me' + (host.port ? ':'+host.port : '');
|
||||||
|
}
|
||||||
|
// Test for IPv4 and IPv6 addresses. These patterns will match some invalid addresses,
|
||||||
|
// but since those still won't be valid domains that won't really be a problem.
|
||||||
|
if (ipv4Re.test(host.host) || ipv6Re.test(host.host)) {
|
||||||
|
var dest;
|
||||||
|
if (conf.http.primaryDomain) {
|
||||||
|
dest = conf.http.primaryDomain;
|
||||||
|
} else {
|
||||||
|
dest = determinePrimaryHost();
|
||||||
|
}
|
||||||
|
if (dest) {
|
||||||
|
req.headers.host = dest + (host.port ? ':'+host.port : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
redirecters[port](req, res, next);
|
redirecters[host.port](req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAdmin(req, res, next) {
|
function emitConnection(server, conn, opts) {
|
||||||
|
server.emit('connection', conn);
|
||||||
|
|
||||||
|
// We need to put back whatever data we read off to determine the connection was HTTP
|
||||||
|
// and to parse the headers. Must be done after data handlers added but before any new
|
||||||
|
// data comes in.
|
||||||
|
process.nextTick(function () {
|
||||||
|
conn.unshift(opts.firstChunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convenience return for all the check* functions.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var acmeServer;
|
||||||
|
function checkACME(conn, opts, headers) {
|
||||||
|
if (headers.url.indexOf('/.well-known/acme-challenge/') !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!acmeServer) {
|
||||||
|
acmeServer = require('http').createServer(greenlockMiddleware);
|
||||||
|
}
|
||||||
|
return emitConnection(acmeServer, conn, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpsRedirectServer;
|
||||||
|
function checkHttps(conn, opts, headers) {
|
||||||
|
if (conf.http.allowInsecure || conn.encrypted) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (conf.http.trustProxy && 'https' === headers['x-forwarded-proto']) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!httpsRedirectServer) {
|
||||||
|
httpsRedirectServer = require('http').createServer(redirectHttps);
|
||||||
|
}
|
||||||
|
return emitConnection(httpsRedirectServer, conn, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
var adminServer;
|
||||||
|
function checkAdmin(conn, opts, headers) {
|
||||||
var admin = adminDomains.some(function (re) {
|
var admin = adminDomains.some(function (re) {
|
||||||
return re.test(req.headers.host);
|
return re.test(headers.host);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (admin) {
|
if (admin) {
|
||||||
adminApp(req, res);
|
if (!adminServer) {
|
||||||
|
adminServer = require('./admin').create(deps, conf);
|
||||||
|
}
|
||||||
|
return emitConnection(adminServer, conn, opts);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkProxy(mod, conn, opts, headers) {
|
||||||
|
var index = opts.firstChunk.indexOf('\r\n\r\n');
|
||||||
|
var body = opts.firstChunk.slice(index);
|
||||||
|
|
||||||
|
var head = opts.firstChunk.slice(0, index).toString();
|
||||||
|
var headLines = head.split('\r\n');
|
||||||
|
// First strip any existing `X-Forwarded-*` headers (for security purposes?)
|
||||||
|
headLines = headLines.filter(function (line) {
|
||||||
|
return !/^x-forwarded/i.test(line);
|
||||||
|
});
|
||||||
|
// Then add our own `X-Forwarded` headers at the end.
|
||||||
|
if (conf.http.trustProxy && headers['x-forwarded-proto']) {
|
||||||
|
headLines.push('X-Forwarded-Proto: ' + headers['x-forwarded-proto']);
|
||||||
} else {
|
} else {
|
||||||
next();
|
headLines.push('X-Forwarded-Proto: ' + conn.encrypted ? 'https' : 'http');
|
||||||
}
|
}
|
||||||
|
var proxyChain = (headers['x-forwarded-for'] || '').split(/ *, */).filter(Boolean);
|
||||||
|
proxyChain.push(opts.remoteAddress || opts.address || conn.remoteAddress);
|
||||||
|
headLines.push('X-Forwarded-For: ' + proxyChain.join(', '));
|
||||||
|
headLines.push('X-Forwarded-Host: ' + headers.host);
|
||||||
|
// Then convert all of the head lines back into a header buffer.
|
||||||
|
head = Buffer.from(headLines.join('\r\n'));
|
||||||
|
|
||||||
|
opts.firstChunk = Buffer.concat([head, body]);
|
||||||
|
|
||||||
|
var newConnOpts = separatePort(mod.address || '');
|
||||||
|
newConnOpts.port = newConnOpts.port || mod.port;
|
||||||
|
newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
|
||||||
|
newConnOpts.servername = separatePort(headers.host).host;
|
||||||
|
newConnOpts.data = opts.firstChunk;
|
||||||
|
|
||||||
|
newConnOpts.remoteFamily = opts.family || conn.remoteFamily;
|
||||||
|
newConnOpts.remoteAddress = opts.address || conn.remoteAddress;
|
||||||
|
newConnOpts.remotePort = opts.port || conn.remotePort;
|
||||||
|
|
||||||
|
deps.proxy(conn, newConnOpts, opts.firstChunk);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function respond404(req, res) {
|
function checkRedirect(mod, conn, opts, headers) {
|
||||||
res.writeHead(404);
|
if (!mod.fromRe || mod.fromRe.origSrc !== mod.from) {
|
||||||
res.end('Not Found');
|
// Escape any characters that (can) have special meaning in regular expression
|
||||||
}
|
// but that aren't the special characters we have interest in.
|
||||||
|
var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
|
||||||
function createProxyRoute(mod) {
|
// Then modify the characters we are interested in so they do what we want in
|
||||||
// This is the easiest way to override the createConnections function the proxy
|
// the regular expression after being compiled.
|
||||||
// module uses, but take note the since we don't have control over where this is
|
from = from.replace(/\*/g, '(.*)');
|
||||||
// called the extra options availabled will be different.
|
var fromRe = new RegExp('^' + from + '/?$');
|
||||||
var agent = new require('http').Agent({});
|
fromRe.origSrc = mod.from;
|
||||||
agent.createConnection = deps.net.createConnection;
|
// We don't want this property showing up in the actual config file or the API,
|
||||||
|
// so we define it this way so it's not enumberable.
|
||||||
var proxy = require('http-proxy').createProxyServer({
|
Object.defineProperty(mod, 'fromRe', {value: fromRe, configurable: true});
|
||||||
agent: agent
|
|
||||||
, target: 'http://' + mod.address
|
|
||||||
, xfwd: true
|
|
||||||
, 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
proxy.on('error', function (err, req, res) {
|
|
||||||
console.log(err);
|
|
||||||
res.statusCode = 502;
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.setHeader('Connection', 'close');
|
|
||||||
res.end(require('../proxy-err-resp').getRespBody(err, conf.debug));
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
web: function (req, res, next) {
|
|
||||||
if (moduleMatchesHost(req, mod)) {
|
|
||||||
proxy.web(req, res);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, ws: function (req, socket, head, next) {
|
|
||||||
if (moduleMatchesHost(req, mod)) {
|
|
||||||
proxy.ws(req, socket, head);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRedirectRoute(mod) {
|
|
||||||
// Escape any characters that (can) have special meaning in regular expression
|
|
||||||
// but that aren't the special characters we have interest in.
|
|
||||||
var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&');
|
|
||||||
// Then modify the characters we are interested in so they do what we want in
|
|
||||||
// the regular expression after being compiled.
|
|
||||||
from = from.replace(/\*/g, '(.*)');
|
|
||||||
var fromRe = new RegExp('^' + from + '/?$');
|
|
||||||
|
|
||||||
return function (req, res, next) {
|
|
||||||
if (!moduleMatchesHost(req, mod)) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var match = fromRe.exec(req.url);
|
|
||||||
if (!match) {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var to = mod.to;
|
|
||||||
match.slice(1).forEach(function (globMatch, index) {
|
|
||||||
to = to.replace(':'+(index+1), globMatch);
|
|
||||||
});
|
|
||||||
res.writeHead(mod.status || 301, { 'Location': to });
|
|
||||||
res.end();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStaticRoute(mod) {
|
|
||||||
var getStaticApp, staticApp;
|
|
||||||
if (/:hostname/.test(mod.root)) {
|
|
||||||
staticApp = {};
|
|
||||||
getStaticApp = function (hostname) {
|
|
||||||
if (!staticApp[hostname]) {
|
|
||||||
staticApp[hostname] = express.static(mod.root.replace(':hostname', hostname));
|
|
||||||
}
|
|
||||||
return staticApp[hostname];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
staticApp = express.static(mod.root);
|
|
||||||
getStaticApp = function () {
|
|
||||||
return staticApp;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (req, res, next) {
|
var match = mod.fromRe.exec(headers.url);
|
||||||
if (moduleMatchesHost(req, mod)) {
|
if (!match) {
|
||||||
getStaticApp(separatePort(req.headers.host).host)(req, res, next);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var to = mod.to;
|
||||||
|
match.slice(1).forEach(function (globMatch, index) {
|
||||||
|
to = to.replace(':'+(index+1), globMatch);
|
||||||
|
});
|
||||||
|
var status = mod.status || 301;
|
||||||
|
var code = require('http').STATUS_CODES[status] || 'Unknown';
|
||||||
|
|
||||||
|
conn.end([
|
||||||
|
'HTTP/1.1 ' + status + ' ' + code
|
||||||
|
, 'Date: ' + (new Date()).toUTCString()
|
||||||
|
, 'Location: ' + to
|
||||||
|
, 'Connection: close'
|
||||||
|
, 'Content-Length: 0'
|
||||||
|
, ''
|
||||||
|
, ''
|
||||||
|
].join('\r\n'));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var staticServer;
|
||||||
|
var staticHandlers = {};
|
||||||
|
function serveStatic(req, res) {
|
||||||
|
var rootDir = req.connection.rootDir;
|
||||||
|
|
||||||
|
if (!staticHandlers[rootDir]) {
|
||||||
|
staticHandlers[rootDir] = require('express').static(rootDir, { fallthrough: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
staticHandlers[rootDir](req, res, function (err) {
|
||||||
|
if (err) {
|
||||||
|
res.statusCode = err.statusCode;
|
||||||
} else {
|
} else {
|
||||||
next();
|
res.statusCode = 404;
|
||||||
}
|
}
|
||||||
};
|
res.setHeader('Content-Type', 'text/html');
|
||||||
|
|
||||||
|
if (res.statusCode === 404) {
|
||||||
|
res.end('File Not Found');
|
||||||
|
} else {
|
||||||
|
res.end(require('http').STATUS_CODES[res.statusCode]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function checkStatic(mod, conn, opts, headers) {
|
||||||
|
var rootDir = mod.root.replace(':hostname', separatePort(headers.host).host);
|
||||||
|
return statAsync(rootDir)
|
||||||
|
.then(function (stats) {
|
||||||
|
if (!stats || !stats.isDirectory()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!staticServer) {
|
||||||
|
staticServer = require('http').createServer(serveStatic);
|
||||||
|
}
|
||||||
|
conn.rootDir = rootDir;
|
||||||
|
return emitConnection(staticServer, conn, opts);
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
if (err.code !== 'ENOENT') {
|
||||||
|
console.warn('errored stating', rootDir, 'for serving static files', err);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(greenlockMiddleware);
|
var moduleChecks = {
|
||||||
app.use(redirectHttps);
|
proxy: checkProxy
|
||||||
app.use(handleAdmin);
|
, redirect: checkRedirect
|
||||||
|
, static: checkStatic
|
||||||
|
};
|
||||||
|
|
||||||
(conf.http.modules || []).forEach(function (mod) {
|
function handleConnection(conn) {
|
||||||
if (mod.name === 'proxy') {
|
var opts = conn.__opts;
|
||||||
var proxyRoute = createProxyRoute(mod);
|
parseHeaders(conn, opts)
|
||||||
proxyRoutes.push(proxyRoute);
|
.then(function (headers) {
|
||||||
app.use(proxyRoute.web);
|
if (checkACME(conn, opts, headers)) { return; }
|
||||||
}
|
if (checkHttps(conn, opts, headers)) { return; }
|
||||||
else if (mod.name === 'redirect') {
|
if (checkAdmin(conn, opts, headers)) { return; }
|
||||||
app.use(createRedirectRoute(mod));
|
|
||||||
}
|
|
||||||
else if (mod.name === 'static') {
|
|
||||||
app.use(createStaticRoute(mod));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.warn('unknown HTTP module', mod);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(respond404);
|
var prom = PromiseA.resolve(false);
|
||||||
|
(conf.http.domains || []).forEach(function (dom) {
|
||||||
|
prom = prom.then(function (handled) {
|
||||||
|
if (handled) {
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
if (!hostMatchesDomains(headers, dom.names)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var server = require('http').createServer(function (req, res) {
|
return dom.modules.some(function (mod) {
|
||||||
app(req, res)
|
if (moduleChecks[mod.name]) {
|
||||||
});
|
return moduleChecks[mod.name](mod, conn, opts, headers);
|
||||||
|
}
|
||||||
|
console.warn('unknown HTTP module under domains', dom.names.join(','), mod);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
(conf.http.modules || []).forEach(function (mod) {
|
||||||
|
prom = prom.then(function (handled) {
|
||||||
|
if (handled) {
|
||||||
|
return handled;
|
||||||
|
}
|
||||||
|
if (!hostMatchesDomains(headers, mod.domains)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
server.on('upgrade', function (req, socket, head) {
|
if (moduleChecks[mod.name]) {
|
||||||
if (!proxyRoutes.length) {
|
return moduleChecks[mod.name](mod, conn, opts, headers);
|
||||||
socket.end();
|
}
|
||||||
}
|
console.warn('unknown HTTP module found', mod);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
var prs = proxyRoutes.slice();
|
prom.then(function (handled) {
|
||||||
function proxyWs() {
|
if (!handled) {
|
||||||
var proxyRoute = prs.shift();
|
conn.end([
|
||||||
if (!proxyRoute) {
|
'HTTP/1.1 404 Not Found'
|
||||||
socket.end();
|
, 'Date: ' + (new Date()).toUTCString()
|
||||||
return;
|
, 'Content-Type: text/html'
|
||||||
|
, 'Content-Length: 9'
|
||||||
|
, 'Connection: close'
|
||||||
|
, ''
|
||||||
|
, 'Not Found'
|
||||||
|
].join('\r\n'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emit: function (type, value) {
|
||||||
|
if (type === 'connection') {
|
||||||
|
handleConnection(value);
|
||||||
}
|
}
|
||||||
proxyRoute.ws(req, socket, head, proxyWs);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
proxyWs();
|
|
||||||
});
|
|
||||||
|
|
||||||
return server;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,52 +9,81 @@ module.exports.create = function (deps, config, netHandler) {
|
||||||
|
|
||||||
function extractSocketProp(socket, propName) {
|
function extractSocketProp(socket, propName) {
|
||||||
// remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854
|
// remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854
|
||||||
return socket[propName]
|
var value = socket[propName] || socket['_' + propName];
|
||||||
|| socket['_' + propName]
|
try {
|
||||||
|| socket._handle._parent.owner.stream[propName]
|
value = value || socket._handle._parent.owner.stream[propName];
|
||||||
;
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
value = value || socket._handle._parentWrap[propName];
|
||||||
|
value = value || socket._handle._parentWrap._handle.owner.stream[propName];
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return value || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nameMatchesDomains(name, domains) {
|
||||||
|
return domains.some(function (pattern) {
|
||||||
|
return domainMatches(pattern, name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressNames = [
|
||||||
|
'remoteAddress'
|
||||||
|
, 'remotePort'
|
||||||
|
, 'remoteFamily'
|
||||||
|
, 'localAddress'
|
||||||
|
, 'localPort'
|
||||||
|
];
|
||||||
function wrapSocket(socket, opts) {
|
function wrapSocket(socket, opts) {
|
||||||
var myDuplex = require('tunnel-packer').Stream.create(socket);
|
var reader = require('socket-pair').create(function (err, writer) {
|
||||||
myDuplex.remoteFamily = opts.remoteFamily || myDuplex.remoteFamily;
|
if (err) {
|
||||||
myDuplex.remoteAddress = opts.remoteAddress || myDuplex.remoteAddress;
|
reader.emit('error', err);
|
||||||
myDuplex.remotePort = opts.remotePort || myDuplex.remotePort;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
socket.on('data', function (chunk) {
|
process.nextTick(function () {
|
||||||
console.log('[' + Date.now() + '] tls socket data', chunk.byteLength);
|
socket.unshift(opts.firstChunk);
|
||||||
myDuplex.push(chunk);
|
});
|
||||||
});
|
|
||||||
socket.on('error', function (err) {
|
socket.pipe(writer);
|
||||||
console.error('[error] httpsTunnel (Admin) TODO close');
|
writer.pipe(socket);
|
||||||
console.error(err);
|
|
||||||
myDuplex.emit('error', err);
|
socket.on('error', function (err) {
|
||||||
});
|
console.log('wrapped TLS socket error', err);
|
||||||
socket.on('close', function () {
|
reader.emit('error', err);
|
||||||
myDuplex.end();
|
});
|
||||||
|
writer.on('error', function (err) {
|
||||||
|
console.error('socket-pair writer error', err);
|
||||||
|
// If the writer had an error the reader probably did too, and I don't think we'll
|
||||||
|
// get much out of emitting this on the original socket, so logging is enough.
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.nextTick(function () {
|
// We can't set these properties the normal way because there is a getter without a setter,
|
||||||
// this must happen after the socket is emitted to the next in the chain,
|
// but we can use defineProperty. We reuse the descriptor even though we will be manipulating
|
||||||
// but before any more data comes in via the network
|
// it because we will only ever set the value and we set it every time.
|
||||||
socket.unshift(opts.firstChunk);
|
var descriptor = {enumerable: true, configurable: true, writable: true};
|
||||||
|
addressNames.forEach(function (name) {
|
||||||
|
descriptor.value = opts[name] || extractSocketProp(socket, name);
|
||||||
|
Object.defineProperty(reader, name, descriptor);
|
||||||
});
|
});
|
||||||
|
|
||||||
return myDuplex;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
var le = greenlock.create({
|
var le = greenlock.create({
|
||||||
// server: 'staging'
|
|
||||||
server: 'https://acme-v01.api.letsencrypt.org/directory'
|
server: 'https://acme-v01.api.letsencrypt.org/directory'
|
||||||
|
|
||||||
, challenges: {
|
, challenges: {
|
||||||
'http-01': require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges', debug: config.debug })
|
'http-01': require('le-challenge-fs').create({ debug: config.debug })
|
||||||
, 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug })
|
, 'tls-sni-01': require('le-challenge-sni').create({ debug: config.debug })
|
||||||
// TODO dns-01
|
// TODO dns-01
|
||||||
//, 'dns-01': require('le-challenge-ddns').create()
|
//, 'dns-01': require('le-challenge-ddns').create({ debug: config.debug })
|
||||||
}
|
}
|
||||||
|
, challengeType: 'http-01'
|
||||||
|
|
||||||
, store: require('le-store-certbot').create({ webrootPath: '/tmp/acme-challenges' })
|
, store: require('le-store-certbot').create({ debug: config.debug })
|
||||||
|
|
||||||
, approveDomains: function (opts, certs, cb) {
|
, approveDomains: function (opts, certs, cb) {
|
||||||
// This is where you check your database and associated
|
// This is where you check your database and associated
|
||||||
|
@ -65,60 +94,87 @@ module.exports.create = function (deps, config, netHandler) {
|
||||||
if (certs) {
|
if (certs) {
|
||||||
// TODO make sure the same options are used for renewal as for registration?
|
// TODO make sure the same options are used for renewal as for registration?
|
||||||
opts.domains = certs.altnames;
|
opts.domains = certs.altnames;
|
||||||
|
|
||||||
cb(null, { options: opts, certs: certs });
|
cb(null, { options: opts, certs: certs });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function complete(optsOverride) {
|
function complete(optsOverride, domains) {
|
||||||
Object.keys(optsOverride).forEach(function (key) {
|
if (!cb) {
|
||||||
opts[key] = optsOverride[key];
|
console.warn('tried to complete domain approval multiple times');
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't request certificates for wildcard domains, so filter any of those
|
||||||
|
// out of this list and put the domain that triggered this in the list if needed.
|
||||||
|
domains = (domains || []).filter(function (dom) { return dom[0] !== '*'; });
|
||||||
|
if (domains.indexOf(opts.domain) < 0) {
|
||||||
|
domains.push(opts.domain);
|
||||||
|
}
|
||||||
|
// TODO: allow user to specify options for challenges or storage.
|
||||||
|
|
||||||
|
Object.assign(opts, optsOverride, { domains: domains, agreeTos: true });
|
||||||
cb(null, { options: opts, certs: certs });
|
cb(null, { options: opts, certs: certs });
|
||||||
|
cb = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var handled = false;
|
||||||
|
if (Array.isArray(config.tls.domains)) {
|
||||||
|
handled = config.tls.domains.some(function (dom) {
|
||||||
|
if (!nameMatchesDomains(opts.domain, dom.names)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// check config for domain name
|
return dom.modules.some(function (mod) {
|
||||||
if (-1 !== (config.tls.servernames || []).indexOf(opts.domain)) {
|
if (mod.name !== 'acme') {
|
||||||
// TODO how to handle SANs?
|
return false;
|
||||||
// TODO fetch domain-specific email
|
}
|
||||||
// TODO fetch domain-specific acmeDirectory
|
complete(mod, dom.names);
|
||||||
// NOTE: you can also change other options such as `challengeType` and `challenge`
|
return true;
|
||||||
// opts.challengeType = 'http-01';
|
});
|
||||||
// opts.challenge = require('le-challenge-fs').create({}); // TODO this doesn't actually work yet
|
});
|
||||||
complete({
|
}
|
||||||
|
if (handled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(config.tls.modules)) {
|
||||||
|
handled = config.tls.modules.some(function (mod) {
|
||||||
|
if (mod.name !== 'acme') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!nameMatchesDomains(opts.domain, mod.domains)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
complete(mod, mod.domains);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (handled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var defAcmeConf;
|
||||||
|
if (config.tls.acme) {
|
||||||
|
defAcmeConf = config.tls.acme;
|
||||||
|
} else {
|
||||||
|
defAcmeConf = {
|
||||||
email: config.tls.email
|
email: config.tls.email
|
||||||
, agreeTos: true
|
|
||||||
, server: config.tls.acmeDirectoryUrl || le.server
|
, server: config.tls.acmeDirectoryUrl || le.server
|
||||||
, challengeType: config.tls.challengeType || 'http-01'
|
, challengeType: config.tls.challengeType || le.challengeType
|
||||||
});
|
, approvedDomains: config.tls.servernames
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check config for domain name
|
||||||
|
// TODO: if `approvedDomains` isn't defined check all other modules to see if they can
|
||||||
|
// handle this domain (and what other domains it's grouped with).
|
||||||
|
if (-1 !== (defAcmeConf.approvedDomains || []).indexOf(opts.domain)) {
|
||||||
|
complete(defAcmeConf, defAcmeConf.approvedDomains);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO ask http module (and potentially all other modules) about what domains it can
|
|
||||||
// handle. We can allow any domains that other modules will handle after we terminate TLS.
|
|
||||||
cb(new Error('domain is not allowed'));
|
cb(new Error('domain is not allowed'));
|
||||||
// if (!modules.http) {
|
|
||||||
// modules.http = require('./modules/http.js').create(deps, config);
|
|
||||||
// }
|
|
||||||
// modules.http.checkServername(opts.domain).then(function (stuff) {
|
|
||||||
// if (!stuff || !stuff.domains) {
|
|
||||||
// // TODO once precheck is implemented we can just let it pass if it passes, yknow?
|
|
||||||
// cb(new Error('domain is not allowed'));
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// complete({
|
|
||||||
// domain: stuff.domain || stuff.domains[0]
|
|
||||||
// , domains: stuff.domains
|
|
||||||
// , email: stuff.email || program.email
|
|
||||||
// , server: stuff.acmeDirectoryUrl || program.acmeDirectoryUrl
|
|
||||||
// , challengeType: stuff.challengeType || program.challengeType
|
|
||||||
// , challenge: stuff.challenge
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }, cb);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
le.tlsOptions = le.tlsOptions || le.httpsOptions;
|
le.tlsOptions = le.tlsOptions || le.httpsOptions;
|
||||||
|
@ -163,49 +219,27 @@ module.exports.create = function (deps, config, netHandler) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function proxy(socket, opts, mod) {
|
function proxy(socket, opts, mod) {
|
||||||
var destination = mod.address.split(':');
|
var newConnOpts = require('../domain-utils').separatePort(mod.address || '');
|
||||||
var connected = false;
|
newConnOpts.port = newConnOpts.port || mod.port;
|
||||||
|
newConnOpts.host = newConnOpts.host || mod.host || 'localhost';
|
||||||
|
newConnOpts.servername = opts.servername;
|
||||||
|
newConnOpts.data = opts.firstChunk;
|
||||||
|
|
||||||
var newConn = deps.net.createConnection({
|
newConnOpts.remoteFamily = opts.family || extractSocketProp(socket, 'remoteFamily');
|
||||||
port: destination[1]
|
newConnOpts.remoteAddress = opts.address || extractSocketProp(socket, 'remoteAddress');
|
||||||
, host: destination[0] || '127.0.0.1'
|
newConnOpts.remotePort = opts.port || extractSocketProp(socket, 'remotePort');
|
||||||
|
|
||||||
, servername: opts.servername
|
deps.proxy(socket, newConnOpts, opts.firstChunk, function () {
|
||||||
, data: opts.firstChunk
|
// This function is called in the event of a connection error and should decrypt
|
||||||
, remoteFamily: opts.family || extractSocketProp(socket, 'remoteFamily')
|
// the socket so the proxy module can send a 502 HTTP response.
|
||||||
, remoteAddress: opts.address || extractSocketProp(socket, 'remoteAddress')
|
var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
|
||||||
, remotePort: opts.port || extractSocketProp(socket, 'remotePort')
|
if (opts.hyperPeek) {
|
||||||
}, function () {
|
return new tls.TLSSocket(socket, tlsOpts);
|
||||||
connected = true;
|
|
||||||
if (!opts.hyperPeek) {
|
|
||||||
newConn.write(opts.firstChunk);
|
|
||||||
}
|
|
||||||
newConn.pipe(socket);
|
|
||||||
socket.pipe(newConn);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Not sure how to effectively report this to the user or client, but we need to listen
|
|
||||||
// for the event to prevent it from crashing us.
|
|
||||||
newConn.on('error', function (err) {
|
|
||||||
if (connected) {
|
|
||||||
console.error('TLS proxy remote error', err);
|
|
||||||
socket.end();
|
|
||||||
} else {
|
} else {
|
||||||
console.log('TLS proxy connection error', err);
|
return new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
|
||||||
var tlsOpts = localhostCerts.mergeTlsOptions('localhost.daplie.me', {isServer: true});
|
|
||||||
var decrypted;
|
|
||||||
if (opts.hyperPeek) {
|
|
||||||
decrypted = new tls.TLSSocket(socket, tlsOpts);
|
|
||||||
} else {
|
|
||||||
decrypted = new tls.TLSSocket(wrapSocket(socket, opts), tlsOpts);
|
|
||||||
}
|
|
||||||
require('../proxy-err-resp').sendBadGateway(decrypted, err, config.debug);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
socket.on('error', function (err) {
|
return true;
|
||||||
console.error('TLS proxy client error', err);
|
|
||||||
newConn.end();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function terminate(socket, opts) {
|
function terminate(socket, opts) {
|
||||||
|
@ -246,30 +280,39 @@ module.exports.create = function (deps, config, netHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var handled = (config.tls.modules || []).some(function (mod) {
|
function checkModule(mod) {
|
||||||
var relevant = mod.domains.some(function (pattern) {
|
|
||||||
return domainMatches(pattern, opts.servername);
|
|
||||||
});
|
|
||||||
if (!relevant) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mod.name === 'proxy') {
|
if (mod.name === 'proxy') {
|
||||||
proxy(socket, opts, mod);
|
return proxy(socket, opts, mod);
|
||||||
}
|
}
|
||||||
else {
|
if (mod.name !== 'acme') {
|
||||||
console.error('saw unknown TLS module', mod);
|
console.error('saw unknown TLS module', mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var handled = (config.tls.domains || []).some(function (dom) {
|
||||||
|
if (!nameMatchesDomains(opts.servername, dom.names)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return dom.modules.some(checkModule);
|
||||||
});
|
});
|
||||||
|
if (handled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handled = (config.tls.modules || []).some(function (mod) {
|
||||||
|
if (!nameMatchesDomains(opts.servername, mod.domains)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return checkModule(mod);
|
||||||
|
});
|
||||||
|
if (handled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: figure out all of the domains that the other modules intend to handle, and only
|
// TODO: figure out all of the domains that the other modules intend to handle, and only
|
||||||
// terminate those ones, closing connections for all others.
|
// terminate those ones, closing connections for all others.
|
||||||
if (!handled) {
|
terminate(socket, opts);
|
||||||
terminate(socket, opts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function getRespBody(err, debug) {
|
||||||
|
if (debug) {
|
||||||
|
return err.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'ECONNREFUSED') {
|
||||||
|
return 'The connection was refused. Most likely the service being connected to '
|
||||||
|
+ 'has stopped running or the configuration is wrong.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Bad Gateway: ' + err.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBadGateway(conn, err, debug) {
|
||||||
|
var body = getRespBody(err, debug);
|
||||||
|
|
||||||
|
conn.write([
|
||||||
|
'HTTP/1.1 502 Bad Gateway'
|
||||||
|
, 'Date: ' + (new Date()).toUTCString()
|
||||||
|
, 'Connection: close'
|
||||||
|
, 'Content-Type: text/html'
|
||||||
|
, 'Content-Length: ' + body.length
|
||||||
|
, ''
|
||||||
|
, body
|
||||||
|
].join('\r\n'));
|
||||||
|
conn.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.getRespBody = getRespBody;
|
||||||
|
module.exports.sendBadGateway = sendBadGateway;
|
||||||
|
|
||||||
|
module.exports.create = function (deps, config) {
|
||||||
|
return function proxy(conn, newConnOpts, firstChunk, decrypt) {
|
||||||
|
var connected = false;
|
||||||
|
var newConn = deps.net.createConnection(newConnOpts, function () {
|
||||||
|
connected = true;
|
||||||
|
|
||||||
|
if (firstChunk) {
|
||||||
|
newConn.write(firstChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
newConn.pipe(conn);
|
||||||
|
conn.pipe(newConn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listening for this largely to prevent uncaught exceptions.
|
||||||
|
conn.on('error', function (err) {
|
||||||
|
console.log('proxy client error', err);
|
||||||
|
});
|
||||||
|
newConn.on('error', function (err) {
|
||||||
|
if (connected) {
|
||||||
|
// Not sure how to report this to a user or a client. We can assume that some data
|
||||||
|
// has already been exchanged, so we can't really be sure what we can send in addition
|
||||||
|
// that wouldn't result in a parse error.
|
||||||
|
console.log('proxy remote error', err);
|
||||||
|
} else {
|
||||||
|
console.log('proxy connection error', err);
|
||||||
|
if (decrypt) {
|
||||||
|
sendBadGateway(decrypt(conn), err, config.debug);
|
||||||
|
} else {
|
||||||
|
sendBadGateway(conn, err, config.debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make sure that once one side closes, no I/O activity will happen on the other side.
|
||||||
|
conn.on('close', function () {
|
||||||
|
newConn.destroy();
|
||||||
|
});
|
||||||
|
newConn.on('close', function () {
|
||||||
|
conn.destroy();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,32 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
function getRespBody(err, debug) {
|
|
||||||
if (debug) {
|
|
||||||
return err.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.code === 'ECONNREFUSED') {
|
|
||||||
return 'The connection was refused. Most likely the service being connected to '
|
|
||||||
+ 'has stopped running or the configuration is wrong.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Bad Gateway: ' + err.code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendBadGateway(conn, err, debug) {
|
|
||||||
var body = getRespBody(err, debug);
|
|
||||||
|
|
||||||
conn.write([
|
|
||||||
'HTTP/1.1 502 Bad Gateway'
|
|
||||||
, 'Date: ' + (new Date()).toUTCString()
|
|
||||||
, 'Connection: close'
|
|
||||||
, 'Content-Type: text/html'
|
|
||||||
, 'Content-Length: ' + body.length
|
|
||||||
, ''
|
|
||||||
, body
|
|
||||||
].join('\r\n'));
|
|
||||||
conn.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.getRespBody = getRespBody;
|
|
||||||
module.exports.sendBadGateway = sendBadGateway;
|
|
144
lib/tunnel.js
144
lib/tunnel.js
|
@ -1,144 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
module.exports.create = function (opts, servers) {
|
|
||||||
// servers = { plainserver, server }
|
|
||||||
var Oauth3 = require('oauth3-cli');
|
|
||||||
var Tunnel = require('daplie-tunnel').create({
|
|
||||||
Oauth3: Oauth3
|
|
||||||
, PromiseA: opts.PromiseA
|
|
||||||
, CLI: {
|
|
||||||
init: function (rs, ws/*, state, options*/) {
|
|
||||||
// noop
|
|
||||||
return ws;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).Tunnel;
|
|
||||||
var stunnel = require('stunnel');
|
|
||||||
var killcount = 0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
var Dup = {
|
|
||||||
write: function (chunk, encoding, cb) {
|
|
||||||
this.__my_socket.push(chunk, encoding);
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
, read: function (size) {
|
|
||||||
var x = this.__my_socket.read(size);
|
|
||||||
if (x) { this.push(x); }
|
|
||||||
}
|
|
||||||
, setTimeout: function () {
|
|
||||||
console.log('TODO implement setTimeout on Duplex');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var httpServer = require('http').createServer(function (req, res) {
|
|
||||||
console.log('req.socket.encrypted', req.socket.encrypted);
|
|
||||||
res.end('Hello, tunneled World!');
|
|
||||||
});
|
|
||||||
|
|
||||||
var tlsServer = require('tls').createServer(opts.httpsOptions, function (tlsSocket) {
|
|
||||||
console.log('tls connection');
|
|
||||||
// things get a little messed up here
|
|
||||||
httpServer.emit('connection', tlsSocket);
|
|
||||||
|
|
||||||
// try again
|
|
||||||
//servers.server.emit('connection', tlsSocket);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
process.on('SIGINT', function () {
|
|
||||||
killcount += 1;
|
|
||||||
console.log('[quit] closing http and https servers');
|
|
||||||
if (killcount >= 3) {
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
if (servers.server) {
|
|
||||||
servers.server.close();
|
|
||||||
}
|
|
||||||
if (servers.insecureServer) {
|
|
||||||
servers.insecureServer.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return Tunnel.token({
|
|
||||||
refreshToken: opts.refreshToken
|
|
||||||
, email: opts.email
|
|
||||||
, domains: opts.sites.map(function (site) {
|
|
||||||
return site.name;
|
|
||||||
})
|
|
||||||
, device: { hostname: opts.devicename || opts.device }
|
|
||||||
}).then(function (result) {
|
|
||||||
// { jwt, tunnelUrl }
|
|
||||||
var locals = [];
|
|
||||||
opts.sites.map(function (site) {
|
|
||||||
locals.push({
|
|
||||||
protocol: 'https'
|
|
||||||
, hostname: site.name
|
|
||||||
, port: opts.port
|
|
||||||
});
|
|
||||||
locals.push({
|
|
||||||
protocol: 'http'
|
|
||||||
, hostname: site.name
|
|
||||||
, port: opts.insecurePort || opts.port
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return stunnel.connect({
|
|
||||||
token: result.jwt
|
|
||||||
, stunneld: result.tunnelUrl
|
|
||||||
// XXX TODO BUG // this is just for testing
|
|
||||||
, insecure: /*opts.insecure*/ true
|
|
||||||
, locals: locals
|
|
||||||
// a simple passthru is proving to not be so simple
|
|
||||||
, net: require('net') /*
|
|
||||||
{
|
|
||||||
createConnection: function (info, cb) {
|
|
||||||
// data is the hello packet / first chunk
|
|
||||||
// info = { data, servername, port, host, remoteAddress: { family, address, port } }
|
|
||||||
|
|
||||||
var myDuplex = new (require('stream').Duplex)();
|
|
||||||
var myDuplex2 = new (require('stream').Duplex)();
|
|
||||||
// duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
|
|
||||||
|
|
||||||
myDuplex2.__my_socket = myDuplex;
|
|
||||||
myDuplex.__my_socket = myDuplex2;
|
|
||||||
|
|
||||||
myDuplex2._write = Dup.write;
|
|
||||||
myDuplex2._read = Dup.read;
|
|
||||||
|
|
||||||
myDuplex._write = Dup.write;
|
|
||||||
myDuplex._read = Dup.read;
|
|
||||||
|
|
||||||
myDuplex.remoteFamily = info.remoteFamily;
|
|
||||||
myDuplex.remoteAddress = info.remoteAddress;
|
|
||||||
myDuplex.remotePort = info.remotePort;
|
|
||||||
|
|
||||||
// socket.local{Family,Address,Port}
|
|
||||||
myDuplex.localFamily = 'IPv4';
|
|
||||||
myDuplex.localAddress = '127.0.01';
|
|
||||||
myDuplex.localPort = info.port;
|
|
||||||
|
|
||||||
myDuplex.setTimeout = Dup.setTimeout;
|
|
||||||
|
|
||||||
// this doesn't seem to work so well
|
|
||||||
//servers.server.emit('connection', myDuplex);
|
|
||||||
|
|
||||||
// try a little more manual wrapping / unwrapping
|
|
||||||
var firstByte = info.data[0];
|
|
||||||
if (firstByte < 32 || firstByte >= 127) {
|
|
||||||
tlsServer.emit('connection', myDuplex);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
httpServer.emit('connection', myDuplex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
process.nextTick(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return myDuplex2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//*/
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -9,5 +9,7 @@ process.on('message', function (conf) {
|
||||||
// HTTP proxying connection creation is not something we currently control.
|
// HTTP proxying connection creation is not something we currently control.
|
||||||
, net: require('net')
|
, net: require('net')
|
||||||
};
|
};
|
||||||
|
deps.proxy = require('./proxy-conn').create(deps, conf);
|
||||||
|
|
||||||
require('./goldilocks.js').create(deps, conf);
|
require('./goldilocks.js').create(deps, conf);
|
||||||
});
|
});
|
||||||
|
|
13
package.json
13
package.json
|
@ -41,30 +41,27 @@
|
||||||
"bluebird": "^3.4.6",
|
"bluebird": "^3.4.6",
|
||||||
"body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
|
"body-parser": "git+https://github.com/expressjs/body-parser.git#1.16.1",
|
||||||
"commander": "^2.9.0",
|
"commander": "^2.9.0",
|
||||||
"daplie-tunnel": "git+https://git.daplie.com/Daplie/daplie-cli-tunnel.git#master",
|
"dns-suite": "git+https://git@git.daplie.com:Daplie/dns-suite#v1",
|
||||||
"ddns-cli": "git+https://git.daplie.com/Daplie/node-ddns-client.git#master",
|
|
||||||
"express": "git+https://github.com/expressjs/express.git#4.x",
|
"express": "git+https://github.com/expressjs/express.git#4.x",
|
||||||
"finalhandler": "^0.4.0",
|
"finalhandler": "^0.4.0",
|
||||||
"greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
|
"greenlock": "git+https://git.daplie.com/Daplie/node-greenlock.git#master",
|
||||||
"greenlock-express": "git+https://git.daplie.com/Daplie/greenlock-express.git#master",
|
|
||||||
"http-proxy": "^1.16.2",
|
|
||||||
"httpolyglot": "^0.1.1",
|
|
||||||
"ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
|
"ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
|
||||||
"ipify": "^1.1.0",
|
"ipify": "^1.1.0",
|
||||||
"js-yaml": "^3.8.3",
|
"js-yaml": "^3.8.3",
|
||||||
|
"jsonwebtoken": "^7.4.0",
|
||||||
"le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
|
"le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
|
||||||
"le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
|
"le-challenge-fs": "git+https://git.daplie.com/Daplie/le-challenge-webroot.git#master",
|
||||||
"le-challenge-sni": "^2.0.1",
|
"le-challenge-sni": "^2.0.1",
|
||||||
"livereload": "^0.6.0",
|
"le-store-certbot": "git+https://git.daplie.com/Daplie/le-store-certbot.git#master",
|
||||||
"localhost.daplie.me-certificates": "^1.3.0",
|
"localhost.daplie.me-certificates": "^1.3.0",
|
||||||
"minimist": "^1.1.1",
|
|
||||||
"oauth3-cli": "git+https://git.daplie.com/OAuth3/oauth3-cli.git#master",
|
|
||||||
"recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
|
"recase": "git+https://git.daplie.com/coolaj86/recase-js.git#v1.0.4",
|
||||||
"redirect-https": "^1.1.0",
|
"redirect-https": "^1.1.0",
|
||||||
|
"request": "^2.81.0",
|
||||||
"scmp": "git+https://github.com/freewil/scmp.git#1.x",
|
"scmp": "git+https://github.com/freewil/scmp.git#1.x",
|
||||||
"serve-index": "^1.7.0",
|
"serve-index": "^1.7.0",
|
||||||
"serve-static": "^1.10.0",
|
"serve-static": "^1.10.0",
|
||||||
"server-destroy": "^1.0.1",
|
"server-destroy": "^1.0.1",
|
||||||
|
"sni": "^1.0.0",
|
||||||
"socket-pair": "^1.0.0",
|
"socket-pair": "^1.0.0",
|
||||||
"stream-pair": "^1.0.3",
|
"stream-pair": "^1.0.3",
|
||||||
"stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1"
|
"stunnel": "git+https://git.daplie.com/Daplie/node-tunnel-client.git#v1"
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var https = require('httpolyglot');
|
|
||||||
var httpsOptions = require('localhost.daplie.me-certificates').merge({});
|
|
||||||
var httpsPort = 8443;
|
|
||||||
var redirectApp = require('redirect-https')({
|
|
||||||
port: httpsPort
|
|
||||||
});
|
|
||||||
|
|
||||||
var server = https.createServer(httpsOptions);
|
|
||||||
|
|
||||||
server.on('request', function (req, res) {
|
|
||||||
if (!req.socket.encrypted) {
|
|
||||||
redirectApp(req, res);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.end("Hello, Encrypted World!");
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(httpsPort, function () {
|
|
||||||
console.log('https://' + 'localhost.daplie.me' + (443 === httpsPort ? ':' : ':' + httpsPort));
|
|
||||||
});
|
|
Loading…
Reference in New Issue