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",