2019-01-21 17:05:50 -05:00
var http = require ( 'http' ) ;
var childProcess = require ( 'child_process' ) ;
2019-01-22 20:47:51 -05:00
var meshCoreObj = { "action" : "coreinfo" , "value" : "MeshCore Recovery" , "caps" : 14 } ; // Capability bitmask: 1 = Desktop, 2 = Terminal, 4 = Files, 8 = Console, 16 = JavaScript
2019-01-21 17:05:50 -05:00
var nextTunnelIndex = 1 ;
var tunnels = { } ;
2019-01-22 20:47:51 -05:00
var fs = require ( 'fs' ) ;
//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
2019-01-21 17:05:50 -05:00
function sendConsoleText ( msg )
{
require ( 'MeshAgent' ) . SendCommand ( { "action" : "msg" , "type" : "console" , "value" : msg } ) ;
}
2019-01-22 20:47:51 -05:00
// Return p number of spaces
function addPad ( p , ret ) { var r = '' ; for ( var i = 0 ; i < p ; i ++ ) { r += ret ; } return r ; }
var path =
{
join : function ( )
{
var x = [ ] ;
for ( var i in arguments )
{
var w = arguments [ i ] ;
if ( w != null )
{
while ( w . endsWith ( '/' ) || w . endsWith ( '\\' ) ) { w = w . substring ( 0 , w . length - 1 ) ; }
if ( i != 0 )
{
while ( w . startsWith ( '/' ) || w . startsWith ( '\\' ) ) { w = w . substring ( 1 ) ; }
}
x . push ( w ) ;
}
}
if ( x . length == 0 ) return '/' ;
return x . join ( '/' ) ;
}
} ;
// Convert an object to string with all functions
function objToString ( x , p , pad , ret ) {
if ( ret == undefined ) ret = '' ;
if ( p == undefined ) p = 0 ;
if ( x == null ) { return '[null]' ; }
if ( p > 8 ) { return '[...]' ; }
if ( x == undefined ) { return '[undefined]' ; }
if ( typeof x == 'string' ) { if ( p == 0 ) return x ; return '"' + x + '"' ; }
if ( typeof x == 'buffer' ) { return '[buffer]' ; }
if ( typeof x != 'object' ) { return x ; }
var r = '{' + ( ret ? '\r\n' : ' ' ) ;
for ( var i in x ) { if ( i != '_ObjectID' ) { r += ( addPad ( p + 2 , pad ) + i + ': ' + objToString ( x [ i ] , p + 2 , pad , ret ) + ( ret ? '\r\n' : ' ' ) ) ; } }
return r + addPad ( p , pad ) + '}' ;
}
2019-01-21 17:05:50 -05:00
// Split a string taking into account the quoats. Used for command line parsing
function splitArgs ( str )
{
var myArray = [ ] , myRegexp = /[^\s"]+|"([^"]*)"/gi ;
do { var match = myRegexp . exec ( str ) ; if ( match != null ) { myArray . push ( match [ 1 ] ? match [ 1 ] : match [ 0 ] ) ; } } while ( match != null ) ;
return myArray ;
}
// Parse arguments string array into an object
function parseArgs ( argv )
{
var results = { '_' : [ ] } , current = null ;
for ( var i = 1 , len = argv . length ; i < len ; i ++ ) {
var x = argv [ i ] ;
if ( x . length > 2 && x [ 0 ] == '-' && x [ 1 ] == '-' ) {
if ( current != null ) { results [ current ] = true ; }
current = x . substring ( 2 ) ;
} else {
if ( current != null ) { results [ current ] = toNumberIfNumber ( x ) ; current = null ; } else { results [ '_' ] . push ( toNumberIfNumber ( x ) ) ; }
}
}
if ( current != null ) { results [ current ] = true ; }
return results ;
}
// Get server target url with a custom path
function getServerTargetUrl ( path )
{
var x = require ( 'MeshAgent' ) . ServerUrl ;
//sendConsoleText("mesh.ServerUrl: " + mesh.ServerUrl);
if ( x == null ) { return null ; }
if ( path == null ) { path = '' ; }
x = http . parseUri ( x ) ;
if ( x == null ) return null ;
return x . protocol + '//' + x . host + ':' + x . port + '/' + path ;
}
// Get server url. If the url starts with "*/..." change it, it not use the url as is.
function getServerTargetUrlEx ( url )
{
if ( url . substring ( 0 , 2 ) == '*/' ) { return getServerTargetUrl ( url . substring ( 2 ) ) ; }
return url ;
}
require ( 'MeshAgent' ) . on ( 'Connected' , function ( )
{
require ( 'os' ) . name ( ) . then ( function ( v )
{
sendConsoleText ( "Mesh Agent Receovery Console, OS: " + v ) ;
require ( 'MeshAgent' ) . SendCommand ( meshCoreObj ) ;
} ) ;
} ) ;
// Tunnel callback operations
function onTunnelUpgrade ( response , s , head ) {
this . s = s ;
s . httprequest = this ;
s . end = onTunnelClosed ;
s . tunnel = this ;
//sendConsoleText('onTunnelUpgrade');
if ( this . tcpport != null ) {
// This is a TCP relay connection, pause now and try to connect to the target.
s . pause ( ) ;
s . data = onTcpRelayServerTunnelData ;
var connectionOptions = { port : parseInt ( this . tcpport ) } ;
if ( this . tcpaddr != null ) { connectionOptions . host = this . tcpaddr ; } else { connectionOptions . host = '127.0.0.1' ; }
s . tcprelay = net . createConnection ( connectionOptions , onTcpRelayTargetTunnelConnect ) ;
s . tcprelay . peerindex = this . index ;
} else {
// This is a normal connect for KVM/Terminal/Files
s . data = onTunnelData ;
}
}
require ( 'MeshAgent' ) . AddCommandHandler ( function ( data )
{
if ( typeof data == 'object' )
{
// If this is a console command, parse it and call the console handler
switch ( data . action )
{
case 'msg' :
{
switch ( data . type )
{
case 'console' : { // Process a console command
if ( data . value && data . sessionid )
{
var args = splitArgs ( data . value ) ;
processConsoleCommand ( args [ 0 ] . toLowerCase ( ) , parseArgs ( args ) , data . rights , data . sessionid ) ;
}
break ;
}
case 'tunnel' :
{
if ( data . value != null ) { // Process a new tunnel connection request
// Create a new tunnel object
var xurl = getServerTargetUrlEx ( data . value ) ;
if ( xurl != null ) {
var woptions = http . parseUri ( xurl ) ;
woptions . rejectUnauthorized = 0 ;
//sendConsoleText(JSON.stringify(woptions));
var tunnel = http . request ( woptions ) ;
tunnel . on ( 'upgrade' , function ( response , s , head )
{
this . s = s ;
s . httprequest = this ;
s . tunnel = this ;
s . on ( 'end' , function ( )
{
if ( tunnels [ this . httprequest . index ] == null ) return ; // Stop duplicate calls.
2019-01-22 20:47:51 -05:00
// If there is a upload or download active on this connection, close the file
if ( this . httprequest . uploadFile ) { fs . closeSync ( this . httprequest . uploadFile ) ; this . httprequest . uploadFile = undefined ; }
if ( this . httprequest . downloadFile ) { fs . closeSync ( this . httprequest . downloadFile ) ; this . httprequest . downloadFile = undefined ; }
2019-01-21 17:05:50 -05:00
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
delete tunnels [ this . httprequest . index ] ;
// Clean up WebSocket
this . removeAllListeners ( 'data' ) ;
} ) ;
s . on ( 'data' , function ( data )
{
2019-01-22 20:47:51 -05:00
// If this is upload data, save it to file
if ( this . httprequest . uploadFile )
{
try { fs . writeSync ( this . httprequest . uploadFile , data ) ; } catch ( e ) { this . write ( new Buffer ( JSON . stringify ( { action : 'uploaderror' } ) ) ) ; return ; } // Write to the file, if there is a problem, error out.
this . write ( new Buffer ( JSON . stringify ( { action : 'uploadack' , reqid : this . httprequest . uploadFileid } ) ) ) ; // Ask for more data
return ;
}
2019-01-21 17:05:50 -05:00
if ( this . httprequest . state == 0 ) {
// Check if this is a relay connection
if ( data == 'c' ) { this . httprequest . state = 1 ; sendConsoleText ( "Tunnel #" + this . httprequest . index + " now active" , this . httprequest . sessionid ) ; }
} else {
// Handle tunnel data
if ( this . httprequest . protocol == 0 )
{
// Take a look at the protocol
this . httprequest . protocol = parseInt ( data ) ;
if ( typeof this . httprequest . protocol != 'number' ) { this . httprequest . protocol = 0 ; }
if ( this . httprequest . protocol == 1 )
{
// Remote terminal using native pipes
if ( process . platform == "win32" )
{
this . httprequest . _term = require ( 'win-terminal' ) . Start ( 80 , 25 ) ;
this . httprequest . _term . pipe ( this , { dataTypeSkip : 1 } ) ;
this . pipe ( this . httprequest . _term , { dataTypeSkip : 1 , end : false } ) ;
this . prependListener ( 'end' , function ( ) { this . httprequest . _term . end ( function ( ) { sendConsoleText ( 'Terminal was closed' ) ; } ) ; } ) ;
}
else
{
this . httprequest . process = childProcess . execFile ( "/bin/sh" , [ "sh" ] , { type : childProcess . SpawnTypes . TERM } ) ;
this . httprequest . process . tunnel = this ;
this . httprequest . process . on ( 'exit' , function ( ecode , sig ) { this . tunnel . end ( ) ; } ) ;
this . httprequest . process . stderr . on ( 'data' , function ( chunk ) { this . parent . tunnel . write ( chunk ) ; } ) ;
this . httprequest . process . stdout . pipe ( this , { dataTypeSkip : 1 } ) ; // 0 = Binary, 1 = Text.
this . pipe ( this . httprequest . process . stdin , { dataTypeSkip : 1 , end : false } ) ; // 0 = Binary, 1 = Text.
this . prependListener ( 'end' , function ( ) { this . httprequest . process . kill ( ) ; } ) ;
}
this . on ( 'end' , function ( ) {
if ( process . platform == "win32" )
{
// Unpipe the web socket
this . unpipe ( this . httprequest . _term ) ;
this . httprequest . _term . unpipe ( this ) ;
// Clean up
this . httprequest . _term . end ( ) ;
this . httprequest . _term = null ;
}
} ) ;
}
}
2019-01-22 20:47:51 -05:00
else if ( this . httprequest . protocol == 5 )
{
// Process files commands
var cmd = null ;
try { cmd = JSON . parse ( data ) ; } catch ( e ) { } ;
if ( cmd == null ) { return ; }
if ( ( cmd . ctrlChannel == '102938' ) || ( ( cmd . type == 'offer' ) && ( cmd . sdp != null ) ) ) { return ; } // If this is control data, handle it now.
if ( cmd . action == undefined ) { return ; }
console . log ( 'action: ' , cmd . action ) ;
//sendConsoleText('CMD: ' + JSON.stringify(cmd));
if ( ( cmd . path != null ) && ( process . platform != 'win32' ) && ( cmd . path [ 0 ] != '/' ) ) { cmd . path = '/' + cmd . path ; } // Add '/' to paths on non-windows
//console.log(objToString(cmd, 0, ' '));
switch ( cmd . action )
{
case 'ls' :
// Send the folder content to the browser
var response = getDirectoryInfo ( cmd . path ) ;
if ( cmd . reqid != undefined ) { response . reqid = cmd . reqid ; }
this . write ( new Buffer ( JSON . stringify ( response ) ) ) ;
break ;
case 'mkdir' : {
// Create a new empty folder
fs . mkdirSync ( cmd . path ) ;
break ;
}
case 'rm' : {
// Delete, possibly recursive delete
for ( var i in cmd . delfiles )
{
try { deleteFolderRecursive ( path . join ( cmd . path , cmd . delfiles [ i ] ) , cmd . rec ) ; } catch ( e ) { }
}
break ;
}
case 'rename' : {
// Rename a file or folder
var oldfullpath = path . join ( cmd . path , cmd . oldname ) ;
var newfullpath = path . join ( cmd . path , cmd . newname ) ;
try { fs . renameSync ( oldfullpath , newfullpath ) ; } catch ( e ) { console . log ( e ) ; }
break ;
}
case 'upload' : {
// Upload a file, browser to agent
if ( this . httprequest . uploadFile != undefined ) { fs . closeSync ( this . httprequest . uploadFile ) ; this . httprequest . uploadFile = undefined ; }
if ( cmd . path == undefined ) break ;
var filepath = cmd . name ? path . join ( cmd . path , cmd . name ) : cmd . path ;
try { this . httprequest . uploadFile = fs . openSync ( filepath , 'wbN' ) ; } catch ( e ) { this . write ( new Buffer ( JSON . stringify ( { action : 'uploaderror' , reqid : cmd . reqid } ) ) ) ; break ; }
this . httprequest . uploadFileid = cmd . reqid ;
if ( this . httprequest . uploadFile ) { this . write ( new Buffer ( JSON . stringify ( { action : 'uploadstart' , reqid : this . httprequest . uploadFileid } ) ) ) ; }
break ;
}
case 'copy' : {
// Copy a bunch of files from scpath to dspath
for ( var i in cmd . names ) {
var sc = path . join ( cmd . scpath , cmd . names [ i ] ) , ds = path . join ( cmd . dspath , cmd . names [ i ] ) ;
if ( sc != ds ) { try { fs . copyFileSync ( sc , ds ) ; } catch ( e ) { } }
}
break ;
}
case 'move' : {
// Move a bunch of files from scpath to dspath
for ( var i in cmd . names ) {
var sc = path . join ( cmd . scpath , cmd . names [ i ] ) , ds = path . join ( cmd . dspath , cmd . names [ i ] ) ;
if ( sc != ds ) { try { fs . copyFileSync ( sc , ds ) ; fs . unlinkSync ( sc ) ; } catch ( e ) { } }
}
break ;
}
}
}
2019-01-21 17:05:50 -05:00
}
} ) ;
} ) ;
tunnel . onerror = function ( e ) { sendConsoleText ( 'ERROR: ' + JSON . stringify ( e ) ) ; }
tunnel . sessionid = data . sessionid ;
tunnel . rights = data . rights ;
tunnel . state = 0 ;
tunnel . url = xurl ;
tunnel . protocol = 0 ;
tunnel . tcpaddr = data . tcpaddr ;
tunnel . tcpport = data . tcpport ;
tunnel . end ( ) ;
// Put the tunnel in the tunnels list
var index = nextTunnelIndex ++ ;
tunnel . index = index ;
tunnels [ index ] = tunnel ;
//sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
}
}
break ;
}
default :
// Unknown action, ignore it.
break ;
}
break ;
}
default :
// Unknown action, ignore it.
break ;
}
}
} ) ;
function processConsoleCommand ( cmd , args , rights , sessionid )
{
try
{
var response = null ;
switch ( cmd )
{
case 'help' :
response = 'Available commands are: osinfo, dbkeys, dbget, dbset, dbcompact, netinfo.' ;
break ;
case 'osinfo' : { // Return the operating system information
var i = 1 ;
if ( args [ '_' ] . length > 0 ) { i = parseInt ( args [ '_' ] [ 0 ] ) ; if ( i > 8 ) { i = 8 ; } response = 'Calling ' + i + ' times.' ; }
for ( var j = 0 ; j < i ; j ++ ) {
var pr = require ( 'os' ) . name ( ) ;
pr . sessionid = sessionid ;
pr . then ( function ( v ) { sendConsoleText ( "OS: " + v , this . sessionid ) ; } ) ;
}
break ;
}
case 'dbkeys' : { // Return all data store keys
response = JSON . stringify ( db . Keys ) ;
break ;
}
case 'dbget' : { // Return the data store value for a given key
if ( db == null ) { response = 'Database not accessible.' ; break ; }
if ( args [ '_' ] . length != 1 ) {
response = 'Proper usage: dbget (key)' ; // Display the value for a given database key
} else {
response = db . Get ( args [ '_' ] [ 0 ] ) ;
}
break ;
}
case 'dbset' : { // Set a data store key and value pair
if ( db == null ) { response = 'Database not accessible.' ; break ; }
if ( args [ '_' ] . length != 2 ) {
response = 'Proper usage: dbset (key) (value)' ; // Set a database key
} else {
var r = db . Put ( args [ '_' ] [ 0 ] , args [ '_' ] [ 1 ] ) ;
response = 'Key set: ' + r ;
}
break ;
}
case 'dbcompact' : { // Compact the data store
if ( db == null ) { response = 'Database not accessible.' ; break ; }
var r = db . Compact ( ) ;
response = 'Database compacted: ' + r ;
break ;
}
case 'tunnels' : { // Show the list of current tunnels
response = '' ;
for ( var i in tunnels ) { response += 'Tunnel #' + i + ', ' + tunnels [ i ] . url + '\r\n' ; }
if ( response == '' ) { response = 'No websocket sessions.' ; }
break ;
}
case 'netinfo' : { // Show network interface information
//response = objToString(mesh.NetInfo, 0, ' ');
var interfaces = require ( 'os' ) . networkInterfaces ( ) ;
response = objToString ( interfaces , 0 , ' ' , true ) ;
break ;
}
default : { // This is an unknown command, return an error message
response = 'Unknown command \"' + cmd + '\", type \"help\" for list of avaialble commands.' ;
break ;
}
}
} catch ( e ) { response = 'Command returned an exception error: ' + e ; console . log ( e ) ; }
if ( response != null ) { sendConsoleText ( response , sessionid ) ; }
}
2019-01-22 20:47:51 -05:00
// Get a formated response for a given directory path
function getDirectoryInfo ( reqpath )
{
var response = { path : reqpath , dir : [ ] } ;
if ( ( ( reqpath == undefined ) || ( reqpath == '' ) ) && ( process . platform == 'win32' ) ) {
// List all the drives in the root, or the root itself
var results = null ;
try { results = fs . readDrivesSync ( ) ; } catch ( e ) { } // TODO: Anyway to get drive total size and free space? Could draw a progress bar.
if ( results != null ) {
for ( var i = 0 ; i < results . length ; ++ i ) {
var drive = { n : results [ i ] . name , t : 1 } ;
if ( results [ i ] . type == 'REMOVABLE' ) { drive . dt = 'removable' ; } // TODO: See if this is USB/CDROM or something else, we can draw icons.
response . dir . push ( drive ) ;
}
}
} else {
// List all the files and folders in this path
if ( reqpath == '' ) { reqpath = '/' ; }
var results = null , xpath = path . join ( reqpath , '*' ) ;
//if (process.platform == "win32") { xpath = xpath.split('/').join('\\'); }
try { results = fs . readdirSync ( xpath ) ; } catch ( e ) { }
if ( results != null ) {
for ( var i = 0 ; i < results . length ; ++ i ) {
if ( ( results [ i ] != '.' ) && ( results [ i ] != '..' ) ) {
var stat = null , p = path . join ( reqpath , results [ i ] ) ;
//if (process.platform == "win32") { p = p.split('/').join('\\'); }
try { stat = fs . statSync ( p ) ; } catch ( e ) { } // TODO: Get file size/date
if ( ( stat != null ) && ( stat != undefined ) ) {
if ( stat . isDirectory ( ) == true ) {
response . dir . push ( { n : results [ i ] , t : 2 , d : stat . mtime } ) ;
} else {
response . dir . push ( { n : results [ i ] , t : 3 , s : stat . size , d : stat . mtime } ) ;
}
}
}
}
}
}
return response ;
}
// Delete a directory with a files and directories within it
function deleteFolderRecursive ( path , rec ) {
if ( fs . existsSync ( path ) ) {
if ( rec == true ) {
fs . readdirSync ( path . join ( path , '*' ) ) . forEach ( function ( file , index ) {
var curPath = path . join ( path , file ) ;
if ( fs . statSync ( curPath ) . isDirectory ( ) ) { // recurse
deleteFolderRecursive ( curPath , true ) ;
} else { // delete file
fs . unlinkSync ( curPath ) ;
}
} ) ;
}
fs . unlinkSync ( path ) ;
}
} ;