Comparing version 0.8.0 to 0.8.1
@@ -0,1 +1,2 @@ | ||
#!/usr/bin/env node | ||
(function () { | ||
@@ -7,2 +8,3 @@ 'use strict'; | ||
var program = require('commander'); | ||
var url = require('url'); | ||
var stunnel = require('../wsclient.js'); | ||
@@ -37,3 +39,5 @@ | ||
}; | ||
}).forEach(memo.push); | ||
}).forEach(function (val) { | ||
memo.push(val); | ||
}); | ||
@@ -50,14 +54,32 @@ return memo; | ||
}) | ||
.option('-k --insecure', 'Allow TLS connections to stunneld without valid certs (H)') | ||
.option('-k --insecure', 'Allow TLS connections to stunneld without valid certs (rejectUnauthorized: false)') | ||
.option('--locals <LINE>', 'comma separated list of <proto>:<//><servername>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https://john.example.com,tls:*:1337', collectProxies, [ ]) // --reverse-proxies | ||
.option('--stunneld <URL>', 'the domain (or ip address) at which you are running stunneld.js (the proxy)') // --proxy | ||
.option('--secret', 'the same secret used by stunneld (used for JWT authentication)') | ||
.option('--token', 'a pre-generated token for use with stunneld (instead of generating one with --secret)') | ||
.option('--secret <STRING>', 'the same secret used by stunneld (used for JWT authentication)') | ||
.option('--token <STRING>', 'a pre-generated token for use with stunneld (instead of generating one with --secret)') | ||
.parse(process.argv) | ||
; | ||
// Assumption: will not get next tcp packet unless previous packet succeeded | ||
var hostname = 'aj.daplie.me'; // 'pokemap.hellabit.com' | ||
program.stunneld = program.stunneld || 'wss://pokemap.hellabit.com:3000'; | ||
var jwt = require('jsonwebtoken'); | ||
var domainsMap = {}; | ||
var tokenData = { | ||
name: null | ||
, domains: null | ||
}; | ||
var location = url.parse(program.stunneld); | ||
if (!location.protocol || /\./.test(location.protocol)) { | ||
program.stunneld = 'wss://' + program.stunneld; | ||
location = url.parse(program.stunneld); | ||
} | ||
program.stunneld = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : ''); | ||
program.locals.forEach(function (proxy) { | ||
domainsMap[proxy.hostname] = true; | ||
}); | ||
tokenData.domains = Object.keys(domainsMap); | ||
tokenData.name = tokenData.domains[0]; | ||
program.services = {}; | ||
@@ -68,4 +90,3 @@ program.locals.forEach(function (proxy) { | ||
}); | ||
program.token = program.token || jwt.sign({ name: hostname }, program.secret || 'shhhhh'); | ||
program.stunneld = program.stunneld || 'wss://pokemap.hellabit.com:3000'; | ||
program.token = program.token || jwt.sign(tokenData, program.secret || 'shhhhh'); | ||
@@ -72,0 +93,0 @@ stunnel.connect(program); |
{ | ||
"name": "stunnel", | ||
"version": "0.8.0", | ||
"version": "0.8.1", | ||
"description": "A pure-JavaScript tunnel client for http and https similar to localtunnel.me, but uses TLS (SSL) with ServerName Indication (SNI) over https to work even in harsh network conditions such as in student dorms and behind HOAs, corporate firewalls, public libraries, airports, airplanes, etc. Can also tunnel tls and plain tcp.", | ||
@@ -8,2 +8,3 @@ "main": "wsclient.js", | ||
"jstunnel": "bin/stunnel.js", | ||
"stunnel.js": "bin/stunnel.js", | ||
"stunnel-js": "bin/stunnel.js" | ||
@@ -19,2 +20,4 @@ }, | ||
"keywords": [ | ||
"cli", | ||
"client", | ||
"tcp", | ||
@@ -21,0 +24,0 @@ "tls", |
# stunnel.js | ||
Works in combination with [stunneld.js](https://github.com/Daplie/node-tunnel-server) | ||
to allow you to serve http and https from provide a secure tunnelA paired client for our node tunnel server | ||
A client that works in combination with [stunneld.js](https://github.com/Daplie/node-tunnel-server) | ||
to allow you to serve http and https from any computer, anywhere through a secure tunnel. | ||
@@ -6,0 +6,0 @@ CLI |
279
wsclient.js
@@ -7,2 +7,4 @@ (function () { | ||
var sni = require('sni'); | ||
var pack = require('tunnel-packer').pack; | ||
var authenticated = false; | ||
@@ -35,133 +37,124 @@ // TODO move these helpers to tunnel-packer package | ||
function run(copts) { | ||
var services = copts.services; // TODO pair with hostname / sni | ||
var token = copts.token; | ||
var tunnelUrl = copts.stunneld + '/?access_token=' + token; | ||
var wstunneler; | ||
var retry = true; | ||
var localclients = {}; | ||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: false }); | ||
function run(copts) { | ||
var services = copts.services; // TODO pair with hostname / sni | ||
var token = copts.token; | ||
var tunnelUrl = copts.stunneld + '/?access_token=' + token; | ||
var wstunneler; | ||
var retry = true; | ||
var localclients = {}; | ||
// BaaS / Backendless / noBackend / horizon.io | ||
// user authentication | ||
// a place to store data | ||
// file management | ||
// Synergy Teamwork Paradigm = Jabberwocky | ||
var handlers = { | ||
onmessage: function (opts) { | ||
var cid = addrToId(opts); | ||
var service = opts.service; | ||
var port = services[service]; | ||
var servername; | ||
var str; | ||
var m; | ||
function onOpen() { | ||
console.log('[open] tunneler connected'); | ||
authenticated = true; | ||
/* | ||
setInterval(function () { | ||
console.log(''); | ||
console.log('localclients.length:', Object.keys(localclients).length); | ||
console.log(''); | ||
}, 5000); | ||
*/ | ||
if (localclients[cid]) { | ||
//console.log("[=>] received data from '" + cid + "' =>", opts.data.byteLength); | ||
localclients[cid].write(opts.data); | ||
return; | ||
} | ||
else if ('http' === service) { | ||
str = opts.data.toString(); | ||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); | ||
servername = (m && m[1].toLowerCase() || '').split(':')[0]; | ||
} | ||
else if ('https' === service) { | ||
servername = sni(opts.data); | ||
} | ||
else { | ||
handlers._onLocalError(cid, opts, new Error("unsupported service '" + service + "'")); | ||
return; | ||
} | ||
//wstunneler.send(token); | ||
if (!servername) { | ||
console.info("[error] missing servername for '" + cid + "'", opts.data.byteLength); | ||
//console.warn(opts.data.toString()); | ||
wstunneler.send(pack(opts, null, 'error'), { binary: true }); | ||
return; | ||
} | ||
// BaaS / Backendless / noBackend / horizon.io | ||
// user authentication | ||
// a place to store data | ||
// file management | ||
// Synergy Teamwork Paradigm = Jabberwocky | ||
var pack = require('tunnel-packer').pack; | ||
var handlers = { | ||
onmessage: function (opts) { | ||
var cid = addrToId(opts); | ||
console.log('[wsclient] onMessage:', cid); | ||
var service = opts.service; | ||
var port = services[service]; | ||
var lclient; | ||
var servername; | ||
var str; | ||
var m; | ||
console.info("[connect] new client '" + cid + "' for '" + servername + "' (" + (handlers._numClients() + 1) + " clients)"); | ||
function endWithError() { | ||
try { | ||
wstunneler.send(pack(opts, null, 'error'), { binary: true }); | ||
} catch(e) { | ||
// ignore | ||
} | ||
} | ||
if (localclients[cid]) { | ||
console.log("[=>] received data from '" + cid + "' =>", opts.data.byteLength); | ||
localclients[cid].write(opts.data); | ||
return; | ||
} | ||
else if ('http' === service) { | ||
str = opts.data.toString(); | ||
m = str.match(/(?:^|[\r\n])Host: ([^\r\n]+)[\r\n]*/im); | ||
servername = (m && m[1].toLowerCase() || '').split(':')[0]; | ||
} | ||
else if ('https' === service) { | ||
servername = sni(opts.data); | ||
} | ||
else { | ||
endWithError(); | ||
return; | ||
} | ||
if (!servername) { | ||
console.warn("|__ERROR__| no servername found for '" + cid + "'", opts.data.byteLength); | ||
//console.warn(opts.data.toString()); | ||
wstunneler.send(pack(opts, null, 'error'), { binary: true }); | ||
return; | ||
} | ||
console.log("servername: '" + servername + "'"); | ||
lclient = localclients[cid] = net.createConnection({ port: port, host: '127.0.0.1' }, function () { | ||
console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength); | ||
lclient.write(opts.data); | ||
}); | ||
lclient.on('data', function (chunk) { | ||
console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes"); | ||
//console.log(JSON.stringify(chunk.toString())); | ||
wstunneler.send(pack(opts, chunk), { binary: true }); | ||
}); | ||
lclient.on('error', function (err) { | ||
console.error("[error] local '" + opts.service + "' '" + cid + "'"); | ||
console.error(err); | ||
delete localclients[cid]; | ||
try { | ||
wstunneler.send(pack(opts, null, 'error'), { binary: true }); | ||
} catch(e) { | ||
// ignore | ||
} | ||
}); | ||
lclient.on('end', function () { | ||
console.log("[end] local '" + opts.service + "' '" + cid + "'"); | ||
delete localclients[cid]; | ||
try { | ||
wstunneler.send(pack(opts, null, 'end'), { binary: true }); | ||
} catch(e) { | ||
// ignore | ||
} | ||
}); | ||
localclients[cid] = net.createConnection({ port: port, host: '127.0.0.1' }, function () { | ||
//console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength); | ||
localclients[cid].write(opts.data); | ||
}); | ||
localclients[cid].on('data', function (chunk) { | ||
//console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes"); | ||
//console.log(JSON.stringify(chunk.toString())); | ||
wstunneler.send(pack(opts, chunk), { binary: true }); | ||
}); | ||
localclients[cid].on('error', function (err) { | ||
handlers._onLocalError(cid, opts, err); | ||
}); | ||
localclients[cid].on('end', function () { | ||
console.info("[end] closing client '" + cid + "' for '" + servername + "' (" + (handlers._numClients() - 1) + " clients)"); | ||
handlers._onLocalClose(cid, opts); | ||
}); | ||
} | ||
, onend: function (opts) { | ||
var cid = addrToId(opts); | ||
//console.log("[end] '" + cid + "'"); | ||
handlers._onend(cid); | ||
} | ||
, onerror: function (opts) { | ||
var cid = addrToId(opts); | ||
//console.log("[error] '" + cid + "'", opts.code || '', opts.message); | ||
handlers._onend(cid); | ||
} | ||
, _onend: function (cid) { | ||
if (localclients[cid]) { | ||
try { | ||
localclients[cid].end(); | ||
} catch(e) { | ||
// ignore | ||
} | ||
, onend: function (opts) { | ||
var cid = addrToId(opts); | ||
console.log("[end] '" + cid + "'"); | ||
handlers._onend(cid); | ||
} | ||
, onerror: function (opts) { | ||
var cid = addrToId(opts); | ||
console.log("[error] '" + cid + "'", opts.code || '', opts.message); | ||
handlers._onend(cid); | ||
} | ||
, _onend: function (cid) { | ||
if (localclients[cid]) { | ||
localclients[cid].end(); | ||
} | ||
delete localclients[cid]; | ||
} | ||
}; | ||
var machine = require('tunnel-packer').create(handlers); | ||
wstunneler.on('message', machine.fns.addChunk); | ||
} | ||
delete localclients[cid]; | ||
} | ||
, _onLocalClose: function (cid, opts, err) { | ||
try { | ||
wstunneler.send(pack(opts, null, err && 'error' || 'end'), { binary: true }); | ||
} catch(e) { | ||
// ignore | ||
} | ||
delete localclients[cid]; | ||
} | ||
, _onLocalError: function (cid, opts, err) { | ||
console.info("[error] closing '" + cid + "' because '" + err.message + "' (" + (handlers._numClients() - 1) + " clients)"); | ||
handlers._onLocalClose(cid, opts, err); | ||
} | ||
, _numClients: function () { | ||
return Object.keys(localclients).length; | ||
} | ||
}; | ||
var wsHandlers = { | ||
onOpen: function () { | ||
console.info("[open] connected to '" + copts.stunneld + "'"); | ||
} | ||
wstunneler.on('open', onOpen); | ||
, onClose: function () { | ||
if (!authenticated) { | ||
console.info('[close] failed on first attempt... check authentication.'); | ||
} | ||
else if (retry) { | ||
console.info('[retry] disconnected and waiting...'); | ||
setTimeout(run, 5000, copts); | ||
} | ||
else { | ||
console.info('[close] closing tunnel to exit...'); | ||
} | ||
wstunneler.on('close', function () { | ||
console.log('closing tunnel...'); | ||
process.removeListener('exit', onExit); | ||
process.removeListener('SIGINT', onExit); | ||
process.removeListener('exit', wsHandlers.onExit); | ||
process.removeListener('SIGINT', wsHandlers.onExit); | ||
Object.keys(localclients).forEach(function (cid) { | ||
@@ -173,23 +166,17 @@ try { | ||
} | ||
delete localclients[cid]; | ||
}); | ||
} | ||
if (retry) { | ||
console.log('retry on close'); | ||
setTimeout(run, 5000); | ||
} | ||
}); | ||
wstunneler.on('error', function (err) { | ||
console.error("[error] will retry on 'close'"); | ||
, onError: function (err) { | ||
console.error("[tunnel error] " + err.message); | ||
console.error(err); | ||
}); | ||
} | ||
function onExit() { | ||
, onExit: function () { | ||
retry = false; | ||
console.log('on exit...'); | ||
try { | ||
wstunneler.close(); | ||
} catch(e) { | ||
console.error("[error] wstunneler.close()"); | ||
console.error(e); | ||
@@ -199,8 +186,24 @@ // ignore | ||
} | ||
}; | ||
var machine = require('tunnel-packer').create(handlers); | ||
process.on('exit', onExit); | ||
process.on('SIGINT', onExit); | ||
} | ||
console.info("[connect] '" + copts.stunneld + "'"); | ||
module.exports.connect = run; | ||
wstunneler = new WebSocket(tunnelUrl, { rejectUnauthorized: !copts.insecure }); | ||
wstunneler.on('open', wsHandlers.onOpen); | ||
wstunneler.on('message', function (data, flags) { | ||
if (data.error || '{' === data[0]) { | ||
console.log(data); | ||
return; | ||
} | ||
machine.fns.addChunk(data, flags); | ||
}); | ||
wstunneler.on('close', wsHandlers.onClose); | ||
wstunneler.on('error', wsHandlers.onError); | ||
process.on('exit', wsHandlers.onExit); | ||
process.on('SIGINT', wsHandlers.onExit); | ||
} | ||
module.exports.connect = run; | ||
}()); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27755
367