2019-01-21 17:05:50 -05:00
var http = require ( 'http' ) ;
var childProcess = require ( 'child_process' ) ;
2020-01-07 16:56:26 -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' ) ;
2021-01-14 20:08:02 -05:00
if ( require ( 'MeshAgent' ) . ARCHID == null )
{
var id = null ;
switch ( process . platform )
{
case 'win32' :
id = require ( '_GenericMarshal' ) . PointerSize == 4 ? 3 : 4 ;
break ;
case 'freebsd' :
id = require ( '_GenericMarshal' ) . PointerSize == 4 ? 31 : 30 ;
break ;
case 'darwin' :
id = require ( 'os' ) . arch ( ) == 'x64' ? 16 : 29 ;
break ;
}
if ( id != null ) { Object . defineProperty ( require ( 'MeshAgent' ) , 'ARCHID' , { value : id } ) ; }
}
2019-01-22 20:47:51 -05:00
//attachDebugger({ webport: 9994, wait: 1 }).then(function (p) { console.log('Debug on port: ' + p); });
2019-01-21 17:05:50 -05:00
2021-01-14 20:08:02 -05:00
function sendConsoleText ( msg , sessionid )
{
if ( sessionid != null )
{
require ( 'MeshAgent' ) . SendCommand ( { action : 'msg' , type : 'console' , value : msg , sessionid : sessionid } ) ;
}
else
{
require ( 'MeshAgent' ) . SendCommand ( { action : 'msg' , type : 'console' , value : msg } ) ;
}
}
function sendAgentMessage ( msg , icon )
{
if ( sendAgentMessage . messages == null )
{
sendAgentMessage . messages = { } ;
sendAgentMessage . nextid = 1 ;
}
sendAgentMessage . messages [ sendAgentMessage . nextid ++ ] = { msg : msg , icon : icon } ;
require ( 'MeshAgent' ) . SendCommand ( { action : 'sessions' , type : 'msg' , value : sendAgentMessage . messages } ) ;
2019-01-21 17:05:50 -05:00
}
2021-01-14 20:08:02 -05:00
2021-01-15 02:57:52 -05:00
// Start a JavaScript based Agent Self-Update
2021-01-14 20:08:02 -05:00
function agentUpdate _Start ( updateurl , updateoptions )
{
2021-01-15 02:57:52 -05:00
// If this value is null
var sessionid = updateoptions != null ? updateoptions . session : null ; // If this is null, messages will be broadcast. Otherwise they will be unicasted
2021-01-14 20:08:02 -05:00
if ( this . _selfupdate != null )
{
2021-01-15 02:57:52 -05:00
// We were already called, so we will ignore this duplicate request
2021-01-14 20:08:02 -05:00
if ( sessionid != null ) { sendConsoleText ( 'Self update already in progress...' , sessionid ) ; }
}
else
{
if ( require ( 'MeshAgent' ) . ARCHID == null && updateurl == null )
{
2021-01-15 02:57:52 -05:00
// This agent doesn't have the ability to tell us which ARCHID it is, so we don't know which agent to pull
sendConsoleText ( 'Unable to initiate update, agent ARCHID is not defined' , sessionid ) ;
2021-01-14 20:08:02 -05:00
}
else
{
2021-01-15 02:57:52 -05:00
var agentfilename = process . execPath . split ( process . platform == 'win32' ? '\\' : '/' ) . pop ( ) ; // Local File Name, ie: MeshAgent.exe
2021-01-14 20:08:02 -05:00
var name = require ( 'MeshAgent' ) . serviceName ;
2021-01-15 02:57:52 -05:00
if ( name == null ) { name = process . platform == 'win32' ? 'Mesh Agent' : 'meshagent' ; } // This is an older agent that doesn't expose the service name, so use the default
2021-01-14 20:08:02 -05:00
try
{
var s = require ( 'service-manager' ) . manager . getService ( name ) ;
if ( ! s . isMe ( ) )
{
if ( process . platform == 'win32' ) { s . close ( ) ; }
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update cannot continue, this agent is not an instance of (' + name + ')' , sessionid ) ;
2021-01-14 20:08:02 -05:00
return ;
}
if ( process . platform == 'win32' ) { s . close ( ) ; }
}
catch ( zz )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update Failed because this agent is not an instance of (' + name + ')' , sessionid ) ;
sendAgentMessage ( 'Self Update Failed because this agent is not an instance of (' + name + ')' , 3 ) ;
2021-01-14 20:08:02 -05:00
return ;
}
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Downloading update...' , sessionid ) ;
2021-01-14 20:08:02 -05:00
var options = require ( 'http' ) . parseUri ( updateurl != null ? updateurl : require ( 'MeshAgent' ) . ServerUrl ) ;
options . protocol = 'https:' ;
if ( updateurl == null ) { options . path = ( '/meshagents?id=' + require ( 'MeshAgent' ) . ARCHID ) ; }
options . rejectUnauthorized = false ;
options . checkServerIdentity = function checkServerIdentity ( certs )
{
// If the tunnel certificate matches the control channel certificate, accept the connection
try { if ( require ( 'MeshAgent' ) . ServerInfo . ControlChannelCertificate . digest == certs [ 0 ] . digest ) return ; } catch ( ex ) { }
try { if ( require ( 'MeshAgent' ) . ServerInfo . ControlChannelCertificate . fingerprint == certs [ 0 ] . fingerprint ) return ; } catch ( ex ) { }
// Check that the certificate is the one expected by the server, fail if not.
if ( checkServerIdentity . servertlshash == null )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update failed, because the url cannot be verified' , sessionid ) ;
sendAgentMessage ( 'Self Update failed, because the url cannot be verified' , 3 ) ;
2021-01-14 20:08:02 -05:00
throw new Error ( 'BadCert' ) ;
}
if ( ( checkServerIdentity . servertlshash != null ) && ( checkServerIdentity . servertlshash . toLowerCase ( ) != certs [ 0 ] . digest . split ( ':' ) . join ( '' ) . toLowerCase ( ) ) )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update failed, because the supplied certificate does not match' , sessionid ) ;
sendAgentMessage ( 'Self Update failed, because the supplied certificate does not match' , 3 ) ;
2021-01-14 20:08:02 -05:00
throw new Error ( 'BadCert' )
}
}
options . checkServerIdentity . servertlshash = ( updateoptions != null ? updateoptions . tlshash : null ) ;
this . _selfupdate = require ( 'https' ) . get ( options ) ;
this . _selfupdate . on ( 'error' , function ( e )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update failed, because there was a problem trying to download the update' , sessionid ) ;
sendAgentMessage ( 'Self Update failed, because there was a problem trying to download the update' , 3 ) ;
2021-01-14 20:08:02 -05:00
} ) ;
this . _selfupdate . on ( 'response' , function ( img )
{
this . _file = require ( 'fs' ) . createWriteStream ( agentfilename + '.update' , { flags : 'wb' } ) ;
this . _filehash = require ( 'SHA384Stream' ) . create ( ) ;
this . _filehash . on ( 'hash' , function ( h )
{
if ( updateoptions != null && updateoptions . hash != null )
{
if ( updateoptions . hash . toLowerCase ( ) == h . toString ( 'hex' ) . toLowerCase ( ) )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Download complete. HASH verified.' , sessionid ) ;
2021-01-14 20:08:02 -05:00
}
else
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update FAILED because the downloaded agent FAILED hash check' , sessionid ) ;
sendAgentMessage ( 'Self Update FAILED because the downloaded agent FAILED hash check' , 3 ) ;
2021-01-14 20:08:02 -05:00
return ;
}
}
else
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Download complete. HASH=' + h . toString ( 'hex' ) , sessionid ) ;
2021-01-14 20:08:02 -05:00
}
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Updating and restarting agent...' , sessionid ) ;
2021-01-14 20:08:02 -05:00
if ( process . platform == 'win32' )
{
2021-01-15 02:57:52 -05:00
// Use _wexecve() equivalent to perform the update
2021-01-14 20:08:02 -05:00
this . child = require ( 'child_process' ) . execFile ( process . env [ 'windir' ] + '\\system32\\cmd.exe' ,
[ '/C wmic service "' + name + '" call stopservice && copy "' + process . cwd ( ) + agentfilename + '.update" "' + process . execPath + '" && wmic service "' + name + '" call startservice && erase "' + process . cwd ( ) + agentfilename + '.update"' ] , { type : 4 | 0x8000 } ) ;
}
else
{
// remove binary
require ( 'fs' ) . unlinkSync ( process . execPath ) ;
// copy update
require ( 'fs' ) . copyFileSync ( process . cwd ( ) + agentfilename + '.update' , process . execPath ) ;
// erase update
require ( 'fs' ) . unlinkSync ( process . cwd ( ) + agentfilename + '.update' ) ;
// add execute permissions
var m = require ( 'fs' ) . statSync ( process . execPath ) . mode ;
m |= ( require ( 'fs' ) . CHMOD _MODES . S _IXUSR | require ( 'fs' ) . CHMOD _MODES . S _IXGRP | require ( 'fs' ) . CHMOD _MODES . S _IXOTH ) ;
require ( 'fs' ) . chmodSync ( process . execPath , m ) ;
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Restarting service...' , sessionid ) ;
2021-01-14 20:08:02 -05:00
try
{
// restart service
var s = require ( 'service-manager' ) . manager . getService ( name ) ;
s . restart ( ) ;
}
catch ( zz )
{
2021-01-15 02:57:52 -05:00
sendConsoleText ( 'Self Update encountered an error trying to restart service' , sessionid ) ;
sendAgentMessage ( 'Self Update encountered an error trying to restart service' , 3 ) ;
2021-01-14 20:08:02 -05:00
}
}
} ) ;
img . pipe ( this . _file ) ;
img . pipe ( this . _filehash ) ;
} ) ;
}
}
}
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 ; }
2020-01-07 16:56:26 -05:00
setInterval ( function ( ) { sendConsoleText ( 'Timer!' ) ; } , 2000 ) ;
2019-01-22 20:47:51 -05:00
var path =
{
2020-01-07 16:56:26 -05:00
join : function ( ) {
2019-01-22 20:47:51 -05:00
var x = [ ] ;
2020-01-07 16:56:26 -05:00
for ( var i in arguments ) {
2019-01-22 20:47:51 -05:00
var w = arguments [ i ] ;
2020-01-07 16:56:26 -05:00
if ( w != null ) {
2019-01-22 20:47:51 -05:00
while ( w . endsWith ( '/' ) || w . endsWith ( '\\' ) ) { w = w . substring ( 0 , w . length - 1 ) ; }
2020-01-07 16:56:26 -05:00
if ( i != 0 ) { while ( w . startsWith ( '/' ) || w . startsWith ( '\\' ) ) { w = w . substring ( 1 ) ; } }
2019-01-22 20:47:51 -05:00
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
2020-01-07 16:56:26 -05:00
function splitArgs ( str ) {
2019-01-21 17:05:50 -05:00
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
2020-01-07 16:56:26 -05:00
function parseArgs ( argv ) {
2019-01-21 17:05:50 -05:00
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
2020-01-07 16:56:26 -05:00
function getServerTargetUrl ( path ) {
2019-01-21 17:05:50 -05:00
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.
2020-01-07 16:56:26 -05:00
function getServerTargetUrlEx ( url ) {
2019-01-21 17:05:50 -05:00
if ( url . substring ( 0 , 2 ) == '*/' ) { return getServerTargetUrl ( url . substring ( 2 ) ) ; }
return url ;
}
2020-01-07 16:56:26 -05:00
require ( 'MeshAgent' ) . on ( 'Connected' , function ( ) {
require ( 'os' ) . name ( ) . then ( function ( v ) {
2021-01-14 23:27:15 -05:00
//sendConsoleText("Mesh Agent Recovery Console, OS: " + v);
2019-01-21 17:05:50 -05:00
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 ;
}
}
2020-04-23 05:15:04 -04:00
// Called when receiving control data on websocket
function onTunnelControlData ( data , ws )
{
var obj ;
if ( ws == null ) { ws = this ; }
if ( typeof data == 'string' ) { try { obj = JSON . parse ( data ) ; } catch ( e ) { sendConsoleText ( 'Invalid control JSON: ' + data ) ; return ; } }
else if ( typeof data == 'object' ) { obj = data ; } else { return ; }
//sendConsoleText('onTunnelControlData(' + ws.httprequest.protocol + '): ' + JSON.stringify(data));
//console.log('onTunnelControlData: ' + JSON.stringify(data));
if ( obj . action )
{
switch ( obj . action )
{
case 'lock' : {
// Lock the current user out of the desktop
try
{
if ( process . platform == 'win32' )
{
MeshServerLog ( "Locking remote user out of desktop" , ws . httprequest ) ;
var child = require ( 'child_process' ) ;
child . execFile ( process . env [ 'windir' ] + '\\system32\\cmd.exe' , [ '/c' , 'RunDll32.exe user32.dll,LockWorkStation' ] , { type : 1 } ) ;
}
} catch ( e ) { }
break ;
}
default :
// Unknown action, ignore it.
break ;
}
return ;
}
switch ( obj . type )
{
case 'options' : {
// These are additional connection options passed in the control channel.
//sendConsoleText('options: ' + JSON.stringify(obj));
delete obj . type ;
ws . httprequest . xoptions = obj ;
// Set additional user consent options if present
if ( ( obj != null ) && ( typeof obj . consent == 'number' ) ) { ws . httprequest . consent |= obj . consent ; }
break ;
}
case 'close' : {
// We received the close on the websocket
//sendConsoleText('Tunnel #' + ws.tunnel.index + ' WebSocket control close');
try { ws . close ( ) ; } catch ( e ) { }
break ;
}
case 'termsize' : {
// Indicates a change in terminal size
if ( process . platform == 'win32' )
{
if ( ws . httprequest . _dispatcher == null ) return ;
if ( ws . httprequest . _dispatcher . invoke ) { ws . httprequest . _dispatcher . invoke ( 'resizeTerminal' , [ obj . cols , obj . rows ] ) ; }
}
else
{
if ( ws . httprequest . process == null || ws . httprequest . process . pty == 0 ) return ;
if ( ws . httprequest . process . tcsetsize ) { ws . httprequest . process . tcsetsize ( obj . rows , obj . cols ) ; }
}
break ;
}
}
}
2021-01-14 20:08:02 -05:00
require ( 'MeshAgent' ) . AddCommandHandler ( function ( data )
{
2020-04-23 05:15:04 -04:00
if ( typeof data == 'object' )
{
2019-01-21 17:05:50 -05:00
// If this is a console command, parse it and call the console handler
2021-01-14 20:08:02 -05:00
switch ( data . action )
{
case 'agentupdate' :
agentUpdate _Start ( data . url , { hash : data . hash , tlshash : data . servertlshash } ) ;
break ;
2019-01-21 17:05:50 -05:00
case 'msg' :
{
2021-01-14 20:08:02 -05:00
switch ( data . type )
{
2020-01-07 16:56:26 -05:00
case 'console' : { // Process a console command
2021-01-14 20:08:02 -05:00
if ( data . value && data . sessionid )
{
2020-01-07 16:56:26 -05:00
var args = splitArgs ( data . value ) ;
processConsoleCommand ( args [ 0 ] . toLowerCase ( ) , parseArgs ( args ) , data . rights , data . sessionid ) ;
}
break ;
2019-01-21 17:05:50 -05:00
}
case 'tunnel' :
{
2021-01-14 20:08:02 -05:00
if ( data . value != null )
{ // Process a new tunnel connection request
2020-01-07 16:56:26 -05:00
// Create a new tunnel object
var xurl = getServerTargetUrlEx ( data . value ) ;
2021-01-14 20:08:02 -05:00
if ( xurl != null )
{
2020-01-07 16:56:26 -05:00
var woptions = http . parseUri ( xurl ) ;
woptions . rejectUnauthorized = 0 ;
//sendConsoleText(JSON.stringify(woptions));
var tunnel = http . request ( woptions ) ;
2021-01-14 20:08:02 -05:00
tunnel . on ( 'upgrade' , function ( response , s , head )
{
2020-01-07 16:56:26 -05:00
this . s = s ;
s . httprequest = this ;
s . tunnel = this ;
2021-01-14 20:08:02 -05:00
s . on ( 'end' , function ( )
{
2020-01-07 16:56:26 -05:00
if ( tunnels [ this . httprequest . index ] == null ) return ; // Stop duplicate calls.
2019-01-22 20:47:51 -05:00
2020-01-07 16:56:26 -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-22 20:47:51 -05:00
2020-01-07 16:56:26 -05:00
//sendConsoleText("Tunnel #" + this.httprequest.index + " closed.", this.httprequest.sessionid);
delete tunnels [ this . httprequest . index ] ;
2019-01-21 17:05:50 -05:00
2020-01-07 16:56:26 -05:00
// Clean up WebSocket
this . removeAllListeners ( 'data' ) ;
} ) ;
2021-01-14 20:08:02 -05:00
s . on ( 'data' , function ( data )
{
2020-01-07 16:56:26 -05:00
// If this is upload data, save it to file
2021-01-14 20:08:02 -05:00
if ( this . httprequest . uploadFile )
{
2020-08-06 16:23:17 -04:00
try { fs . writeSync ( this . httprequest . uploadFile , data ) ; } catch ( e ) { this . write ( Buffer . from ( JSON . stringify ( { action : 'uploaderror' } ) ) ) ; return ; } // Write to the file, if there is a problem, error out.
this . write ( Buffer . from ( JSON . stringify ( { action : 'uploadack' , reqid : this . httprequest . uploadFileid } ) ) ) ; // Ask for more data
2020-01-07 16:56:26 -05:00
return ;
}
2019-01-22 20:47:51 -05:00
2021-01-14 20:08:02 -05:00
if ( this . httprequest . state == 0 )
{
2020-01-07 16:56:26 -05:00
// Check if this is a relay connection
if ( ( data == 'c' ) || ( data == 'cr' ) ) { this . httprequest . state = 1 ; sendConsoleText ( "Tunnel #" + this . httprequest . index + " now active" , this . httprequest . sessionid ) ; }
2021-01-14 20:08:02 -05:00
} else
{
2020-01-07 16:56:26 -05:00
// Handle tunnel data
2020-04-23 05:15:04 -04:00
if ( this . httprequest . protocol == 0 )
{
if ( ( data . length > 3 ) && ( data [ 0 ] == '{' ) ) { onTunnelControlData ( data , this ) ; return ; }
2020-01-07 16:56:26 -05:00
// Take a look at the protocol
this . httprequest . protocol = parseInt ( data ) ;
if ( typeof this . httprequest . protocol != 'number' ) { this . httprequest . protocol = 0 ; }
2020-04-23 13:08:12 -04:00
if ( ( this . httprequest . protocol == 1 ) || ( this . httprequest . protocol == 6 ) || ( this . httprequest . protocol == 8 ) || ( this . httprequest . protocol == 9 ) )
{
2020-01-07 16:56:26 -05:00
// Remote terminal using native pipes
2020-04-23 05:15:04 -04:00
if ( process . platform == "win32" )
{
var cols = 80 , rows = 25 ;
if ( this . httprequest . xoptions )
{
if ( this . httprequest . xoptions . rows ) { rows = this . httprequest . xoptions . rows ; }
if ( this . httprequest . xoptions . cols ) { cols = this . httprequest . xoptions . cols ; }
}
// Admin Terminal
if ( require ( 'win-virtual-terminal' ) . supported )
{
// ConPTY PseudoTerminal
// this.httprequest._term = require('win-virtual-terminal')[this.httprequest.protocol == 6 ? 'StartPowerShell' : 'Start'](80, 25);
// The above line is commented out, because there is a bug with ClosePseudoConsole() API, so this is the workaround
this . httprequest . _dispatcher = require ( 'win-dispatcher' ) . dispatch ( { modules : [ { name : 'win-virtual-terminal' , script : getJSModule ( 'win-virtual-terminal' ) } ] , launch : { module : 'win-virtual-terminal' , method : 'Start' , args : [ cols , rows ] } } ) ;
this . httprequest . _dispatcher . ws = this ;
this . httprequest . _dispatcher . on ( 'connection' , function ( c )
{
this . ws . _term = c ;
c . pipe ( this . ws , { dataTypeSkip : 1 } ) ;
this . ws . pipe ( c , { dataTypeSkip : 1 } ) ;
} ) ;
}
else
{
// Legacy Terminal
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' ) ; } ) ; } ) ;
}
2020-01-07 16:56:26 -05:00
}
2020-04-23 05:15:04 -04:00
else
{
var env = { HISTCONTROL : 'ignoreboth' } ;
if ( this . httprequest . xoptions )
{
if ( this . httprequest . xoptions . rows ) { env . LINES = ( '' + this . httprequest . xoptions . rows ) ; }
if ( this . httprequest . xoptions . cols ) { env . COLUMNS = ( '' + this . httprequest . xoptions . cols ) ; }
}
var options = { type : childProcess . SpawnTypes . TERM , env : env } ;
2020-04-23 12:50:55 -04:00
2021-01-14 20:08:02 -05:00
if ( require ( 'fs' ) . existsSync ( '/bin/bash' ) )
2020-04-23 12:50:55 -04:00
{
this . httprequest . process = childProcess . execFile ( '/bin/bash' , [ 'bash' ] , options ) ; // Start bash
}
else
{
this . httprequest . process = childProcess . execFile ( '/bin/sh' , [ 'sh' ] , options ) ; // Start sh
}
2020-04-23 05:15:04 -04:00
// Spaces at the beginning of lines are needed to hide commands from the command history
if ( process . platform == 'linux' ) { this . httprequest . process . stdin . write ( ' alias ls=\'ls --color=auto\';clear\n' ) ; }
2020-01-07 16:56:26 -05:00
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 ( ) ; } ) ;
}
2019-01-21 17:05:50 -05:00
}
2020-01-07 16:56:26 -05:00
}
2021-01-14 20:08:02 -05:00
else if ( this . httprequest . protocol == 5 )
{
2020-01-07 16:56:26 -05:00
// 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 ) ;
2019-01-22 20:47:51 -05:00
2020-01-07 16:56:26 -05:00
//sendConsoleText('CMD: ' + JSON.stringify(cmd));
2019-01-22 20:47:51 -05:00
2020-01-07 16:56:26 -05:00
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, ' '));
2021-01-14 20:08:02 -05:00
switch ( cmd . action )
{
2020-01-07 16:56:26 -05:00
case 'ls' :
// Send the folder content to the browser
var response = getDirectoryInfo ( cmd . path ) ;
if ( cmd . reqid != undefined ) { response . reqid = cmd . reqid ; }
2020-08-06 16:23:17 -04:00
this . write ( Buffer . from ( JSON . stringify ( response ) ) ) ;
2020-01-07 16:56:26 -05:00
break ;
case 'mkdir' : {
// Create a new empty folder
fs . mkdirSync ( cmd . path ) ;
break ;
}
case 'rm' : {
// Delete, possibly recursive delete
2021-01-14 20:08:02 -05:00
for ( var i in cmd . delfiles )
{
2020-01-07 16:56:26 -05:00
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 ;
2020-08-06 16:23:17 -04:00
try { this . httprequest . uploadFile = fs . openSync ( filepath , 'wbN' ) ; } catch ( e ) { this . write ( Buffer . from ( JSON . stringify ( { action : 'uploaderror' , reqid : cmd . reqid } ) ) ) ; break ; }
2020-01-07 16:56:26 -05:00
this . httprequest . uploadFileid = cmd . reqid ;
2020-08-06 16:23:17 -04:00
if ( this . httprequest . uploadFile ) { this . write ( Buffer . from ( JSON . stringify ( { action : 'uploadstart' , reqid : this . httprequest . uploadFileid } ) ) ) ; }
2020-01-07 16:56:26 -05:00
break ;
}
case 'copy' : {
// Copy a bunch of files from scpath to dspath
2021-01-14 20:08:02 -05:00
for ( var i in cmd . names )
{
2020-01-07 16:56:26 -05:00
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
2021-01-14 20:08:02 -05:00
for ( var i in cmd . names )
{
2020-01-07 16:56:26 -05:00
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-22 20:47:51 -05:00
}
}
}
2020-01-07 16:56:26 -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 ;
2019-01-21 17:05:50 -05:00
2020-01-07 16:56:26 -05:00
//sendConsoleText('New tunnel connection #' + index + ': ' + tunnel.url + ', rights: ' + tunnel.rights, data.sessionid);
}
}
break ;
2019-01-21 17:05:50 -05:00
}
2020-01-07 16:56:26 -05:00
default :
// Unknown action, ignore it.
break ;
}
break ;
2019-01-21 17:05:50 -05:00
}
default :
// Unknown action, ignore it.
break ;
}
}
} ) ;
2020-01-07 16:56:26 -05:00
function processConsoleCommand ( cmd , args , rights , sessionid ) {
try {
2019-01-21 17:05:50 -05:00
var response = null ;
2020-01-07 16:56:26 -05:00
switch ( cmd ) {
2019-01-21 17:05:50 -05:00
case 'help' :
2021-01-15 02:57:52 -05:00
response = "Available commands are: agentupdate, dbkeys, dbget, dbset, dbcompact, netinfo, osinfo, versions." ;
2021-01-14 20:08:02 -05:00
break ;
case 'versions' :
response = JSON . stringify ( process . versions , null , ' ' ) ;
break ;
case 'agentupdate' :
2021-01-14 23:27:15 -05:00
// Request that the server send a agent update command
require ( 'MeshAgent' ) . SendCommand ( { action : 'agentupdate' } ) ;
break ;
case 'agentupdateex' :
// Perform an direct agent update without requesting any information from the server, this should not typically be used.
2021-01-14 20:08:02 -05:00
agentUpdate _Start ( null , { session : sessionid } ) ;
2019-01-21 17:05:50 -05:00
break ;
case 'osinfo' : { // Return the operating system information
var i = 1 ;
2020-04-23 05:15:04 -04:00
if ( args [ '_' ] . length > 0 ) { i = parseInt ( args [ '_' ] [ 0 ] ) ; if ( i > 8 ) { i = 8 ; } response = 'Calling ' + i + ' times.' ; }
for ( var j = 0 ; j < i ; j ++ )
{
2019-01-21 17:05:50 -05:00
var pr = require ( 'os' ) . name ( ) ;
pr . sessionid = sessionid ;
2020-04-23 05:15:04 -04:00
pr . then ( function ( v )
{
sendConsoleText ( "OS: " + v + ( process . platform == 'win32' ? ( require ( 'win-virtual-terminal' ) . supported ? ' [ConPTY: YES]' : ' [ConPTY: NO]' ) : '' ) , this . sessionid ) ;
} ) ;
2019-01-21 17:05:50 -05:00
}
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
2020-01-07 16:56:26 -05:00
if ( db == null ) { response = "Database not accessible." ; break ; }
2019-01-21 17:05:50 -05:00
if ( args [ '_' ] . length != 1 ) {
2020-01-07 16:56:26 -05:00
response = "Proper usage: dbget (key)" ; // Display the value for a given database key
2019-01-21 17:05:50 -05:00
} else {
response = db . Get ( args [ '_' ] [ 0 ] ) ;
}
break ;
}
case 'dbset' : { // Set a data store key and value pair
2020-01-07 16:56:26 -05:00
if ( db == null ) { response = "Database not accessible." ; break ; }
2019-01-21 17:05:50 -05:00
if ( args [ '_' ] . length != 2 ) {
2020-01-07 16:56:26 -05:00
response = "Proper usage: dbset (key) (value)" ; // Set a database key
2019-01-21 17:05:50 -05:00
} else {
var r = db . Put ( args [ '_' ] [ 0 ] , args [ '_' ] [ 1 ] ) ;
2020-01-07 16:56:26 -05:00
response = "Key set: " + r ;
2019-01-21 17:05:50 -05:00
}
break ;
}
case 'dbcompact' : { // Compact the data store
2020-01-07 16:56:26 -05:00
if ( db == null ) { response = "Database not accessible." ; break ; }
2019-01-21 17:05:50 -05:00
var r = db . Compact ( ) ;
2020-01-07 16:56:26 -05:00
response = "Database compacted: " + r ;
2019-01-21 17:05:50 -05:00
break ;
}
case 'tunnels' : { // Show the list of current tunnels
response = '' ;
2020-01-07 16:56:26 -05:00
for ( var i in tunnels ) { response += "Tunnel #" + i + ", " + tunnels [ i ] . url + '\r\n' ; }
if ( response == '' ) { response = "No websocket sessions." ; }
2019-01-21 17:05:50 -05:00
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
2020-12-23 23:03:44 -05:00
response = 'Unknown command \"' + cmd + '\", type \"help\" for list of available commands.' ;
2019-01-21 17:05:50 -05:00
break ;
}
}
2020-01-07 16:56:26 -05:00
} catch ( e ) { response = "Command returned an exception error: " + e ; console . log ( e ) ; }
2019-01-21 17:05:50 -05:00
if ( response != null ) { sendConsoleText ( response , sessionid ) ; }
}
2019-01-22 20:47:51 -05:00
// Get a formated response for a given directory path
2020-01-07 16:56:26 -05:00
function getDirectoryInfo ( reqpath ) {
2019-01-22 20:47:51 -05:00
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 ) ;
}
} ;