Merge branch 'master' of bitbucket.org:coolaj86/walnut
This commit is contained in:
commit
c1b550f219
|
@ -14,6 +14,7 @@ cli.parse({
|
||||||
, insecure: [ false, '(deprecated) allow insecure non-https connections', 'boolean' ]
|
, insecure: [ false, '(deprecated) allow insecure non-https connections', 'boolean' ]
|
||||||
, cacert: [ false, '(not implemented) specify a CA for "self-signed" https certificates', 'string' ]
|
, cacert: [ false, '(not implemented) specify a CA for "self-signed" https certificates', 'string' ]
|
||||||
, answer: [ 'a', 'The answer', 'string' ]
|
, answer: [ 'a', 'The answer', 'string' ]
|
||||||
|
, token: [ false, 'Token (TODO or filepath to token)', 'string' ]
|
||||||
});
|
});
|
||||||
|
|
||||||
cli.main(function (args, options) {
|
cli.main(function (args, options) {
|
||||||
|
@ -22,8 +23,9 @@ cli.main(function (args, options) {
|
||||||
options.answer = options.answer || args[1]
|
options.answer = options.answer || args[1]
|
||||||
|
|
||||||
if (options.insecure) {
|
if (options.insecure) {
|
||||||
console.error('--insecure is not supported. You must use secure connections.');
|
//console.error('--insecure is not supported. You must use secure connections.');
|
||||||
return;
|
//return;
|
||||||
|
options.cacert = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.hostname) {
|
if (!options.hostname) {
|
||||||
|
@ -41,20 +43,26 @@ cli.main(function (args, options) {
|
||||||
updater: options.service
|
updater: options.service
|
||||||
, port: options.port
|
, port: options.port
|
||||||
, cacert: options.cacert
|
, cacert: options.cacert
|
||||||
|
, token: options.token
|
||||||
, ddns: [
|
, ddns: [
|
||||||
{ "name": options.hostname
|
{ "name": options.hostname
|
||||||
, "value": options.answer
|
, "value": options.answer
|
||||||
, "type": options.type
|
, "type": options.type
|
||||||
, "priority": options.priority
|
, "priority": options.priority
|
||||||
|
, "token": options.token
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}).then(function (data) {
|
}).then(function (data) {
|
||||||
if ('string') {
|
if ('string') {
|
||||||
data = JSON.parse(data);
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch(e) {
|
||||||
|
console.error(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(JSON.stringify(data, null, ' '));
|
console.log(JSON.stringify(data, null, ' '));
|
||||||
console.log('Test with');
|
console.log('Test with');
|
||||||
console.log('dig ' + options.hostname + ' ' + options.type);
|
console.log('dig ' + options.hostname + ' ' + options.type);
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,38 +1,52 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// dig -p 53 @redirect-www.org pi.nadal.daplie.com A
|
// TODO have a quick timeout
|
||||||
var updateIp = require('./holepunch/helpers/update-ip.js').update;
|
require('ipify')(function (err, ip) {
|
||||||
|
console.log('ip', ip);
|
||||||
|
|
||||||
var redirects = require('./redirects');
|
var path = require('path');
|
||||||
var ddns = [];
|
// dig -p 53 @redirect-www.org pi.nadal.daplie.com A
|
||||||
var ddnsMap = {};
|
var updateIp = require('./holepunch/helpers/update-ip.js').update;
|
||||||
|
|
||||||
function add(hostname) {
|
var redirects = require('./redirects');
|
||||||
ddns.push({
|
var ddns = [];
|
||||||
"name": hostname
|
var ddnsMap = {};
|
||||||
|
|
||||||
|
function add(hostname) {
|
||||||
|
ddns.push({
|
||||||
|
"name": hostname
|
||||||
|
, "answer": ip
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
redirects.forEach(function (r) {
|
||||||
|
if (!ddnsMap[r.from.hostname.toLowerCase()]) {
|
||||||
|
add(r.from.hostname);
|
||||||
|
}
|
||||||
|
if (!ddnsMap[r.to.hostname.toLowerCase()]) {
|
||||||
|
add(r.to.hostname);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
redirects.forEach(function (r) {
|
|
||||||
if (!ddnsMap[r.from.hostname.toLowerCase()]) {
|
|
||||||
add(r.from.hostname);
|
|
||||||
}
|
|
||||||
if (!ddnsMap[r.to.hostname.toLowerCase()]) {
|
|
||||||
add(r.to.hostname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return updateIp({
|
return updateIp({
|
||||||
updater: 'redirect-www.org'
|
updater: 'ns1.redirect-www.org'
|
||||||
, port: 65443
|
, port: 65443
|
||||||
, cacert: null
|
, cacert: path.join(__dirname, 'certs/ca/ns1-test.root.crt.pem')
|
||||||
, ddns: ddns
|
, ddns: ddns
|
||||||
}).then(function (data) {
|
, token: require('./dyndns-token').token
|
||||||
if ('string') {
|
}).then(function (data) {
|
||||||
data = JSON.parse(data);
|
if ('string' === typeof data) {
|
||||||
}
|
try {
|
||||||
|
data = JSON.parse(data);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('[ERROR] bad json response');
|
||||||
|
console.error(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
console.log(JSON.stringify(data, null, ' '));
|
console.log(JSON.stringify(data, null, ' '));
|
||||||
console.log('Test with');
|
console.log('Test with');
|
||||||
console.log('dig <<hostname>> A');
|
console.log('dig <<hostname>> A');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
module.exports = { token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" };
|
|
@ -1,14 +1,13 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var PromiseA = require('bluebird').Promise
|
var PromiseA = require('bluebird').Promise;
|
||||||
, updateIp = require('./helpers/update-ip.js').update
|
var updateIp = require('./helpers/update-ip.js').update;
|
||||||
, request = PromiseA.promisifyAll(require('request'))
|
var request = PromiseA.promisifyAll(require('request'));
|
||||||
, requestAsync = PromiseA.promisify(require('request'))
|
var requestAsync = PromiseA.promisify(require('request'));
|
||||||
, upnpForward = require('./helpers/upnp-forward').upnpForward
|
var upnpForward = require('./helpers/upnp-forward').upnpForward;
|
||||||
, pmpForward = require('./helpers/pmp-forward').pmpForward
|
var pmpForward = require('./helpers/pmp-forward').pmpForward;
|
||||||
, loopbackHttps = require('./loopback-https')
|
var loopbackHttps = require('./loopback-https');
|
||||||
//, checkip = require('check-ip-address')
|
//var checkip = require('check-ip-address');
|
||||||
;
|
|
||||||
|
|
||||||
function openPort(ip, port) {
|
function openPort(ip, port) {
|
||||||
if (!/tcp|https|http/.test(port.protocol || 'tcp')) {
|
if (!/tcp|https|http/.test(port.protocol || 'tcp')) {
|
||||||
|
@ -52,7 +51,7 @@ function beacon(hostnames, ports) {
|
||||||
|
|
||||||
console.log("Updated DynDNS");
|
console.log("Updated DynDNS");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
ports.forEach(function (port) {
|
ports.forEach(function (port) {
|
||||||
promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port));
|
promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port));
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var PromiseA = require('bluebird').Promise
|
var PromiseA = require('bluebird').Promise;
|
||||||
, natpmp = require('nat-pmp')
|
var natpmp = require('nat-pmp');
|
||||||
, exec = require('child_process').exec
|
var exec = require('child_process').exec;
|
||||||
;
|
|
||||||
|
|
||||||
exports.pmpForward = function (port) {
|
exports.pmpForward = function (port) {
|
||||||
return new PromiseA(function (resolve, reject) {
|
return new PromiseA(function (resolve, reject) {
|
||||||
exec('ip route show default', function (err, stdout, stderr) {
|
exec('ip route show default', function (err, stdout, stderr) {
|
||||||
var gw
|
var gw;
|
||||||
;
|
|
||||||
|
|
||||||
if (err || stderr) { reject(err || stderr); return; }
|
if (err || stderr) { reject(err || stderr); return; }
|
||||||
|
|
||||||
// default via 192.168.1.1 dev eth0
|
// default via 192.168.1.1 dev eth0
|
||||||
gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1');
|
gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1');
|
||||||
console.log('Possible PMP gateway is', gw);
|
console.log('Possible PMP gateway is', gw);
|
||||||
|
@ -55,3 +53,29 @@ exports.pmpForward = function (port) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
console.warn("");
|
||||||
|
console.warn("node helpers/pmp-forward [public port] [private port] [ttl]");
|
||||||
|
console.warn("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var pubPort = parseInt(process.argv[2], 10) || 0;
|
||||||
|
var privPort = parseInt(process.argv[3], 10) || pubPort;
|
||||||
|
var ttl = parseInt(process.argv[4], 10) || 0;
|
||||||
|
var options = { public: pubPort, private: privPort, ttl: ttl };
|
||||||
|
|
||||||
|
if (!pubPort) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.pmpForward(options).then(function () {
|
||||||
|
console.log('done');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var PromiseA = require('bluebird').Promise
|
var PromiseA = require('bluebird').Promise;
|
||||||
, https = require('https')
|
var https = require('https');
|
||||||
, fs = require('fs')
|
var fs = require('fs');
|
||||||
, path = require('path')
|
var path = require('path');
|
||||||
;
|
|
||||||
|
|
||||||
module.exports.update = function (opts) {
|
module.exports.update = function (opts) {
|
||||||
return new PromiseA(function (resolve, reject) {
|
return new PromiseA(function (resolve, reject) {
|
||||||
var options
|
var options;
|
||||||
, hostname = opts.updater || 'redirect-www.org'
|
var hostname = opts.updater || 'redirect-www.org';
|
||||||
, port = opts.port || 65443
|
var port = opts.port || 65443;
|
||||||
;
|
var req;
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
host: hostname
|
host: hostname
|
||||||
|
@ -22,12 +21,36 @@ module.exports.update = function (opts) {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
}
|
}
|
||||||
, path: '/api/ddns'
|
, path: '/api/ddns'
|
||||||
, auth: opts.auth || 'admin:secret'
|
//, auth: opts.auth || 'admin:secret'
|
||||||
, ca: [ fs.readFileSync(path.join(__dirname, '..', 'certs', 'ca', 'my-root-ca.crt.pem')) ]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (opts.cacert) {
|
||||||
|
if (!Array.isArray(opts.cacert)) {
|
||||||
|
opts.cacert = [opts.cacert];
|
||||||
|
}
|
||||||
|
options.ca = opts.cacert;
|
||||||
|
} else {
|
||||||
|
options.ca = [path.join(__dirname, '..', 'certs', 'ca', 'my-root-ca.crt.pem')]
|
||||||
|
}
|
||||||
|
|
||||||
|
options.ca = options.ca.map(function (str) {
|
||||||
|
if ('string' === typeof str && str.length < 1000) {
|
||||||
|
str = fs.readFileSync(str);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (opts.token || opts.jwt) {
|
||||||
|
options.headers['Authorization'] = 'Bearer ' + (opts.token || opts.jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false === opts.cacert) {
|
||||||
|
options.rejectUnauthorized = false;
|
||||||
|
}
|
||||||
|
|
||||||
options.agent = new https.Agent(options);
|
options.agent = new https.Agent(options);
|
||||||
|
|
||||||
https.request(options, function(res) {
|
req = https.request(options, function(res) {
|
||||||
var textData = '';
|
var textData = '';
|
||||||
|
|
||||||
res.on('error', function (err) {
|
res.on('error', function (err) {
|
||||||
|
@ -38,8 +61,22 @@ module.exports.update = function (opts) {
|
||||||
// console.log(chunk.toString());
|
// console.log(chunk.toString());
|
||||||
});
|
});
|
||||||
res.on('end', function () {
|
res.on('end', function () {
|
||||||
resolve(textData);
|
var err;
|
||||||
|
try {
|
||||||
|
resolve(JSON.parse(textData));
|
||||||
|
} catch(e) {
|
||||||
|
err = new Error("Unparsable Server Response");
|
||||||
|
err.code = 'E_INVALID_SERVER_RESPONSE';
|
||||||
|
err.data = textData;
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}).end(JSON.stringify(opts.ddns, null, ' '));
|
});
|
||||||
|
|
||||||
|
req.on('error', function () {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.end(JSON.stringify(opts.ddns, null, ' '));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,7 +19,7 @@ exports.upnpForward = function (port) {
|
||||||
console.log('mappings');
|
console.log('mappings');
|
||||||
console.log(mappings);
|
console.log(mappings);
|
||||||
});
|
});
|
||||||
|
|
||||||
return promitter;
|
return promitter;
|
||||||
})*/;
|
})*/;
|
||||||
});
|
});
|
||||||
|
@ -49,8 +49,24 @@ client.externalIp(function(err, ip) {
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (require.main === module) {
|
function usage() {
|
||||||
exports.upnpForward({ public: 65080, private: 65080, ttl: 0 }).then(function () {
|
console.warn("");
|
||||||
|
console.warn("node helpers/upnp-forward [public port] [private port] [ttl]");
|
||||||
|
console.warn("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
var pubPort = parseInt(process.argv[2], 10) || 0;
|
||||||
|
var privPort = parseInt(process.argv[3], 10) || pubPort;
|
||||||
|
var ttl = parseInt(process.argv[4], 10) || 0;
|
||||||
|
var options = { public: pubPort, private: privPort, ttl: ttl };
|
||||||
|
|
||||||
|
if (!pubPort) {
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.upnpForward(options).then(function () {
|
||||||
console.log('done');
|
console.log('done');
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
console.error('ERROR');
|
console.error('ERROR');
|
||||||
|
@ -58,3 +74,8 @@ if (require.main === module) {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var updateIp = require('../holepunch/helpers/update-ip.js').update;
|
||||||
|
// TODO XXX use API + storage
|
||||||
|
var token = require('../dyndns-token.js').token;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @param {string[]} hostnames - A list of hostnames
|
||||||
|
* @param {Object[]} addresses - A list of { address: <ip-address>, family: <4|6> }
|
||||||
|
*/
|
||||||
|
function update(hostnames, addresses) {
|
||||||
|
// TODO use not-yet-built API to get and store tokens
|
||||||
|
// TODO use API to add and remove nameservers
|
||||||
|
var services = [
|
||||||
|
// TODO XXX don't disable cacert checking
|
||||||
|
{ hostname: 'ns1.redirect-www.org', port: 65443, cacert: false }
|
||||||
|
, { hostname: 'ns2.redirect-www.org', port: 65443, cacert: false }
|
||||||
|
];
|
||||||
|
var answers = [];
|
||||||
|
var promises;
|
||||||
|
var results = [];
|
||||||
|
var PromiseA;
|
||||||
|
|
||||||
|
hostnames.forEach(function (hostname) {
|
||||||
|
addresses.forEach(function (address) {
|
||||||
|
var answer = {
|
||||||
|
"name": hostname
|
||||||
|
, "value": address.address
|
||||||
|
, "type": null
|
||||||
|
, "priority": null
|
||||||
|
, "token": token
|
||||||
|
};
|
||||||
|
|
||||||
|
if (4 === address.family) {
|
||||||
|
answer.type = 'A';
|
||||||
|
}
|
||||||
|
else if (6 === address.family) {
|
||||||
|
answer.type = 'AAAA';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('[ERROR] unspported address:');
|
||||||
|
console.error(address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
answers.push(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
promises = services.map(function (service, i) {
|
||||||
|
return updateIp({
|
||||||
|
updater: service.hostname
|
||||||
|
, port: service.port
|
||||||
|
, cacert: service.cacert
|
||||||
|
, token: token
|
||||||
|
, ddns: answers
|
||||||
|
}).then(function (data) {
|
||||||
|
results[i] = { service: service, data: data };
|
||||||
|
return data;
|
||||||
|
}).error(function (err) {
|
||||||
|
results[i] = { service: service, error: err };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
PromiseA = require('bluebird').Promise;
|
||||||
|
return PromiseA.all(promises).then(function () {
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.update = function () {
|
||||||
|
var allMap = {};
|
||||||
|
var hostnames = require('../redirects.json').reduce(function (all, redirect) {
|
||||||
|
if (!allMap[redirect.from.hostname]) {
|
||||||
|
allMap[redirect.from.hostname] = true;
|
||||||
|
all.push(redirect.from.hostname);
|
||||||
|
}
|
||||||
|
if (!all[redirect.to.hostname]) {
|
||||||
|
allMap[redirect.to.hostname] = true;
|
||||||
|
all.push(redirect.to.hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return require('./ip-checker').getExternalAddresses().then(function (result) {
|
||||||
|
//console.log(Object.keys(allMap), result);
|
||||||
|
//console.log(hostnames)
|
||||||
|
//console.log(result.addresses);
|
||||||
|
console.log('[IP CHECKER] hostnames.length', hostnames.length);
|
||||||
|
console.log('[IP CHECKER] result.addresses.length', result.addresses.length);
|
||||||
|
return update(hostnames, result.addresses);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
module.exports.update().then(function (results) {
|
||||||
|
console.log('results');
|
||||||
|
console.log(results);
|
||||||
|
});
|
||||||
|
}
|
|
@ -6,6 +6,8 @@ module.exports.create = function (securePort, insecurePort, redirects) {
|
||||||
var escapeRe;
|
var escapeRe;
|
||||||
|
|
||||||
function redirectHttps(req, res) {
|
function redirectHttps(req, res) {
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=10886400; includeSubDomains; preload');
|
||||||
|
|
||||||
var insecureRedirects;
|
var insecureRedirects;
|
||||||
var host = req.headers.host || '';
|
var host = req.headers.host || '';
|
||||||
var url = req.url;
|
var url = req.url;
|
||||||
|
@ -40,19 +42,21 @@ module.exports.create = function (securePort, insecurePort, redirects) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var escapeHtml = require('escape-html');
|
||||||
var newLocation = 'https://'
|
var newLocation = 'https://'
|
||||||
+ host.replace(/:\d+/, ':' + securePort) + url
|
+ host.replace(/:\d+/, ':' + securePort) + url
|
||||||
;
|
;
|
||||||
|
var safeLocation = escapeHtml(newLocation);
|
||||||
|
|
||||||
var metaRedirect = ''
|
var metaRedirect = ''
|
||||||
+ '<html>\n'
|
+ '<html>\n'
|
||||||
+ '<head>\n'
|
+ '<head>\n'
|
||||||
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||||
+ ' <META http-equiv="refresh" content="0;URL=' + newLocation + '">\n'
|
+ ' <META http-equiv="refresh" content="0;URL=' + safeLocation + '">\n'
|
||||||
+ '</head>\n'
|
+ '</head>\n'
|
||||||
+ '<body style="display: none;">\n'
|
+ '<body style="display: none;">\n'
|
||||||
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
+ ' <p>You requested an insecure resource. Please use this instead: \n'
|
||||||
+ ' <a href="' + newLocation + '">' + newLocation + '</a></p>\n'
|
+ ' <a href="' + safeLocation + '">' + safeLocation + '</a></p>\n'
|
||||||
+ '</body>\n'
|
+ '</body>\n'
|
||||||
+ '</html>\n'
|
+ '</html>\n'
|
||||||
;
|
;
|
||||||
|
@ -72,7 +76,7 @@ module.exports.create = function (securePort, insecurePort, redirects) {
|
||||||
// To minimize this, we give browser users a mostly optimal experience,
|
// To minimize this, we give browser users a mostly optimal experience,
|
||||||
// but people experimenting with the API get a message letting them know
|
// but people experimenting with the API get a message letting them know
|
||||||
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
// that they're doing it wrong and thus forces them to ensure they encrypt.
|
||||||
res.setHeader('Content-Type', 'text/html');
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||||
res.end(metaRedirect);
|
res.end(metaRedirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var PromiseA = require('bluebird').Promise;
|
||||||
|
var ifaces = require('os').networkInterfaces();
|
||||||
|
var dns = PromiseA.promisifyAll(require('dns'));
|
||||||
|
var https = require('https');
|
||||||
|
|
||||||
|
function getExternalAddresses() {
|
||||||
|
var iftypes = {};
|
||||||
|
|
||||||
|
Object.keys(ifaces).forEach(function (ifname) {
|
||||||
|
ifaces[ifname].forEach(function (iface) {
|
||||||
|
if (iface.internal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (/^(::|f[cde])/.test(iface.address)) {
|
||||||
|
console.log('non-public ipv6');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
iftypes[iface.family] = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
|
return PromiseA.all([
|
||||||
|
dns.lookupAsync('api.ipify.org', { family: 4/*, all: true*/ }).then(function (ans) {
|
||||||
|
iftypes.IPv4 = { address: ans[0], family: ans[1], time: Date.now() - now };
|
||||||
|
}).error(function () {
|
||||||
|
//console.log('no ipv4', Date.now() - now);
|
||||||
|
iftypes.IPv4 = false;
|
||||||
|
})
|
||||||
|
, dns.lookupAsync('api.ipify.org', { family: 6/*, all: true*/ }).then(function (ans) {
|
||||||
|
iftypes.IPv6 = { address: ans[0], family: ans[1], time: Date.now() - now };
|
||||||
|
}).error(function () {
|
||||||
|
//console.log('no ipv6', Date.now() - now);
|
||||||
|
iftypes.IPv6 = false;
|
||||||
|
})
|
||||||
|
]).then(function () {
|
||||||
|
var requests = [];
|
||||||
|
|
||||||
|
if (iftypes.IPv4) {
|
||||||
|
requests.push(new PromiseA(function (resolve) {
|
||||||
|
var req = https.request({
|
||||||
|
method: 'GET'
|
||||||
|
, hostname: iftypes.IPv4.address
|
||||||
|
, port: 443
|
||||||
|
, headers: {
|
||||||
|
Host: 'api.ipify.org'
|
||||||
|
}
|
||||||
|
, path: '/'
|
||||||
|
//, family: 4
|
||||||
|
// TODO , localAddress: <<external_ipv4>>
|
||||||
|
}, function (res) {
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
res.on('error', function (/*err*/) {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('data', function (chunk) {
|
||||||
|
result += chunk.toString('utf8');
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
resolve({ address: result, family: 4/*, wan: result === iftypes.IPv4.localAddress*/, time: iftypes.IPv4.time });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', function () {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iftypes.IPv6) {
|
||||||
|
requests.push(new PromiseA(function (resolve) {
|
||||||
|
var req = https.request({
|
||||||
|
method: 'GET'
|
||||||
|
, hostname: iftypes.IPv6.address
|
||||||
|
, port: 443
|
||||||
|
, headers: {
|
||||||
|
Host: 'api.ipify.org'
|
||||||
|
}
|
||||||
|
, path: '/'
|
||||||
|
//, family: 6
|
||||||
|
// TODO , localAddress: <<external_ipv6>>
|
||||||
|
}, function (res) {
|
||||||
|
var result = '';
|
||||||
|
|
||||||
|
res.on('error', function (/*err*/) {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('data', function (chunk) {
|
||||||
|
result += chunk.toString('utf8');
|
||||||
|
});
|
||||||
|
res.on('end', function () {
|
||||||
|
resolve({ address: result, family: 6/*, wan: result === iftypes.IPv6.localAaddress*/, time: iftypes.IPv4.time });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('error', function () {
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
req.end();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return PromiseA.all(requests).then(function (ips) {
|
||||||
|
ips = ips.filter(function (ip) {
|
||||||
|
return ip;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
addresses: ips
|
||||||
|
, time: Date.now() - now
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getExternalAddresses = getExternalAddresses;
|
|
@ -8,7 +8,9 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var dummyCerts;
|
var dummyCerts;
|
||||||
var serveFavicon;
|
var serveFavicon;
|
||||||
var secureContexts = {};
|
var secureContexts = {};
|
||||||
|
var loopbackApp;
|
||||||
|
var loopbackToken = require('crypto').randomBytes(32).toString('hex');
|
||||||
|
|
||||||
function loadDummyCerts() {
|
function loadDummyCerts() {
|
||||||
if (dummyCerts) {
|
if (dummyCerts) {
|
||||||
|
@ -132,6 +134,7 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
|
|
||||||
console.log('[log] [once] Preparing mount for', domaininfo.hostname + '/' + domaininfo.dirpathname);
|
console.log('[log] [once] Preparing mount for', domaininfo.hostname + '/' + domaininfo.dirpathname);
|
||||||
domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] = function (req, res, next) {
|
domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] = function (req, res, next) {
|
||||||
|
res.setHeader('Strict-Transport-Security', 'max-age=10886400; includeSubDomains; preload');
|
||||||
function loadThatApp() {
|
function loadThatApp() {
|
||||||
var time = Date.now();
|
var time = Date.now();
|
||||||
|
|
||||||
|
@ -206,6 +209,13 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function suckItDubDubDub(req, res) {
|
||||||
|
var newLoc = 'https://' + (req.headers.host||'').replace(/^www\./) + req.url;
|
||||||
|
res.statusCode = 301;
|
||||||
|
res.setHeader('Location', newLoc);
|
||||||
|
res.end("<html><head><title></title></head><body><!-- redirecting nowww --></body><html>");
|
||||||
|
}
|
||||||
|
|
||||||
function nextify() {
|
function nextify() {
|
||||||
if (!appContext) {
|
if (!appContext) {
|
||||||
appContext = loadThatApp();
|
appContext = loadThatApp();
|
||||||
|
@ -230,17 +240,27 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
connectContext.static = serveStatic(path.join(vhostsdir, domaininfo.dirname, 'public'));
|
connectContext.static = serveStatic(path.join(vhostsdir, domaininfo.dirname, 'public'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/^\/api\//.test(req.url)) {
|
if (/^www\./.test(req.headers.host)) {
|
||||||
nextify();
|
if (/\.appcache\b/.test(req.url)) {
|
||||||
|
res.setHeader('Content-Type', 'text/cache-manifest');
|
||||||
|
res.end('CACHE MANIFEST\n\n# v0__DELETE__CACHE__MANIFEST__\n\nNETWORK:\n*');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
suckItDubDubDub(req, res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectContext.static(req, res, nextify);
|
if (/^\/api\//.test(req.url)) {
|
||||||
|
nextify();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectContext.static(req, res, nextify);
|
||||||
};
|
};
|
||||||
domainMergeMap[domaininfo.hostname].apps.use(
|
domainMergeMap[domaininfo.hostname].apps.use(
|
||||||
'/' + domaininfo.pathname
|
'/' + domaininfo.pathname
|
||||||
, domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]
|
, domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]
|
||||||
);
|
);
|
||||||
|
|
||||||
return PromiseA.resolve();
|
return PromiseA.resolve();
|
||||||
}
|
}
|
||||||
|
@ -276,9 +296,20 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLoopbackApp() {
|
||||||
|
return function (req, res) {
|
||||||
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||||
|
res.end(JSON.stringify({ "success": true, "token": loopbackToken }));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getAppContext(domaininfo) {
|
function getAppContext(domaininfo) {
|
||||||
var localApp;
|
var localApp;
|
||||||
|
|
||||||
|
if ('loopback.daplie.invalid' === domaininfo.dirname) {
|
||||||
|
return getLoopbackApp();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// TODO live reload required modules
|
// TODO live reload required modules
|
||||||
localApp = require(path.join(vhostsdir, domaininfo.dirname, 'app.js'));
|
localApp = require(path.join(vhostsdir, domaininfo.dirname, 'app.js'));
|
||||||
|
@ -287,9 +318,9 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
// TODO pass in websocket
|
// TODO pass in websocket
|
||||||
localApp = localApp.create(secureServer, {
|
localApp = localApp.create(secureServer, {
|
||||||
dummyCerts: dummyCerts
|
dummyCerts: dummyCerts
|
||||||
, hostname: domaininfo.hostname
|
, hostname: domaininfo.hostname
|
||||||
, port: securePort
|
, port: securePort
|
||||||
, url: domaininfo.pathname
|
, url: domaininfo.pathname
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!localApp) {
|
if (!localApp) {
|
||||||
|
@ -324,7 +355,38 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
console.log('[log] [once] Loading all mounts for ' + domainApp.hostname);
|
console.log('[log] [once] Loading all mounts for ' + domainApp.hostname);
|
||||||
domainApp._loaded = true;
|
domainApp._loaded = true;
|
||||||
app.use(vhost(domainApp.hostname, domainApp.apps));
|
app.use(vhost(domainApp.hostname, domainApp.apps));
|
||||||
app.use(vhost('www.' + domainApp.hostname, domainApp.apps));
|
app.use(vhost('www.' + domainApp.hostname, function (req, res, next) {
|
||||||
|
if (/\.appcache\b/.test(req.url)) {
|
||||||
|
res.setHeader('Content-Type', 'text/cache-manifest');
|
||||||
|
res.end('CACHE MANIFEST\n\n# v0__DELETE__CACHE__MANIFEST__\n\nNETWORK:\n*');
|
||||||
|
//domainApp.apps(req, res, next);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO XXX this is in the api section, so it should hard break
|
||||||
|
//res.statusCode = 301;
|
||||||
|
//res.setHeader('Location', newLoc);
|
||||||
|
|
||||||
|
// TODO port number for non-443
|
||||||
|
var escapeHtml = require('escape-html');
|
||||||
|
var newLocation = 'https://' + domainApp.hostname + req.url;
|
||||||
|
var safeLocation = escapeHtml(newLocation);
|
||||||
|
|
||||||
|
var metaRedirect = ''
|
||||||
|
+ '<html>\n'
|
||||||
|
+ '<head>\n'
|
||||||
|
+ ' <style>* { background-color: white; color: white; text-decoration: none; }</style>\n'
|
||||||
|
+ ' <META http-equiv="refresh" content="0;URL=' + safeLocation + '">\n'
|
||||||
|
+ '</head>\n'
|
||||||
|
+ '<body style="display: none;">\n'
|
||||||
|
+ ' <p>You requested an old resource. Please use this instead: \n'
|
||||||
|
+ ' <a href="' + safeLocation + '">' + safeLocation + '</a></p>\n'
|
||||||
|
+ '</body>\n'
|
||||||
|
+ '</html>\n'
|
||||||
|
;
|
||||||
|
|
||||||
|
// 301 redirects will not work for appcache
|
||||||
|
res.end(metaRedirect);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,14 +462,28 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
var secOpts;
|
var secOpts;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var nodes = fs.readdirSync(path.join(certsPath, 'server'));
|
var nodes = fs.readdirSync(certsPath);
|
||||||
var keyNode = nodes.filter(function (node) { return /\.key\.pem$/.test(node); })[0];
|
var keyNode = nodes.filter(function (node) { return 'privkey.pem' === node; })[0];
|
||||||
var crtNode = nodes.filter(function (node) { return /\.crt\.pem$/.test(node); })[0];
|
var crtNode = nodes.filter(function (node) { return 'fullchain.pem' === node; })[0];
|
||||||
|
|
||||||
|
if (keyNode && crtNode) {
|
||||||
|
keyNode = path.join(certsPath, keyNode);
|
||||||
|
crtNode = path.join(certsPath, crtNode);
|
||||||
|
} else {
|
||||||
|
nodes = fs.readdirSync(path.join(certsPath, 'server'));
|
||||||
|
keyNode = nodes.filter(function (node) { return /^privkey(\.key)?\.pem$/.test(node) || /\.key\.pem$/.test(node); })[0];
|
||||||
|
crtNode = nodes.filter(function (node) { return /^fullchain(\.crt)?\.pem$/.test(node) || /\.crt\.pem$/.test(node); })[0];
|
||||||
|
keyNode = path.join(certsPath, 'server', keyNode);
|
||||||
|
crtNode = path.join(certsPath, 'server', crtNode);
|
||||||
|
}
|
||||||
|
|
||||||
secOpts = {
|
secOpts = {
|
||||||
key: fs.readFileSync(path.join(certsPath, 'server', keyNode))
|
key: fs.readFileSync(keyNode)
|
||||||
, cert: fs.readFileSync(path.join(certsPath, 'server', crtNode))
|
, cert: fs.readFileSync(crtNode)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// I misunderstood what the ca option was for
|
||||||
|
/*
|
||||||
if (fs.existsSync(path.join(certsPath, 'ca'))) {
|
if (fs.existsSync(path.join(certsPath, 'ca'))) {
|
||||||
secOpts.ca = fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
|
secOpts.ca = fs.readdirSync(path.join(certsPath, 'ca')).filter(function (node) {
|
||||||
console.log('[log ca]', node);
|
console.log('[log ca]', node);
|
||||||
|
@ -416,6 +492,7 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
return fs.readFileSync(path.join(certsPath, 'ca', node));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
// TODO Let's Encrypt / ACME HTTPS
|
// TODO Let's Encrypt / ACME HTTPS
|
||||||
console.error("[ERROR] Couldn't READ HTTPS certs from '" + certsPath + "':");
|
console.error("[ERROR] Couldn't READ HTTPS certs from '" + certsPath + "':");
|
||||||
|
@ -446,14 +523,18 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
// fallback / default dummy certs
|
// fallback / default dummy certs
|
||||||
key: localDummyCerts.key
|
key: localDummyCerts.key
|
||||||
, cert: localDummyCerts.cert
|
, cert: localDummyCerts.cert
|
||||||
, ca: localDummyCerts.ca
|
//, ca: localDummyCerts.ca
|
||||||
// changes from default: disallow RC4
|
// io.js defaults have disallowed insecure algorithms as of 2015-06-29
|
||||||
, ciphers: "ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:!RC4:HIGH:!MD5:!aNULL"
|
// https://iojs.org/api/tls.html
|
||||||
|
// previous version could use something like this
|
||||||
|
//, ciphers: "ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:!RC4:HIGH:!MD5:!aNULL"
|
||||||
};
|
};
|
||||||
|
|
||||||
function addSniWorkaroundCallback() {
|
function addSniWorkaroundCallback() {
|
||||||
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
//SNICallback is passed the domain name, see NodeJS docs on TLS
|
||||||
secureOpts.SNICallback = function (domainname, cb) {
|
secureOpts.SNICallback = function (domainname, cb) {
|
||||||
|
domainname = domainname.replace(/^www\./, '')
|
||||||
|
|
||||||
if (/(^|\.)proxyable\./.test(domainname)) {
|
if (/(^|\.)proxyable\./.test(domainname)) {
|
||||||
// device-id-12345678.proxyable.myapp.mydomain.com => myapp.mydomain.com
|
// device-id-12345678.proxyable.myapp.mydomain.com => myapp.mydomain.com
|
||||||
// proxyable.myapp.mydomain.com => myapp.mydomain.com
|
// proxyable.myapp.mydomain.com => myapp.mydomain.com
|
||||||
|
@ -521,5 +602,23 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateIps() {
|
||||||
|
console.log('[UPDATE IP]');
|
||||||
|
require('./ddns-updater').update().then(function (results) {
|
||||||
|
results.forEach(function (result) {
|
||||||
|
if (result.error) {
|
||||||
|
console.error(result);
|
||||||
|
} else {
|
||||||
|
console.log('[SUCCESS]', result.service.hostname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).error(function (err) {
|
||||||
|
console.error('[UPDATE IP] ERROR');
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO check the IP every 5 minutes and update it every hour
|
||||||
|
setInterval(updateIps, 60 * 60 * 1000);
|
||||||
|
updateIps();
|
||||||
return runServer();
|
return runServer();
|
||||||
};
|
};
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"ee-first": "^1.1.0",
|
"ee-first": "^1.1.0",
|
||||||
"errorhandler": "1.x",
|
"errorhandler": "1.x",
|
||||||
"es6-promise": "2.x",
|
"es6-promise": "2.x",
|
||||||
"escape-html": "^1.0.1",
|
"escape-html": "^1.0.2",
|
||||||
"escape-string-regexp": "1.x",
|
"escape-string-regexp": "1.x",
|
||||||
"etag": "^1.5.1",
|
"etag": "^1.5.1",
|
||||||
"express": "4.x",
|
"express": "4.x",
|
||||||
|
@ -74,6 +74,7 @@
|
||||||
"fresh": "^0.2.4",
|
"fresh": "^0.2.4",
|
||||||
"human-readable-ids": "1.x",
|
"human-readable-ids": "1.x",
|
||||||
"inherits": "^2.0.1",
|
"inherits": "^2.0.1",
|
||||||
|
"ipify": "^1.0.5",
|
||||||
"jarson": "1.x",
|
"jarson": "1.x",
|
||||||
"json-storage": "2.x",
|
"json-storage": "2.x",
|
||||||
"knex": "^0.6.23",
|
"knex": "^0.6.23",
|
||||||
|
@ -87,7 +88,7 @@
|
||||||
"negotiator": "^0.5.1",
|
"negotiator": "^0.5.1",
|
||||||
"node-pre-gyp": "^0.6.4",
|
"node-pre-gyp": "^0.6.4",
|
||||||
"node-uuid": "1.x",
|
"node-uuid": "1.x",
|
||||||
"nodemailer": "1.x",
|
"nodemailer": "^1.4.0",
|
||||||
"nodemailer-mailgun-transport": "1.x",
|
"nodemailer-mailgun-transport": "1.x",
|
||||||
"oauth": "0.9.x",
|
"oauth": "0.9.x",
|
||||||
"oauth2orize": "git://github.com/coolaj86/oauth2orize.git#v1.0.1+scope.1",
|
"oauth2orize": "git://github.com/coolaj86/oauth2orize.git#v1.0.1+scope.1",
|
||||||
|
|
Loading…
Reference in New Issue