Socket
Socket
Sign inDemoInstall

stunnel

Package Overview
Dependencies
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

stunnel - npm Package Compare versions

Comparing version 0.8.1 to 0.9.0

httpsclient.js

171

bin/stunnel.js

@@ -13,6 +13,26 @@ #!/usr/bin/env node

var vals = val.split(/,/g);
vals.map(function (location) {
function parseProxy(location) {
// john.example.com
// https:3443
// http:john.example.com:3000
// http://john.example.com:3000
var parts = location.split(':');
var dual = false;
if (1 === parts.length) {
// john.example.com -> :john.example.com:0
parts[1] = parts[0];
parts[0] = '';
parts[2] = 0;
dual = true;
}
else if (2 === parts.length) {
// https:3443 -> https:*:3443
parts[2] = parts[1];
parts[1] = '*';
}
parts[0] = parts[0].toLowerCase();

@@ -23,3 +43,3 @@ parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*';

// TODO grab OS list of standard ports?
if ('http' === parts[0]) {
if (!parts[0] || 'http' === parts[0]) {
parts[2] = 80;

@@ -35,9 +55,19 @@ }

return {
protocol: parts[0]
memo.push({
protocol: parts[0] || 'https'
, hostname: parts[1]
, port: parts[2]
};
}).forEach(function (val) {
memo.push(val);
, port: parts[2] || 443
});
if (dual) {
memo.push({
protocol: 'http'
, hostname: parts[1]
, port: 80
});
}
}
vals.map(function (val) {
return parseProxy(val);
});

@@ -56,40 +86,119 @@

.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('--locals <LIST>', 'comma separated list of <proto>:<port> to which matching incoming http and https should forward (reverse proxy). Ex: https:8443,smtps:8465', collectProxies, [ ]) // --reverse-proxies
.option('--domains <LIST>', 'comma separated list of domain names to set to the tunnel (to caputer a specific protocol to a specific local port use the format https:example.com:1337 instead). Ex: example.com,example.net', collectProxies, [ ])
.option('--device [HOSTNAME]', 'Tunnel all domains associated with this device instead of specific domainnames. Use with --locals <proto>:*:<port>. Ex: macbook-pro.local (the output of `hostname`)')
.option('--stunneld <URL>', 'the domain (or ip address) at which you are running stunneld.js (the proxy)') // --proxy
.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)')
.option('--agree-tos', 'agree to the Daplie Terms of Service (requires user validation)')
.option('--email <EMAIL>', 'email address (or cloud address) for user validation')
.option('--oauth3-url <URL>', 'Cloud Authentication to use (default: https://oauth3.org)')
.parse(process.argv)
;
program.stunneld = program.stunneld || 'wss://pokemap.hellabit.com:3000';
function connectTunnel() {
program.net = {
createConnection: function (info, cb) {
// data is the hello packet / first chunk
// info = { data, servername, port, host, remoteFamily, remoteAddress, remotePort }
var net = require('net');
// socket = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
var socket = net.createConnection({ port: info.port, host: info.host }, cb);
return socket;
}
};
var jwt = require('jsonwebtoken');
var domainsMap = {};
var tokenData = {
name: null
, domains: null
};
var location = url.parse(program.stunneld);
program.locals.forEach(function (proxy) {
console.log('[local proxy]', proxy.protocol + '://' + proxy.hostname + ':' + proxy.port);
});
if (!location.protocol || /\./.test(location.protocol)) {
program.stunneld = 'wss://' + program.stunneld;
location = url.parse(program.stunneld);
stunnel.connect({
stunneld: program.stunneld
, locals: program.locals
, services: program.services
, net: program.net
, insecure: program.insecure
, token: program.token
});
}
program.stunneld = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
function rawTunnel() {
program.stunneld = program.stunneld || 'wss://tunnel.daplie.com';
if (!(program.secret || program.token)) {
console.error("You must use --secret or --token with --stunneld");
process.exit(1);
return;
}
var jwt = require('jsonwebtoken');
var tokenData = {
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 : '');
tokenData.domains = Object.keys(domainsMap).filter(Boolean);
program.token = program.token || jwt.sign(tokenData, program.secret);
connectTunnel();
}
function daplieTunnel() {
//var OAUTH3 = require('oauth3.js');
var Oauth3Cli = require('oauth3.js/bin/oauth3.js');
require('oauth3.js/oauth3.tunnel.js');
return Oauth3Cli.login({
email: program.email
, providerUri: program.oauth3Url
}).then(function (oauth3) {
var data = { device: null, domains: [] };
var domains = Object.keys(domainsMap).filter(Boolean);
if (program.device) {
// TODO use device API to select device by id
data.device = { hostname: program.device };
if (true === program.device) {
data.device.hostname = require('os').hostname();
console.log("Using device hostname '" + data.device.hostname + "'");
}
}
if (domains.length) {
data.domains = domains;
}
return oauth3.api('tunnel.token', { data: data }).then(function (results) {
var token = new Buffer(results.jwt.split('.')[1], 'base64').toString('utf8');
console.log('tunnel token issued:');
console.log(token);
program.token = results.jwt;
program.stunneld = results.tunnelUrl || ('wss://' + token.aud + '/');
connectTunnel();
});
});
}
var domainsMap = {};
program.locals = program.locals.concat(program.domains);
program.locals.forEach(function (proxy) {
domainsMap[proxy.hostname] = true;
});
tokenData.domains = Object.keys(domainsMap);
tokenData.name = tokenData.domains[0];
if (domainsMap.hasOwnProperty('*')) {
//delete domainsMap['*'];
domainsMap['*'] = false;
}
program.services = {};
program.locals.forEach(function (proxy) {
//program.services = { 'ssh': 22, 'http': 80, 'https': 443 };
program.services[proxy.protocol] = proxy.port;
});
program.token = program.token || jwt.sign(tokenData, program.secret || 'shhhhh');
if (!(program.secret || program.token) && !program.stunneld) {
daplieTunnel();
}
else {
rawTunnel();
}
stunnel.connect(program);
}());

2

client.js

@@ -12,3 +12,3 @@ 'use strict';

var services = { 'ssh': 22, 'http': 4080, 'https': 8443 };
var hostname = 'aj.daplie.me'; // 'pokemap.hellabit.com'
var hostname = 'aj.daplie.me'; // 'test.hellabit.com'

@@ -15,0 +15,0 @@ function addrToId(address) {

{
"name": "stunnel",
"version": "0.8.1",
"version": "0.9.0",
"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.",

@@ -16,3 +16,3 @@ "main": "wsclient.js",

"type": "git",
"url": "git+ssh://git@github.com/Daplie/node-tunnel-client.git"
"url": "git+ssh://git@git.daplie.com/Daplie/node-tunnel-client.git"
},

@@ -46,12 +46,13 @@ "keywords": [

"bugs": {
"url": "https://github.com/Daplie/node-tunnel-client/issues"
"url": "https://git.daplie.com/Daplie/node-tunnel-client/issues"
},
"homepage": "https://github.com/Daplie/node-tunnel-client#readme",
"homepage": "https://git.daplie.com/Daplie/node-tunnel-client#readme",
"dependencies": {
"commander": "^2.9.0",
"oauth3.js": "git+https://git.daplie.com:OAuth3/oauth3.js.git#v1",
"jsonwebtoken": "^7.1.9",
"sni": "^1.0.0",
"tunnel-packer": "^1.0.0",
"tunnel-packer": "^1.1.0",
"ws": "^1.1.1"
}
}

@@ -0,1 +1,16 @@

<!-- BANNER_TPL_BEGIN -->
About Daplie: We're taking back the Internet!
--------------
Down with Google, Apple, and Facebook!
We're re-decentralizing the web and making it read-write again - one home cloud system at a time.
Tired of serving the Empire? Come join the Rebel Alliance:
<a href="mailto:jobs@daplie.com">jobs@daplie.com</a> | [Invest in Daplie on Wefunder](https://daplie.com/invest/) | [Pre-order Cloud](https://daplie.com/preorder/), The World's First Home Server for Everyone
<!-- BANNER_TPL_END -->
# stunnel.js

@@ -6,2 +21,5 @@

* CLI
* Library
CLI

@@ -19,11 +37,52 @@ ===

### Advanced Usage
### Usage with OAuth3.org
Daplie's OAuth3.org tunnel service is in Beta.
**Terms of Service**: The Software and Services shall be used for Good, not Evil.
Examples of good: education, business, pleasure. Examples of evil: crime, abuse, extortion.
```bash
stunnel.js --agree-tos --email john@example.com --locals http:*:4080,https:*:8443 --device
```
```bash
stunnel.js \
--agree-tos --email <EMAIL> \
--locals <List of <SCHEME>:<EXTERNAL_DOMAINNAME>:<INTERNAL_PORT>> \
--device [HOSTNAME] \
--domains [Comma-separated list of domains to attach to device] \
--oauth3-url <Tunnel Service OAuth3 URL>
```
### Advanced Usage (DIY)
How to use `stunnel.js` with your own instance of `stunneld.js`:
```bash
stunnel.js --locals http:john.example.com:3000,https:john.example.com --stunneld https://tunnel.example.com:443 --secret abc123
stunnel.js \
--locals <<external domain name>> \
--stunneld wss://<<tunnel domain>>:<<tunnel port>> \
--secret <<128-bit hex key>>
```
```bash
stunnel.js --locals john.example.com --stunneld wss://tunnel.example.com:443 --secret abc123
```
```bash
stunnel.js \
--locals <<protocol>>:<<external domain name>>:<<local port>> \
--stunneld wss://<<tunnel domain>>:<<tunnel port>> \
--secret <<128-bit hex key>>
```
```bash
stunnel.js \
--locals http:john.example.com:3000,https:john.example.com \
--stunneld wss://tunnel.example.com:443 \
--secret abc123
```
```
--secret the same secret used by stunneld (used for authentication)

@@ -36,13 +95,99 @@ --locals comma separated list of <proto>:<servername>:<port> to which

### Usage
Library
=======
**NOT YET IMPLEMENTED**
### Example
Daplie's tunneling service is not yet publicly available.
```javascript
var stunnel = require('stunnel');
**Terms of Service**: The Software and Services shall be used for Good, not Evil.
Examples of good: education, business, pleasure. Examples of evil: crime, abuse, extortion.
stunnel.connect({
stunneld: 'wss://tunnel.example.com'
, token: '...'
, locals: [
// defaults to sending http to local port 80 and https to local port 443
{ hostname: 'doe.net' }
```bash
stunnel.js --agree-tos --email john@example.com --locals http:john.example.com:4080,https:john.example.com:8443
// sends both http and https to local port 3000 (httpolyglot)
, { protocol: 'https', hostname: 'john.doe.net', port: 3000 }
// send http to local port 4080 and https to local port 8443
, { protocol: 'https', hostname: 'jane.doe.net', port: 4080 }
, { protocol: 'https', hostname: 'jane.doe.net', port: 8443 }
]
, net: require('net')
, insecure: false
});
```
* You can get sneaky with `net` and provide a `createConnection` that returns a `stream.Duplex`.
### Token
```javascript
var tokenData = { domains: [ 'doe.net', 'john.doe.net', 'jane.doe.net' ] }
var secret = 'shhhhh';
var token = jwt.sign(tokenData, secret);
```
### net
Let's say you want to handle http requests in-process
or decrypt https before passing it to the local http handler.
You'll need to create a pair of streams to connect between the
local handler and the tunnel handler.
You could do a little magic like this:
```js
var Dup = {
write: function (chunk, encoding, cb) {
this.__my_socket.write(chunk, encoding);
cb();
}
, read: function (size) {
var x = this.__my_socket.read(size);
if (x) { this.push(x); }
}
};
stunnel.connect({
// ...
, net: {
createConnection: function (info, cb) {
// data is the hello packet / first chunk
// info = { data, servername, port, host, remoteAddress: { family, address, port } }
var myDuplex = new (require('stream').Duplex)();
var myDuplex2 = new (require('stream').Duplex)();
// duplex = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] };
myDuplex2.__my_socket = myDuplex;
myDuplex2._write = Dup.write;
myDuplex2._read = Dup.read;
myDuplex.__my_socket = myDuplex2;
myDuplex._write = Dup.write;
myDuplex._read = Dup.read;
myDuplex.remoteFamily = info.remoteFamily;
myDuplex.remoteAddress = info.remoteAddress;
myDuplex.remotePort = info.remotePort;
// socket.local{Family,Address,Port}
myDuplex.localFamily = 'IPv4';
myDuplex.localAddress = '127.0.01';
myDuplex.localPort = info.port;
httpsServer.emit('connection', myDuplex);
if (cb) {
process.nextTick(cb);
}
return myDuplex2;
}
});
```
(function () {
'use strict';
var net = require('net');
var WebSocket = require('ws');
var sni = require('sni');
var pack = require('tunnel-packer').pack;
var Packer = require('tunnel-packer');
var authenticated = false;
// TODO move these helpers to tunnel-packer package
function addrToId(address) {
return address.family + ',' + address.address + ',' + address.port;
}
function run(copts) {
// TODO pair with hostname / sni
copts.services = {};
copts.locals.forEach(function (proxy) {
//program.services = { 'ssh': 22, 'http': 80, 'https': 443 };
copts.services[proxy.protocol] = proxy.port;
});
/*
function socketToAddr(socket) {
return { family: socket.remoteFamily, address: socket.remoteAddress, port: socket.remotePort };
}
function socketToId(socket) {
return addrToId(socketToAddr(socket));
}
*/
/*
var request = require('request');
request.get('https://pokemap.hellabit.com:3000?access_token=' + token, { rejectUnauthorized: false }, function (err, resp) {
console.log('resp.body');
console.log(resp.body);
});
return;
//*/
function run(copts) {
var services = copts.services; // TODO pair with hostname / sni
var token = copts.token;
var tunnelUrl = copts.stunneld + '/?access_token=' + token;
var tunnelUrl = copts.stunneld.replace(/\/$/, '') + '/?access_token=' + copts.token;
var wstunneler;
var retry = true;
var localclients = {};

@@ -50,5 +27,6 @@ // BaaS / Backendless / noBackend / horizon.io

onmessage: function (opts) {
var cid = addrToId(opts);
var net = copts.net || require('net');
var cid = Packer.addrToId(opts);
var service = opts.service;
var port = services[service];
var port = copts.services[service];
var servername;

@@ -81,3 +59,3 @@ var str;

//console.warn(opts.data.toString());
wstunneler.send(pack(opts, null, 'error'), { binary: true });
wstunneler.send(Packer.pack(opts, null, 'error'), { binary: true });
return;

@@ -88,11 +66,49 @@ }

localclients[cid] = net.createConnection({ port: port, host: '127.0.0.1' }, function () {
console.log('port', port, opts.port, service, copts.services);
localclients[cid] = net.createConnection({
port: port
, host: '127.0.0.1'
, servername: servername
, data: opts.data
, remoteFamily: opts.family
, remoteAddress: opts.address
, remotePort: opts.port
}, function () {
//console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength);
// this will happen before 'data' is triggered
localclients[cid].write(opts.data);
});
// '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 });
wstunneler.send(Packer.pack(opts, chunk), { binary: true });
});
//*/
///*
localclients[cid].on('readable', function (size) {
var chunk;
if (!localclients[cid]) {
console.error("[error] localclients[cid]", cid);
return;
}
if (!localclients[cid].read) {
console.error("[error] localclients[cid].read", cid);
console.log(localclients[cid]);
return;
}
do {
chunk = localclients[cid].read(size);
//console.log("[<=] local '" + opts.service + "' sent to '" + cid + "' <= ", chunk.byteLength, "bytes");
//console.log(JSON.stringify(chunk.toString()));
if (chunk) {
wstunneler.send(Packer.pack(opts, chunk), { binary: true });
}
} while (chunk);
});
//*/
localclients[cid].on('error', function (err) {

@@ -107,3 +123,3 @@ handlers._onLocalError(cid, opts, err);

, onend: function (opts) {
var cid = addrToId(opts);
var cid = Packer.addrToId(opts);
//console.log("[end] '" + cid + "'");

@@ -113,3 +129,3 @@ handlers._onend(cid);

, onerror: function (opts) {
var cid = addrToId(opts);
var cid = Packer.addrToId(opts);
//console.log("[error] '" + cid + "'", opts.code || '', opts.message);

@@ -119,2 +135,3 @@ handlers._onend(cid);

, _onend: function (cid) {
console.log('[_onend]');
if (localclients[cid]) {

@@ -130,4 +147,5 @@ try {

, _onLocalClose: function (cid, opts, err) {
console.log('[_onLocalClose]');
try {
wstunneler.send(pack(opts, null, err && 'error' || 'end'), { binary: true });
wstunneler.send(Packer.pack(opts, null, err && 'error' || 'end'), { binary: true });
} catch(e) {

@@ -151,7 +169,21 @@ // ignore

, retry: true
, closeClients: function () {
console.log('[close clients]');
Object.keys(localclients).forEach(function (cid) {
try {
localclients[cid].end();
} catch(e) {
// ignore
}
delete localclients[cid];
});
}
, onClose: function () {
console.log('ON CLOSE');
if (!authenticated) {
console.info('[close] failed on first attempt... check authentication.');
}
else if (retry) {
else if (wsHandlers.retry) {
console.info('[retry] disconnected and waiting...');

@@ -166,10 +198,3 @@ setTimeout(run, 5000, copts);

process.removeListener('SIGINT', wsHandlers.onExit);
Object.keys(localclients).forEach(function (cid) {
try {
localclients[cid].end();
} catch(e) {
// ignore
}
delete localclients[cid];
});
wsHandlers.closeClients();
}

@@ -183,3 +208,5 @@

, onExit: function () {
retry = false;
console.log('[wait] closing wstunneler...');
wsHandlers.retry = false;
wsHandlers.closeClients();
try {

@@ -209,4 +236,13 @@ wstunneler.close();

wstunneler.on('error', wsHandlers.onError);
process.on('exit', wsHandlers.onExit);
process.on('SIGINT', wsHandlers.onExit);
process.on('beforeExit', function (x) {
console.log('[beforeExit] event loop closing?', x);
});
process.on('exit', function (x) {
console.log('[exit] loop closed', x);
//wsHandlers.onExit(x);
});
process.on('SIGINT', function (x) {
console.log('SIGINT');
wsHandlers.onExit(x);
});
}

@@ -213,0 +249,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc