diff --git a/lib/index.js b/lib/index.js index 92296aa..4a84e50 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,44 +10,131 @@ module.exports.create = function (args) { } var servers = loopback.create(args); - var promises = []; + //var promises = []; if (args.debug) { console.log('[HP] create servers'); console.log(servers); } - if (-1 !== args.protocols.indexOf('none')) { - promises.push(PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) { + function getExternalIps() { + return PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) { var getIp = require('./external-ip'); return getIp({ hostname: ipifyUrl, debug: args.debug }); - })).then(function (ips) { - // TODO how (if at all) should ip.address === ip.localAddress be treated differently? - // TODO check local firewall? - // TODO would it ever make sense for a public ip to respond to upnp? - var requestAsync = require('./request'); - - return PromiseA.all(ips.map(function (ip) { - return requestAsync({ - secure: false - , hostname: ip.address - , path: '/api/com.daplie.loopback' - , servername: 'daplie.invalid' - , localAddress: ip.localAddress - , port: 65080 - , headers: { - Host: 'daplie.invalid' - } - }); - })); })); } - return PromiseA.all(promises).then(function (results) { - if (args.debug) { - console.log('[HP] all done'); - console.log(results); - } + function testOpenPort(ip, portInfo) { + var requestAsync = require('./request'); + + return requestAsync({ + secure: portInfo.secure + , rejectUnauthorized: false + , hostname: ip.address + // '/.well-known/com.daplie.loopback/' + , path: args.loopbackPrefix + args.key + // 'loopback.daplie.invalid' + , servername: args.loopbackHostname + , localAddress: ip.localAddress + , port: portInfo.external || portInfo.internal + , headers: { + // 'loopback.daplie.invalid' + Host: args.loopbackHostname + } + }).then(function (val) { + if (val !== args.value) { + return PromiseA.reject(new Error("invalid loopback token value")); + } + + ip.validated = true; + + ip.ports.push(portInfo); + portInfo.ips.push(ip); + + return portInfo; + }); + } + + function testPort(opts) { + // TODO should ip.address === ip.localAddress be treated differently? + // TODO check local firewall? + // TODO would it ever make sense for a public ip to respond to upnp? + + // TODO should we pass any or require all? + opts.portInfo.ips = []; + + return PromiseA.any(opts.ips.map(function (ip) { + ip.ports = []; + + if (!opts.pretest) { + //console.log('[HP pretest]', opts.pretest); + return ip; + } + + return testOpenPort(ip, opts.portInfo); + })); + } + + return getExternalIps().then(function (ips) { + var pretest = (-1 !== args.protocols.indexOf('none')); + var portInfos = args.plainPorts.concat(args.tlsPorts.map(function (info) { + info.secure = true; + return info; + })); + + return PromiseA.all(portInfos.map(function (info) { + // TODO clone-merge args + return testPort({ + portInfo: info + , ips: ips + , pretest: pretest + }); + })).then(function (portInfos) { + if (args.debug) { + console.log('[HP] all done on the first try'); + console.log(portInfos); + } + return portInfos; + }, function () { + // at least one port could not be mapped + var mappers = []; + + if (-1 !== args.protocols.indexOf('upnp') + || -1 !== args.protocols.indexOf('ssdp') + ) { + mappers.push(require('./upnp')); + } + + if (-1 !== args.protocols.indexOf('pmp') + || -1 !== args.protocols.indexOf('nat-pmp') + ) { + mappers.push(require('./pmp')); + } + + return PromiseA.all(portInfos.map(function (portInfo) { + return PromiseA.any(mappers.map(function (fn) { + var p = fn(args, ips, portInfo); + + if (portInfos.ips.length) { + return portInfos; + } + + return p; + })); + })).then(function () { + if (args.debug) { + console.log("[HP] all ports successfully mapped"); + console.log(portInfos); + } + + return portInfos; + }); + }).then(function () { + return portInfos; + }, function () { + console.warn('[HP] RVPN not implemented'); + return portInfos; + }); }); }; diff --git a/lib/loopback-listener.js b/lib/loopback-listener.js index 8f31ca3..f5f5bf8 100644 --- a/lib/loopback-listener.js +++ b/lib/loopback-listener.js @@ -56,5 +56,8 @@ module.exports.create = function (opts) { }); }); + results.key = opts.key; + results.value = opts.value; + return results; }; diff --git a/lib/middleware.js b/lib/middleware.js index 573d006..f56dcb3 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -6,14 +6,20 @@ function middleware(opts) { var key = opts.key; var val = opts.value; var vhost = opts.vhost; - var pathnamePrefix = opts.prefix || '/.well-known/com.daplie.loopback/'; - var defaultHostname = 'loopback.daplie.invalid'; + var pathnamePrefix = opts.loopbackPrefix; + var defaultHostname = opts.loopbackHostname; + if (!defaultHostname) { + defaultHostname = opts.loopbackHostname = 'loopback.daplie.invalid'; + } + if (!pathnamePrefix) { + pathnamePrefix = opts.loopbackPrefix = '/.well-known/com.daplie.loopback/'; + } if (!key) { - opts.key = require('crypto').randomBytes(8).toString('hex'); + key = opts.key = require('crypto').randomBytes(8).toString('hex'); } if (!val) { - opts.value = require('crypto').randomBytes(16).toString('hex'); + val = opts.value = require('crypto').randomBytes(16).toString('hex'); } if (!vhost && vhost !== false) { vhost = defaultHostname;