2016-09-30 17:47:43 +00:00
#!/usr/bin/env node
2016-09-30 16:33:38 +00:00
( function ( ) {
'use strict' ;
var pkg = require ( '../package.json' ) ;
var program = require ( 'commander' ) ;
2016-09-30 19:04:27 +00:00
var url = require ( 'url' ) ;
2016-09-30 16:33:38 +00:00
var stunnel = require ( '../wsclient.js' ) ;
2016-10-11 23:43:24 +00:00
function collectProxies ( val , memo ) {
var vals = val . split ( /,/g ) ;
function parseProxy ( location ) {
2017-03-28 21:31:45 +00:00
// john.example.com
// https:3443
2016-10-11 23:43:24 +00:00
// http:john.example.com:3000
// http://john.example.com:3000
var parts = location . split ( ':' ) ;
var dual = false ;
2017-03-28 21:31:45 +00:00
if ( 1 === parts . length ) {
// john.example.com -> :john.example.com:0
2016-10-11 23:43:24 +00:00
parts [ 1 ] = parts [ 0 ] ;
2017-03-28 21:31:45 +00:00
parts [ 0 ] = '' ;
parts [ 2 ] = 0 ;
2016-10-11 23:43:24 +00:00
dual = true ;
2016-10-06 05:27:57 +00:00
}
2017-03-28 21:31:45 +00:00
else if ( 2 === parts . length ) {
// https:3443 -> https:*:3443
parts [ 2 ] = parts [ 1 ] ;
parts [ 1 ] = '*' ;
}
2016-10-11 23:43:24 +00:00
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?
2017-03-28 21:31:45 +00:00
if ( ! parts [ 0 ] || 'http' === parts [ 0 ] ) {
2016-10-11 23:43:24 +00:00
parts [ 2 ] = 80 ;
}
else if ( 'https' === parts [ 0 ] ) {
parts [ 2 ] = 443 ;
}
else {
throw new Error ( "port must be specified - ex: tls:*:1337" ) ;
}
2016-09-30 16:33:38 +00:00
}
2016-10-11 23:43:24 +00:00
memo . push ( {
2017-03-28 21:31:45 +00:00
protocol : parts [ 0 ] || 'https'
2016-10-11 23:43:24 +00:00
, hostname : parts [ 1 ]
2017-03-28 21:31:45 +00:00
, port : parts [ 2 ] || 443
2016-10-11 23:43:24 +00:00
} ) ;
if ( dual ) {
memo . push ( {
protocol : 'http'
, hostname : parts [ 1 ]
2017-03-28 21:31:45 +00:00
, port : 80
2016-10-11 23:43:24 +00:00
} ) ;
2016-10-06 21:01:58 +00:00
}
}
2016-10-11 23:43:24 +00:00
vals . map ( function ( val ) {
return parseProxy ( val ) ;
2016-09-30 19:04:27 +00:00
} ) ;
2016-09-30 16:33:38 +00:00
return memo ;
}
program
. version ( pkg . version )
//.command('jsurl <url>')
. arguments ( '<url>' )
. action ( function ( url ) {
program . url = url ;
} )
2016-09-30 19:04:27 +00:00
. option ( '-k --insecure' , 'Allow TLS connections to stunneld without valid certs (rejectUnauthorized: false)' )
2016-09-30 16:33:38 +00:00
. 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
2017-03-28 21:31:45 +00:00
. 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`)' )
2016-09-30 16:33:38 +00:00
. option ( '--stunneld <URL>' , 'the domain (or ip address) at which you are running stunneld.js (the proxy)' ) // --proxy
2016-09-30 19:04:27 +00:00
. 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)' )
2017-03-26 07:37:26 +00:00
. 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)' )
2016-09-30 16:33:38 +00:00
. parse ( process . argv )
;
2017-03-26 07:37:26 +00:00
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 ;
}
} ;
2016-09-30 19:04:27 +00:00
2017-03-26 07:37:26 +00:00
program . locals . forEach ( function ( proxy ) {
console . log ( '[local proxy]' , proxy . protocol + '://' + proxy . hostname + ':' + proxy . port ) ;
} ) ;
2017-03-28 21:31:45 +00:00
stunnel . connect ( {
stunneld : program . stunneld
, locals : program . locals
, services : program . services
, net : program . net
, insecure : program . insecure
, token : program . token
} ) ;
2016-09-30 19:04:27 +00:00
}
2017-03-26 07:37:26 +00:00
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 ) ;
2016-10-06 05:02:28 +00:00
}
2017-03-26 07:37:26 +00:00
program . stunneld = location . protocol + '//' + location . hostname + ( location . port ? ':' + location . port : '' ) ;
2017-03-28 21:31:45 +00:00
tokenData . domains = Object . keys ( domainsMap ) . filter ( Boolean ) ;
2016-10-06 05:27:57 +00:00
2017-03-26 07:37:26 +00:00
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 ) {
2017-03-28 21:31:45 +00:00
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 ( ) ;
2017-03-26 07:37:26 +00:00
} ) ;
} ) ;
}
var domainsMap = { } ;
2016-10-06 05:27:57 +00:00
program . locals . forEach ( function ( proxy ) {
2017-03-26 07:37:26 +00:00
domainsMap [ proxy . hostname ] = true ;
2016-10-06 05:27:57 +00:00
} ) ;
2017-03-28 21:31:45 +00:00
if ( domainsMap . hasOwnProperty ( '*' ) ) {
//delete domainsMap['*'];
domainsMap [ '*' ] = false ;
}
2016-10-06 05:27:57 +00:00
2017-03-26 07:37:26 +00:00
if ( ! ( program . secret || program . token ) && ! program . stunneld ) {
daplieTunnel ( ) ;
}
else {
rawTunnel ( ) ;
}
2016-09-30 16:33:38 +00:00
} ( ) ) ;