From 08051e3401d969eff38eda3a48c3ab6f06a25af2 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 6 Oct 2016 15:01:58 -0600 Subject: [PATCH] update docs, refactor --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++ bin/stunnel.js | 100 ++++++++++++++++++------------------------------- wsclient.js | 14 ++++--- 3 files changed, 137 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index a8fb2aa..53d80d7 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ 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. +* CLI +* Library + CLI === @@ -43,3 +46,92 @@ Examples of good: education, business, pleasure. Examples of evil: crime, abuse, ```bash stunnel.js --agree-tos --email john@example.com --locals http:john.example.com:4080,https:john.example.com:8443 ``` + +Library +======= + +### Example + +```javascript +var stunnel = require('stunnel'); + +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' } + + // 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 `streams.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 could do a little magic like this: + +``` +stunnel.connect({ + // ... +, net: { + createConnection: function (info, cb) { + // data is the hello packet / first chunk + // info = { data, servername, port, host, remoteAddress: { family, address, port } } + // socket = { write, push, end, events: [ 'readable', 'data', 'error', 'end' ] }; + + 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) { + console.log('_read', size); + this.push(x); + } + } + }; + + var myDuplex = new (require('streams').Duplex); + + myDuplex.__my_socket = socket; + 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); + + return myDuplex; + } +}); +``` diff --git a/bin/stunnel.js b/bin/stunnel.js index 81ba1d4..1c7b75b 100755 --- a/bin/stunnel.js +++ b/bin/stunnel.js @@ -8,38 +8,43 @@ var program = require('commander'); var url = require('url'); var stunnel = require('../wsclient.js'); +function parseProxy(location) { + // http:john.example.com:3000 + // http://john.example.com:3000 + var parts = location.split(':'); + //var dual = false; + if (/\./.test(parts[0])) { + //dual = true; + parts[2] = parts[1]; + parts[1] = parts[0]; + parts[0] = 'https'; + } + parts[0] = parts[0].toLowerCase(); + parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*'; + parts[2] = parseInt(parts[2], 10) || 0; + if (!parts[2]) { + // TODO grab OS list of standard ports? + if ('http' === parts[0]) { + parts[2] = 80; + } + else if ('https' === parts[0]) { + parts[2] = 443; + } + else { + throw new Error("port must be specified - ex: tls:*:1337"); + } + } + + return { + protocol: parts[0] + , hostname: parts[1] + , port: parts[2] + }; +} + function collectProxies(val, memo) { var vals = val.split(/,/g); - vals.map(function (location) { - // http:john.example.com:3000 - // http://john.example.com:3000 - var parts = location.split(':'); - if (!parts[1]) { - parts[1] = parts[0]; - parts[0] = 'https'; - } - parts[0] = parts[0].toLowerCase(); - parts[1] = parts[1].toLowerCase().replace(/(\/\/)?/, '') || '*'; - parts[2] = parseInt(parts[2], 10) || 0; - if (!parts[2]) { - // TODO grab OS list of standard ports? - if ('http' === parts[0]) { - parts[2] = 80; - } - else if ('https' === parts[0]) { - parts[2] = 443; - } - else { - throw new Error("port must be specified - ex: tls:*:1337"); - } - } - - return { - protocol: parts[0] - , hostname: parts[1] - , port: parts[2] - }; - }).forEach(function (val) { + vals.map(parseProxy).forEach(function (val) { memo.push(val); }); @@ -83,45 +88,12 @@ program.locals.forEach(function (proxy) { tokenData.domains = Object.keys(domainsMap); tokenData.name = tokenData.domains[0]; -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'); program.net = { createConnection: function (info, cb) { - /* - var Dup = { - write: function (chunk, encoding, cb) { - //console.log('_write', chunk.byteLength); - this.__my_socket.write(chunk, encoding); - cb(); - } - , read: function (size) { - //console.log('_read'); - var x = this.__my_socket.read(size); - if (x) { - console.log('_read', size); - this.push(x); - } - } - }; - var myDuplex = new (require('streams').Duplex); - myDuplex._write = Dup.write; - myDuplex._read = Dup.read; - myDuplex.remoteFamily = socket.remoteFamily; - myDuplex.remoteAddress = socket.remoteAddress; - myDuplex.remotePort = socket.remotePort; - myDuplex.localFamily = socket.localFamily; - myDuplex.localAddress = socket.localAddress; - myDuplex.localPort = socket.localPort; - httpsServer.emit('connection', myDuplex); - */ - // data is the hello packet / first chunk - // info = { data, servername, port, host, remoteAddress: { family, address, port } } + // 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); diff --git a/wsclient.js b/wsclient.js index 0edbe42..5cf8f76 100644 --- a/wsclient.js +++ b/wsclient.js @@ -17,6 +17,12 @@ return; //*/ function run(copts) { + copts.services = {}; + copts.locals.forEach(function (proxy) { + //program.services = { 'ssh': 22, 'http': 80, 'https': 443 }; + copts.services[proxy.protocol] = proxy.port; + }); + var services = copts.services; // TODO pair with hostname / sni var token = copts.token; var tunnelUrl = copts.stunneld.replace(/\/$/, '') + '/?access_token=' + token; @@ -71,11 +77,9 @@ function run(copts) { , port: port , host: '127.0.0.1' , data: opts.data - , remoteAddress: { - family: opts.family - , address: opts.address - , port: opts.port - } + , remoteFamily: opts.family + , remoteAddress: opts.address + , remotePort: opts.port }, function () { //console.log("[=>] first packet from tunneler to '" + cid + "' as '" + opts.service + "'", opts.data.byteLength); localclients[cid].write(opts.data);