2020-02-11 22:02:30 -05:00
/ * *
* @ description MeshCentral MeshAgent
* @ author Ylian Saint - Hilaire
* @ copyright Intel Corporation 2019 - 2020
* @ license Apache - 2.0
* @ version v0 . 0.1
* /
var fs = require ( 'fs' ) ;
var path = require ( 'path' ) ;
var worker = null ;
const NodeJSVer = Number ( process . version . match ( /^v(\d+\.\d+)/ ) [ 1 ] ) ;
var directRun = ( require . main === module ) ;
function log ( ) { if ( directRun ) { console . log ( ... arguments ) ; } else { if ( worker != null ) { worker . parentPort . postMessage ( { msg : arguments [ 0 ] } ) ; } } }
if ( directRun && ( NodeJSVer >= 12 ) ) { const xworker = require ( 'worker_threads' ) ; try { if ( xworker . isMainThread == false ) { worker = xworker ; } } catch ( ex ) { log ( ex ) ; } }
function start ( ) { startEx ( process . argv ) ; }
if ( directRun ) { setup ( ) ; }
function setup ( ) { InstallModules ( [ 'image-size' ] , start ) ; }
function start ( ) { startEx ( process . argv ) ; }
function startEx ( argv ) {
var state = { recFileName : null , recFile : null , recFileSize : 0 , recFilePtr : 0 } ;
var infile = null ;
if ( argv . length > 2 ) { infile = argv [ 2 ] ; } else {
log ( 'MeshCentral Session Recodings Processor' ) ;
log ( 'This tool will index a .mcrec file so that the player can seek thru the file.' ) ;
log ( '' ) ;
log ( ' Usage: node mcrec [file]' ) ;
return ;
}
if ( fs . existsSync ( infile ) == false ) { log ( "Missing file: " + infile ) ; return ; }
state . recFileName = infile ;
state . recFileSize = fs . statSync ( infile ) . size ;
if ( state . recFileSize < 32 ) { log ( "Invalid file: " + infile ) ; return ; }
log ( "Processing file: " + infile + ", " + state . recFileSize + " bytes." ) ;
2020-02-12 19:57:05 -05:00
state . recFile = fs . openSync ( infile , 'r+' ) ;
2020-02-11 22:02:30 -05:00
state . indexTime = 10 ; // Interval between indexes in seconds
state . lastIndex = 0 ; // Last time an index was writen in seconds
state . indexes = [ ] ;
state . width = 0 ;
state . height = 0 ;
state . basePtr = null ;
2020-02-12 19:57:05 -05:00
readLastBlock ( state , function ( state , result , time , extras ) {
2020-02-11 22:02:30 -05:00
if ( result == false ) { log ( "Invalid file: " + infile ) ; return ; }
2020-02-12 19:57:05 -05:00
if ( extras != null ) { log ( "File already indexed: " + infile ) ; return ; }
state . lastTimeStamp = time ;
2020-02-11 22:02:30 -05:00
readNextBlock ( state , processBlock ) ;
} ) ;
}
function createIndex ( state , ptr ) {
var index = [ ] ;
for ( var i in state . screen ) { if ( index . indexOf ( state . screen [ i ] ) == - 1 ) { index . push ( state . screen [ i ] ) ; } }
index . sort ( function ( a , b ) { return a - b } ) ;
index . unshift ( state . height ) ;
index . unshift ( state . width ) ;
index . unshift ( ptr - state . basePtr ) ;
state . indexes . push ( index ) ; // Index = [ Ptr, Width, Height, Block Pointers... ]
//log('Index', state.lastIndex, index.length);
//log('Index', index);
state . lastIndex += 10 ;
}
function processBlock ( state , block ) {
2020-02-12 19:57:05 -05:00
if ( block == null ) { writeIndex ( state , function ( ) { log ( "Done." ) ; } ) ; return ; }
2020-02-11 22:02:30 -05:00
var elapseMilliSeconds = 0 ;
if ( state . startTime != null ) { elapseMilliSeconds = ( block . time - state . startTime ) ; }
var flagBinary = ( block . flags & 1 ) != 0 ;
var flagUser = ( block . flags & 2 ) != 0 ;
// Start indexing at the first type 2 block
if ( ( state . basePtr == null ) && ( block . type == 2 ) ) { state . basePtr = block . ptr ; state . startTime = block . time ; }
// Check if we need to create one or more indexes
while ( ( ( state . lastIndex + state . indexTime ) * 1000 ) < elapseMilliSeconds ) { createIndex ( state , block . ptr ) ; }
if ( block . type == 1 ) {
// Metadata
state . metadata = JSON . parse ( block . data . toString ( ) ) ;
if ( state . metadata . indexInterval != null ) { log ( "This file is already indexed." ) ; return ; }
if ( state . metadata . protocol != 2 ) { log ( "Only remote desktop sessions can currently be indexed." ) ; return ; }
state . metadataFlags = block . flags ;
state . metadataTime = block . time ;
state . recFileProtocol = state . metadata . protocol ;
state . dataStartPtr = state . recFilePtr ;
if ( typeof state . recFileProtocol == 'string' ) { state . recFileProtocol = parseInt ( state . recFileProtocol ) ; }
} else if ( ( block . type == 2 ) && flagBinary && ! flagUser ) {
// Device --> User data
if ( state . recFileProtocol == 1 ) {
// MeshCentral Terminal
// TODO
log ( 'Terminal' ) ;
} else if ( state . recFileProtocol == 2 ) {
// MeshCentral Remote Desktop
// TODO
if ( block . data . length >= 4 ) {
var command = block . data . readInt16BE ( 0 ) ;
var cmdsize = block . data . readInt16BE ( 2 ) ;
if ( ( command == 27 ) && ( cmdsize == 8 ) ) {
// Jumbo packet
if ( block . data . length >= 12 ) {
command = block . data . readInt16BE ( 8 ) ;
cmdsize = block . data . readInt32BE ( 4 ) ;
if ( block . data . length == ( cmdsize + 8 ) ) {
block . data = block . data . slice ( 8 , block . data . length ) ;
} else {
console . log ( 'TODO-PARTIAL-JUMBO' , command , cmdsize , block . data . length ) ;
return ; // TODO
}
}
}
switch ( command ) {
case 3 : // Tile
var x = block . data . readInt16BE ( 4 ) ;
var y = block . data . readInt16BE ( 6 ) ;
var dimensions = require ( 'image-size' ) ( block . data . slice ( 8 ) ) ;
//log("Tile", x, y, dimensions.width, dimensions.height, block.ptr);
//console.log(elapseSeconds);
// Update the screen with the correct pointers.
var sx = x / 16 , sy = y / 16 , sw = dimensions . width / 16 , sh = dimensions . height / 16 ;
for ( var i = 0 ; i < sw ; i ++ ) {
for ( var j = 0 ; j < sh ; j ++ ) {
var k = ( ( state . swidth * ( j + sy ) ) + ( i + sx ) ) ;
state . screen [ k ] = ( block . ptr - state . basePtr ) ;
}
}
break ;
case 4 : // Tile copy
var x = block . data . readInt16BE ( 4 ) ;
var y = block . data . readInt16BE ( 6 ) ;
//log("TileCopy", x, y);
break ;
case 7 : // Screen Size, clear the screen state and computer the tile count
state . width = block . data . readInt16BE ( 4 ) ;
state . height = block . data . readInt16BE ( 6 ) ;
state . swidth = state . width / 16 ;
state . sheight = state . height / 16 ;
if ( Math . floor ( state . swidth ) != state . swidth ) { state . swidth = Math . floor ( state . swidth ) + 1 ; }
if ( Math . floor ( state . sheight ) != state . sheight ) { state . sheight = Math . floor ( state . sheight ) + 1 ; }
state . screen = { } ;
//log("ScreenSize", state.width, state.height, state.swidth, state.sheight, state.swidth * state.sheight);
break ;
}
//log('Desktop', command, cmdsize);
}
} else if ( state . recFileProtocol == 101 ) {
// Intel AMT KVM
// TODO
log ( 'AMTKVM' ) ;
}
} else if ( ( block . type == 2 ) && flagBinary && flagUser ) {
// User --> Device data
if ( state . recFileProtocol == 101 ) {
// Intel AMT KVM
//if (rstr2hex(data) == '0000000008080001000700070003050200000000') { amtDesktop.bpp = 1; } // Switch to 1 byte per pixel.
}
}
//console.log(block);
readNextBlock ( state , processBlock ) ;
}
2020-02-12 19:57:05 -05:00
function writeIndex ( state , func ) {
// Add the new indexes in extra metadata at the end of the file.
var extraMetadata = { } ;
extraMetadata . indexInterval = state . indexTime ;
extraMetadata . indexStartTime = state . startTime ;
extraMetadata . indexes = state . indexes ;
recordingEntry ( state . recFile , 4 , 0 , state . lastTimeStamp , JSON . stringify ( extraMetadata ) , function ( state ) {
recordingEntry ( state . recFile , 3 , 0 , state . recFileSize - 32 , 'MeshCentralMCNDX' , function ( state ) {
func ( state ) ;
} , state ) ;
} , state , state . recFileSize - 32 ) ;
2020-02-11 22:02:30 -05:00
}
// Record a new entry in a recording log
2020-02-12 19:57:05 -05:00
function recordingEntry ( fd , type , flags , time , data , func , tag , position ) {
2020-02-11 22:02:30 -05:00
try {
if ( typeof data == 'string' ) {
// String write
var blockData = Buffer . from ( data ) , header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( blockData . length , 4 ) ; // Size
header . writeIntBE ( time , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , blockData ] ) ;
2020-02-12 19:57:05 -05:00
if ( typeof position == 'number' ) {
fs . write ( fd , block , 0 , block . length , position , function ( ) { func ( tag ) ; } ) ;
} else {
fs . write ( fd , block , 0 , block . length , function ( ) { func ( tag ) ; } ) ;
}
2020-02-11 22:02:30 -05:00
} else {
// Binary write
var header = Buffer . alloc ( 16 ) ; // Header: Type (2) + Flags (2) + Size(4) + Time(8)
header . writeInt16BE ( type , 0 ) ; // Type (1 = Header, 2 = Network Data)
header . writeInt16BE ( flags | 1 , 2 ) ; // Flags (1 = Binary, 2 = User)
header . writeInt32BE ( data . length , 4 ) ; // Size
header . writeIntBE ( time , 10 , 6 ) ; // Time
var block = Buffer . concat ( [ header , data ] ) ;
2020-02-12 19:57:05 -05:00
if ( typeof position == 'number' ) {
fs . write ( fd , block , 0 , block . length , position , function ( ) { func ( tag ) ; } ) ;
} else {
fs . write ( fd , block , 0 , block . length , function ( ) { func ( tag ) ; } ) ;
}
2020-02-11 22:02:30 -05:00
}
} catch ( ex ) { console . log ( ex ) ; func ( state , tag ) ; }
}
function readLastBlock ( state , func ) {
var buf = Buffer . alloc ( 32 ) ;
fs . read ( state . recFile , buf , 0 , 32 , state . recFileSize - 32 , function ( err , bytesRead , buf ) {
var type = buf . readInt16BE ( 0 ) ;
var flags = buf . readInt16BE ( 2 ) ;
var size = buf . readInt32BE ( 4 ) ;
var time = ( buf . readInt32BE ( 8 ) << 32 ) + buf . readInt32BE ( 12 ) ;
var magic = buf . toString ( 'utf8' , 16 , 32 ) ;
2020-02-12 19:57:05 -05:00
if ( ( type == 3 ) && ( size == 16 ) && ( magic == 'MeshCentralMCNDX' ) ) {
// Extra metadata present, lets read it.
extraMetadata = null ;
var buf2 = Buffer . alloc ( 16 ) ;
fs . read ( state . recFile , buf2 , 0 , 16 , time , function ( err , bytesRead , buf2 ) {
var xtype = buf2 . readInt16BE ( 0 ) ;
var xflags = buf2 . readInt16BE ( 2 ) ;
var xsize = buf2 . readInt32BE ( 4 ) ;
var xtime = ( buf2 . readInt32BE ( 8 ) << 32 ) + buf . readInt32BE ( 12 ) ;
var buf3 = Buffer . alloc ( xsize ) ;
fs . read ( state . recFile , buf3 , 0 , xsize , time + 16 , function ( err , bytesRead , buf3 ) {
func ( state , true , xtime , JSON . parse ( buf3 . toString ( ) ) ) ;
} ) ;
} ) ;
} else {
// No extra metadata or fail
func ( state , ( type == 3 ) && ( size == 16 ) && ( magic == 'MeshCentralMCREC' ) , time , null ) ;
}
2020-02-11 22:02:30 -05:00
} ) ;
}
function readNextBlock ( state , func ) {
if ( ( state . recFilePtr + 16 ) > state . recFileSize ) { func ( state , null ) ; return ; }
var r = { } , buf = Buffer . alloc ( 16 ) ;
fs . read ( state . recFile , buf , 0 , 16 , state . recFilePtr , function ( err , bytesRead , buf ) {
r . type = buf . readInt16BE ( 0 ) ;
r . flags = buf . readInt16BE ( 2 ) ;
r . size = buf . readInt32BE ( 4 ) ;
r . time = buf . readIntBE ( 8 , 8 ) ;
r . date = new Date ( r . time ) ;
r . ptr = state . recFilePtr ;
if ( ( state . recFilePtr + 16 + r . size ) > state . recFileSize ) { func ( state , null ) ; return ; }
if ( r . size == 0 ) {
r . data = null ;
func ( state , r ) ;
} else {
r . data = Buffer . alloc ( r . size ) ;
fs . read ( state . recFile , r . data , 0 , r . size , state . recFilePtr + 16 , function ( err , bytesRead , buf ) {
state . recFilePtr += ( 16 + r . size ) ;
func ( state , r ) ;
} ) ;
}
} ) ;
}
function isNumber ( x ) { return ( ( '' + parseInt ( x ) ) === x ) || ( ( '' + parseFloat ( x ) ) === x ) ; }
function format ( format ) { var args = Array . prototype . slice . call ( arguments , 1 ) ; return format . replace ( /{(\d+)}/g , function ( match , number ) { return typeof args [ number ] != 'undefined' ? args [ number ] : match ; } ) ; } ;
// Check if a list of modules are present and install any missing ones
var InstallModuleChildProcess = null ;
var previouslyInstalledModules = { } ;
function InstallModules ( modules , func ) {
var missingModules = [ ] ;
if ( previouslyInstalledModules == null ) { previouslyInstalledModules = { } ; }
if ( modules . length > 0 ) {
for ( var i in modules ) {
try {
var xxmodule = require ( modules [ i ] ) ;
} catch ( e ) {
if ( previouslyInstalledModules [ modules [ i ] ] !== true ) { missingModules . push ( modules [ i ] ) ; }
}
}
if ( missingModules . length > 0 ) { InstallModule ( missingModules . shift ( ) , InstallModules , modules , func ) ; } else { func ( ) ; }
}
}
// Check if a module is present and install it if missing
function InstallModule ( modulename , func , tag1 , tag2 ) {
log ( 'Installing ' + modulename + '...' ) ;
var child _process = require ( 'child_process' ) ;
var parentpath = _ _dirname ;
// Get the working directory
if ( ( _ _dirname . endsWith ( '/node_modules/meshcentral' ) ) || ( _ _dirname . endsWith ( '\\node_modules\\meshcentral' ) ) || ( _ _dirname . endsWith ( '/node_modules/meshcentral/' ) ) || ( _ _dirname . endsWith ( '\\node_modules\\meshcentral\\' ) ) ) { parentpath = require ( 'path' ) . join ( _ _dirname , '../..' ) ; }
// Looks like we need to keep a global reference to the child process object for this to work correctly.
InstallModuleChildProcess = child _process . exec ( 'npm install --no-optional --save ' + modulename , { maxBuffer : 512000 , timeout : 120000 , cwd : parentpath } , function ( error , stdout , stderr ) {
InstallModuleChildProcess = null ;
if ( ( error != null ) && ( error != '' ) ) {
log ( 'ERROR: Unable to install required module "' + modulename + '". May not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n' ) ;
process . exit ( ) ;
return ;
}
previouslyInstalledModules [ modulename ] = true ;
func ( tag1 , tag2 ) ;
return ;
} ) ;
}
// Export table
module . exports . startEx = startEx ;