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' ) ;
2016-09-30 19:04:27 +00:00
var url = require ( 'url' ) ;
2018-06-05 08:04:32 +00:00
var path = require ( 'path' ) ;
2018-06-08 06:46:07 +00:00
var http = require ( 'http' ) ;
2018-06-09 20:00:47 +00:00
var YAML = require ( 'js-yaml' ) ;
2018-06-08 06:46:07 +00:00
var state = { servernames : { } , ports : { } } ;
2018-05-27 10:26:34 +00:00
var argv = process . argv . slice ( 2 ) ;
var confIndex = argv . indexOf ( '--config' ) ;
var confpath ;
2018-06-07 22:09:30 +00:00
var confargs ;
2018-05-27 10:26:34 +00:00
if ( - 1 === confIndex ) {
confIndex = argv . indexOf ( '-c' ) ;
}
2018-06-07 22:09:30 +00:00
if ( - 1 !== confIndex ) {
confargs = argv . splice ( confIndex , 2 ) ;
confpath = confargs [ 1 ] ;
}
2018-05-27 10:26:34 +00:00
function help ( ) {
2018-05-27 11:02:19 +00:00
console . info ( '' ) ;
console . info ( 'Telebit Remote v' + pkg . version ) ;
2018-05-27 10:26:34 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( 'Daemon Usage:' ) ;
console . info ( '' ) ;
console . info ( '\tsudo telebit daemon --config <path>' ) ;
console . info ( '\tex: sudo telebit daemon --config /opt/telebit/etc/telebit.yml' ) ;
console . info ( '' ) ;
console . info ( 'Remote Usage:' ) ;
2018-05-27 10:26:34 +00:00
console . info ( '' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '\ttelebit [--config <path>] <module> <module-option>' ) ;
2018-05-27 10:26:34 +00:00
console . info ( '' ) ;
2018-06-08 06:46:07 +00:00
console . info ( 'Examples:' ) ;
2018-05-27 10:26:34 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit status # whether enabled or disabled' ) ;
console . info ( '\ttelebit enable # disallow incoming connections' ) ;
console . info ( '\ttelebit disable # allow incoming connections' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit list # list rules for servernames and ports' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit http none # remove all https handlers' ) ;
console . info ( '\ttelebit http 3000 # forward all https traffic to port 3000' ) ;
console . info ( '\ttelebit http /module/path # load a node module to handle all https traffic' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit http none example.com # remove https handler from example.com' ) ;
console . info ( '\ttelebit http 3001 example.com # forward https traffic for example.com to port 3001' ) ;
console . info ( '\ttelebit http /module/path example.com # forward https traffic for example.com to port 3001' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit tcp none # remove all tcp handlers' ) ;
console . info ( '\ttelebit tcp 5050 # forward all tcp to port 5050' ) ;
console . info ( '\ttelebit tcp /module/path # handle all tcp with a node module' ) ;
2018-06-08 06:46:07 +00:00
console . info ( '' ) ;
2018-06-08 08:50:00 +00:00
console . info ( '\ttelebit tcp none 6565 # remove tcp handler from external port 6565' ) ;
console . info ( '\ttelebit tcp 5050 6565 # forward external port 6565 to local 5050' ) ;
console . info ( '\ttelebit tcp /module/path 6565 # handle external port 6565 with a node module' ) ;
2018-05-27 10:26:34 +00:00
console . info ( '' ) ;
console . info ( 'Config:' ) ;
console . info ( '' ) ;
console . info ( '\tSee https://git.coolaj86.com/coolaj86/telebit.js' ) ;
console . info ( '' ) ;
console . info ( '' ) ;
process . exit ( 0 ) ;
}
2018-06-08 16:46:18 +00:00
var verstr = '' + pkg . name + ' v' + pkg . version ;
2018-05-31 10:10:47 +00:00
if ( - 1 === confIndex ) {
2018-06-05 08:04:32 +00:00
confpath = path . join ( require ( 'os' ) . homedir ( ) , '.config/telebit/telebit.yml' ) ;
2018-06-08 16:46:18 +00:00
verstr += ' (--config "' + confpath + '")' ;
2018-05-31 10:10:47 +00:00
}
2018-06-08 16:46:18 +00:00
console . info ( verstr + '\n' ) ;
2018-05-31 10:10:47 +00:00
if ( - 1 !== argv . indexOf ( '-h' ) || - 1 !== argv . indexOf ( '--help' ) ) {
2018-05-27 10:26:34 +00:00
help ( ) ;
}
if ( ! confpath || /^--/ . test ( confpath ) ) {
help ( ) ;
}
2018-06-07 22:09:30 +00:00
var defaultSockname = '/opt/telebit/var/telebit.sock' ;
2018-06-07 07:27:56 +00:00
var tokenfile = 'access_token.txt' ;
var tokenpath = path . join ( path . dirname ( confpath ) , tokenfile ) ;
var token ;
try {
token = require ( 'fs' ) . readFileSync ( tokenpath , 'ascii' ) . trim ( ) ;
} catch ( e ) {
// ignore
}
2018-06-08 06:46:07 +00:00
var controlServer ;
2018-05-27 11:02:19 +00:00
require ( 'fs' ) . readFile ( confpath , 'utf8' , function ( err , text ) {
var config ;
var recase = require ( 'recase' ) . create ( { } ) ;
var camelCopy = recase . camelCopy . bind ( recase ) ;
2018-06-08 06:46:07 +00:00
var snakeCopy = recase . snakeCopy . bind ( recase ) ;
2018-05-27 11:02:19 +00:00
if ( err ) {
console . error ( "\nCouldn't load config:\n\n\t" + err . message + "\n" ) ;
process . exit ( 1 ) ;
return ;
}
try {
config = JSON . parse ( text ) ;
} catch ( e1 ) {
try {
2018-06-09 20:00:47 +00:00
config = YAML . safeLoad ( text ) ;
2018-05-27 11:02:19 +00:00
} catch ( e2 ) {
console . error ( e1 . message ) ;
console . error ( e2 . message ) ;
process . exit ( 1 ) ;
return ;
}
}
2018-06-05 08:04:32 +00:00
state . _confpath = confpath ;
2018-05-27 11:02:19 +00:00
state . config = camelCopy ( config ) ;
2018-06-07 07:27:56 +00:00
if ( state . config . token && token ) {
console . warn ( ) ;
console . warn ( "Found two tokens:" ) ;
console . warn ( ) ;
console . warn ( "\t1. " + tokenpath ) ;
console . warn ( "\n2. " + confpath ) ;
console . warn ( ) ;
console . warn ( "Choosing the first." ) ;
console . warn ( ) ;
}
2018-06-08 06:46:07 +00:00
state . token = token ;
if ( ! state . config . servernames ) {
state . config . servernames = { } ;
}
if ( ! state . config . ports ) {
state . config . ports = { } ;
}
state . servernames = JSON . parse ( JSON . stringify ( state . config . servernames ) ) ;
state . ports = JSON . parse ( JSON . stringify ( state . config . ports ) ) ;
2018-06-07 22:09:30 +00:00
2018-06-08 06:46:07 +00:00
function putConfig ( service , args ) {
2018-06-09 20:00:47 +00:00
// console.log('got it', service, args);
2018-06-07 22:09:30 +00:00
var http = require ( 'http' ) ;
var req = http . get ( {
socketPath : state . config . sock || defaultSockname
, method : 'POST'
2018-06-08 06:46:07 +00:00
, path : '/rpc/' + service + '?_body=' + JSON . stringify ( args )
2018-06-07 22:09:30 +00:00
} , function ( resp ) {
2018-06-08 06:46:07 +00:00
function finish ( ) {
if ( 200 !== resp . statusCode ) {
console . warn ( "'" + service + "' may have failed."
+ " Consider peaking at the logs either with 'journalctl -xeu telebit' or /opt/telebit/var/log/error.log" ) ;
2018-06-09 20:00:47 +00:00
console . warn ( resp . statusCode , body ) ;
2018-06-08 06:46:07 +00:00
} else {
if ( body ) {
console . info ( 'Response' ) ;
console . info ( body ) ;
} else {
console . info ( "👌" ) ;
}
}
}
var body = '' ;
if ( resp . headers [ 'content-length' ] ) {
resp . on ( 'data' , function ( chunk ) {
body += chunk . toString ( ) ;
} ) ;
resp . on ( 'end' , function ( ) {
finish ( ) ;
} ) ;
2018-06-07 22:09:30 +00:00
} else {
2018-06-08 06:46:07 +00:00
finish ( ) ;
2018-06-07 22:09:30 +00:00
}
} ) ;
req . on ( 'error' , function ( err ) {
console . error ( 'Error' ) ;
console . error ( err ) ;
return ;
} ) ;
}
2018-06-08 06:46:07 +00:00
var tun ;
function serveControls ( ) {
if ( ! state . config . disable ) {
tun = rawTunnel ( ) ;
}
controlServer = http . createServer ( function ( req , res ) {
var opts = url . parse ( req . url , true ) ;
if ( opts . query . _body ) {
try {
opts . body = JSON . parse ( opts . query . _body , true ) ;
} catch ( e ) {
res . statusCode = 500 ;
res . end ( '{"error":{"message":"?_body={{bad_format}}"}}' ) ;
return ;
}
}
2018-06-07 22:09:30 +00:00
2018-06-09 20:00:47 +00:00
function listSuccess ( ) {
res . end ( YAML . safeDump ( {
servernames : state . servernames
, ports : state . ports
, ssh : state . config . sshAuto || 'disabled'
} ) ) ;
2018-06-08 06:46:07 +00:00
}
2018-06-09 20:00:47 +00:00
function sshSuccess ( ) {
fs . writeFile ( confpath , YAML . safeDump ( snakeCopy ( state . config ) ) , function ( err ) {
2018-06-08 06:46:07 +00:00
if ( err ) {
res . statusCode = 500 ;
res . end ( '{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}' ) ;
return ;
}
res . end ( '{"success":true}' ) ;
} ) ;
}
if ( /http/ . test ( opts . path ) ) {
if ( ! opts . body ) {
res . statusCode = 422 ;
res . end ( '{"error":{"message":"needs more arguments"}}' ) ;
return ;
}
if ( opts . body [ 1 ] ) {
if ( ! state . servernames [ opts . body [ 1 ] ] ) {
res . statusCode = 400 ;
res . end ( '{"error":{"message":"bad servername \'' + opts . body [ 1 ] + '\'"' ) ;
return ;
}
state . servernames [ opts . body [ 1 ] ] . handler = opts . body [ 0 ] ;
} else {
Object . keys ( state . servernames ) . forEach ( function ( key ) {
state . servernames [ key ] . handler = opts . body [ 0 ] ;
} ) ;
}
res . end ( '{"success":true}' ) ;
return ;
}
if ( /tcp/ . test ( opts . path ) ) {
if ( ! opts . body ) {
res . statusCode = 422 ;
res . end ( '{"error":{"message":"needs more arguments"}}' ) ;
return ;
}
2018-06-08 08:50:00 +00:00
// portnum
2018-06-08 06:46:07 +00:00
if ( opts . body [ 1 ] ) {
2018-06-08 08:50:00 +00:00
if ( ! state . ports [ opts . body [ 1 ] ] ) {
2018-06-08 06:46:07 +00:00
res . statusCode = 400 ;
2018-06-09 20:00:47 +00:00
res . end ( '{"error":{"message":"bad port \'' + opts . body [ 1 ] + '\'"' ) ;
2018-06-08 06:46:07 +00:00
return ;
}
2018-06-08 08:50:00 +00:00
// forward-to port-or-module
state . ports [ opts . body [ 1 ] ] . handler = opts . body [ 0 ] ;
2018-06-08 06:46:07 +00:00
} else {
2018-06-08 08:50:00 +00:00
Object . keys ( state . ports ) . forEach ( function ( key ) {
state . ports [ key ] . handler = opts . body [ 0 ] ;
2018-06-08 06:46:07 +00:00
} ) ;
}
res . end ( '{"success":true}' ) ;
return ;
}
2018-06-09 20:00:47 +00:00
if ( /save|commit/ . test ( opts . path ) ) {
state . config . servernames = state . servernames ;
state . config . ports = state . ports ;
fs . writeFile ( confpath , YAML . safeDump ( snakeCopy ( state . config ) ) , function ( err ) {
if ( err ) {
res . statusCode = 500 ;
res . end ( '{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}' ) ;
return ;
}
listSuccess ( ) ;
} ) ;
return ;
}
if ( /ssh/ . test ( opts . path ) ) {
var sshAuto ;
if ( ! opts . body ) {
res . statusCode = 422 ;
res . end ( '{"error":{"message":"needs more arguments"}}' ) ;
return ;
}
sshAuto = opts . body [ 0 ] ;
if ( - 1 !== [ 'false' , 'none' , 'off' , 'disable' ] . indexOf ( sshAuto ) ) {
state . config . sshAuto = false ;
sshSuccess ( ) ;
return ;
}
if ( - 1 !== [ 'true' , 'auto' , 'on' , 'enable' ] . indexOf ( sshAuto ) ) {
state . config . sshAuto = 22 ;
sshSuccess ( ) ;
return ;
}
sshAuto = parseInt ( sshAuto , 10 ) ;
if ( ! sshAuto || sshAuto <= 0 || sshAuto > 65535 ) {
res . statusCode = 400 ;
res . end ( '{"error":{"message":"bad ssh_auto option \'' + opts . body [ 0 ] + '\'"' ) ;
return ;
}
state . config . sshAuto = sshAuto ;
sshSuccess ( ) ;
return ;
}
if ( /enable/ . test ( opts . path ) ) {
delete state . config . disable ; // = undefined;
if ( ! tun ) { tun = rawTunnel ( ) ; }
fs . writeFile ( confpath , YAML . safeDump ( snakeCopy ( state . config ) ) , function ( err ) {
if ( err ) {
res . statusCode = 500 ;
res . end ( '{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}' ) ;
return ;
}
listSuccess ( ) ;
} ) ;
return ;
}
if ( /disable/ . test ( opts . path ) ) {
state . config . disable = true ;
if ( tun ) { tun . end ( ) ; tun = null ; }
fs . writeFile ( confpath , YAML . safeDump ( snakeCopy ( state . config ) ) , function ( err ) {
if ( err ) {
res . statusCode = 500 ;
res . end ( '{"error":{"message":"Could not save config file. Perhaps you\'re not running as root?"}}' ) ;
return ;
}
res . end ( '{"success":true}' ) ;
} ) ;
return ;
}
if ( /status/ . test ( opts . path ) ) {
res . end ( '{"status":' + ( state . config . disable ? 'disabled' : 'enabled' ) + '}' ) ;
return ;
}
if ( /restart/ . test ( opts . path ) ) {
tun . end ( ) ;
res . end ( '{"success":true}' ) ;
controlServer . close ( function ( ) {
// TODO closeAll other things
process . nextTick ( function ( ) {
// system daemon will restart the process
process . exit ( 22 ) ; // use non-success exit code
} ) ;
} ) ;
return ;
}
if ( /list/ . test ( opts . path ) ) {
listSuccess ( ) ;
return ;
}
2018-06-07 22:09:30 +00:00
res . end ( '{"error":{"message":"unrecognized rpc"}}' ) ;
} ) ;
var pipename = ( state . config . sock || defaultSockname ) ;
2018-06-08 06:46:07 +00:00
var fs = require ( 'fs' ) ;
if ( fs . existsSync ( pipename ) ) {
fs . unlinkSync ( pipename ) ;
}
2018-06-07 22:09:30 +00:00
if ( /^win/i . test ( require ( 'os' ) . platform ( ) ) ) {
pipename = '\\\\?\\pipe' + pipename . replace ( /\// , '\\' ) ;
}
2018-06-08 06:46:07 +00:00
// mask is so that processes owned by other users
// can speak to this process, which is probably root-owned
2018-06-07 22:09:30 +00:00
var oldUmask = process . umask ( 0x0000 ) ;
2018-06-08 06:46:07 +00:00
controlServer . listen ( {
2018-06-07 22:09:30 +00:00
path : pipename
, writableAll : true
, readableAll : true
, exclusive : false
} , function ( ) {
process . umask ( oldUmask ) ;
} ) ;
}
2018-06-08 06:46:07 +00:00
// Two styles:
// http 3000
// http modulename
function makeRpc ( key ) {
2018-06-09 20:00:47 +00:00
if ( key !== argv [ 0 ] ) {
return false ;
2018-06-08 06:46:07 +00:00
}
2018-06-09 20:00:47 +00:00
putConfig ( argv [ 0 ] , argv . slice ( 1 ) ) ;
return true ;
2018-06-08 06:46:07 +00:00
}
2018-06-09 20:00:47 +00:00
if ( [ 'ssh' , 'http' , 'tcp' ] . some ( function ( key ) {
if ( key !== argv [ 0 ] ) {
return false ;
}
if ( argv [ 1 ] ) {
putConfig ( argv [ 0 ] , argv . slice ( 1 ) ) ;
2018-06-08 06:46:07 +00:00
return true ;
}
2018-06-09 20:00:47 +00:00
help ( ) ;
return true ;
2018-06-08 06:46:07 +00:00
} ) ) {
return true ;
}
2018-06-07 22:09:30 +00:00
2018-06-09 20:00:47 +00:00
if ( [ 'status' , 'enable' , 'disable' , 'restart' , 'list' , 'save' ] . some ( makeRpc ) ) {
return ;
}
2018-06-08 08:50:00 +00:00
if ( - 1 !== argv . indexOf ( 'daemon' ) ) {
serveControls ( ) ;
return ;
}
help ( ) ;
2018-05-27 11:02:19 +00:00
} ) ;
function connectTunnel ( ) {
2018-06-08 06:46:07 +00:00
function sigHandler ( ) {
console . info ( 'Received kill signal. Attempting to exit cleanly...' ) ;
// We want to handle cleanup properly unless something is broken in our cleanup process
// that prevents us from exitting, in which case we want the user to be able to send
// the signal again and exit the way it normally would.
process . removeListener ( 'SIGINT' , sigHandler ) ;
tun . end ( ) ;
controlServer . close ( ) ;
}
process . on ( 'SIGINT' , sigHandler ) ;
state . net = state . net || {
2018-05-27 11:02:19 +00:00
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 ;
}
} ;
2018-05-31 10:10:47 +00:00
state . greenlock = state . config . greenlock || { } ;
2018-06-08 06:46:07 +00:00
state . sortingHat = state . config . sortingHat || path . resolve ( _ _dirname , '..' , 'lib/sorting-hat.js' ) ;
2018-06-07 07:42:10 +00:00
// TODO sortingHat.print(); ?
2018-05-27 11:02:19 +00:00
2018-06-07 07:42:10 +00:00
if ( state . config . email && ! state . token ) {
console . info ( ) ;
console . info ( '==================================' ) ;
console . info ( '= HEY! LISTEN! =' ) ;
console . info ( '==================================' ) ;
console . info ( '= =' ) ;
console . info ( '= 1. Open your email =' ) ;
console . info ( '= 2. Click the magic login link =' ) ;
console . info ( '= 3. Check back here for deets =' ) ;
console . info ( '= =' ) ;
console . info ( '==================================' ) ;
console . info ( ) ;
}
2018-05-29 09:00:25 +00:00
// TODO Check undefined vs false for greenlock config
2018-06-08 06:46:07 +00:00
var remote = require ( '../' ) ;
state . handlers = {
grant : function ( grants ) {
console . info ( "" ) ;
console . info ( "Connect to your device by any of the following means:" ) ;
console . info ( "" ) ;
grants . forEach ( function ( arr ) {
if ( 'https' === arr [ 0 ] ) {
if ( ! state . servernames [ arr [ 1 ] ] ) {
state . servernames [ arr [ 1 ] ] = { } ;
2018-06-07 06:08:26 +00:00
}
2018-06-08 06:46:07 +00:00
} else if ( 'tcp' === arr [ 0 ] ) {
if ( ! state . ports [ arr [ 2 ] ] ) {
state . ports [ arr [ 2 ] ] = { } ;
2018-06-07 06:08:26 +00:00
}
2018-06-07 07:27:56 +00:00
}
2018-06-08 06:46:07 +00:00
if ( 'ssh+https' === arr [ 0 ] ) {
console . info ( "SSH+HTTPS" ) ;
} else if ( 'ssh' === arr [ 0 ] ) {
console . info ( "SSH" ) ;
} else if ( 'tcp' === arr [ 0 ] ) {
console . info ( "TCP" ) ;
} else if ( 'https' === arr [ 0 ] ) {
console . info ( "HTTPS" ) ;
}
console . info ( '\t' + arr [ 0 ] + '://' + arr [ 1 ] + ( arr [ 2 ] ? ( ':' + arr [ 2 ] ) : '' ) ) ;
if ( 'ssh+https' === arr [ 0 ] ) {
console . info ( "\tex: ssh -o ProxyCommand='openssl s_client -connect %h:%p -quiet' " + arr [ 1 ] + " -p 443\n" ) ;
} else if ( 'ssh' === arr [ 0 ] ) {
console . info ( "\tex: ssh " + arr [ 1 ] + " -p " + arr [ 2 ] + "\n" ) ;
} else if ( 'tcp' === arr [ 0 ] ) {
console . info ( "\tex: netcat " + arr [ 1 ] + " " + arr [ 2 ] + "\n" ) ;
} else if ( 'https' === arr [ 0 ] ) {
console . info ( "\tex: curl https://" + arr [ 1 ] + "\n" ) ;
}
} ) ;
}
, access _token : function ( opts ) {
console . info ( "Updating '" + tokenpath + "' with new token:" ) ;
try {
require ( 'fs' ) . writeFileSync ( tokenpath , opts . jwt ) ;
} catch ( e ) {
console . error ( "Token not saved:" ) ;
console . error ( e ) ;
2018-06-07 06:08:26 +00:00
}
}
2018-06-08 06:46:07 +00:00
} ;
state . greenlockConfig = {
version : state . greenlock . version || 'draft-11'
, server : state . greenlock . server || 'https://acme-v02.api.letsencrypt.org/directory'
, communityMember : state . greenlock . communityMember || state . config . communityMember
, telemetry : state . greenlock . telemetry || state . config . telemetry
2018-06-08 16:32:18 +00:00
, configDir : state . greenlock . configDir || path . resolve ( _ _dirname , '..' , 'etc/acme/' )
2018-06-08 06:46:07 +00:00
// TODO, store: require(state.greenlock.store.name || 'le-store-certbot').create(state.greenlock.store.options || {})
, approveDomains : function ( opts , certs , cb ) {
// Certs being renewed are listed in certs.altnames
if ( certs ) {
opts . domains = certs . altnames ;
cb ( null , { options : opts , certs : certs } ) ;
return ;
}
2018-05-29 09:00:25 +00:00
2018-06-08 06:46:07 +00:00
// by virtue of the fact that it's being tunneled through a
// trusted source that is already checking, we're good
//if (-1 !== state.config.servernames.indexOf(opts.domains[0])) {
opts . email = state . greenlock . email || state . config . email ;
opts . agreeTos = state . greenlock . agree || state . config . agreeTos ;
cb ( null , { options : opts , certs : certs } ) ;
return ;
//}
2018-05-31 10:10:47 +00:00
2018-06-08 06:46:07 +00:00
//cb(new Error("servername not found in allowed list"));
2018-05-29 09:00:25 +00:00
}
2018-06-08 06:46:07 +00:00
} ;
state . insecure = state . config . relay _ignore _invalid _certificates ;
// { relay, config, servernames, ports, sortingHat, net, insecure, token, handlers, greenlockConfig }
2018-05-27 11:02:19 +00:00
2018-06-08 06:46:07 +00:00
var tun = remote . connect ( {
relay : state . relay
, config : state . config
, sortingHat : state . sortingHat
, net : state . net
, insecure : state . insecure
, token : state . token
, servernames : state . servernames
, ports : state . ports
, handlers : state . handlers
, greenlockConfig : state . greenlockConfig
} ) ;
2018-05-27 11:02:19 +00:00
2018-06-07 22:09:30 +00:00
return tun ;
2018-05-27 11:02:19 +00:00
}
function rawTunnel ( ) {
2018-06-08 06:46:07 +00:00
state . relay = state . config . relay ;
if ( ! state . relay ) {
2018-06-05 08:04:32 +00:00
throw new Error ( "'" + state . _confpath + "' is missing 'relay'" ) ;
2018-05-27 11:02:19 +00:00
}
2018-06-05 08:04:32 +00:00
/ *
2018-05-27 11:02:19 +00:00
if ( ! ( state . config . secret || state . config . token ) ) {
console . error ( "You must use --secret or --token with --relay" ) ;
process . exit ( 1 ) ;
return ;
}
2018-06-05 08:04:32 +00:00
* /
2018-05-27 11:02:19 +00:00
2018-06-08 06:46:07 +00:00
var location = url . parse ( state . relay ) ;
2018-05-27 11:02:19 +00:00
if ( ! location . protocol || /\./ . test ( location . protocol ) ) {
2018-06-08 06:46:07 +00:00
state . relay = 'wss://' + state . relay ;
location = url . parse ( state . relay ) ;
2018-05-27 11:02:19 +00:00
}
var aud = location . hostname + ( location . port ? ':' + location . port : '' ) ;
2018-06-08 06:46:07 +00:00
state . relay = location . protocol + '//' + aud ;
2018-05-27 11:02:19 +00:00
2018-06-05 08:04:32 +00:00
if ( ! state . config . token && state . config . secret ) {
2018-05-27 11:02:19 +00:00
var jwt = require ( 'jsonwebtoken' ) ;
var tokenData = {
2018-06-05 08:04:32 +00:00
domains : Object . keys ( state . config . servernames || { } ) . filter ( function ( name ) { return /\./ . test ( name ) ; } )
2018-05-27 11:02:19 +00:00
, aud : aud
, iss : Math . round ( Date . now ( ) / 1000 )
} ;
state . token = jwt . sign ( tokenData , state . config . secret ) ;
}
state . token = state . token || state . config . token ;
2018-06-05 08:04:32 +00:00
// TODO sign token with own private key, including public key and thumbprint
// (much like ACME JOSE account)
2018-06-07 22:09:30 +00:00
return connectTunnel ( ) ;
2018-05-27 11:02:19 +00:00
}
/ *
2017-09-08 16:54:47 +00:00
var domainsMap = { } ;
var services = { } ;
2017-03-30 01:29:37 +00:00
function collectDomains ( val , memo ) {
var vals = val . split ( /,/g ) ;
function parseProxy ( location ) {
// john.example.com
// http:john.example.com:3000
// http://john.example.com:3000
var parts = location . split ( ':' ) ;
if ( 1 === parts . length ) {
// john.example.com -> :john.example.com:0
parts [ 1 ] = parts [ 0 ] ;
parts [ 0 ] = '' ;
parts [ 2 ] = 0 ;
}
else if ( 2 === parts . length ) {
throw new Error ( "invalid arguments for --domains, should use the format <domainname> or <scheme>:<domainname>:<local-port>" ) ;
}
if ( ! parts [ 1 ] ) {
throw new Error ( "invalid arguments for --domains, should use the format <domainname> or <scheme>:<domainname>:<local-port>" ) ;
}
parts [ 0 ] = parts [ 0 ] . toLowerCase ( ) ;
parts [ 1 ] = parts [ 1 ] . toLowerCase ( ) . replace ( /(\/\/)?/ , '' ) ;
parts [ 2 ] = parseInt ( parts [ 2 ] , 10 ) || 0 ;
memo . push ( {
protocol : parts [ 0 ]
, hostname : parts [ 1 ]
, port : parts [ 2 ]
} ) ;
}
vals . map ( function ( val ) {
return parseProxy ( val ) ;
} ) ;
return memo ;
}
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 ;
}
2018-05-27 10:26:34 +00:00
var program = require ( 'commander' ) ;
program
. version ( pkg . version )
//.command('jsurl <url>')
. arguments ( '<url>' )
. action ( function ( url ) {
program . url = url ;
} )
. option ( '-k --insecure' , 'Allow TLS connections to a Telebit Relay without valid certs (rejectUnauthorized: false)' )
. 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 capture a specific protocol to a specific local port use the format https:example.com:1337 instead). Ex: example.com,example.net' , collectDomains , [ ] )
. 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 ( '--relay <URL>' , 'the domain (or ip address) at which you are running Telebit Relay (the proxy)' ) // --proxy
. option ( '--secret <STRING>' , 'the same secret used by the Telebit Relay (used for JWT authentication)' )
. option ( '--token <STRING>' , 'a pre-generated token for use with the Telebit Relay (instead of generating one with --secret)' )
. option ( '--agree-tos' , 'agree to the Telebit 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 )
;
2017-03-26 07:37:26 +00:00
2017-04-03 21:11:59 +00:00
program . locals = ( program . locals || [ ] ) . concat ( program . domains || [ ] ) ;
2017-03-30 01:29:37 +00:00
program . locals . forEach ( function ( proxy ) {
2017-04-03 21:11:59 +00:00
// Create a map from which we can derive a list of all domains we want forwarded to us.
if ( proxy . hostname && proxy . hostname !== '*' ) {
domainsMap [ proxy . hostname ] = true ;
}
// Create a map of which port different protocols should be forwarded to, allowing for specific
// domains to go to different ports if need be (though that only works for HTTP and HTTPS).
if ( proxy . protocol && proxy . port ) {
services [ proxy . protocol ] = services [ proxy . protocol ] || { } ;
if ( /http/ . test ( proxy . protocol ) && proxy . hostname && proxy . hostname !== '*' ) {
services [ proxy . protocol ] [ proxy . hostname ] = proxy . port ;
2017-03-30 01:29:37 +00:00
}
2017-04-03 21:11:59 +00:00
else {
if ( services [ proxy . protocol ] [ '*' ] && services [ proxy . protocol ] [ '*' ] !== proxy . port ) {
console . error ( 'cannot forward generic' , proxy . protocol , 'traffic to multiple ports' ) ;
process . exit ( 1 ) ;
}
else {
services [ proxy . protocol ] [ '*' ] = proxy . port ;
}
2017-03-30 01:29:37 +00:00
}
}
} ) ;
2017-04-03 21:11:59 +00:00
if ( Object . keys ( domainsMap ) . length === 0 ) {
console . error ( 'no domains specified' ) ;
process . exit ( 1 ) ;
return ;
2017-03-28 21:31:45 +00:00
}
2016-10-06 05:27:57 +00:00
2017-04-03 21:11:59 +00:00
// Make sure we have generic ports for HTTP and HTTPS
services . https = services . https || { } ;
services . https [ '*' ] = services . https [ '*' ] || 8443 ;
services . http = services . http || { } ;
services . http [ '*' ] = services . http [ '*' ] || services . https [ '*' ] ;
program . services = services ;
2018-05-27 11:02:19 +00:00
* /
2016-09-30 16:33:38 +00:00
} ( ) ) ;