From 945ec23965184bcbcee07184285d16c6054fe96a Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Tue, 30 Jun 2015 02:57:29 +0000
Subject: [PATCH 01/10] accept io.js default ciphers (updated and secure)
---
lib/vhost-sni-server.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js
index 8590dcc..75e61e6 100644
--- a/lib/vhost-sni-server.js
+++ b/lib/vhost-sni-server.js
@@ -447,8 +447,10 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
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"
+ // 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() {
From 0d9025574556d5691a033a7dc876151c03a6634e Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Wed, 8 Jul 2015 21:20:57 -0600
Subject: [PATCH 02/10] escape HTML on redirects
---
lib/insecure-server.js | 8 +++++---
package.json | 2 +-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/lib/insecure-server.js b/lib/insecure-server.js
index fbda75b..0b8e827 100644
--- a/lib/insecure-server.js
+++ b/lib/insecure-server.js
@@ -40,19 +40,21 @@ module.exports.create = function (securePort, insecurePort, redirects) {
);
});
+ var escapeHtml = require('escapeHtml');
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 +74,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/package.json b/package.json
index dcee18e..13ef213 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,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",
From 5ba70878de4ad942db8435398ff6943fa35ff47f Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Tue, 14 Jul 2015 21:36:20 +0000
Subject: [PATCH 03/10] prefer letsencrypt-style certs
---
lib/vhost-sni-server.js | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js
index 75e61e6..3f7301a 100644
--- a/lib/vhost-sni-server.js
+++ b/lib/vhost-sni-server.js
@@ -400,14 +400,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 +430,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,7 +461,7 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
// fallback / default dummy certs
key: localDummyCerts.key
, cert: localDummyCerts.cert
- , ca: localDummyCerts.ca
+ //, 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
From fe02a72e022e31a84f6b96605ae4c9fb82d30548 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Tue, 14 Jul 2015 21:44:02 +0000
Subject: [PATCH 04/10] fix typo escapeHtml -> escape-html
---
lib/insecure-server.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/insecure-server.js b/lib/insecure-server.js
index 0b8e827..251673b 100644
--- a/lib/insecure-server.js
+++ b/lib/insecure-server.js
@@ -40,7 +40,7 @@ module.exports.create = function (securePort, insecurePort, redirects) {
);
});
- var escapeHtml = require('escapeHtml');
+ var escapeHtml = require('escape-html');
var newLocation = 'https://'
+ host.replace(/:\d+/, ':' + securePort) + url
;
From ed4e23e924e9a1b2ad5297176dbafd15a10118b7 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Fri, 14 Aug 2015 17:00:28 +0000
Subject: [PATCH 05/10] minor updates to holepunch
---
ddns-client.js | 2 ++
holepunch/beacon.js | 19 ++++++++--------
holepunch/helpers/pmp-forward.js | 38 +++++++++++++++++++++++++------
holepunch/helpers/update-ip.js | 21 +++++++++--------
holepunch/helpers/upnp-forward.js | 27 +++++++++++++++++++---
5 files changed, 78 insertions(+), 29 deletions(-)
diff --git a/ddns-client.js b/ddns-client.js
index bf81a46..38169cd 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', 'string' ]
});
cli.main(function (args, options) {
@@ -46,6 +47,7 @@ cli.main(function (args, options) {
, "value": options.answer
, "type": options.type
, "priority": options.priority
+ , "token": options.token
}
]
}).then(function (data) {
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..7c3c90e 100644
--- a/holepunch/helpers/update-ip.js
+++ b/holepunch/helpers/update-ip.js
@@ -1,18 +1,16 @@
#!/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;
options = {
host: hostname
@@ -25,6 +23,11 @@ module.exports.update = function (opts) {
, auth: opts.auth || 'admin:secret'
, ca: [ fs.readFileSync(path.join(__dirname, '..', 'certs', 'ca', 'my-root-ca.crt.pem')) ]
};
+
+ if (opts.jwt) {
+ options.headers['Authorization'] = 'Bearer ' + opts.jwt;
+ }
+
options.agent = new https.Agent(options);
https.request(options, function(res) {
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;
+}
From c6034b3992748da516110f8e5726d30915ae4408 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Fri, 14 Aug 2015 21:09:32 +0000
Subject: [PATCH 06/10] tested handling dns updates with token
---
ddns-redirects.js | 72 ++++++++++++++++++++--------------
holepunch/helpers/update-ip.js | 27 +++++++++++--
package.json | 1 +
3 files changed, 67 insertions(+), 33 deletions(-)
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/holepunch/helpers/update-ip.js b/holepunch/helpers/update-ip.js
index 7c3c90e..8a3c7a9 100644
--- a/holepunch/helpers/update-ip.js
+++ b/holepunch/helpers/update-ip.js
@@ -20,12 +20,31 @@ 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.jwt) {
- options.headers['Authorization'] = 'Bearer ' + opts.jwt;
+ 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);
diff --git a/package.json b/package.json
index 13ef213..19779a5 100644
--- a/package.json
+++ b/package.json
@@ -73,6 +73,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",
From 3de6e4843d913d63b2c3b6ca537cbb7a4457e099 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Fri, 25 Sep 2015 08:06:47 +0000
Subject: [PATCH 07/10] add HSTS header
---
lib/insecure-server.js | 2 ++
lib/vhost-sni-server.js | 1 +
2 files changed, 3 insertions(+)
diff --git a/lib/insecure-server.js b/lib/insecure-server.js
index 251673b..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;
diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js
index 3f7301a..74a4bfd 100644
--- a/lib/vhost-sni-server.js
+++ b/lib/vhost-sni-server.js
@@ -132,6 +132,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();
From 7d899a7f04ca9fc56b679221f4a23130cfe490a7 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Fri, 25 Sep 2015 08:07:08 +0000
Subject: [PATCH 08/10] add another option
---
ddns-client.js | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/ddns-client.js b/ddns-client.js
index 38169cd..b883077 100755
--- a/ddns-client.js
+++ b/ddns-client.js
@@ -14,7 +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', 'string' ]
+, token: [ false, 'Token (TODO or filepath to token)', 'string' ]
});
cli.main(function (args, options) {
@@ -23,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) {
@@ -42,6 +43,7 @@ cli.main(function (args, options) {
updater: options.service
, port: options.port
, cacert: options.cacert
+ , token: options.token
, ddns: [
{ "name": options.hostname
, "value": options.answer
@@ -52,7 +54,11 @@ cli.main(function (args, options) {
]
}).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, ' '));
From 2166090e33f30cccd69f88b0c1125f683029b646 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Wed, 30 Sep 2015 23:05:20 +0000
Subject: [PATCH 09/10] fix www. prefixes and related bricked application
caches
---
lib/vhost-sni-server.js | 70 +++++++++++++++++++++++++++++++++++------
1 file changed, 60 insertions(+), 10 deletions(-)
diff --git a/lib/vhost-sni-server.js b/lib/vhost-sni-server.js
index 74a4bfd..f0510ab 100644
--- a/lib/vhost-sni-server.js
+++ b/lib/vhost-sni-server.js
@@ -207,6 +207,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();
@@ -231,17 +238,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();
}
@@ -288,9 +305,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) {
@@ -325,7 +342,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);
+ }));
});
}
@@ -472,6 +520,8 @@ module.exports.create = function (securePort, certsPath, vhostsdir) {
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
From 7d17139a4b39d5b5019303e32d224cc12cdebd70 Mon Sep 17 00:00:00 2001
From: AJ ONeal
Date: Mon, 5 Oct 2015 19:51:58 +0000
Subject: [PATCH 10/10] hourly update ip address
---
ddns-client.js | 2 +-
dyndns-token.sample.js | 1 +
holepunch/helpers/update-ip.js | 21 +++++-
lib/ddns-updater.js | 101 ++++++++++++++++++++++++++
lib/ip-checker.js | 126 +++++++++++++++++++++++++++++++++
lib/vhost-sni-server.js | 33 ++++++++-
package.json | 2 +-
7 files changed, 280 insertions(+), 6 deletions(-)
create mode 100644 dyndns-token.sample.js
create mode 100644 lib/ddns-updater.js
create mode 100644 lib/ip-checker.js
diff --git a/ddns-client.js b/ddns-client.js
index b883077..0b016ae 100755
--- a/ddns-client.js
+++ b/ddns-client.js
@@ -64,5 +64,5 @@ cli.main(function (args, options) {
console.log(JSON.stringify(data, null, ' '));
console.log('Test with');
console.log('dig ' + options.hostname + ' ' + options.type);
- })
+ });
});
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/helpers/update-ip.js b/holepunch/helpers/update-ip.js
index 8a3c7a9..3e3606b 100644
--- a/holepunch/helpers/update-ip.js
+++ b/holepunch/helpers/update-ip.js
@@ -11,6 +11,7 @@ module.exports.update = function (opts) {
var options;
var hostname = opts.updater || 'redirect-www.org';
var port = opts.port || 65443;
+ var req;
options = {
host: hostname
@@ -49,7 +50,7 @@ module.exports.update = function (opts) {
options.agent = new https.Agent(options);
- https.request(options, function(res) {
+ req = https.request(options, function(res) {
var textData = '';
res.on('error', function (err) {
@@ -60,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/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/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 f0510ab..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) {
@@ -294,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'));
@@ -589,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 19779a5..2f603df 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,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",