diff --git a/ddns-client.js b/ddns-client.js
index bf81a46..0b016ae 100755
--- a/ddns-client.js
+++ b/ddns-client.js
@@ -14,6 +14,7 @@ cli.parse({
, insecure: [ false, '(deprecated) allow insecure non-https connections', 'boolean' ]
, cacert: [ false, '(not implemented) specify a CA for "self-signed" https certificates', 'string' ]
, answer: [ 'a', 'The answer', 'string' ]
+, token: [ false, 'Token (TODO or filepath to token)', 'string' ]
});
cli.main(function (args, options) {
@@ -22,8 +23,9 @@ cli.main(function (args, options) {
options.answer = options.answer || args[1]
if (options.insecure) {
- console.error('--insecure is not supported. You must use secure connections.');
- return;
+ //console.error('--insecure is not supported. You must use secure connections.');
+ //return;
+ options.cacert = false;
}
if (!options.hostname) {
@@ -41,20 +43,26 @@ cli.main(function (args, options) {
updater: options.service
, port: options.port
, cacert: options.cacert
+ , token: options.token
, ddns: [
{ "name": options.hostname
, "value": options.answer
, "type": options.type
, "priority": options.priority
+ , "token": options.token
}
]
}).then(function (data) {
if ('string') {
- data = JSON.parse(data);
+ try {
+ data = JSON.parse(data);
+ } catch(e) {
+ console.error(data);
+ }
}
console.log(JSON.stringify(data, null, ' '));
console.log('Test with');
console.log('dig ' + options.hostname + ' ' + options.type);
- })
+ });
});
diff --git a/ddns-redirects.js b/ddns-redirects.js
index d356459..3cf435e 100644
--- a/ddns-redirects.js
+++ b/ddns-redirects.js
@@ -1,38 +1,52 @@
#!/usr/bin/env node
'use strict';
-// dig -p 53 @redirect-www.org pi.nadal.daplie.com A
-var updateIp = require('./holepunch/helpers/update-ip.js').update;
+// TODO have a quick timeout
+require('ipify')(function (err, ip) {
+ console.log('ip', ip);
-var redirects = require('./redirects');
-var ddns = [];
-var ddnsMap = {};
+ var path = require('path');
+ // dig -p 53 @redirect-www.org pi.nadal.daplie.com A
+ var updateIp = require('./holepunch/helpers/update-ip.js').update;
-function add(hostname) {
- ddns.push({
- "name": hostname
+ var redirects = require('./redirects');
+ var ddns = [];
+ 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({
- updater: 'redirect-www.org'
-, port: 65443
-, cacert: null
-, ddns: ddns
-}).then(function (data) {
- if ('string') {
- data = JSON.parse(data);
- }
+ return updateIp({
+ updater: 'ns1.redirect-www.org'
+ , port: 65443
+ , cacert: path.join(__dirname, 'certs/ca/ns1-test.root.crt.pem')
+ , ddns: ddns
+ , token: require('./dyndns-token').token
+ }).then(function (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('Test with');
- console.log('dig <> A');
+ console.log(JSON.stringify(data, null, ' '));
+ console.log('Test with');
+ console.log('dig <> A');
+ });
});
diff --git a/dyndns-token.sample.js b/dyndns-token.sample.js
new file mode 100644
index 0000000..b5cefe5
--- /dev/null
+++ b/dyndns-token.sample.js
@@ -0,0 +1 @@
+module.exports = { token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" };
diff --git a/holepunch/beacon.js b/holepunch/beacon.js
index b894aff..c5124bc 100644
--- a/holepunch/beacon.js
+++ b/holepunch/beacon.js
@@ -1,14 +1,13 @@
'use strict';
-var PromiseA = require('bluebird').Promise
- , updateIp = require('./helpers/update-ip.js').update
- , request = PromiseA.promisifyAll(require('request'))
- , requestAsync = PromiseA.promisify(require('request'))
- , upnpForward = require('./helpers/upnp-forward').upnpForward
- , pmpForward = require('./helpers/pmp-forward').pmpForward
- , loopbackHttps = require('./loopback-https')
- //, checkip = require('check-ip-address')
- ;
+var PromiseA = require('bluebird').Promise;
+var updateIp = require('./helpers/update-ip.js').update;
+var request = PromiseA.promisifyAll(require('request'));
+var requestAsync = PromiseA.promisify(require('request'));
+var upnpForward = require('./helpers/upnp-forward').upnpForward;
+var pmpForward = require('./helpers/pmp-forward').pmpForward;
+var loopbackHttps = require('./loopback-https');
+//var checkip = require('check-ip-address');
function openPort(ip, port) {
if (!/tcp|https|http/.test(port.protocol || 'tcp')) {
@@ -52,7 +51,7 @@ function beacon(hostnames, ports) {
console.log("Updated DynDNS");
console.log(data);
-
+
ports.forEach(function (port) {
promises.push(openPort(JSON.parse(data)[0].answers[0] || hostname, port));
});
diff --git a/holepunch/helpers/pmp-forward.js b/holepunch/helpers/pmp-forward.js
index f971019..c105bf3 100644
--- a/holepunch/helpers/pmp-forward.js
+++ b/holepunch/helpers/pmp-forward.js
@@ -1,18 +1,16 @@
'use strict';
-var PromiseA = require('bluebird').Promise
- , natpmp = require('nat-pmp')
- , exec = require('child_process').exec
- ;
+var PromiseA = require('bluebird').Promise;
+var natpmp = require('nat-pmp');
+var exec = require('child_process').exec;
exports.pmpForward = function (port) {
return new PromiseA(function (resolve, reject) {
exec('ip route show default', function (err, stdout, stderr) {
- var gw
- ;
+ var gw;
if (err || stderr) { reject(err || stderr); return; }
-
+
// default via 192.168.1.1 dev eth0
gw = stdout.replace(/^default via (\d+\.\d+\.\d+\.\d+) dev[\s\S]+/m, '$1');
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();
+}
diff --git a/holepunch/helpers/update-ip.js b/holepunch/helpers/update-ip.js
index de15b7a..3e3606b 100644
--- a/holepunch/helpers/update-ip.js
+++ b/holepunch/helpers/update-ip.js
@@ -1,18 +1,17 @@
#!/usr/bin/env node
'use strict';
-var PromiseA = require('bluebird').Promise
- , https = require('https')
- , fs = require('fs')
- , path = require('path')
- ;
+var PromiseA = require('bluebird').Promise;
+var https = require('https');
+var fs = require('fs');
+var path = require('path');
module.exports.update = function (opts) {
return new PromiseA(function (resolve, reject) {
- var options
- , hostname = opts.updater || 'redirect-www.org'
- , port = opts.port || 65443
- ;
+ var options;
+ var hostname = opts.updater || 'redirect-www.org';
+ var port = opts.port || 65443;
+ var req;
options = {
host: hostname
@@ -22,12 +21,36 @@ module.exports.update = function (opts) {
'Content-Type': 'application/json'
}
, path: '/api/ddns'
- , auth: opts.auth || 'admin:secret'
- , ca: [ fs.readFileSync(path.join(__dirname, '..', 'certs', 'ca', 'my-root-ca.crt.pem')) ]
+ //, auth: opts.auth || 'admin:secret'
};
+
+ 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);
- https.request(options, function(res) {
+ req = https.request(options, function(res) {
var textData = '';
res.on('error', function (err) {
@@ -38,8 +61,22 @@ module.exports.update = function (opts) {
// console.log(chunk.toString());
});
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, ' '));
});
};
diff --git a/holepunch/helpers/upnp-forward.js b/holepunch/helpers/upnp-forward.js
index 3bd2224..f42dfe0 100644
--- a/holepunch/helpers/upnp-forward.js
+++ b/holepunch/helpers/upnp-forward.js
@@ -19,7 +19,7 @@ exports.upnpForward = function (port) {
console.log('mappings');
console.log(mappings);
});
-
+
return promitter;
})*/;
});
@@ -49,8 +49,24 @@ client.externalIp(function(err, ip) {
});
*/
-if (require.main === module) {
- exports.upnpForward({ public: 65080, private: 65080, ttl: 0 }).then(function () {
+function usage() {
+ 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');
}).catch(function (err) {
console.error('ERROR');
@@ -58,3 +74,8 @@ if (require.main === module) {
throw err;
});
}
+
+if (require.main === module) {
+ run();
+ return;
+}
diff --git a/lib/ddns-updater.js b/lib/ddns-updater.js
new file mode 100644
index 0000000..cbc0589
--- /dev/null
+++ b/lib/ddns-updater.js
@@ -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: , 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);
+ });
+}
diff --git a/lib/insecure-server.js b/lib/insecure-server.js
index fbda75b..93a6f40 100644
--- a/lib/insecure-server.js
+++ b/lib/insecure-server.js
@@ -6,6 +6,8 @@ module.exports.create = function (securePort, insecurePort, redirects) {
var escapeRe;
function redirectHttps(req, res) {
+ res.setHeader('Strict-Transport-Security', 'max-age=10886400; includeSubDomains; preload');
+
var insecureRedirects;
var host = req.headers.host || '';
var url = req.url;
@@ -40,19 +42,21 @@ module.exports.create = function (securePort, insecurePort, redirects) {
);
});
+ var escapeHtml = require('escape-html');
var newLocation = 'https://'
+ host.replace(/:\d+/, ':' + securePort) + url
;
+ var safeLocation = escapeHtml(newLocation);
var metaRedirect = ''
+ '\n'
+ '\n'
+ ' \n'
- + ' \n'
+ + ' \n'
+ '\n'
+ '\n'
+ ' You requested an insecure resource. Please use this instead: \n'
- + ' ' + newLocation + '
\n'
+ + ' ' + safeLocation + '
\n'
+ '\n'
+ '\n'
;
@@ -72,7 +76,7 @@ module.exports.create = function (securePort, insecurePort, redirects) {
// To minimize this, we give browser users a mostly optimal experience,
// 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.
- res.setHeader('Content-Type', 'text/html');
+ res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(metaRedirect);
}
diff --git a/lib/ip-checker.js b/lib/ip-checker.js
new file mode 100644
index 0000000..9a05592
--- /dev/null
+++ b/lib/ip-checker.js
@@ -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: <>
+ }, 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: <>
+ }, 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;
diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js
index 8590dcc..c24705e 100644
--- a/lib/vhost-sni-server.js
+++ b/lib/vhost-sni-server.js
@@ -8,7 +8,9 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
var path = require('path');
var dummyCerts;
var serveFavicon;
- var secureContexts = {};
+ var secureContexts = {};
+ var loopbackApp;
+ var loopbackToken = require('crypto').randomBytes(32).toString('hex');
function loadDummyCerts() {
if (dummyCerts) {
@@ -132,6 +134,7 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
console.log('[log] [once] Preparing mount for', domaininfo.hostname + '/' + domaininfo.dirpathname);
domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname] = function (req, res, next) {
+ res.setHeader('Strict-Transport-Security', 'max-age=10886400; includeSubDomains; preload');
function loadThatApp() {
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("");
+ }
+
function nextify() {
if (!appContext) {
appContext = loadThatApp();
@@ -230,17 +240,27 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
connectContext.static = serveStatic(path.join(vhostsdir, domaininfo.dirname, 'public'));
}
- if (/^\/api\//.test(req.url)) {
- nextify();
+ if (/^www\./.test(req.headers.host)) {
+ 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;
}
- connectContext.static(req, res, nextify);
+ if (/^\/api\//.test(req.url)) {
+ nextify();
+ return;
+ }
+
+ connectContext.static(req, res, nextify);
};
domainMergeMap[domaininfo.hostname].apps.use(
- '/' + domaininfo.pathname
- , domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]
- );
+ '/' + domaininfo.pathname
+ , domainMergeMap[domaininfo.hostname].mountsMap['/' + domaininfo.dirpathname]
+ );
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) {
var localApp;
+ if ('loopback.daplie.invalid' === domaininfo.dirname) {
+ return getLoopbackApp();
+ }
+
try {
// TODO live reload required modules
localApp = require(path.join(vhostsdir, domaininfo.dirname, 'app.js'));
@@ -287,9 +318,9 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
// TODO pass in websocket
localApp = localApp.create(secureServer, {
dummyCerts: dummyCerts
- , hostname: domaininfo.hostname
- , port: securePort
- , url: domaininfo.pathname
+ , hostname: domaininfo.hostname
+ , port: securePort
+ , url: domaininfo.pathname
});
if (!localApp) {
@@ -324,7 +355,38 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
console.log('[log] [once] Loading all mounts for ' + domainApp.hostname);
domainApp._loaded = true;
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 = ''
+ + '\n'
+ + '\n'
+ + ' \n'
+ + ' \n'
+ + '\n'
+ + '\n'
+ + ' You requested an old resource. Please use this instead: \n'
+ + ' ' + safeLocation + '
\n'
+ + '\n'
+ + '\n'
+ ;
+
+ // 301 redirects will not work for appcache
+ res.end(metaRedirect);
+ }));
});
}
@@ -400,14 +462,28 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
var secOpts;
try {
- var nodes = fs.readdirSync(path.join(certsPath, 'server'));
- var keyNode = nodes.filter(function (node) { return /\.key\.pem$/.test(node); })[0];
- var crtNode = nodes.filter(function (node) { return /\.crt\.pem$/.test(node); })[0];
+ var nodes = fs.readdirSync(certsPath);
+ var keyNode = nodes.filter(function (node) { return 'privkey.pem' === 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 = {
- key: fs.readFileSync(path.join(certsPath, 'server', keyNode))
- , cert: fs.readFileSync(path.join(certsPath, 'server', crtNode))
+ key: fs.readFileSync(keyNode)
+ , cert: fs.readFileSync(crtNode)
};
+ // I misunderstood what the ca option was for
+ /*
if (fs.existsSync(path.join(certsPath, 'ca'))) {
secOpts.ca = fs.readdirSync(path.join(certsPath, 'ca')).filter(function (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));
});
}
+ */
} catch(err) {
// TODO Let's Encrypt / ACME HTTPS
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
key: localDummyCerts.key
, cert: localDummyCerts.cert
- , ca: localDummyCerts.ca
- // changes from default: disallow RC4
- , ciphers: "ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:AES128-GCM-SHA256:!RC4:HIGH:!MD5:!aNULL"
+ //, ca: localDummyCerts.ca
+ // io.js defaults have disallowed insecure algorithms as of 2015-06-29
+ // 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() {
//SNICallback is passed the domain name, see NodeJS docs on TLS
secureOpts.SNICallback = function (domainname, cb) {
+ domainname = domainname.replace(/^www\./, '')
+
if (/(^|\.)proxyable\./.test(domainname)) {
// device-id-12345678.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();
};
diff --git a/package.json b/package.json
index 942dd2e..b3d80aa 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"ee-first": "^1.1.0",
"errorhandler": "1.x",
"es6-promise": "2.x",
- "escape-html": "^1.0.1",
+ "escape-html": "^1.0.2",
"escape-string-regexp": "1.x",
"etag": "^1.5.1",
"express": "4.x",
@@ -74,6 +74,7 @@
"fresh": "^0.2.4",
"human-readable-ids": "1.x",
"inherits": "^2.0.1",
+ "ipify": "^1.0.5",
"jarson": "1.x",
"json-storage": "2.x",
"knex": "^0.6.23",
@@ -87,7 +88,7 @@
"negotiator": "^0.5.1",
"node-pre-gyp": "^0.6.4",
"node-uuid": "1.x",
- "nodemailer": "1.x",
+ "nodemailer": "^1.4.0",
"nodemailer-mailgun-transport": "1.x",
"oauth": "0.9.x",
"oauth2orize": "git://github.com/coolaj86/oauth2orize.git#v1.0.1+scope.1",