2017-08-28 19:27:45 +03:00
/ * *
2018-01-04 23:15:21 +03:00
* @ description MeshCentral MeshAgent communication module
2017-08-28 19:27:45 +03:00
* @ author Ylian Saint - Hilaire & Bryan Roe
2022-01-24 10:21:24 +03:00
* @ copyright Intel Corporation 2018 - 2022
2018-01-04 23:15:21 +03:00
* @ license Apache - 2.0
2017-08-28 19:27:45 +03:00
* @ version v0 . 0.1
* /
2018-08-30 03:40:30 +03:00
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict" ;
2018-08-27 22:24:15 +03:00
2017-08-28 19:27:45 +03:00
// Construct a MeshAgent object, called upon connection
module . exports . CreateMeshAgent = function ( parent , db , ws , req , args , domain ) {
2019-03-08 09:47:27 +03:00
const forge = parent . parent . certificateOperations . forge ;
const common = parent . parent . common ;
2019-05-02 01:02:03 +03:00
parent . agentStats . createMeshAgentCount ++ ;
2020-05-22 07:25:11 +03:00
parent . parent . debug ( 'agent' , 'New agent at ' + req . clientIp + ':' + ws . _socket . remotePort ) ;
2019-03-08 09:47:27 +03:00
2017-08-28 19:27:45 +03:00
var obj = { } ;
obj . domain = domain ;
2019-03-08 09:47:27 +03:00
obj . authenticated = 0 ;
2017-08-28 19:27:45 +03:00
obj . receivedCommands = 0 ;
obj . agentCoreCheck = 0 ;
2020-05-22 07:25:11 +03:00
obj . remoteaddr = req . clientIp ;
2019-03-08 09:47:27 +03:00
obj . remoteaddrport = obj . remoteaddr + ':' + ws . _socket . remotePort ;
obj . nonce = parent . crypto . randomBytes ( 48 ) . toString ( 'binary' ) ;
2019-06-18 01:20:47 +03:00
//ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes
if ( args . agentidletimeout != 0 ) { ws . _socket . setTimeout ( args . agentidletimeout , function ( ) { obj . close ( 1 ) ; } ) ; } // Inactivity timeout of 2:30 minutes, by default agent will WebSocket ping every 2 minutes and server will pong back.
2019-03-08 09:47:27 +03:00
//obj.nodeid = null;
//obj.meshid = null;
//obj.dbNodeKey = null;
//obj.dbMeshKey = null;
//obj.connectTime = null;
//obj.agentInfo = null;
2017-08-28 19:27:45 +03:00
2021-05-05 12:30:23 +03:00
ws . _socket . bytesReadEx = 0 ;
ws . _socket . bytesWrittenEx = 0 ;
// Perform data accounting
function dataAccounting ( ) {
2021-05-05 23:17:58 +03:00
parent . trafficStats . AgentCtrlIn += ( ws . _socket . bytesRead - ws . _socket . bytesReadEx ) ;
parent . trafficStats . AgentCtrlOut += ( ws . _socket . bytesWritten - ws . _socket . bytesWrittenEx ) ;
2021-05-05 12:30:23 +03:00
ws . _socket . bytesReadEx = ws . _socket . bytesRead ;
ws . _socket . bytesWrittenEx = ws . _socket . bytesWritten ;
}
2017-08-28 19:27:45 +03:00
// Send a message to the mesh agent
2019-08-10 01:13:53 +03:00
obj . send = function ( data , func ) { try { if ( typeof data == 'string' ) { ws . send ( Buffer . from ( data ) , func ) ; } else { ws . send ( data , func ) ; } } catch ( e ) { } } ;
obj . sendBinary = function ( data , func ) { try { if ( typeof data == 'string' ) { ws . send ( Buffer . from ( data , 'binary' ) , func ) ; } else { ws . send ( data , func ) ; } } catch ( e ) { } } ;
2017-08-28 19:27:45 +03:00
// Disconnect this agent
obj . close = function ( arg ) {
2021-05-05 12:30:23 +03:00
dataAccounting ( ) ;
2019-08-23 01:31:39 +03:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { ws . close ( ) ; if ( obj . nodeid != null ) { parent . parent . debug ( 'agent' , 'Soft disconnect ' + obj . nodeid + ' (' + obj . remoteaddrport + ')' ) ; } } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
2022-01-08 21:43:01 +03:00
if ( arg == 2 ) {
try {
2022-01-08 22:01:37 +03:00
if ( ws . _socket . _parent != null )
2022-01-08 21:43:01 +03:00
ws . _socket . _parent . end ( ) ;
else
ws . _socket . end ( ) ;
if ( obj . nodeid != null ) {
parent . parent . debug ( 'agent' , 'Hard disconnect ' + obj . nodeid + ' (' + obj . remoteaddrport + ')' ) ;
}
} catch ( e ) { console . log ( e ) ; }
}
// If arg == 2, hard close, close the TCP socket
2019-02-26 04:30:19 +03:00
// If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
2019-03-10 01:28:08 +03:00
2021-11-10 09:51:40 +03:00
// Stop any current self-share
if ( obj . guestSharing === true ) { removeGuestSharing ( ) ; }
2019-04-10 20:41:10 +03:00
// Remove this agent from the webserver list
if ( parent . wsagents [ obj . dbNodeKey ] == obj ) {
delete parent . wsagents [ obj . dbNodeKey ] ;
2021-10-10 22:43:20 +03:00
parent . parent . ClearConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , 1 , null , { remoteaddrport : obj . remoteaddrport , name : obj . name } ) ;
2019-04-10 20:41:10 +03:00
}
2018-09-27 00:58:55 +03:00
2019-10-30 02:17:29 +03:00
// Remove this agent from the list of agents with bad web certificates
if ( obj . badWebCert ) { delete parent . wsagentsWithBadWebCerts [ obj . badWebCert ] ; }
2019-04-10 20:41:10 +03:00
// Get the current mesh
const mesh = parent . meshes [ obj . dbMeshKey ] ;
// If this is a temporary or recovery agent, or all devices in this group are temporary, remove the agent (0x20 = Temporary, 0x40 = Recovery)
if ( ( ( obj . agentInfo ) && ( obj . agentInfo . capabilities ) && ( ( obj . agentInfo . capabilities & 0x20 ) || ( obj . agentInfo . capabilities & 0x40 ) ) ) || ( ( mesh ) && ( mesh . flags ) && ( mesh . flags & 1 ) ) ) {
// Delete this node including network interface information and events
2020-09-10 00:26:03 +03:00
db . Remove ( obj . dbNodeKey ) ; // Remove node with that id
db . Remove ( 'if' + obj . dbNodeKey ) ; // Remove interface information
db . Remove ( 'nt' + obj . dbNodeKey ) ; // Remove notes
db . Remove ( 'lc' + obj . dbNodeKey ) ; // Remove last connect time
db . Remove ( 'si' + obj . dbNodeKey ) ; // Remove system information
2021-04-18 00:44:19 +03:00
db . Remove ( 'al' + obj . dbNodeKey ) ; // Remove error log last time
2020-09-10 00:26:03 +03:00
if ( db . RemoveSMBIOS ) { db . RemoveSMBIOS ( obj . dbNodeKey ) ; } // Remove SMBios data
db . RemoveAllNodeEvents ( obj . dbNodeKey ) ; // Remove all events for this node
db . removeAllPowerEventsForNode ( obj . dbNodeKey ) ; // Remove all power events for this node
2019-04-10 20:41:10 +03:00
// Event node deletion
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , { etype : 'node' , action : 'removenode' , nodeid : obj . dbNodeKey , domain : domain . id , nolog : 1 } ) ;
2019-04-10 20:41:10 +03:00
// Disconnect all connections if needed
const state = parent . parent . GetConnectivityState ( obj . dbNodeKey ) ;
if ( ( state != null ) && ( state . connectivity != null ) ) {
if ( ( state . connectivity & 1 ) != 0 ) { parent . wsagents [ obj . dbNodeKey ] . close ( ) ; } // Disconnect mesh agent
2020-10-10 01:44:09 +03:00
if ( ( state . connectivity & 2 ) != 0 ) { parent . parent . mpsserver . closeAllForNode ( obj . dbNodeKey ) ; } // Disconnect CIRA connection
2018-04-12 21:15:01 +03:00
}
}
2019-03-10 01:28:08 +03:00
2019-10-06 07:48:17 +03:00
// Set this agent as no longer authenticated
obj . authenticated = - 1 ;
2021-01-17 10:35:39 +03:00
// If we where updating the agent using native method, clean that up.
2019-03-10 01:28:08 +03:00
if ( obj . agentUpdate != null ) {
if ( obj . agentUpdate . fd ) { try { parent . fs . close ( obj . agentUpdate . fd ) ; } catch ( ex ) { } }
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-10 01:28:08 +03:00
delete obj . agentUpdate ;
}
2021-01-17 10:35:39 +03:00
// If we where updating the agent meshcore method, clean that up.
if ( obj . agentCoreUpdateTaskId != null ) {
parent . parent . taskLimiter . completed ( obj . agentCoreUpdateTaskId ) ;
delete obj . agentCoreUpdateTaskId ;
}
2019-05-28 20:58:46 +03:00
// Perform timer cleanup
if ( obj . pingtimer ) { clearInterval ( obj . pingtimer ) ; delete obj . pingtimer ; }
if ( obj . pongtimer ) { clearInterval ( obj . pongtimer ) ; delete obj . pongtimer ; }
2019-03-10 01:28:08 +03:00
// Perform aggressive cleanup
2021-10-10 22:43:20 +03:00
delete obj . name ;
delete obj . nonce ;
delete obj . nodeid ;
delete obj . unauth ;
delete obj . remoteaddr ;
delete obj . remoteaddrport ;
delete obj . meshid ;
delete obj . connectTime ;
delete obj . agentInfo ;
delete obj . agentExeInfo ;
2019-10-29 02:52:12 +03:00
ws . removeAllListeners ( [ 'message' , 'close' , 'error' ] ) ;
2018-08-30 03:40:30 +03:00
} ;
2017-08-28 19:27:45 +03:00
// When data is received from the mesh agent web socket
ws . on ( 'message' , function ( msg ) {
2021-05-05 12:30:23 +03:00
dataAccounting ( ) ;
2017-08-28 19:27:45 +03:00
if ( msg . length < 2 ) return ;
2017-10-15 09:22:19 +03:00
if ( typeof msg == 'object' ) { msg = msg . toString ( 'binary' ) ; } // TODO: Could change this entire method to use Buffer instead of binary string
2017-08-28 19:27:45 +03:00
if ( obj . authenticated == 2 ) { // We are authenticated
2018-07-03 00:34:10 +03:00
if ( ( obj . agentUpdate == null ) && ( msg . charCodeAt ( 0 ) == 123 ) ) { processAgentData ( msg ) ; } // Only process JSON messages if meshagent update is not in progress
2017-08-28 19:27:45 +03:00
if ( msg . length < 2 ) return ;
2019-03-08 09:47:27 +03:00
const cmdid = common . ReadShort ( msg , 0 ) ;
2017-08-28 19:27:45 +03:00
if ( cmdid == 11 ) { // MeshCommand_CoreModuleHash
2019-10-29 02:52:12 +03:00
if ( msg . length == 4 ) { ChangeAgentCoreInfo ( { 'caps' : 0 } ) ; } // If the agent indicated that no core is running, clear the core information string.
2017-08-28 19:27:45 +03:00
// Mesh core hash, sent by agent with the hash of the current mesh core.
2019-02-19 04:50:10 +03:00
2019-10-28 20:35:58 +03:00
// If we are performing an agent update, don't update the core.
if ( obj . agentUpdate != null ) { return ; }
2019-02-19 04:50:10 +03:00
// If we are using a custom core, don't try to update it.
if ( obj . agentCoreCheck == 1000 ) {
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 16 ) + common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
2019-02-19 04:50:10 +03:00
agentCoreIsStable ( ) ;
return ;
}
2018-12-30 02:24:33 +03:00
// Get the current meshcore hash
const agentMeshCoreHash = ( msg . length == 52 ) ? msg . substring ( 4 , 52 ) : null ;
2019-03-08 09:47:27 +03:00
// If the agent indicates this is a custom core, we are done.
2019-03-10 01:28:08 +03:00
if ( ( agentMeshCoreHash != null ) && ( agentMeshCoreHash == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' ) ) {
2019-02-19 04:50:10 +03:00
obj . agentCoreCheck = 0 ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 16 ) + common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
2019-02-19 04:50:10 +03:00
agentCoreIsStable ( ) ;
return ;
}
2019-03-08 09:47:27 +03:00
// We need to check if the core is current. Figure out what core we need.
2019-06-18 01:37:13 +03:00
var corename = null ;
2021-01-25 03:09:15 +03:00
if ( ( obj . agentInfo != null ) && ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] != null ) ) {
2021-01-15 07:27:15 +03:00
if ( ( obj . agentCoreCheck == 1001 ) || ( obj . agentCoreUpdate == true ) ) {
2019-06-18 01:37:13 +03:00
// If the user asked, use the recovery core.
corename = parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . rcore ;
2021-01-25 05:43:15 +03:00
} else if ( obj . agentCoreCheck == 1011 ) {
// If the user asked, use the tiny core.
corename = parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . tcore ;
2019-06-18 01:37:13 +03:00
} else if ( obj . agentInfo . capabilities & 0x40 ) {
// If this is a recovery agent, use the agent recovery core.
corename = parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . arcore ;
} else {
// This is the normal core for this agent type.
corename = parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core ;
}
2019-03-08 09:47:27 +03:00
}
2019-02-26 01:35:08 +03:00
// If we have a core, use it.
2018-12-30 02:24:33 +03:00
if ( corename != null ) {
2019-03-08 09:47:27 +03:00
const meshcorehash = parent . parent . defaultMeshCoresHash [ corename ] ;
2021-01-15 07:27:15 +03:00
if ( agentMeshCoreHash != meshcorehash ) {
2021-01-25 05:43:15 +03:00
if ( ( obj . agentCoreCheck < 5 ) || ( obj . agentCoreCheck == 1001 ) || ( obj . agentCoreCheck == 1011 ) || ( obj . agentCoreUpdate == true ) ) {
2018-12-30 02:24:33 +03:00
if ( meshcorehash == null ) {
2019-01-18 02:07:34 +03:00
// Clear the core
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreModule, ask mesh agent to clear the core
2019-05-02 01:02:03 +03:00
parent . agentStats . clearingCoreCount ++ ;
2019-10-29 02:52:12 +03:00
parent . parent . debug ( 'agent' , "Clearing core" ) ;
2018-12-30 02:24:33 +03:00
} else {
2020-07-17 21:14:24 +03:00
// Setup task limiter options, this system limits how many tasks can run at the same time to spread the server load.
var taskLimiterOptions = { hash : meshcorehash , core : parent . parent . defaultMeshCores [ corename ] , name : corename } ;
// If the agent supports compression, sent the core compressed.
2020-07-29 04:45:17 +03:00
if ( ( obj . agentInfo . capabilities & 0x100 ) && ( parent . parent . defaultMeshCoresDeflate [ corename ] ) ) {
2020-07-22 02:20:17 +03:00
args . core = parent . parent . defaultMeshCoresDeflate [ corename ] ;
}
2019-01-29 02:47:54 +03:00
2019-02-09 01:17:35 +03:00
// Update new core with task limiting so not to flood the server. This is a high priority task.
2019-02-26 03:07:27 +03:00
obj . agentCoreUpdatePending = true ;
2019-03-08 09:47:27 +03:00
parent . parent . taskLimiter . launch ( function ( argument , taskid , taskLimiterQueue ) {
2019-02-26 04:30:19 +03:00
if ( obj . authenticated == 2 ) {
2021-01-15 03:17:56 +03:00
// Send the updated core.
2019-03-08 09:47:27 +03:00
delete obj . agentCoreUpdatePending ;
2022-01-14 01:34:39 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) + argument . hash + argument . core . toString ( 'binary' ) , function ( ) { parent . parent . taskLimiter . completed ( taskid ) ; } ) ; // MeshCommand_CoreModule, start core update
2019-05-02 01:02:03 +03:00
parent . agentStats . updatingCoreCount ++ ;
2019-10-29 02:52:12 +03:00
parent . parent . debug ( 'agent' , "Updating core " + argument . name ) ;
2019-02-26 04:30:19 +03:00
} else {
// This agent is probably disconnected, nothing to do.
2019-03-08 09:47:27 +03:00
parent . parent . taskLimiter . completed ( taskid ) ;
2019-02-26 04:30:19 +03:00
}
2020-07-17 21:14:24 +03:00
} , taskLimiterOptions , 0 ) ;
2018-12-30 02:24:33 +03:00
}
obj . agentCoreCheck ++ ;
}
} else {
obj . agentCoreCheck = 0 ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 16 ) + common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
2019-02-08 02:00:10 +03:00
agentCoreIsStable ( ) ; // No updates needed, agent is ready to go.
2018-12-30 02:24:33 +03:00
}
}
/ *
2017-08-28 19:27:45 +03:00
// TODO: Check if we have a mesh specific core. If so, use that.
var agentMeshCoreHash = null ;
2017-10-15 09:22:19 +03:00
if ( msg . length == 52 ) { agentMeshCoreHash = msg . substring ( 4 , 52 ) ; }
2019-03-08 09:47:27 +03:00
if ( ( agentMeshCoreHash != parent . parent . defaultMeshCoreHash ) && ( agentMeshCoreHash != parent . parent . defaultMeshCoreNoMeiHash ) ) {
2017-08-28 19:27:45 +03:00
if ( obj . agentCoreCheck < 5 ) { // This check is in place to avoid a looping core update.
2019-03-08 09:47:27 +03:00
if ( parent . parent . defaultMeshCoreHash == null ) {
2017-08-28 19:27:45 +03:00
// Update no core
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ; // Command 10, ask mesh agent to clear the core
2017-08-28 19:27:45 +03:00
} else {
// Update new core
2020-02-18 00:01:13 +03:00
if ( ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] != null ) && ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . amt == true ) ) {
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) + parent . parent . defaultMeshCoreHash + parent . parent . defaultMeshCore ) ; // Command 10, ask mesh agent to set the core (with MEI support)
2017-12-13 03:04:54 +03:00
} else {
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) + parent . parent . defaultMeshCoreNoMeiHash + parent . parent . defaultMeshCoreNoMei ) ; // Command 10, ask mesh agent to set the core (No MEI)
2017-12-13 03:04:54 +03:00
}
2017-08-28 19:27:45 +03:00
}
obj . agentCoreCheck ++ ;
}
} else {
obj . agentCoreCheck = 0 ;
}
2018-12-30 02:24:33 +03:00
* /
2017-08-28 19:27:45 +03:00
}
else if ( cmdid == 12 ) { // MeshCommand_AgentHash
2017-10-15 09:22:19 +03:00
if ( ( msg . length == 52 ) && ( obj . agentExeInfo != null ) && ( obj . agentExeInfo . update == true ) ) {
2019-03-08 09:47:27 +03:00
const agenthash = msg . substring ( 4 ) ;
2022-02-22 11:21:39 +03:00
const agentUpdateMethod = compareAgentBinaryHash ( obj . agentExeInfo , agenthash ) ;
2021-01-15 03:17:56 +03:00
if ( agentUpdateMethod === 2 ) { // Use meshcore agent update system
// Send the recovery core to the agent, if the agent is capable of running one
if ( ( ( obj . agentInfo . capabilities & 16 ) != 0 ) && ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core != null ) ) {
2021-02-04 02:36:25 +03:00
parent . agentStats . agentMeshCoreBinaryUpdate ++ ;
2021-01-15 03:17:56 +03:00
obj . agentCoreUpdate = true ;
2021-01-25 05:11:51 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ; // Ask to clear the core
obj . sendBinary ( common . ShortToStr ( 11 ) + common . ShortToStr ( 0 ) ) ; // Ask for meshcore hash
2021-01-15 03:17:56 +03:00
}
} else if ( agentUpdateMethod === 1 ) { // Use native agent update system
2019-02-09 01:17:35 +03:00
// Mesh agent update required, do it using task limiter so not to flood the network. Medium priority task.
2019-03-08 09:47:27 +03:00
parent . parent . taskLimiter . launch ( function ( argument , taskid , taskLimiterQueue ) {
if ( obj . authenticated != 2 ) { parent . parent . taskLimiter . completed ( taskid ) ; return ; } // If agent disconnection, complete and exit now.
2019-10-29 02:52:12 +03:00
if ( obj . nodeid != null ) { parent . parent . debug ( 'agent' , "Agent update required, NodeID=0x" + obj . nodeid . substring ( 0 , 16 ) + ', ' + obj . agentExeInfo . desc ) ; }
2019-05-25 01:11:00 +03:00
parent . agentStats . agentBinaryUpdate ++ ;
2020-07-29 04:45:17 +03:00
if ( ( obj . agentExeInfo . data == null ) && ( ( ( obj . agentInfo . capabilities & 0x100 ) == 0 ) || ( obj . agentExeInfo . zdata == null ) ) ) {
2019-02-15 09:16:15 +03:00
// Read the agent from disk
2019-03-08 09:47:27 +03:00
parent . fs . open ( obj . agentExeInfo . path , 'r' , function ( err , fd ) {
2019-03-10 02:04:43 +03:00
if ( obj . agentExeInfo == null ) return ; // Agent disconnected during this call.
2019-10-28 20:35:58 +03:00
if ( err ) { parent . parent . debug ( 'agentupdate' , "ERROR: " + err ) ; return console . error ( err ) ; }
2019-10-29 02:52:12 +03:00
obj . agentUpdate = { ptr : 0 , buf : Buffer . alloc ( parent . parent . agentUpdateBlockSize + 4 ) , fd : fd , taskid : taskid } ;
2019-02-15 09:16:15 +03:00
// MeshCommand_CoreModule, ask mesh agent to clear the core.
// The new core will only be sent after the agent updates.
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ;
2019-02-15 09:16:15 +03:00
2020-07-18 00:29:02 +03:00
// We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result
2019-02-15 09:16:15 +03:00
//console.log("Agent update file open.");
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 13 ) + common . ShortToStr ( 0 ) ) ; // Command 13, start mesh agent download
2019-02-15 09:16:15 +03:00
// Send the first mesh agent update data block
obj . agentUpdate . buf [ 0 ] = 0 ;
obj . agentUpdate . buf [ 1 ] = 14 ;
obj . agentUpdate . buf [ 2 ] = 0 ;
obj . agentUpdate . buf [ 3 ] = 1 ;
2019-10-29 02:52:12 +03:00
parent . fs . read ( obj . agentUpdate . fd , obj . agentUpdate . buf , 4 , parent . parent . agentUpdateBlockSize , obj . agentUpdate . ptr , function ( err , bytesRead , buffer ) {
2020-12-07 09:24:51 +03:00
if ( obj . agentUpdate == null ) return ;
2019-03-08 09:47:27 +03:00
if ( ( err != null ) || ( bytesRead == 0 ) ) {
// Error reading the agent file, stop here.
try { parent . fs . close ( obj . agentUpdate . fd ) ; } catch ( ex ) { }
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "ERROR: Unable to read first block of agent binary from disk." ) ;
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-08 09:47:27 +03:00
delete obj . agentUpdate ;
} else {
// Send the first block to the agent
obj . agentUpdate . ptr += bytesRead ;
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Sent first block of " + bytesRead + " bytes from disk." ) ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( obj . agentUpdate . buf ) ; // Command 14, mesh agent first data block
2019-03-08 09:47:27 +03:00
}
} ) ;
2019-02-15 09:16:15 +03:00
} ) ;
} else {
// Send the agent from RAM
2019-10-29 02:52:12 +03:00
obj . agentUpdate = { ptr : 0 , buf : Buffer . alloc ( parent . parent . agentUpdateBlockSize + 4 ) , taskid : taskid } ;
2019-01-29 03:53:11 +03:00
// MeshCommand_CoreModule, ask mesh agent to clear the core.
// The new core will only be sent after the agent updates.
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ;
2019-01-29 03:53:11 +03:00
2020-07-18 00:29:02 +03:00
// We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 13 ) + common . ShortToStr ( 0 ) ) ; // Command 13, start mesh agent download
2019-01-29 03:53:11 +03:00
// Send the first mesh agent update data block
obj . agentUpdate . buf [ 0 ] = 0 ;
obj . agentUpdate . buf [ 1 ] = 14 ;
obj . agentUpdate . buf [ 2 ] = 0 ;
obj . agentUpdate . buf [ 3 ] = 1 ;
2019-02-15 09:16:15 +03:00
2020-07-18 00:29:02 +03:00
// If agent supports compression, send the compressed agent if possible.
2020-07-29 04:45:17 +03:00
if ( ( obj . agentInfo . capabilities & 0x100 ) && ( obj . agentExeInfo . zdata != null ) ) {
2020-07-18 00:29:02 +03:00
// Send compressed data
obj . agentUpdate . agentUpdateData = obj . agentExeInfo . zdata ;
obj . agentUpdate . agentUpdateHash = obj . agentExeInfo . zhash ;
} else {
// Send uncompressed data
obj . agentUpdate . agentUpdateData = obj . agentExeInfo . data ;
obj . agentUpdate . agentUpdateHash = obj . agentExeInfo . hash ;
}
const len = Math . min ( parent . parent . agentUpdateBlockSize , obj . agentUpdate . agentUpdateData . length - obj . agentUpdate . ptr ) ;
2019-02-15 09:16:15 +03:00
if ( len > 0 ) {
// Send the first block
2020-07-18 00:29:02 +03:00
obj . agentUpdate . agentUpdateData . copy ( obj . agentUpdate . buf , 4 , obj . agentUpdate . ptr , obj . agentUpdate . ptr + len ) ;
2019-01-29 03:53:11 +03:00
obj . agentUpdate . ptr += len ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( obj . agentUpdate . buf ) ; // Command 14, mesh agent first data block
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Sent first block of " + len + " bytes from RAM." ) ;
2019-02-15 09:16:15 +03:00
} else {
// Error
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "ERROR: Len of " + len + " is invalid." ) ;
2019-03-08 09:47:27 +03:00
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-08 09:47:27 +03:00
delete obj . agentUpdate ;
2019-01-29 03:53:11 +03:00
}
2019-02-15 09:16:15 +03:00
}
2019-02-09 01:17:35 +03:00
} , null , 1 ) ;
2019-01-29 03:53:11 +03:00
2019-01-22 01:05:50 +03:00
} else {
// Check the mesh core, if the agent is capable of running one
2019-03-08 09:47:27 +03:00
if ( ( ( obj . agentInfo . capabilities & 16 ) != 0 ) && ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core != null ) ) {
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 11 ) + common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2019-01-23 02:40:08 +03:00
}
2017-08-28 19:27:45 +03:00
}
}
}
else if ( cmdid == 14 ) { // MeshCommand_AgentBinaryBlock
if ( ( msg . length == 4 ) && ( obj . agentUpdate != null ) ) {
2019-03-08 09:47:27 +03:00
const status = common . ReadShort ( msg , 2 ) ;
2017-08-28 19:27:45 +03:00
if ( status == 1 ) {
2019-02-15 09:16:15 +03:00
if ( obj . agentExeInfo . data == null ) {
// Read the agent from disk
2019-10-29 02:52:12 +03:00
parent . fs . read ( obj . agentUpdate . fd , obj . agentUpdate . buf , 4 , parent . parent . agentUpdateBlockSize , obj . agentUpdate . ptr , function ( err , bytesRead , buffer ) {
2019-03-10 02:04:43 +03:00
if ( ( obj . agentExeInfo == null ) || ( obj . agentUpdate == null ) ) return ; // Agent disconnected during this async call.
2019-03-08 09:47:27 +03:00
if ( ( err != null ) || ( bytesRead < 0 ) ) {
// Error reading the agent file, stop here.
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "ERROR: Unable to read agent #" + obj . agentExeInfo . id + " binary from disk." ) ;
2019-03-08 09:47:27 +03:00
try { parent . fs . close ( obj . agentUpdate . fd ) ; } catch ( ex ) { }
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-08 09:47:27 +03:00
delete obj . agentUpdate ;
} else {
// Send the next block to the agent
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Sending disk agent #" + obj . agentExeInfo . id + " block, ptr=" + obj . agentUpdate . ptr + ", len=" + bytesRead + "." ) ;
2019-03-08 09:47:27 +03:00
obj . agentUpdate . ptr += bytesRead ;
2019-10-29 02:52:12 +03:00
if ( bytesRead == parent . parent . agentUpdateBlockSize ) { obj . sendBinary ( obj . agentUpdate . buf ) ; } else { obj . sendBinary ( obj . agentUpdate . buf . slice ( 0 , bytesRead + 4 ) ) ; } // Command 14, mesh agent next data block
if ( ( bytesRead < parent . parent . agentUpdateBlockSize ) || ( obj . agentUpdate . ptr == obj . agentExeInfo . size ) ) {
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Completed agent #" + obj . agentExeInfo . id + " update from disk, ptr=" + obj . agentUpdate . ptr + "." ) ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 13 ) + common . ShortToStr ( 0 ) + obj . agentExeInfo . hash ) ; // Command 13, end mesh agent download, send agent SHA384 hash
2019-03-08 09:47:27 +03:00
try { parent . fs . close ( obj . agentUpdate . fd ) ; } catch ( ex ) { }
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-08 09:47:27 +03:00
delete obj . agentUpdate ;
}
2019-02-15 09:16:15 +03:00
}
2019-03-08 09:47:27 +03:00
} ) ;
2017-08-28 19:27:45 +03:00
} else {
2019-02-15 09:16:15 +03:00
// Send the agent from RAM
2020-07-18 00:29:02 +03:00
const len = Math . min ( parent . parent . agentUpdateBlockSize , obj . agentUpdate . agentUpdateData . length - obj . agentUpdate . ptr ) ;
2019-02-15 09:16:15 +03:00
if ( len > 0 ) {
2020-07-18 00:29:02 +03:00
obj . agentUpdate . agentUpdateData . copy ( obj . agentUpdate . buf , 4 , obj . agentUpdate . ptr , obj . agentUpdate . ptr + len ) ;
2019-10-29 02:52:12 +03:00
if ( len == parent . parent . agentUpdateBlockSize ) { obj . sendBinary ( obj . agentUpdate . buf ) ; } else { obj . sendBinary ( obj . agentUpdate . buf . slice ( 0 , len + 4 ) ) ; } // Command 14, mesh agent next data block
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Sending RAM agent #" + obj . agentExeInfo . id + " block, ptr=" + obj . agentUpdate . ptr + ", len=" + len + "." ) ;
2019-02-15 09:16:15 +03:00
obj . agentUpdate . ptr += len ;
}
2017-08-28 19:27:45 +03:00
2020-07-18 00:29:02 +03:00
if ( obj . agentUpdate . ptr == obj . agentUpdate . agentUpdateData . length ) {
2019-10-28 20:35:58 +03:00
parent . parent . debug ( 'agentupdate' , "Completed agent #" + obj . agentExeInfo . id + " update from RAM, ptr=" + obj . agentUpdate . ptr + "." ) ;
2020-07-18 00:29:02 +03:00
obj . sendBinary ( common . ShortToStr ( 13 ) + common . ShortToStr ( 0 ) + obj . agentUpdate . agentUpdateHash ) ; // Command 13, end mesh agent download, send agent SHA384 hash
2019-03-08 09:47:27 +03:00
parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2019-03-10 21:00:14 +03:00
delete obj . agentUpdate . buf ;
2019-03-08 09:47:27 +03:00
delete obj . agentUpdate ;
2017-08-28 19:27:45 +03:00
}
}
}
}
}
else if ( cmdid == 15 ) { // MeshCommand_AgentTag
2018-01-24 01:15:59 +03:00
var tag = msg . substring ( 2 ) ;
while ( tag . charCodeAt ( tag . length - 1 ) == 0 ) { tag = tag . substring ( 0 , tag . length - 1 ) ; } // Remove end-of-line zeros.
ChangeAgentTag ( tag ) ;
2017-08-28 19:27:45 +03:00
}
2018-01-24 01:15:59 +03:00
} else if ( obj . authenticated < 2 ) { // We are not authenticated
2020-01-30 01:38:41 +03:00
// Check if this is a un-authenticated JSON
if ( msg . charCodeAt ( 0 ) == 123 ) {
var str = msg . toString ( 'utf8' ) , command = null ;
if ( str [ 0 ] == '{' ) {
try { command = JSON . parse ( str ) ; } catch ( ex ) { } // If the command can't be parsed, ignore it.
if ( ( command != null ) && ( command . action === 'agentName' ) && ( typeof command . value == 'string' ) && ( command . value . length > 0 ) && ( command . value . length < 256 ) ) { obj . agentName = command . value ; }
}
return ;
}
2019-03-08 09:47:27 +03:00
const cmd = common . ReadShort ( msg , 0 ) ;
2017-08-28 19:27:45 +03:00
if ( cmd == 1 ) {
// Agent authentication request
2017-10-15 09:22:19 +03:00
if ( ( msg . length != 98 ) || ( ( obj . receivedCommands & 1 ) != 0 ) ) return ;
2017-08-28 19:27:45 +03:00
obj . receivedCommands += 1 ; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
2020-12-31 03:54:02 +03:00
if ( isIgnoreHashCheck ( ) ) {
2018-12-15 23:34:55 +03:00
// Send the agent web hash back to the agent
2020-05-22 22:54:22 +03:00
// Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 1 ) + msg . substring ( 2 , 50 ) + obj . nonce ) ; // Command 1, hash + nonce. Use the web hash given by the agent.
2018-12-15 23:34:55 +03:00
} else {
// Check that the server hash matches our own web certificate hash (SHA384)
2021-02-15 09:22:53 +03:00
obj . agentSeenCerthash = msg . substring ( 2 , 50 ) ;
if ( ( getWebCertHash ( domain ) != obj . agentSeenCerthash ) && ( getWebCertFullHash ( domain ) != obj . agentSeenCerthash ) && ( parent . defaultWebCertificateHash != obj . agentSeenCerthash ) && ( parent . defaultWebCertificateFullHash != obj . agentSeenCerthash ) ) {
2019-10-30 02:17:29 +03:00
if ( parent . parent . supportsProxyCertificatesRequest !== false ) {
obj . badWebCert = Buffer . from ( parent . crypto . randomBytes ( 16 ) , 'binary' ) . toString ( 'base64' ) ;
parent . wsagentsWithBadWebCerts [ obj . badWebCert ] = obj ; // Add this agent to the list of of agents with bad web certificates.
2019-10-30 02:31:03 +03:00
parent . parent . updateProxyCertificates ( false ) ;
2019-10-30 02:17:29 +03:00
}
2019-05-02 01:02:03 +03:00
parent . agentStats . agentBadWebCertHashCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "BadWebCertHash: " + Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent bad web cert hash (Agent:' + ( Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' != Server:' + ( Buffer . from ( getWebCertHash ( domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' or ' + ( Buffer . from ( getWebCertFullHash ( domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + '), holding connection (' + obj . remoteaddrport + ').' ) ;
parent . parent . debug ( 'agent' , 'Agent reported web cert hash:' + ( Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) ) + '.' ) ;
2020-03-19 23:42:37 +03:00
console . log ( 'Agent bad web cert hash (Agent:' + ( Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' != Server:' + ( Buffer . from ( getWebCertHash ( domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' or ' + ( Buffer . from ( getWebCertFullHash ( domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + '), holding connection (' + obj . remoteaddrport + ').' ) ;
2019-02-15 01:44:38 +03:00
console . log ( 'Agent reported web cert hash:' + ( Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) ) + '.' ) ;
2021-02-23 01:41:54 +03:00
delete obj . agentSeenCerthash ;
2019-02-15 01:44:38 +03:00
return ;
2020-05-22 22:54:22 +03:00
} else {
// The hash matched one of the acceptable values, send the agent web hash back to the agent
// Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
// Command 1, hash + nonce. Use the web hash given by the agent.
2021-02-15 09:22:53 +03:00
obj . sendBinary ( common . ShortToStr ( 1 ) + obj . agentSeenCerthash + obj . nonce ) ;
2019-02-15 01:44:38 +03:00
}
2018-12-15 23:34:55 +03:00
}
2017-08-28 19:27:45 +03:00
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
2018-11-01 02:03:09 +03:00
obj . agentnonce = msg . substring ( 50 , 98 ) ;
2018-08-22 01:08:15 +03:00
// Check if we got the agent auth confirmation
if ( ( obj . receivedCommands & 8 ) == 0 ) {
// If we did not get an indication that the agent already validated this server, send the server signature.
if ( obj . useSwarmCert == true ) {
// Perform the hash signature using older swarm server certificate
2019-08-13 00:58:06 +03:00
parent . parent . certificateOperations . acceleratorPerformSignature ( 1 , msg . substring ( 2 ) + obj . nonce , null , function ( tag , signature ) {
2018-08-22 01:08:15 +03:00
// Send back our certificate + signature
2019-08-13 23:55:25 +03:00
obj . sendBinary ( common . ShortToStr ( 2 ) + common . ShortToStr ( parent . swarmCertificateAsn1 . length ) + parent . swarmCertificateAsn1 + signature ) ; // Command 2, certificate + signature
2018-08-22 01:08:15 +03:00
} ) ;
} else {
// Perform the hash signature using the server agent certificate
2019-08-13 00:58:06 +03:00
parent . parent . certificateOperations . acceleratorPerformSignature ( 0 , msg . substring ( 2 ) + obj . nonce , null , function ( tag , signature ) {
2018-08-22 01:08:15 +03:00
// Send back our certificate + signature
2019-08-13 23:55:25 +03:00
obj . sendBinary ( common . ShortToStr ( 2 ) + common . ShortToStr ( parent . agentCertificateAsn1 . length ) + parent . agentCertificateAsn1 + signature ) ; // Command 2, certificate + signature
2018-08-22 01:08:15 +03:00
} ) ;
}
2017-11-08 04:05:22 +03:00
}
2017-08-28 19:27:45 +03:00
// Check the agent signature if we can
2017-09-15 21:45:06 +03:00
if ( obj . unauthsign != null ) {
2019-05-02 01:02:03 +03:00
if ( processAgentSignature ( obj . unauthsign ) == false ) {
parent . agentStats . agentBadSignature1Count ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "BadSignature1" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with bad signature, holding connection (' + obj . remoteaddrport + ').' ) ;
2019-05-02 01:02:03 +03:00
console . log ( 'Agent connected with bad signature, holding connection (' + obj . remoteaddrport + ').' ) ; return ;
} else { completeAgentConnection ( ) ; }
2017-08-28 19:27:45 +03:00
}
}
else if ( cmd == 2 ) {
// Agent certificate
if ( ( msg . length < 4 ) || ( ( obj . receivedCommands & 2 ) != 0 ) ) return ;
obj . receivedCommands += 2 ; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Decode the certificate
2019-03-08 09:47:27 +03:00
const certlen = common . ReadShort ( msg , 2 ) ;
2017-08-28 19:27:45 +03:00
obj . unauth = { } ;
2020-04-24 23:17:06 +03:00
try { obj . unauth . nodeid = Buffer . from ( forge . pki . getPublicKeyFingerprint ( forge . pki . certificateFromAsn1 ( forge . asn1 . fromDer ( msg . substring ( 4 , 4 + certlen ) ) ) . publicKey , { md : forge . md . sha384 . create ( ) } ) . data , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ; } catch ( ex ) { console . log ( ex ) ; parent . parent . debug ( 'agent' , ex ) ; return ; }
2019-01-03 05:03:34 +03:00
obj . unauth . nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer . from ( msg . substring ( 4 , 4 + certlen ) , 'binary' ) . toString ( 'base64' ) + '\r\n-----END CERTIFICATE-----' ;
2017-08-28 19:27:45 +03:00
// Check the agent signature if we can
2019-05-02 01:02:03 +03:00
if ( obj . agentnonce == null ) { obj . unauthsign = msg . substring ( 4 + certlen ) ; } else {
if ( processAgentSignature ( msg . substring ( 4 + certlen ) ) == false ) {
parent . agentStats . agentBadSignature2Count ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "BadSignature2" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with bad signature, holding connection (' + obj . remoteaddrport + ').' ) ;
2019-05-02 01:02:03 +03:00
console . log ( 'Agent connected with bad signature, holding connection (' + obj . remoteaddrport + ').' ) ; return ;
}
}
2017-08-28 19:27:45 +03:00
completeAgentConnection ( ) ;
}
else if ( cmd == 3 ) {
// Agent meshid
2020-01-30 01:38:41 +03:00
if ( ( msg . length < 70 ) || ( ( obj . receivedCommands & 4 ) != 0 ) ) return ;
2017-08-28 19:27:45 +03:00
obj . receivedCommands += 4 ; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Set the meshid
obj . agentInfo = { } ;
2019-03-08 09:47:27 +03:00
obj . agentInfo . infoVersion = common . ReadInt ( msg , 2 ) ;
obj . agentInfo . agentId = common . ReadInt ( msg , 6 ) ;
obj . agentInfo . agentVersion = common . ReadInt ( msg , 10 ) ;
obj . agentInfo . platformType = common . ReadInt ( msg , 14 ) ;
2020-04-17 21:27:34 +03:00
if ( obj . agentInfo . platformType > 8 || obj . agentInfo . platformType < 1 ) { obj . agentInfo . platformType = 1 ; }
2017-11-08 04:05:22 +03:00
if ( msg . substring ( 50 , 66 ) == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' ) {
2019-01-03 05:03:34 +03:00
obj . meshid = Buffer . from ( msg . substring ( 18 , 50 ) , 'binary' ) . toString ( 'hex' ) ; // Older HEX MeshID
2017-11-08 04:05:22 +03:00
} else {
2019-01-03 05:03:34 +03:00
obj . meshid = Buffer . from ( msg . substring ( 18 , 66 ) , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ; // New Base64 MeshID
2017-11-08 04:05:22 +03:00
}
//console.log('MeshID', obj.meshid);
2019-03-08 09:47:27 +03:00
obj . agentInfo . capabilities = common . ReadInt ( msg , 66 ) ;
2020-01-30 01:38:41 +03:00
if ( msg . length > 70 ) {
const computerNameLen = common . ReadShort ( msg , 70 ) ;
obj . agentInfo . computerName = Buffer . from ( msg . substring ( 72 , 72 + computerNameLen ) , 'binary' ) . toString ( 'utf8' ) ;
//console.log('computerName', msg.length, computerNameLen, obj.agentInfo.computerName);
} else {
obj . agentInfo . computerName = '' ;
//console.log('computerName-none');
}
2021-01-16 04:05:29 +03:00
2019-03-08 09:47:27 +03:00
obj . dbMeshKey = 'mesh/' + domain . id + '/' + obj . meshid ;
2017-08-28 19:27:45 +03:00
completeAgentConnection ( ) ;
2018-08-22 01:08:15 +03:00
} else if ( cmd == 4 ) {
if ( ( msg . length < 2 ) || ( ( obj . receivedCommands & 8 ) != 0 ) ) return ;
obj . receivedCommands += 8 ; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Agent already authenticated the server, wants to skip the server signature - which is great for server performance.
2017-11-08 04:05:22 +03:00
} else if ( cmd == 5 ) {
// ServerID. Agent is telling us what serverid it expects. Useful if we have many server certificates.
2019-03-08 09:47:27 +03:00
if ( ( msg . substring ( 2 , 34 ) == parent . swarmCertificateHash256 ) || ( msg . substring ( 2 , 50 ) == parent . swarmCertificateHash384 ) ) { obj . useSwarmCert = true ; }
2021-01-14 23:34:27 +03:00
} else if ( cmd == 30 ) {
// Agent Commit Date. This is future proofing. Can be used to change server behavior depending on the date range of the agent.
2021-01-15 07:27:15 +03:00
try { obj . AgentCommitDate = Date . parse ( msg . substring ( 2 ) ) } catch ( ex ) { }
2021-01-15 03:17:56 +03:00
//console.log('Connected Agent Commit Date: ' + msg.substring(2) + ", " + Date.parse(msg.substring(2)));
2017-08-28 19:27:45 +03:00
}
}
} ) ;
// If error, do nothing
2020-04-24 23:17:06 +03:00
ws . on ( 'error' , function ( err ) { parent . parent . debug ( 'agent' , 'AGENT WSERR: ' + err ) ; console . log ( 'AGENT WSERR: ' + err ) ; obj . close ( 0 ) ; } ) ;
2017-08-28 19:27:45 +03:00
// If the mesh agent web socket is closed, clean up.
2019-01-23 04:47:51 +03:00
ws . on ( 'close' , function ( req ) {
2019-05-25 01:11:00 +03:00
parent . agentStats . agentClose ++ ;
2019-01-23 04:47:51 +03:00
if ( obj . nodeid != null ) {
2019-03-08 09:47:27 +03:00
const agentId = ( obj . agentInfo && obj . agentInfo . agentId ) ? obj . agentInfo . agentId : 'Unknown' ;
2019-02-13 00:37:01 +03:00
//console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
2019-08-23 01:31:39 +03:00
parent . parent . debug ( 'agent' , 'Agent disconnect ' + obj . nodeid + ' (' + obj . remoteaddrport + ') id=' + agentId ) ;
2019-01-23 06:34:55 +03:00
2021-01-14 23:34:27 +03:00
// Log the agent disconnection if we are not testing agent update
2021-01-15 07:27:15 +03:00
if ( args . agentupdatetest == null ) {
2021-01-14 23:34:27 +03:00
if ( parent . wsagentsDisconnections [ obj . nodeid ] == null ) {
parent . wsagentsDisconnections [ obj . nodeid ] = 1 ;
} else {
parent . wsagentsDisconnections [ obj . nodeid ] = ++ parent . wsagentsDisconnections [ obj . nodeid ] ;
}
2019-01-23 06:34:55 +03:00
}
2019-01-23 04:47:51 +03:00
}
obj . close ( 0 ) ;
} ) ;
2017-08-28 19:27:45 +03:00
2019-02-15 01:44:38 +03:00
// Return the mesh for this device, in some cases, we may auto-create the mesh.
function getMeshAutoCreate ( ) {
2019-03-18 05:34:52 +03:00
var mesh = parent . meshes [ obj . dbMeshKey ] ;
2019-10-04 21:38:47 +03:00
// If the mesh was not found and we are in LAN mode, check of the domain can be corrected
if ( ( args . lanonly == true ) && ( mesh == null ) ) {
var smesh = obj . dbMeshKey . split ( '/' ) ;
for ( var i in parent . parent . config . domains ) {
mesh = parent . meshes [ 'mesh/' + i + '/' + smesh [ 2 ] ] ;
if ( mesh != null ) {
obj . domain = domain = parent . parent . config . domains [ i ] ;
obj . meshid = smesh [ 2 ] ;
obj . dbMeshKey = 'mesh/' + i + '/' + smesh [ 2 ] ;
obj . dbNodeKey = 'node/' + domain . id + '/' + obj . nodeid ;
break ;
}
}
}
2019-03-08 09:47:27 +03:00
if ( ( mesh == null ) && ( typeof domain . orphanagentuser == 'string' ) ) {
const adminUser = parent . users [ 'user/' + domain . id + '/' + domain . orphanagentuser . toLowerCase ( ) ] ;
2019-02-15 01:44:38 +03:00
if ( ( adminUser != null ) && ( adminUser . siteadmin == 0xFFFFFFFF ) ) {
// Mesh name is hex instead of base64
2019-03-08 09:47:27 +03:00
const meshname = obj . meshid . substring ( 0 , 18 ) ;
2019-02-15 01:44:38 +03:00
// Create a new mesh for this device
2019-03-08 09:47:27 +03:00
const links = { } ;
2019-02-15 01:44:38 +03:00
links [ adminUser . _id ] = { name : adminUser . name , rights : 0xFFFFFFFF } ;
mesh = { type : 'mesh' , _id : obj . dbMeshKey , name : meshname , mtype : 2 , desc : '' , domain : domain . id , links : links } ;
2020-03-27 05:33:13 +03:00
db . Set ( mesh ) ;
2019-03-08 09:47:27 +03:00
parent . meshes [ obj . dbMeshKey ] = mesh ;
2019-02-15 01:44:38 +03:00
2019-02-15 02:53:22 +03:00
if ( adminUser . links == null ) adminUser . links = { } ;
2019-02-15 01:44:38 +03:00
adminUser . links [ obj . dbMeshKey ] = { rights : 0xFFFFFFFF } ;
2019-03-08 09:47:27 +03:00
db . SetUser ( adminUser ) ;
2020-09-09 04:59:58 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ adminUser . _id , obj . dbNodeKey ] ) , obj , { etype : 'mesh' , username : adminUser . name , meshid : obj . dbMeshKey , name : meshname , mtype : 2 , desc : '' , action : 'createmesh' , links : links , msgid : 55 , msgArgs : [ obj . meshid ] , msg : "Created device group: " + obj . meshid , domain : domain . id } ) ;
2019-02-15 01:44:38 +03:00
}
2019-02-19 01:32:55 +03:00
} else {
2019-02-20 20:39:41 +03:00
if ( ( mesh != null ) && ( mesh . deleted != null ) && ( mesh . links ) ) {
2019-02-19 01:32:55 +03:00
// Must un-delete this mesh
2020-03-18 03:17:04 +03:00
var ids = parent . CreateMeshDispatchTargets ( mesh . _id , [ obj . dbNodeKey ] ) ;
2019-02-19 01:32:55 +03:00
// See if users still exists, if so, add links to the mesh
for ( var userid in mesh . links ) {
2019-03-08 09:47:27 +03:00
const user = parent . users [ userid ] ;
2019-02-19 01:32:55 +03:00
if ( user ) {
if ( user . links == null ) { user . links = { } ; }
if ( user . links [ mesh . _id ] == null ) {
user . links [ mesh . _id ] = { rights : mesh . links [ userid ] . rights } ;
ids . push ( user . _id ) ;
2019-03-08 09:47:27 +03:00
db . SetUser ( user ) ;
2019-02-19 01:32:55 +03:00
}
}
}
// Send out an event indicating this mesh was "created"
2020-09-09 04:59:58 +03:00
parent . parent . DispatchEvent ( ids , obj , { etype : 'mesh' , meshid : mesh . _id , name : mesh . name , mtype : mesh . mtype , desc : mesh . desc , action : 'createmesh' , links : mesh . links , msgid : 56 , msgArgs : [ mesh . _id ] , msg : "Device group undeleted: " + mesh . _id , domain : domain . id } ) ;
2019-02-19 01:32:55 +03:00
// Mark the mesh as active
delete mesh . deleted ;
2020-03-27 05:33:13 +03:00
db . Set ( mesh ) ;
2019-02-19 01:32:55 +03:00
}
2019-02-15 01:44:38 +03:00
}
return mesh ;
}
2019-05-28 20:58:46 +03:00
// Send a PING/PONG message
function sendPing ( ) { obj . send ( '{"action":"ping"}' ) ; }
function sendPong ( ) { obj . send ( '{"action":"pong"}' ) ; }
2017-08-28 19:27:45 +03:00
// Once we get all the information about an agent, run this to hook everything up to the server
function completeAgentConnection ( ) {
2019-03-10 21:47:03 +03:00
if ( ( obj . authenticated != 1 ) || ( obj . meshid == null ) || obj . pendingCompleteAgentConnection || ( obj . agentInfo == null ) ) { return ; }
2018-08-21 21:02:35 +03:00
obj . pendingCompleteAgentConnection = true ;
2019-05-28 20:58:46 +03:00
// Setup the agent PING/PONG timers
if ( ( typeof args . agentping == 'number' ) && ( obj . pingtimer == null ) ) { obj . pingtimer = setInterval ( sendPing , args . agentping * 1000 ) ; }
else if ( ( typeof args . agentpong == 'number' ) && ( obj . pongtimer == null ) ) { obj . pongtimer = setInterval ( sendPong , args . agentpong * 1000 ) ; }
2019-04-10 20:41:10 +03:00
// If this is a recovery agent
2019-04-10 01:51:05 +03:00
if ( obj . agentInfo . capabilities & 0x40 ) {
2019-04-10 20:41:10 +03:00
// Inform mesh agent that it's authenticated.
delete obj . pendingCompleteAgentConnection ;
obj . authenticated = 2 ;
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 4 ) ) ;
2019-04-10 20:41:10 +03:00
// Ask for mesh core hash.
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 11 ) + common . ShortToStr ( 0 ) ) ;
2019-04-10 01:51:05 +03:00
return ;
}
2019-02-12 01:41:15 +03:00
// Check if we have too many agent sessions
if ( typeof domain . limits . maxagentsessions == 'number' ) {
// Count the number of agent sessions for this domain
var domainAgentSessionCount = 0 ;
2019-03-08 09:47:27 +03:00
for ( var i in parent . wsagents ) { if ( parent . wsagents [ i ] . domain . id == domain . id ) { domainAgentSessionCount ++ ; } }
2019-02-12 01:41:15 +03:00
// Check if we have too many user sessions
2019-05-02 01:02:03 +03:00
if ( domainAgentSessionCount >= domain . limits . maxagentsessions ) {
// Too many, hold the connection.
parent . agentStats . agentMaxSessionHoldCount ++ ;
return ;
}
2019-02-12 01:41:15 +03:00
}
2019-02-15 01:44:38 +03:00
/ *
2017-08-28 19:27:45 +03:00
// Check that the mesh exists
2019-03-08 09:47:27 +03:00
var mesh = parent . meshes [ obj . dbMeshKey ] ;
2019-02-15 01:44:38 +03:00
if ( mesh == null ) {
var holdConnection = true ;
2019-03-08 09:47:27 +03:00
if ( typeof domain . orphanagentuser == 'string' ) {
var adminUser = parent . users [ 'user/' + domain . id + '/' + args . orphanagentuser ] ;
2019-02-15 01:44:38 +03:00
if ( ( adminUser != null ) && ( adminUser . siteadmin == 0xFFFFFFFF ) ) {
// Create a new mesh for this device
holdConnection = false ;
var links = { } ;
links [ user . _id ] = { name : adminUser . name , rights : 0xFFFFFFFF } ;
mesh = { type : 'mesh' , _id : obj . dbMeshKey , name : obj . meshid , mtype : 2 , desc : '' , domain : domain . id , links : links } ;
2020-03-27 05:33:13 +03:00
db . Set ( mesh ) ;
2019-03-08 09:47:27 +03:00
parent . meshes [ obj . meshid ] = mesh ;
2020-03-18 03:17:04 +03:00
parent . parent . AddEventDispatch ( parent . CreateMeshDispatchTargets ( obj . meshid , [ obj . dbNodeKey ] ) , ws ) ;
2019-02-15 01:44:38 +03:00
if ( adminUser . links == null ) user . links = { } ;
adminUser . links [ obj . meshid ] = { rights : 0xFFFFFFFF } ;
2019-03-08 09:47:27 +03:00
//adminUser.subscriptions = parent.subscribe(adminUser._id, ws);
db . SetUser ( user ) ;
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( meshid , [ user . _id , obj . dbNodeKey ] ) , obj , { etype : 'mesh' , username : user . name , meshid : obj . meshid , name : obj . meshid , mtype : 2 , desc : '' , action : 'createmesh' , links : links , msg : 'Mesh created: ' + obj . meshid , domain : domain . id } ) ;
2019-02-15 01:44:38 +03:00
}
}
if ( holdConnection == true ) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
2019-02-15 01:44:38 +03:00
console . log ( 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
return ;
}
}
2020-04-24 23:17:06 +03:00
if ( mesh . mtype != 2 ) { // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
parent . parent . debug ( 'agent' , 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
console . log ( 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
return ;
}
2019-02-15 01:44:38 +03:00
* /
2018-08-22 01:08:15 +03:00
// Check that the node exists
2019-03-08 09:47:27 +03:00
db . Get ( obj . dbNodeKey , function ( err , nodes ) {
2019-03-10 21:47:03 +03:00
if ( obj . agentInfo == null ) { return ; }
2019-05-22 00:19:32 +03:00
var device , mesh ;
2018-08-22 01:08:15 +03:00
2018-09-25 03:47:03 +03:00
// See if this node exists in the database
2019-05-21 04:03:14 +03:00
if ( ( nodes == null ) || ( nodes . length == 0 ) ) {
2019-02-15 01:44:38 +03:00
// This device does not exist, use the meshid given by the device
2019-06-06 01:24:07 +03:00
// Check if we already have too many devices for this domain
if ( domain . limits && ( typeof domain . limits . maxdevices == 'number' ) ) {
db . isMaxType ( domain . limits . maxdevices , 'node' , domain . id , function ( ismax , count ) {
if ( ismax == true ) {
// Too many devices in this domain.
parent . agentStats . maxDomainDevicesReached ++ ;
} else {
// We are under the limit, create the new device.
completeAgentConnection2 ( ) ;
}
} ) ;
2017-08-28 19:27:45 +03:00
} else {
2019-06-06 01:24:07 +03:00
completeAgentConnection2 ( ) ;
2017-08-28 19:27:45 +03:00
}
2019-06-06 01:24:07 +03:00
return ;
2018-08-22 01:08:15 +03:00
} else {
device = nodes [ 0 ] ;
2021-10-10 22:43:20 +03:00
obj . name = device . name ;
2019-02-15 01:44:38 +03:00
// This device exists, meshid given by the device must be ignored, use the server side one.
2020-04-02 21:26:54 +03:00
if ( ( device . meshid != null ) && ( device . meshid != obj . dbMeshKey ) ) {
2019-02-15 01:44:38 +03:00
obj . dbMeshKey = device . meshid ;
obj . meshid = device . meshid . split ( '/' ) [ 2 ] ;
}
// See if this mesh exists, if it does not we may want to create it.
2019-05-22 00:19:32 +03:00
mesh = getMeshAutoCreate ( ) ;
2019-02-15 01:44:38 +03:00
// Check if the mesh exists
if ( mesh == null ) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
2019-05-02 01:02:03 +03:00
parent . agentStats . invalidDomainMesh2Count ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidDomainMesh2" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
2019-02-15 01:44:38 +03:00
console . log ( 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
return ;
}
// Check if the mesh is the right type
if ( mesh . mtype != 2 ) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
2019-05-02 01:02:03 +03:00
parent . agentStats . invalidMeshType2Count ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidMeshType2" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
2019-02-15 01:44:38 +03:00
console . log ( 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
return ;
2020-04-07 07:18:50 +03:00
}
2019-02-15 01:44:38 +03:00
// Mark when this device connected
obj . connectTime = Date . now ( ) ;
2019-04-06 07:21:28 +03:00
// Device already exists, look if changes have occured
2018-08-30 03:40:30 +03:00
var changes = [ ] , change = 0 , log = 0 ;
if ( device . agent == null ) { device . agent = { ver : obj . agentInfo . agentVersion , id : obj . agentInfo . agentId , caps : obj . agentInfo . capabilities } ; change = 1 ; }
if ( device . rname != obj . agentInfo . computerName ) { device . rname = obj . agentInfo . computerName ; change = 1 ; changes . push ( 'computer name' ) ; }
if ( device . agent . ver != obj . agentInfo . agentVersion ) { device . agent . ver = obj . agentInfo . agentVersion ; change = 1 ; changes . push ( 'agent version' ) ; }
if ( device . agent . id != obj . agentInfo . agentId ) { device . agent . id = obj . agentInfo . agentId ; change = 1 ; changes . push ( 'agent type' ) ; }
if ( ( device . agent . caps & 24 ) != ( obj . agentInfo . capabilities & 24 ) ) { device . agent . caps = obj . agentInfo . capabilities ; change = 1 ; changes . push ( 'agent capabilities' ) ; } // If agent console or javascript support changes, update capabilities
2019-04-06 07:21:28 +03:00
if ( mesh . flags && ( mesh . flags & 2 ) && ( device . name != obj . agentInfo . computerName ) ) { device . name = obj . agentInfo . computerName ; change = 1 ; } // We want the server name to be sync'ed to the hostname
2022-01-06 06:49:33 +03:00
if ( device . ip != obj . remoteaddr ) { device . ip = obj . remoteaddr ; change = 1 ; }
2019-04-06 07:21:28 +03:00
2018-08-30 03:40:30 +03:00
if ( change == 1 ) {
2019-04-10 20:41:10 +03:00
// Do some clean up if needed, these values should not be in the database.
if ( device . conn != null ) { delete device . conn ; }
if ( device . pwr != null ) { delete device . pwr ; }
if ( device . agct != null ) { delete device . agct ; }
if ( device . cict != null ) { delete device . cict ; }
// Save the updated device in the database
2019-03-08 09:47:27 +03:00
db . Set ( device ) ;
2018-08-30 03:40:30 +03:00
// If this is a temporary device, don't log changes
if ( obj . agentInfo . capabilities & 0x20 ) { log = 0 ; }
// Event the node change
2019-10-11 21:16:36 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , node : parent . CloneSafeNode ( device ) } ;
2018-09-25 03:47:03 +03:00
if ( log == 0 ) { event . nolog = 1 ; } else { event . msg = 'Changed device ' + device . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) ; }
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( device . meshid , [ obj . dbNodeKey ] ) , obj , event ) ;
2018-08-22 01:08:15 +03:00
}
}
2017-08-28 19:27:45 +03:00
2019-06-07 19:44:39 +03:00
completeAgentConnection3 ( device , mesh ) ;
2019-06-06 01:24:07 +03:00
} ) ;
}
2019-06-07 19:44:39 +03:00
function completeAgentConnection2 ( ) {
2019-06-06 01:24:07 +03:00
// See if this mesh exists, if it does not we may want to create it.
var mesh = getMeshAutoCreate ( ) ;
// Check if the mesh exists
if ( mesh == null ) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent . agentStats . invalidDomainMeshCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidDomainMesh" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
2019-06-06 01:24:07 +03:00
console . log ( 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + obj . dbMeshKey + ').' ) ;
return ;
}
// Check if the mesh is the right type
if ( mesh . mtype != 2 ) {
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
parent . agentStats . invalidMeshTypeCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidMeshType" ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
2019-06-06 01:24:07 +03:00
console . log ( 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ;
return ;
}
// Mark when this device connected
obj . connectTime = Date . now ( ) ;
// This node does not exist, create it.
2020-01-30 01:38:41 +03:00
var agentName = obj . agentName ? obj . agentName : obj . agentInfo . computerName ;
var device = { type : 'node' , mtype : mesh . mtype , _id : obj . dbNodeKey , icon : obj . agentInfo . platformType , meshid : obj . dbMeshKey , name : agentName , rname : obj . agentInfo . computerName , domain : domain . id , agent : { ver : obj . agentInfo . agentVersion , id : obj . agentInfo . agentId , caps : obj . agentInfo . capabilities } , host : null } ;
2019-06-06 01:24:07 +03:00
db . Set ( device ) ;
// Event the new node
if ( obj . agentInfo . capabilities & 0x20 ) {
// This is a temporary agent, don't log.
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , { etype : 'node' , action : 'addnode' , node : device , domain : domain . id , nolog : 1 } ) ;
2019-06-06 01:24:07 +03:00
} else {
2020-09-09 04:59:58 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , { etype : 'node' , action : 'addnode' , node : device , msgid : 57 , msgArgs : [ obj . agentInfo . computerName , mesh . name ] , msg : ( 'Added device ' + obj . agentInfo . computerName + ' to device group ' + mesh . name ) , domain : domain . id } ) ;
2019-06-06 01:24:07 +03:00
}
2019-06-07 19:44:39 +03:00
completeAgentConnection3 ( device , mesh ) ;
2019-06-06 01:24:07 +03:00
}
2019-06-07 19:44:39 +03:00
function completeAgentConnection3 ( device , mesh ) {
2019-06-06 01:24:07 +03:00
// Check if this agent is already connected
const dupAgent = parent . wsagents [ obj . dbNodeKey ] ;
parent . wsagents [ obj . dbNodeKey ] = obj ;
if ( dupAgent ) {
// Record duplicate agents
if ( parent . duplicateAgentsLog [ obj . dbNodeKey ] == null ) {
if ( dupAgent . remoteaddr == obj . remoteaddr ) {
parent . duplicateAgentsLog [ obj . dbNodeKey ] = { name : device . name , group : mesh . name , ip : [ obj . remoteaddr ] , count : 1 } ;
2019-05-22 00:19:32 +03:00
} else {
2019-06-06 01:24:07 +03:00
parent . duplicateAgentsLog [ obj . dbNodeKey ] = { name : device . name , group : mesh . name , ip : [ obj . remoteaddr , dupAgent . remoteaddr ] , count : 1 } ;
2019-05-22 00:19:32 +03:00
}
2018-08-22 01:08:15 +03:00
} else {
2019-06-06 01:24:07 +03:00
parent . duplicateAgentsLog [ obj . dbNodeKey ] . name = device . name ;
parent . duplicateAgentsLog [ obj . dbNodeKey ] . group = mesh . name ;
parent . duplicateAgentsLog [ obj . dbNodeKey ] . count ++ ;
if ( parent . duplicateAgentsLog [ obj . dbNodeKey ] . ip . indexOf ( obj . remoteaddr ) == - 1 ) { parent . duplicateAgentsLog [ obj . dbNodeKey ] . ip . push ( obj . remoteaddr ) ; }
2018-08-22 01:08:15 +03:00
}
2017-09-06 03:19:28 +03:00
2019-06-06 01:24:07 +03:00
// Close the duplicate agent
parent . agentStats . duplicateAgentCount ++ ;
2021-03-08 22:06:39 +03:00
parent . setAgentIssue ( obj , 'duplicateAgent' ) ;
2019-08-23 01:31:39 +03:00
if ( obj . nodeid != null ) { parent . parent . debug ( 'agent' , 'Duplicate agent ' + obj . nodeid + ' (' + obj . remoteaddrport + ')' ) ; }
2019-06-06 01:24:07 +03:00
dupAgent . close ( 3 ) ;
} else {
// Indicate the agent is connected
2021-10-10 22:43:20 +03:00
parent . parent . SetConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , obj . connectTime , 1 , 1 , null , { remoteaddrport : obj . remoteaddrport , name : device . name } ) ;
2019-06-06 01:24:07 +03:00
}
2018-08-22 01:08:15 +03:00
2019-06-06 01:24:07 +03:00
// We are done, ready to communicate with this agent
delete obj . pendingCompleteAgentConnection ;
obj . authenticated = 2 ;
2019-01-23 06:34:55 +03:00
2019-06-06 01:24:07 +03:00
// Check how many times this agent disconnected in the last few minutes.
const disconnectCount = parent . wsagentsDisconnections [ obj . nodeid ] ;
if ( disconnectCount > 6 ) {
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent in big trouble: NodeId=' + obj . nodeid + ', IP=' + obj . remoteaddrport + ', Agent=' + obj . agentInfo . agentId + '.' ) ;
2019-06-06 01:24:07 +03:00
console . log ( 'Agent in big trouble: NodeId=' + obj . nodeid + ', IP=' + obj . remoteaddrport + ', Agent=' + obj . agentInfo . agentId + '.' ) ;
2021-02-07 11:38:20 +03:00
parent . agentStats . agentInBigTrouble ++ ;
2019-06-06 01:24:07 +03:00
// TODO: Log or do something to recover?
return ;
}
2018-08-22 01:08:15 +03:00
2019-06-06 01:24:07 +03:00
// Command 4, inform mesh agent that it's authenticated.
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 4 ) ) ;
2019-01-23 06:34:55 +03:00
2021-02-07 11:38:20 +03:00
// Not sure why, but in rare cases, obj.agentInfo is undefined here.
if ( ( obj . agentInfo == null ) || ( typeof obj . agentInfo . capabilities != 'number' ) ) { return ; } // This is an odd case.
obj . agentExeInfo = parent . parent . meshAgentBinaries [ obj . agentInfo . agentId ] ;
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ obj . agentInfo . agentId ] ) { obj . agentExeInfo = domain . meshAgentBinaries [ obj . agentInfo . agentId ] ; }
2021-02-07 11:38:20 +03:00
// Check if this agent is reconnecting too often.
2019-06-06 01:24:07 +03:00
if ( disconnectCount > 4 ) {
// Too many disconnections, this agent has issues. Just clear the core.
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Agent in trouble: NodeId=' + obj . nodeid + ', IP=' + obj . remoteaddrport + ', Agent=' + obj . agentInfo . agentId + '.' ) ;
2021-02-07 11:38:20 +03:00
parent . agentStats . agentInTrouble ++ ;
2019-06-06 01:24:07 +03:00
//console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
// TODO: Log or do something to recover?
return ;
}
2019-03-11 21:18:50 +03:00
2019-06-06 01:24:07 +03:00
// Check if we need to make an native update check
2019-06-18 01:20:47 +03:00
var corename = null ;
if ( parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] != null ) {
corename = parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core ;
} else {
// MeshCommand_CoreModule, ask mesh agent to clear the core
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 10 ) + common . ShortToStr ( 0 ) ) ;
2019-06-18 01:20:47 +03:00
}
2019-06-06 01:24:07 +03:00
if ( ( obj . agentExeInfo != null ) && ( obj . agentExeInfo . update == true ) ) {
// Ask the agent for it's executable binary hash
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 12 ) + common . ShortToStr ( 0 ) ) ;
2019-06-06 01:24:07 +03:00
} else {
// Check the mesh core, if the agent is capable of running one
if ( ( ( obj . agentInfo . capabilities & 16 ) != 0 ) && ( corename != null ) ) {
2019-08-10 01:13:53 +03:00
obj . sendBinary ( common . ShortToStr ( 11 ) + common . ShortToStr ( 0 ) ) ; // Command 11, ask for mesh core hash.
2019-01-22 01:05:50 +03:00
} else {
2019-06-06 01:24:07 +03:00
agentCoreIsStable ( ) ; // No updates needed, agent is ready to go.
2019-01-22 01:05:50 +03:00
}
2019-06-06 01:24:07 +03:00
}
2019-02-08 02:00:10 +03:00
}
2019-01-22 01:05:50 +03:00
2020-10-23 01:10:39 +03:00
// Indicate to the agent that we want to check Intel AMT configuration
// This may trigger a CIRA-LMS tunnel from the agent so the server can inspect the device.
2019-03-11 07:40:25 +03:00
obj . sendUpdatedIntelAmtPolicy = function ( policy ) {
if ( obj . agentExeInfo && ( obj . agentExeInfo . amt == true ) ) { // Only send Intel AMT policy to agents what could have AMT.
2020-10-22 01:36:07 +03:00
if ( policy == null ) { var mesh = parent . meshes [ obj . dbMeshKey ] ; if ( mesh == null ) return ; policy = mesh . amt ; }
2020-10-23 01:10:39 +03:00
if ( ( policy != null ) && ( policy . type != 0 ) ) {
const cookie = parent . parent . encodeCookie ( { a : 'apf' , n : obj . dbNodeKey , m : obj . dbMeshKey } , parent . parent . loginCookieEncryptionKey ) ;
try { obj . send ( JSON . stringify ( { action : 'amtconfig' , user : '**MeshAgentApfTunnel**' , pass : cookie } ) ) ; } catch ( ex ) { }
}
2019-03-08 06:56:24 +03:00
}
}
2019-04-10 01:51:05 +03:00
function recoveryAgentCoreIsStable ( mesh ) {
2019-05-02 01:02:03 +03:00
parent . agentStats . recoveryCoreIsStableCount ++ ;
2019-04-10 01:51:05 +03:00
// Recovery agent is doing ok, lets perform main agent checking.
//console.log('recoveryAgentCoreIsStable()');
// Fetch the the real agent nodeid
2020-04-07 07:18:50 +03:00
db . Get ( 'da' + obj . dbNodeKey , function ( err , nodes , self ) {
if ( ( nodes != null ) && ( nodes . length == 1 ) ) {
2019-04-12 21:28:57 +03:00
self . realNodeKey = nodes [ 0 ] . raid ;
2019-04-13 02:12:31 +03:00
// Get agent connection state
var agentConnected = false ;
var state = parent . parent . GetConnectivityState ( self . realNodeKey ) ;
if ( state ) { agentConnected = ( ( state . connectivity & 1 ) != 0 ) }
self . send ( JSON . stringify ( { action : 'diagnostic' , value : { command : 'query' , value : self . realNodeKey , agent : agentConnected } } ) ) ;
2020-04-07 07:18:50 +03:00
} else {
2019-04-12 21:28:57 +03:00
self . send ( JSON . stringify ( { action : 'diagnostic' , value : { command : 'query' , value : null } } ) ) ;
2019-04-10 01:51:05 +03:00
}
2019-04-12 21:28:57 +03:00
} , obj ) ;
2019-04-10 01:51:05 +03:00
}
2019-02-08 02:00:10 +03:00
function agentCoreIsStable ( ) {
2019-05-02 01:02:03 +03:00
parent . agentStats . coreIsStableCount ++ ;
2019-02-08 02:00:10 +03:00
// Check that the mesh exists
2019-03-08 09:47:27 +03:00
const mesh = parent . meshes [ obj . dbMeshKey ] ;
2019-02-08 02:00:10 +03:00
if ( mesh == null ) {
2019-05-02 01:02:03 +03:00
parent . agentStats . meshDoesNotExistCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "meshDoesNotExist" ) ;
2019-02-08 02:00:10 +03:00
// TODO: Mark this agent as part of a mesh that does not exists.
return ; // Probably not worth doing anything else. Hold this agent.
}
2019-04-10 20:41:10 +03:00
// Check if this is a recovery agent
if ( obj . agentInfo . capabilities & 0x40 ) {
recoveryAgentCoreIsStable ( mesh ) ;
return ;
}
2019-05-02 01:02:03 +03:00
// Fetch the the diagnostic agent nodeid
2019-04-10 01:51:05 +03:00
db . Get ( 'ra' + obj . dbNodeKey , function ( err , nodes ) {
2019-05-21 04:03:14 +03:00
if ( ( nodes != null ) && ( nodes . length == 1 ) ) {
2019-04-10 01:51:05 +03:00
obj . diagnosticNodeKey = nodes [ 0 ] . daid ;
obj . send ( JSON . stringify ( { action : 'diagnostic' , value : { command : 'query' , value : obj . diagnosticNodeKey } } ) ) ;
}
} ) ;
2020-10-22 01:36:07 +03:00
// Indicate that we want to check the Intel AMT configuration
2020-10-23 01:10:39 +03:00
// This may trigger a CIRA-LMS tunnel to the server for further processing
obj . sendUpdatedIntelAmtPolicy ( ) ;
2019-02-08 02:00:10 +03:00
2019-08-08 02:37:45 +03:00
// Fetch system information
2019-08-07 03:58:29 +03:00
db . GetHash ( 'si' + obj . dbNodeKey , function ( err , results ) {
if ( ( results != null ) && ( results . length == 1 ) ) { obj . send ( JSON . stringify ( { action : 'sysinfo' , hash : results [ 0 ] . hash } ) ) ; } else { obj . send ( JSON . stringify ( { action : 'sysinfo' } ) ) ; }
} ) ;
2021-04-18 00:44:19 +03:00
// Agent error log dump
if ( parent . parent . agentErrorLog != null ) {
db . Get ( 'al' + obj . dbNodeKey , function ( err , docs ) { // Agent Log
if ( ( docs != null ) && ( docs . length == 1 ) && ( typeof docs [ 0 ] . lastEvent ) ) {
obj . send ( '{"action":"errorlog","startTime":' + docs [ 0 ] . lastEvent + '}' ) ; // Ask all events after a given time
} else {
obj . send ( '{"action":"errorlog"}' ) ; // Ask all
}
} ) ;
}
2020-07-07 02:01:44 +03:00
// Set agent core dump
2020-07-07 02:02:11 +03:00
if ( ( parent . parent . config . settings != null ) && ( ( parent . parent . config . settings . agentcoredump === true ) || ( parent . parent . config . settings . agentcoredump === false ) ) ) {
2020-07-07 02:01:44 +03:00
obj . send ( JSON . stringify ( { action : 'coredump' , value : parent . parent . config . settings . agentcoredump } ) ) ;
2020-07-08 21:59:20 +03:00
if ( parent . parent . config . settings . agentcoredump === true ) {
// Check if we requested a core dump file in the last minute, if not, ask if one is present.
if ( ( parent . lastCoreDumpRequest == null ) || ( ( Date . now ( ) - parent . lastCoreDumpRequest ) >= 60000 ) ) { obj . send ( JSON . stringify ( { action : 'getcoredump' } ) ) ; }
}
2020-07-07 02:01:44 +03:00
}
2019-02-08 02:00:10 +03:00
// Do this if IP location is enabled on this domain TODO: Set IP location per device group?
if ( domain . iplocation == true ) {
// Check if we already have IP location information for this node
2019-03-08 09:47:27 +03:00
db . Get ( 'iploc_' + obj . remoteaddr , function ( err , iplocs ) {
2019-05-21 04:03:14 +03:00
if ( ( iplocs != null ) && ( iplocs . length == 1 ) ) {
2019-02-08 02:00:10 +03:00
// We have a location in the database for this remote IP
2019-03-08 09:47:27 +03:00
const iploc = nodes [ 0 ] , x = { } ;
2019-02-08 02:00:10 +03:00
if ( ( iploc != null ) && ( iploc . ip != null ) && ( iploc . loc != null ) ) {
x . publicip = iploc . ip ;
x . iploc = iploc . loc + ',' + ( Math . floor ( ( new Date ( iploc . date ) ) / 1000 ) ) ;
ChangeAgentLocationInfo ( x ) ;
}
} else {
// Check if we need to ask for the IP location
var doIpLocation = 0 ;
if ( device . iploc == null ) {
doIpLocation = 1 ;
2017-09-06 03:19:28 +03:00
} else {
2019-03-08 09:47:27 +03:00
const loc = device . iploc . split ( ',' ) ;
2019-02-08 02:00:10 +03:00
if ( loc . length < 3 ) {
doIpLocation = 2 ;
2017-09-06 20:08:01 +03:00
} else {
2019-02-08 02:00:10 +03:00
var t = new Date ( ( parseFloat ( loc [ 2 ] ) * 1000 ) ) , now = Date . now ( ) ;
t . setDate ( t . getDate ( ) + 20 ) ;
if ( t < now ) { doIpLocation = 3 ; }
2017-09-06 20:08:01 +03:00
}
2019-02-08 02:00:10 +03:00
}
2017-09-06 03:19:28 +03:00
2019-02-08 02:00:10 +03:00
// If we need to ask for IP location, see if we have the quota to do it.
if ( doIpLocation > 0 ) {
2019-03-08 09:47:27 +03:00
db . getValueOfTheDay ( 'ipLocationRequestLimitor' , 10 , function ( ipLocationLimitor ) {
2019-05-21 04:03:14 +03:00
if ( ( ipLocationLimitor != null ) && ( ipLocationLimitor . value > 0 ) ) {
2019-02-08 02:00:10 +03:00
ipLocationLimitor . value -- ;
2019-03-08 09:47:27 +03:00
db . Set ( ipLocationLimitor ) ;
2019-02-08 02:00:10 +03:00
obj . send ( JSON . stringify ( { action : 'iplocation' } ) ) ;
}
} ) ;
2017-09-06 20:08:01 +03:00
}
2019-02-08 02:00:10 +03:00
}
} ) ;
}
2021-08-20 00:10:03 +03:00
// Indicate server information to the agent.
var serverInfo = { action : 'serverInfo' } ;
if ( ( typeof domain . terminal == 'object' ) && ( typeof domain . terminal . launchcommand == 'object' ) ) {
// Send terminal starting command
serverInfo . termlaunchcommand = { } ;
if ( typeof domain . terminal . launchcommand . linux == 'string' ) { serverInfo . termlaunchcommand . linux = domain . terminal . launchcommand . linux ; }
if ( typeof domain . terminal . launchcommand . darwin == 'string' ) { serverInfo . termlaunchcommand . darwin = domain . terminal . launchcommand . darwin ; }
if ( typeof domain . terminal . launchcommand . freebsd == 'string' ) { serverInfo . termlaunchcommand . freebsd = domain . terminal . launchcommand . freebsd ; }
}
2021-11-10 09:51:40 +03:00
// Enable agent self guest sharing if allowed
2021-12-14 04:04:26 +03:00
if ( domain . agentselfguestsharing ) { serverInfo . agentSelfGuestSharing = true ; }
2021-08-20 00:10:03 +03:00
obj . send ( JSON . stringify ( serverInfo ) ) ;
// Plug in handler
2019-10-29 00:27:59 +03:00
if ( parent . parent . pluginHandler != null ) {
parent . parent . pluginHandler . callHook ( 'hook_agentCoreIsStable' , obj , parent ) ;
}
2017-08-28 19:27:45 +03:00
}
2018-01-03 03:52:49 +03:00
2018-12-01 03:42:58 +03:00
// Get the web certificate private key hash for the specified domain
2018-01-03 03:52:49 +03:00
function getWebCertHash ( domain ) {
2019-03-08 09:47:27 +03:00
const hash = parent . webCertificateHashs [ domain . id ] ;
2018-01-12 22:41:26 +03:00
if ( hash != null ) return hash ;
2019-03-08 09:47:27 +03:00
return parent . webCertificateHash ;
2018-01-03 03:52:49 +03:00
}
2018-12-01 03:42:58 +03:00
// Get the web certificate hash for the specified domain
function getWebCertFullHash ( domain ) {
2019-03-08 09:47:27 +03:00
const hash = parent . webCertificateFullHashs [ domain . id ] ;
2018-12-01 03:42:58 +03:00
if ( hash != null ) return hash ;
2019-03-08 09:47:27 +03:00
return parent . webCertificateFullHash ;
2018-12-01 03:42:58 +03:00
}
2017-08-28 19:27:45 +03:00
// Verify the agent signature
function processAgentSignature ( msg ) {
2020-12-31 22:16:47 +03:00
if ( isIgnoreHashCheck ( ) == false ) {
2019-01-11 21:02:54 +03:00
var verified = false ;
2021-02-23 01:41:54 +03:00
// This agent did not report a valid TLS certificate hash, fail now.
if ( obj . agentSeenCerthash == null ) return false ;
2021-02-15 09:22:53 +03:00
// Raw RSA signatures have an exact length of 256 or 384. PKCS7 is larger.
if ( ( msg . length != 384 ) && ( msg . length != 256 ) ) {
2019-01-11 21:02:54 +03:00
// Verify a PKCS7 signature.
var msgDer = null ;
2019-03-08 09:47:27 +03:00
try { msgDer = forge . asn1 . fromDer ( forge . util . createBuffer ( msg , 'binary' ) ) ; } catch ( ex ) { }
2019-01-11 21:02:54 +03:00
if ( msgDer != null ) {
try {
2020-05-22 22:54:22 +03:00
const p7 = forge . pkcs7 . messageFromAsn1 ( msgDer ) ;
const sig = p7 . rawCapture . signature ;
2019-01-11 21:02:54 +03:00
// Verify with key hash
2021-02-15 09:22:53 +03:00
var buf = Buffer . from ( obj . agentSeenCerthash + obj . nonce + obj . agentnonce , 'binary' ) ;
2019-03-08 09:47:27 +03:00
var verifier = parent . crypto . createVerify ( 'RSA-SHA384' ) ;
2019-01-11 21:02:54 +03:00
verifier . update ( buf ) ;
verified = verifier . verify ( obj . unauth . nodeCertPem , sig , 'binary' ) ;
2020-05-22 22:54:22 +03:00
if ( verified !== true ) {
2019-05-02 01:02:03 +03:00
// Not a valid signature
parent . agentStats . invalidPkcsSignatureCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidPkcsSignature" ) ;
2019-05-02 01:02:03 +03:00
return false ;
2020-04-07 07:18:50 +03:00
}
2019-01-11 21:02:54 +03:00
} catch ( ex ) { } ;
}
}
if ( verified == false ) {
// Verify the RSA signature. This is the fast way, without using forge.
2019-03-08 09:47:27 +03:00
const verify = parent . crypto . createVerify ( 'SHA384' ) ;
2021-02-15 09:22:53 +03:00
verify . end ( Buffer . from ( obj . agentSeenCerthash + obj . nonce + obj . agentnonce , 'binary' ) ) ; // Test using the private key hash
2019-01-11 21:02:54 +03:00
if ( verify . verify ( obj . unauth . nodeCertPem , Buffer . from ( msg , 'binary' ) ) !== true ) {
2021-02-15 09:22:53 +03:00
parent . agentStats . invalidRsaSignatureCount ++ ;
parent . setAgentIssue ( obj , "invalidRsaSignature" ) ;
return false ;
2019-01-11 21:02:54 +03:00
}
2018-12-15 23:34:55 +03:00
}
2018-12-01 03:42:58 +03:00
}
2017-08-28 19:27:45 +03:00
// Connection is a success, clean up
2017-10-16 03:36:06 +03:00
obj . nodeid = obj . unauth . nodeid ;
2017-08-28 19:27:45 +03:00
obj . dbNodeKey = 'node/' + domain . id + '/' + obj . nodeid ;
delete obj . nonce ;
delete obj . agentnonce ;
delete obj . unauth ;
2019-03-08 09:47:27 +03:00
delete obj . receivedCommands ;
2021-02-15 09:22:53 +03:00
delete obj . agentSeenCerthash ;
2017-08-28 19:27:45 +03:00
if ( obj . unauthsign ) delete obj . unauthsign ;
2019-05-02 01:02:03 +03:00
parent . agentStats . verifiedAgentConnectionCount ++ ;
2019-08-23 01:31:39 +03:00
parent . parent . debug ( 'agent' , 'Verified agent connection to ' + obj . nodeid + ' (' + obj . remoteaddrport + ').' ) ;
2017-08-28 19:27:45 +03:00
obj . authenticated = 1 ;
return true ;
}
// Process incoming agent JSON data
function processAgentData ( msg ) {
2021-01-26 22:46:22 +03:00
if ( obj . agentInfo == null ) return ;
2019-03-08 09:47:27 +03:00
var i , str = msg . toString ( 'utf8' ) , command = null ;
2017-08-28 19:27:45 +03:00
if ( str [ 0 ] == '{' ) {
2020-04-24 23:17:06 +03:00
try { command = JSON . parse ( str ) ; } catch ( ex ) {
// If the command can't be parsed, ignore it.
parent . agentStats . invalidJsonCount ++ ;
2021-02-04 02:36:25 +03:00
parent . setAgentIssue ( obj , "invalidJson (" + str . length + "): " + str ) ;
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Unable to parse agent JSON (' + obj . remoteaddrport + ')' ) ;
console . log ( 'Unable to parse agent JSON (' + obj . remoteaddrport + '): ' + str , ex ) ;
return ;
}
2018-08-27 22:24:15 +03:00
if ( typeof command != 'object' ) { return ; }
2017-08-28 19:27:45 +03:00
switch ( command . action ) {
case 'msg' :
{
2022-01-10 01:49:27 +03:00
// If the same console command is processed many times, kick out this agent.
// This is a safety mesure to guard against the agent DOS'ing the server.
if ( command . type == 'console' ) {
if ( obj . consoleKickValue == command . value ) {
if ( obj . consoleKickCount ) { obj . consoleKickCount ++ ; } else { obj . consoleKickCount = 1 ; }
if ( obj . consoleKickCount > 30 ) { obj . close ( ) ; return ; } // 30 identical console messages received, kick out this agent.
} else {
obj . consoleKickValue = command . value ;
}
}
2019-10-10 01:56:27 +03:00
// Route a message
parent . routeAgentCommand ( command , obj . domain . id , obj . dbNodeKey , obj . dbMeshKey ) ;
2017-08-28 19:27:45 +03:00
break ;
}
case 'coreinfo' :
{
2021-01-25 22:08:59 +03:00
// Sent by the agent to update agent information
ChangeAgentCoreInfo ( command ) ;
2021-02-04 08:48:54 +03:00
if ( ( obj . agentCoreUpdate === true ) && ( obj . agentExeInfo != null ) && ( typeof obj . agentExeInfo . url == 'string' ) ) {
2021-01-15 03:17:56 +03:00
// Agent update. The recovery core was loaded in the agent, send a command to update the agent
2021-01-17 10:35:39 +03:00
parent . parent . taskLimiter . launch ( function ( argument , taskid , taskLimiterQueue ) { // Medium priority task
// If agent disconnection, complete and exit now.
2021-01-25 22:08:59 +03:00
if ( ( obj . authenticated != 2 ) || ( obj . agentExeInfo == null ) ) { parent . parent . taskLimiter . completed ( taskid ) ; return ; }
2021-01-17 10:35:39 +03:00
// Agent update. The recovery core was loaded in the agent, send a command to update the agent
obj . agentCoreUpdateTaskId = taskid ;
2021-01-29 02:49:42 +03:00
const url = '*' + require ( 'url' ) . parse ( obj . agentExeInfo . url ) . path ;
2021-01-19 03:07:56 +03:00
var cmd = { action : 'agentupdate' , url : url , hash : obj . agentExeInfo . hashhex } ;
2021-03-05 03:27:43 +03:00
parent . parent . debug ( 'agentupdate' , "Sending agent update url: " + cmd . url ) ;
2021-01-17 10:35:39 +03:00
// Add the hash
if ( obj . agentExeInfo . fileHash != null ) { cmd . hash = obj . agentExeInfo . fileHashHex ; } else { cmd . hash = obj . agentExeInfo . hashhex ; }
// Add server TLS cert hash
2021-01-29 00:41:19 +03:00
if ( isIgnoreHashCheck ( ) == false ) {
2021-01-17 10:35:39 +03:00
const tlsCertHash = parent . webCertificateFullHashs [ domain . id ] ;
if ( tlsCertHash != null ) { cmd . servertlshash = Buffer . from ( tlsCertHash , 'binary' ) . toString ( 'hex' ) ; }
}
// Send the agent update command
obj . send ( JSON . stringify ( cmd ) ) ;
} , null , 1 ) ;
2021-01-15 03:17:56 +03:00
}
2018-09-28 02:17:05 +03:00
break ;
}
case 'smbios' :
{
2020-09-08 21:58:23 +03:00
// SMBIOS information must never be saved when NeDB is in use. NeDB will currupt that database.
if ( db . SetSMBIOS == null ) break ;
2020-09-08 06:37:58 +03:00
2020-04-24 23:37:37 +03:00
// See if we need to save SMBIOS information
2020-09-08 06:35:13 +03:00
if ( domain . smbios === true ) {
2020-04-24 23:37:37 +03:00
// Store the RAW SMBios table of this computer
// Perform sanity checks before storing
try {
for ( var i in command . value ) { var k = parseInt ( i ) ; if ( ( k != i ) || ( i > 255 ) || ( typeof command . value [ i ] != 'object' ) || ( command . value [ i ] . length == null ) || ( command . value [ i ] . length > 1024 ) || ( command . value [ i ] . length < 0 ) ) { delete command . value [ i ] ; } }
db . SetSMBIOS ( { _id : obj . dbNodeKey , domain : domain . id , time : new Date ( ) , value : command . value } ) ;
} catch ( ex ) { }
}
2018-09-28 02:17:05 +03:00
// Event the node interface information change (This is a lot of traffic, probably don't need this).
2020-03-18 03:17:04 +03:00
//parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
2018-09-28 02:17:05 +03:00
2017-08-28 19:27:45 +03:00
break ;
}
case 'netinfo' :
{
2020-06-27 00:43:58 +03:00
// Check if network information is present
if ( ( command . netif2 == null ) && ( command . netif == null ) ) return ;
2020-12-29 10:14:15 +03:00
// Escape any field names that have special characters
if ( command . netif2 != null ) {
for ( var i in command . netif2 ) {
var esc = common . escapeFieldName ( i ) ;
if ( esc !== i ) { command . netif2 [ esc ] = command . netif2 [ i ] ; delete command . netif2 [ i ] ; }
}
}
2017-08-28 19:27:45 +03:00
// Sent by the agent to update agent network interface information
delete command . action ;
command . updateTime = Date . now ( ) ;
command . _id = 'if' + obj . dbNodeKey ;
2022-01-12 04:30:50 +03:00
command . domain = domain . id ;
2017-08-28 19:27:45 +03:00
command . type = 'ifinfo' ;
2019-03-08 09:47:27 +03:00
db . Set ( command ) ;
2017-08-28 19:27:45 +03:00
// Event the node interface information change
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . meshid , [ obj . dbNodeKey ] ) , obj , { action : 'ifchange' , nodeid : obj . dbNodeKey , domain : domain . id , nolog : 1 } ) ;
2017-08-28 19:27:45 +03:00
2017-08-28 22:48:53 +03:00
break ;
}
2017-09-06 03:19:28 +03:00
case 'iplocation' :
2017-08-28 22:48:53 +03:00
{
// Sent by the agent to update location information
2017-08-30 23:52:56 +03:00
if ( ( command . type == 'publicip' ) && ( command . value != null ) && ( typeof command . value == 'object' ) && ( command . value . ip ) && ( command . value . loc ) ) {
2017-08-29 01:06:29 +03:00
var x = { } ;
x . publicip = command . value . ip ;
2018-08-30 03:40:30 +03:00
x . iploc = command . value . loc + ',' + ( Math . floor ( Date . now ( ) / 1000 ) ) ;
2017-08-29 01:06:29 +03:00
ChangeAgentLocationInfo ( x ) ;
2017-09-06 20:08:01 +03:00
command . value . _id = 'iploc_' + command . value . ip ;
command . value . type = 'iploc' ;
command . value . date = Date . now ( ) ;
2019-03-08 09:47:27 +03:00
db . Set ( command . value ) ; // Store the IP to location data in the database
2017-09-06 20:08:01 +03:00
// Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' }
2017-08-29 01:06:29 +03:00
}
2017-08-28 19:27:45 +03:00
break ;
}
2017-11-08 04:05:22 +03:00
case 'mc1migration' :
{
if ( command . oldnodeid . length != 64 ) break ;
2019-03-08 09:47:27 +03:00
const oldNodeKey = 'node//' + command . oldnodeid . toLowerCase ( ) ;
db . Get ( oldNodeKey , function ( err , nodes ) {
2021-01-11 01:41:25 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) return ;
2019-03-08 09:47:27 +03:00
const node = nodes [ 0 ] ;
2017-11-08 04:05:22 +03:00
if ( node . meshid == obj . dbMeshKey ) {
// Update the device name & host
2019-03-08 09:47:27 +03:00
const newNode = { "name" : node . name } ;
2017-11-10 04:18:30 +03:00
if ( node . intelamt != null ) { newNode . intelamt = node . intelamt ; }
ChangeAgentCoreInfo ( newNode ) ;
2017-11-08 04:05:22 +03:00
// Delete this node including network interface information and events
2019-03-08 09:47:27 +03:00
db . Remove ( node . _id ) ;
db . Remove ( 'if' + node . _id ) ;
2017-11-08 04:05:22 +03:00
// Event node deletion
2019-03-08 09:47:27 +03:00
const change = 'Migrated device ' + node . name ;
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( node . meshid , [ obj . dbNodeKey ] ) , obj , { etype : 'node' , action : 'removenode' , nodeid : node . _id , msg : change , domain : node . domain } ) ;
2017-11-08 04:05:22 +03:00
}
} ) ;
break ;
}
2019-01-24 23:08:48 +03:00
case 'openUrl' :
{
// Sent by the agent to return the status of a open URL action.
// Nothing is done right now.
break ;
}
2019-07-30 02:35:48 +03:00
case 'log' :
{
// Log a value in the event log
if ( ( typeof command . msg == 'string' ) && ( command . msg . length < 4096 ) ) {
var event = { etype : 'node' , action : 'agentlog' , nodeid : obj . dbNodeKey , domain : domain . id , msg : command . msg } ;
2020-09-09 04:59:58 +03:00
if ( typeof command . msgid == 'number' ) { event . msgid = command . msgid ; }
2021-07-29 22:01:38 +03:00
if ( typeof command . guestname == 'string' ) { event . guestname = command . guestname ; }
2020-09-09 04:59:58 +03:00
if ( Array . isArray ( command . msgArgs ) ) { event . msgArgs = command . msgArgs ; }
if ( typeof command . remoteaddr == 'string' ) { event . remoteaddr = command . remoteaddr ; }
2020-03-18 03:17:04 +03:00
var targets = parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) ;
2019-07-30 02:35:48 +03:00
if ( typeof command . userid == 'string' ) {
var loguser = parent . users [ command . userid ] ;
2019-09-18 22:05:33 +03:00
if ( loguser ) { event . userid = command . userid ; event . username = loguser . name ; targets . push ( command . userid ) ; }
2019-07-30 02:35:48 +03:00
}
2022-01-11 04:42:54 +03:00
if ( typeof command . xuserid == 'string' ) {
var xloguser = parent . users [ command . xuserid ] ;
if ( xloguser ) { targets . push ( command . xuserid ) ; }
}
2019-07-30 02:35:48 +03:00
if ( ( typeof command . sessionid == 'string' ) && ( command . sessionid . length < 500 ) ) { event . sessionid = command . sessionid ; }
2019-09-18 22:05:33 +03:00
parent . parent . DispatchEvent ( targets , obj , event ) ;
2022-09-03 03:03:38 +03:00
// If this is a help request, see if we need to email notify anyone
if ( event . msgid == 98 ) {
// Get the node and change it if needed
db . Get ( obj . dbNodeKey , function ( err , nodes ) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { delete obj . deviceChanging ; return ; }
const device = nodes [ 0 ] ;
if ( typeof device . name == 'string' ) { parent . parent . NotifyUserOfDeviceHelpRequest ( domain , device . meshid , device . _id , device . name , command . msgArgs [ 0 ] , command . msgArgs [ 1 ] ) ; }
} ) ;
}
2019-07-30 02:35:48 +03:00
}
break ;
}
2019-05-28 20:58:46 +03:00
case 'ping' : { sendPong ( ) ; break ; }
case 'pong' : { break ; }
2019-02-08 07:06:01 +03:00
case 'getScript' :
{
// Used by the agent to get configuration scripts.
if ( command . type == 1 ) {
2019-03-08 09:47:27 +03:00
parent . getCiraConfigurationScript ( obj . dbMeshKey , function ( script ) {
2019-02-08 07:06:01 +03:00
obj . send ( JSON . stringify ( { action : 'getScript' , type : 1 , script : script . toString ( ) } ) ) ;
} ) ;
} else if ( command . type == 2 ) {
2019-03-08 09:47:27 +03:00
parent . getCiraCleanupScript ( function ( script ) {
2019-02-08 07:06:01 +03:00
obj . send ( JSON . stringify ( { action : 'getScript' , type : 2 , script : script . toString ( ) } ) ) ;
} ) ;
}
break ;
}
2019-04-10 01:51:05 +03:00
case 'diagnostic' :
{
if ( typeof command . value == 'object' ) {
switch ( command . value . command ) {
case 'register' : {
// Only main agent can do this
2020-04-07 07:18:50 +03:00
if ( ( ( obj . agentInfo . capabilities & 0x40 ) == 0 ) && ( typeof command . value . value == 'string' ) && ( command . value . value . length == 64 ) ) {
2019-04-10 01:51:05 +03:00
// Store links to diagnostic agent id
2019-04-12 21:28:57 +03:00
var daNodeKey = 'node/' + domain . id + '/' + db . escapeBase64 ( command . value . value ) ;
2019-04-10 01:51:05 +03:00
db . Set ( { _id : 'da' + daNodeKey , domain : domain . id , time : obj . connectTime , raid : obj . dbNodeKey } ) ; // DiagnosticAgent --> Agent
db . Set ( { _id : 'ra' + obj . dbNodeKey , domain : domain . id , time : obj . connectTime , daid : daNodeKey } ) ; // Agent --> DiagnosticAgent
}
break ;
}
case 'query' : {
// Only the diagnostic agent can do
if ( ( obj . agentInfo . capabilities & 0x40 ) != 0 ) {
// Return nodeid of main agent + connection status
db . Get ( 'da' + obj . dbNodeKey , function ( err , nodes ) {
2021-01-11 01:41:25 +03:00
if ( ( nodes != null ) && ( nodes . length == 1 ) ) {
2019-04-10 01:51:05 +03:00
obj . realNodeKey = nodes [ 0 ] . raid ;
2019-04-13 02:12:31 +03:00
// Get agent connection state
var agentConnected = false ;
var state = parent . parent . GetConnectivityState ( obj . realNodeKey ) ;
if ( state ) { agentConnected = ( ( state . connectivity & 1 ) != 0 ) }
obj . send ( JSON . stringify ( { action : 'diagnostic' , value : { command : 'query' , value : obj . realNodeKey , agent : agentConnected } } ) ) ;
2019-04-10 01:51:05 +03:00
} else {
obj . send ( JSON . stringify ( { action : 'diagnostic' , value : { command : 'query' , value : null } } ) ) ;
}
} ) ;
}
break ;
}
case 'log' : {
2019-07-30 02:35:48 +03:00
if ( ( ( obj . agentInfo . capabilities & 0x40 ) != 0 ) && ( typeof command . value . value == 'string' ) && ( command . value . value . length < 256 ) ) {
// If this is a diagnostic agent, log the event in the log of the main agent
2020-04-07 07:18:50 +03:00
var event = { etype : 'node' , action : 'diagnostic' , nodeid : obj . realNodeKey , snodeid : obj . dbNodeKey , domain : domain . id , msg : command . value . value } ;
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , event ) ;
2019-04-10 01:51:05 +03:00
}
break ;
}
}
}
break ;
}
2019-08-07 03:58:29 +03:00
case 'sysinfo' : {
2019-09-24 20:34:33 +03:00
if ( ( typeof command . data == 'object' ) && ( typeof command . data . hash == 'string' ) ) {
2021-03-01 10:39:50 +03:00
// Validate command.data.
if ( common . validateObjectForMongo ( command . data , 1024 ) == false ) break ;
// Save to database
2019-09-24 20:34:33 +03:00
command . data . _id = 'si' + obj . dbNodeKey ;
command . data . type = 'sysinfo' ;
command . data . domain = domain . id ;
command . data . time = Date . now ( ) ;
db . Set ( command . data ) ; // Update system information in the database.
// Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed
var event = { etype : 'node' , action : 'sysinfohash' , nodeid : obj . dbNodeKey , domain : domain . id , hash : command . data . hash , nolog : 1 } ;
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , event ) ;
2019-09-24 20:34:33 +03:00
}
2019-08-07 03:58:29 +03:00
break ;
}
case 'sysinfocheck' : {
// Check system information update
db . GetHash ( 'si' + obj . dbNodeKey , function ( err , results ) {
if ( ( results != null ) && ( results . length == 1 ) ) { obj . send ( JSON . stringify ( { action : 'sysinfo' , hash : results [ 0 ] . hash } ) ) ; } else { obj . send ( JSON . stringify ( { action : 'sysinfo' } ) ) ; }
} ) ;
break ;
}
2020-05-12 04:44:46 +03:00
case 'sessions' : {
// This is a list of sessions provided by the agent
if ( obj . sessions == null ) { obj . sessions = { } ; }
2020-10-01 20:30:26 +03:00
if ( typeof command . value != null ) {
if ( command . type == 'kvm' ) { obj . sessions . kvm = command . value ; }
else if ( command . type == 'terminal' ) { obj . sessions . terminal = command . value ; }
else if ( command . type == 'files' ) { obj . sessions . files = command . value ; }
2020-10-02 01:45:44 +03:00
else if ( command . type == 'help' ) { obj . sessions . help = command . value ; }
2020-10-01 20:30:26 +03:00
else if ( command . type == 'tcp' ) { obj . sessions . tcp = command . value ; }
else if ( command . type == 'udp' ) { obj . sessions . udp = command . value ; }
else if ( command . type == 'msg' ) { obj . sessions . msg = command . value ; }
else if ( command . type == 'app' ) { obj . sessions . app = command . value ; }
}
2020-11-28 08:40:30 +03:00
// Any "help" session must have an associated app, if not, remove it.
if ( obj . sessions . help != null ) {
2021-05-12 01:48:28 +03:00
for ( var i in obj . sessions . help ) { if ( obj . sessions . help [ i ] == null ) { delete obj . sessions . help [ i ] ; } }
2020-11-28 08:40:30 +03:00
if ( Object . keys ( obj . sessions . help ) . length == 0 ) { delete obj . sessions . help ; }
}
// Inform everyone of updated sessions
2020-05-12 04:44:46 +03:00
obj . updateSessions ( ) ;
break ;
}
2020-05-31 03:21:45 +03:00
case 'battery' : {
// Device battery and power state
if ( obj . sessions == null ) { obj . sessions = { } ; }
if ( obj . sessions . battery == null ) { obj . sessions . battery = { } ; }
if ( ( command . state == 'ac' ) || ( command . state == 'dc' ) ) { obj . sessions . battery . state = command . state ; } else { delete obj . sessions . battery . state ; }
if ( ( typeof command . level == 'number' ) && ( command . level >= 0 ) && ( command . level <= 100 ) ) { obj . sessions . battery . level = command . level ; } else { delete obj . sessions . battery . level ; }
obj . updateSessions ( ) ;
break ;
}
2020-07-07 03:16:54 +03:00
case 'getcoredump' : {
2020-07-08 21:59:20 +03:00
// Check if we requested a core dump file in the last minute, if so, ignore this.
if ( ( parent . lastCoreDumpRequest != null ) && ( ( Date . now ( ) - parent . lastCoreDumpRequest ) < 60000 ) ) break ;
2020-07-07 03:16:54 +03:00
// Indicates if the agent has a coredump available
2020-07-08 21:59:20 +03:00
if ( ( command . exists === true ) && ( typeof command . agenthashhex == 'string' ) && ( command . agenthashhex . length == 96 ) ) {
// Check if we already have this exact dump file
2020-08-21 21:47:34 +03:00
const coreDumpFile = parent . path . join ( parent . parent . datapath , '..' , 'meshcentral-coredumps' , obj . agentInfo . agentId + '-' + command . agenthashhex + '-' + obj . nodeid + '.dmp' ) ;
2020-07-08 21:59:20 +03:00
parent . fs . stat ( coreDumpFile , function ( err , stats ) {
if ( stats != null ) return ;
obj . coreDumpPresent = true ;
// Check how many files are in the coredumps folder
2020-08-21 21:47:34 +03:00
const coreDumpPath = parent . path . join ( parent . parent . datapath , '..' , 'meshcentral-coredumps' ) ;
2020-07-08 21:59:20 +03:00
parent . fs . readdir ( coreDumpPath , function ( err , files ) {
if ( ( files != null ) && ( files . length >= 20 ) ) return ; // Don't get more than 20 core dump files.
// Get the core dump uploaded to the server.
parent . lastCoreDumpRequest = Date . now ( ) ;
2020-10-28 02:40:12 +03:00
obj . RequestCoreDump ( command . agenthashhex , command . corehashhex ) ;
2020-07-08 21:59:20 +03:00
} ) ;
} ) ;
2020-07-07 03:16:54 +03:00
}
break ;
}
2020-07-18 05:55:49 +03:00
case 'tunnelCloseStats' : {
// TODO: This this extra stats from the tunnel, you can merge this into the tunnel event in the database.
//console.log(command);
2020-07-22 02:20:17 +03:00
2021-08-27 00:48:17 +03:00
// Validate input
if ( ( command . sent == null ) || ( typeof command . sent != 'string' ) ) return ;
if ( ( command . sentActual == null ) || ( typeof command . sentActual != 'string' ) ) return ;
if ( ( command . sentActual == null ) || ( typeof command . sentActual != 'number' ) ) return ;
2020-07-22 02:20:17 +03:00
// Event the session closed compression data.
2021-08-27 00:48:17 +03:00
var event = { etype : 'node' , action : 'sessioncompression' , nodeid : obj . dbNodeKey , domain : domain . id , sent : parseInt ( command . sent ) , sentActual : parseInt ( command . sentActual ) , msgid : 54 , msgArgs : [ command . sentRatio , parseInt ( command . sent ) , parseInt ( command . sentActual ) ] , msg : 'Agent closed session with ' + command . sentRatio + '% agent to server compression. Sent: ' + command . sent + ', Compressed: ' + command . sentActual + '.' } ;
2020-07-22 02:20:17 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , event ) ;
2020-07-18 05:55:49 +03:00
break ;
}
2020-08-12 23:35:36 +03:00
case 'lmsinfo' : {
// Agents send the LMS port bindings
// Example: {"action":"lmsinfo","value":{"ports":["623","16992"]}}
break ;
}
2019-10-08 11:18:40 +03:00
case 'plugin' : {
2019-10-28 19:21:12 +03:00
if ( ( parent . parent . pluginHandler == null ) || ( typeof command . plugin != 'string' ) ) break ;
2020-04-07 07:18:50 +03:00
try {
2019-11-01 23:49:18 +03:00
parent . parent . pluginHandler . plugins [ command . plugin ] . serveraction ( command , obj , parent ) ;
2019-10-10 21:13:25 +03:00
} catch ( e ) {
2020-04-24 23:17:06 +03:00
parent . parent . debug ( 'agent' , 'Error loading plugin handler (' + e + ')' ) ;
2019-10-10 21:13:25 +03:00
console . log ( 'Error loading plugin handler (' + e + ')' ) ;
2019-10-08 11:18:40 +03:00
}
break ;
}
2020-11-04 05:44:43 +03:00
case 'meshToolInfo' : {
2021-04-27 10:53:04 +03:00
// Return information about a MeshCentral tool. Current tools are 'MeshCentralRouter' and 'MeshCentralAssistant'
// Information includes file hash and download location URL
2020-11-04 05:44:43 +03:00
if ( typeof command . name != 'string' ) break ;
var info = parent . parent . meshToolsBinaries [ command . name ] ;
if ( ( command . hash != null ) && ( info . hash == command . hash ) ) return ;
2021-07-08 21:43:47 +03:00
// To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += '/' ;
// Build the response
2021-04-27 10:53:04 +03:00
const responseCmd = { action : 'meshToolInfo' , name : command . name , tag : command . tag , sessionid : command . sessionid , hash : info . hash , size : info . size , url : info . url } ;
2021-07-08 21:43:47 +03:00
if ( ( command . name == 'MeshCentralAssistant' ) && ( command . msh == true ) ) { responseCmd . url = '*/' + xdomain + 'meshagents?id=10006' ; } // If this is Assistant and the MSH needs to be included in the executable, change the URL.
2020-11-04 05:44:43 +03:00
if ( command . cookie === true ) { responseCmd . url += ( '&auth=' + parent . parent . encodeCookie ( { download : info . dlname } , parent . parent . loginCookieEncryptionKey ) ) ; }
if ( command . pipe === true ) { responseCmd . pipe = true ; }
2021-05-18 09:43:47 +03:00
if ( parent . webCertificateHashs [ domain . id ] != null ) { responseCmd . serverhash = Buffer . from ( parent . webCertificateHashs [ domain . id ] , 'binary' ) . toString ( 'hex' ) ; }
2020-11-04 05:44:43 +03:00
try { ws . send ( JSON . stringify ( responseCmd ) ) ; } catch ( ex ) { }
break ;
}
2021-01-15 07:27:15 +03:00
case 'agentupdate' : {
2021-02-04 08:48:54 +03:00
if ( ( obj . agentExeInfo != null ) && ( typeof obj . agentExeInfo . url == 'string' ) ) {
var func = function agentUpdateFunc ( argument , taskid , taskLimiterQueue ) { // Medium priority task
// If agent disconnection, complete and exit now.
if ( obj . authenticated != 2 ) { parent . parent . taskLimiter . completed ( taskid ) ; return ; }
// Agent is requesting an agent update
obj . agentCoreUpdateTaskId = taskid ;
const url = '*' + require ( 'url' ) . parse ( obj . agentExeInfo . url ) . path ;
var cmd = { action : 'agentupdate' , url : url , hash : obj . agentExeInfo . hashhex , sessionid : agentUpdateFunc . sessionid } ;
2021-03-05 03:27:43 +03:00
parent . parent . debug ( 'agentupdate' , "Sending user requested agent update url: " + cmd . url ) ;
2021-02-04 08:48:54 +03:00
// Add the hash
if ( obj . agentExeInfo . fileHash != null ) { cmd . hash = obj . agentExeInfo . fileHashHex ; } else { cmd . hash = obj . agentExeInfo . hashhex ; }
// Add server TLS cert hash
if ( isIgnoreHashCheck ( ) == false ) {
const tlsCertHash = parent . webCertificateFullHashs [ domain . id ] ;
if ( tlsCertHash != null ) { cmd . servertlshash = Buffer . from ( tlsCertHash , 'binary' ) . toString ( 'hex' ) ; }
}
// Send the agent update command
obj . send ( JSON . stringify ( cmd ) ) ;
2021-01-17 10:35:39 +03:00
}
2021-02-04 08:48:54 +03:00
func . sessionid = command . sessionid ;
2021-01-17 10:35:39 +03:00
2021-02-04 08:48:54 +03:00
// Agent update. The recovery core was loaded in the agent, send a command to update the agent
parent . parent . taskLimiter . launch ( func , null , 1 ) ;
2021-01-17 10:35:39 +03:00
}
break ;
}
case 'agentupdatedownloaded' : {
if ( obj . agentCoreUpdateTaskId != null ) {
// Indicate this udpate task is complete
parent . parent . taskLimiter . completed ( obj . agentCoreUpdateTaskId ) ;
delete obj . agentCoreUpdateTaskId ;
2021-01-15 07:27:15 +03:00
}
break ;
}
2021-04-18 00:44:19 +03:00
case 'errorlog' : { // This is the agent error log
if ( ( ! Array . isArray ( command . log ) ) || ( command . log . length == 0 ) || ( parent . parent . agentErrorLog == null ) ) break ;
var lastLogEntry = command . log [ command . log . length - 1 ] ;
if ( ( lastLogEntry != null ) && ( typeof lastLogEntry == 'object' ) && ( typeof lastLogEntry . t == 'number' ) ) {
parent . fs . write ( parent . parent . agentErrorLog , obj . dbNodeKey + ', ' + Date . now ( ) + ', ' + str + '\r\n' , function ( err ) { } ) ;
db . Set ( { _id : 'al' + obj . dbNodeKey , lastEvent : lastLogEntry . t } ) ;
}
break ;
}
2021-04-14 05:59:10 +03:00
case '2faauth' : {
// Validate input
if ( ( typeof command . url != 'string' ) || ( typeof command . approved != 'boolean' ) || ( command . url . startsWith ( '2fa://' ) == false ) ) return ;
// parse the URL
var url = null ;
try { url = require ( 'url' ) . parse ( command . url ) ; } catch ( ex ) { }
if ( url == null ) return ;
// Decode the cookie
var urlSplit = url . query . split ( '&c=' ) ;
if ( urlSplit . length != 2 ) return ;
const authCookie = parent . parent . decodeCookie ( urlSplit [ 1 ] , null , 1 ) ;
if ( ( authCookie == null ) || ( typeof authCookie . c != 'string' ) || ( ( 'code=' + authCookie . c ) != urlSplit [ 0 ] ) ) return ;
if ( ( typeof authCookie . n != 'string' ) || ( authCookie . n != obj . dbNodeKey ) || ( typeof authCookie . u != 'string' ) ) return ;
// Fetch the user
const user = parent . users [ authCookie . u ] ;
if ( user == null ) return ;
// Add this device as the authentication push notification device for this user
if ( authCookie . a == 'addAuth' ) {
2021-04-14 23:28:51 +03:00
// Do nothing if authentication is not approved.
// We do not want to indicate that the remote user responded to this.
if ( command . approved !== true ) return ;
2021-04-14 05:59:10 +03:00
// Change the user
user . otpdev = obj . dbNodeKey ;
parent . db . SetUser ( user ) ;
// Notify change
var targets = [ '*' , 'server-users' , user . _id ] ;
if ( user . groups ) { for ( var i in user . groups ) { targets . push ( 'server-users:' + i ) ; } }
var event = { etype : 'user' , userid : user . _id , username : user . name , account : parent . CloneSafeUser ( user ) , action : 'accountchange' , msgid : 113 , msg : "Added push notification authentication device" , domain : domain . id } ;
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the user. Another event will come.
parent . parent . DispatchEvent ( targets , obj , event ) ;
}
// Complete 2FA checking
if ( authCookie . a == 'checkAuth' ) {
2021-04-14 23:28:51 +03:00
if ( typeof authCookie . s != 'string' ) return ;
// Notify 2FA response
parent . parent . DispatchEvent ( [ '2fadev-' + authCookie . s ] , obj , { etype : '2fadev' , action : '2faresponse' , domain : domain . id , nodeid : obj . dbNodeKey , code : authCookie . a , userid : user . _id , approved : command . approved , sessionid : authCookie . s , nolog : 1 } ) ;
2021-04-14 05:59:10 +03:00
}
break ;
}
2021-05-16 11:25:58 +03:00
case 'getUserImage' : {
// Validate input
2022-01-24 20:56:12 +03:00
if ( typeof command . userid != 'string' ) {
// Send back the default image if required
if ( ( command . default ) || ( command . sentDefault ) ) {
try { command . image = 'data:image/png;base64,' + Buffer . from ( parent . fs . readFileSync ( parent . parent . path . join ( _ _dirname , 'public' , 'images' , 'user-128.png' ) ) , 'binary' ) . toString ( 'base64' ) ; } catch ( ex ) { }
obj . send ( JSON . stringify ( command ) ) ;
}
return ;
}
2021-05-16 11:25:58 +03:00
var useridsplit = command . userid . split ( '/' ) ;
if ( ( useridsplit . length != 3 ) || ( useridsplit [ 1 ] != domain . id ) ) return ;
// Add the user's real name if present
var u = parent . users [ command . userid ] ;
if ( u == null ) return ;
2021-06-28 23:58:06 +03:00
if ( u . name ) { command . name = u . name ; }
2021-05-16 11:25:58 +03:00
if ( u . realname ) { command . realname = u . realname ; }
// An agent can only request images of accounts with rights to the device.
if ( parent . GetNodeRights ( command . userid , obj . dbMeshKey , obj . dbNodeKey ) != 0 ) {
parent . db . Get ( 'im' + command . userid , function ( err , images ) {
2021-12-11 01:11:53 +03:00
if ( ( err == null ) && ( images != null ) && ( images . length == 1 ) ) {
// Send back the account image
command . image = images [ 0 ] . image ;
} else {
// Send back the default image if required
2022-01-17 00:37:26 +03:00
if ( ( command . default ) || ( command . sentDefault ) ) {
2021-12-11 01:11:53 +03:00
try { command . image = 'data:image/png;base64,' + Buffer . from ( parent . fs . readFileSync ( parent . parent . path . join ( _ _dirname , 'public' , 'images' , 'user-128.png' ) ) , 'binary' ) . toString ( 'base64' ) ; } catch ( ex ) { }
}
}
2021-05-16 11:25:58 +03:00
obj . send ( JSON . stringify ( command ) ) ;
} ) ;
}
break ;
}
2021-06-24 02:59:36 +03:00
case 'getServerImage' : {
if ( command . agent === 'assistant' ) {
// Return server title and image for MeshCentral Assistant
if ( ( domain . assistantcustomization != null ) && ( typeof domain . assistantcustomization == 'object' ) ) {
var ok = false ;
if ( typeof domain . assistantcustomization . title == 'string' ) { ok = true ; command . title = domain . assistantcustomization . title ; }
if ( typeof domain . assistantcustomization . image == 'string' ) { try { command . image = 'data:image/jpeg;base64,' + Buffer . from ( parent . fs . readFileSync ( parent . parent . getConfigFilePath ( domain . assistantcustomization . image ) ) , 'binary' ) . toString ( 'base64' ) ; ok = true ; } catch ( ex ) { console . log ( ex ) ; } }
if ( ok ) { obj . send ( JSON . stringify ( command ) ) ; }
}
}
if ( command . agent === 'android' ) {
// Return server title and image for MeshCentral Assistant
if ( ( domain . androidcustomization != null ) && ( typeof domain . androidcustomization == 'object' ) ) {
var ok = false ;
if ( typeof domain . androidcustomization . title == 'string' ) { ok = true ; command . title = domain . androidcustomization . title ; }
if ( typeof domain . androidcustomization . subtitle == 'string' ) { ok = true ; command . subtitle = domain . androidcustomization . subtitle ; }
if ( typeof domain . androidcustomization . image == 'string' ) { try { command . image = 'data:image/jpeg;base64,' + Buffer . from ( parent . fs . readFileSync ( parent . parent . getConfigFilePath ( domain . androidcustomization . image ) ) , 'binary' ) . toString ( 'base64' ) ; ok = true ; } catch ( ex ) { console . log ( ex ) ; } }
if ( ok ) { obj . send ( JSON . stringify ( command ) ) ; }
}
}
break ;
}
2021-11-10 09:51:40 +03:00
case 'guestShare' : {
2021-11-10 21:31:33 +03:00
if ( ( command . flags == null ) || ( command . flags == 0 ) ) {
// Stop any current self-share, this is allowed even if self guest sharing is not allows so to clear any old shares.
2021-11-10 09:51:40 +03:00
removeGuestSharing ( function ( ) {
delete obj . guestSharing ;
obj . send ( JSON . stringify ( { action : 'guestShare' , flags : command . flags , url : null , viewOnly : false } ) ) ;
} ) ;
} else {
// Add a new self-share, this will replace any share for this device
2021-12-14 04:04:26 +03:00
if ( ( domain . agentselfguestsharing == null ) || ( domain . agentselfguestsharing == false ) || ( typeof command . flags != 'number' ) ) return ; // Check if agent self-sharing is allowed, this is off by default.
2021-11-10 21:31:33 +03:00
if ( ( command . flags & 2 ) == 0 ) { command . viewOnly = false ; } // Only allow "view only" if desktop is shared.
2021-11-10 09:51:40 +03:00
addGuestSharing ( command . flags , command . viewOnly , function ( share ) {
obj . guestSharing = true ;
obj . send ( JSON . stringify ( { action : 'guestShare' , url : share . url , flags : share . flags , viewOnly : share . viewOnly } ) ) ;
} )
}
break ;
}
2022-04-12 01:05:23 +03:00
case 'amtconfig' : {
// Sent by the agent when the agent needs a Intel AMT APF connection to the server
const cookie = parent . parent . encodeCookie ( { a : 'apf' , n : obj . dbNodeKey , m : obj . dbMeshKey } , parent . parent . loginCookieEncryptionKey ) ;
try { obj . send ( JSON . stringify ( { action : 'amtconfig' , user : '**MeshAgentApfTunnel**' , pass : cookie } ) ) ; } catch ( ex ) { }
break ;
}
2022-08-19 02:19:39 +03:00
case 'script-task' : {
// These command are for running regular batch jobs on the remote device
2022-08-19 04:03:03 +03:00
if ( parent . parent . taskManager != null ) { parent . parent . taskManager . agentAction ( command , obj ) ; }
2021-08-21 18:38:09 +03:00
break ;
}
2019-01-05 04:59:13 +03:00
default : {
2020-04-07 07:18:50 +03:00
parent . agentStats . unknownAgentActionCount ++ ;
2020-08-12 23:35:36 +03:00
parent . parent . debug ( 'agent' , 'Unknown agent action (' + obj . remoteaddrport + '): ' + JSON . stringify ( command ) + '.' ) ;
console . log ( 'Unknown agent action (' + obj . remoteaddrport + '): ' + JSON . stringify ( command ) + '.' ) ;
2019-01-05 04:59:13 +03:00
break ;
}
2017-08-28 19:27:45 +03:00
}
2019-10-29 00:27:59 +03:00
if ( parent . parent . pluginHandler != null ) {
parent . parent . pluginHandler . callHook ( 'hook_processAgentData' , command , obj , parent ) ;
}
2017-08-28 19:27:45 +03:00
}
}
2021-11-10 09:51:40 +03:00
function addGuestSharing ( flags , viewOnly , func ) {
// Create cookie
2021-11-11 04:56:12 +03:00
const publicid = 'AS:' + obj . dbNodeKey ;
const extrakey = getRandomAmtPassword ( ) ;
const cookie = { a : 6 , pid : publicid , k : extrakey } ; // New style sharing cookie
2021-11-10 09:51:40 +03:00
const inviteCookie = parent . parent . encodeCookie ( cookie , parent . parent . invitationLinkEncryptionKey ) ;
if ( inviteCookie == null ) return ;
// Create the server url
2022-08-25 00:10:40 +03:00
var serverName = parent . getWebServerName ( domain , req ) ;
2021-11-10 09:51:40 +03:00
var httpsPort = ( ( args . aliasport == null ) ? args . port : args . aliasport ) ; // Use HTTPS alias port is specified
var xdomain = ( domain . dns == null ) ? domain . id : '' ;
if ( xdomain != '' ) xdomain += '/' ;
var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie ;
if ( serverName . split ( '.' ) == 1 ) { url = '/' + xdomain + page + '?c=' + inviteCookie ; }
// Create a device sharing database entry
2021-11-11 04:56:12 +03:00
var shareEntry = { _id : 'deviceshare-' + publicid , type : 'deviceshare' , nodeid : obj . dbNodeKey , p : flags , domain : domain . id , publicid : publicid , guestName : 'Agent' , consent : 0x7F , url : url , extrakey : extrakey } ;
2021-12-14 04:04:26 +03:00
// Add expire time
if ( ( typeof domain . agentselfguestsharing == 'object' ) && ( typeof domain . agentselfguestsharing . expire == 'number' ) && ( domain . agentselfguestsharing . expire > 0 ) ) {
shareEntry . startTime = Date . now ( ) ;
shareEntry . expireTime = Date . now ( ) + ( 60000 * domain . agentselfguestsharing . expire ) ;
}
2021-11-10 09:51:40 +03:00
if ( viewOnly === true ) { shareEntry . viewOnly = true ; }
parent . db . Set ( shareEntry ) ;
// Send out an event that we added a device share
var targets = parent . CreateNodeDispatchTargets ( obj . dbMeshKey , obj . dbNodeKey ) ;
2021-11-11 00:21:30 +03:00
var event = { etype : 'node' , nodeid : obj . dbNodeKey , action : 'addedDeviceShare' , msg : 'Added device share with unlimited time' , msgid : 131 , msgArgs : [ 'Agent' ] , domain : domain . id } ;
2021-11-10 09:51:40 +03:00
parent . parent . DispatchEvent ( targets , obj , event ) ;
// Send device share update
parent . db . GetAllTypeNodeFiltered ( [ obj . dbNodeKey ] , domain . id , 'deviceshare' , null , function ( err , docs ) {
if ( err != null ) return ;
// Check device sharing
var now = Date . now ( ) ;
for ( var i = 0 ; i < docs . length ; i ++ ) {
const doc = docs [ i ] ;
if ( doc . expireTime < now ) { parent . db . Remove ( doc . _id , function ( ) { } ) ; delete docs [ i ] ; } else {
// This share is ok, remove extra data we don't need to send.
delete doc . _id ; delete doc . domain ; delete doc . nodeid ; delete doc . type ;
}
}
// Send device share update
var targets = parent . CreateNodeDispatchTargets ( obj . dbMeshKey , obj . dbNodeKey , [ ] ) ;
parent . parent . DispatchEvent ( targets , obj , { etype : 'node' , nodeid : obj . dbNodeKey , action : 'deviceShareUpdate' , domain : domain . id , deviceShares : docs , nolog : 1 } ) ;
// Callback
if ( func ) { func ( { url : url , flags : flags , viewOnly : viewOnly } ) ; }
} ) ;
}
function removeGuestSharing ( func ) {
var publicid = 'AS:' + obj . dbNodeKey ;
parent . db . GetAllTypeNodeFiltered ( [ obj . dbNodeKey ] , domain . id , 'deviceshare' , null , function ( err , docs ) {
if ( err != null ) return ;
// Remove device sharing
var now = Date . now ( ) , removedExact = null , removed = false , okDocs = [ ] ;
for ( var i = 0 ; i < docs . length ; i ++ ) {
const doc = docs [ i ] ;
if ( doc . publicid == publicid ) { parent . db . Remove ( doc . _id , function ( ) { } ) ; removedExact = doc ; removed = true ; }
else if ( doc . expireTime < now ) { parent . db . Remove ( doc . _id , function ( ) { } ) ; removed = true ; } else {
// This share is ok, remove extra data we don't need to send.
delete doc . _id ; delete doc . domain ; delete doc . nodeid ; delete doc . type ;
okDocs . push ( doc ) ;
}
}
// Event device share removal
if ( removedExact != null ) {
// Send out an event that we removed a device share
2022-10-25 21:14:26 +03:00
var targets = parent . CreateNodeDispatchTargets ( obj . dbMeshKey , obj . dbNodeKey , [ 'server-shareremove' ] ) ;
2021-11-11 00:21:30 +03:00
var event = { etype : 'node' , nodeid : obj . dbNodeKey , action : 'removedDeviceShare' , msg : 'Removed Device Share' , msgid : 102 , msgArgs : [ 'Agent' ] , domain : domain . id , publicid : publicid } ;
2021-11-10 09:51:40 +03:00
parent . parent . DispatchEvent ( targets , obj , event ) ;
}
// If we removed any shares, send device share update
if ( removed == true ) {
var targets = parent . CreateNodeDispatchTargets ( obj . dbMeshKey , obj . dbNodeKey , [ ] ) ;
parent . parent . DispatchEvent ( targets , obj , { etype : 'node' , nodeid : obj . dbNodeKey , action : 'deviceShareUpdate' , domain : domain . id , deviceShares : okDocs , nolog : 1 } ) ;
}
// Call back when done
if ( func ) func ( removed ) ;
} ) ;
}
2020-05-12 04:44:46 +03:00
// Notify update of sessions
obj . updateSessions = function ( ) {
// Perform some clean up
for ( var i in obj . sessions ) { if ( Object . keys ( obj . sessions [ i ] ) . length == 0 ) { delete obj . sessions [ i ] ; } }
if ( Object . keys ( obj . sessions ) . length == 0 ) { delete obj . sessions ; }
// Event the new sessions, this will notify everyone that agent sessions have changed
var event = { etype : 'node' , action : 'devicesessions' , nodeid : obj . dbNodeKey , domain : domain . id , sessions : obj . sessions , nolog : 1 } ;
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( obj . dbMeshKey , [ obj . dbNodeKey ] ) , obj , event ) ;
}
2017-08-28 19:27:45 +03:00
// Change the current core information string and event it
function ChangeAgentCoreInfo ( command ) {
2020-10-09 05:47:24 +03:00
if ( ( obj . agentInfo == null ) || ( obj . agentInfo . capabilities & 0x40 ) ) return ;
2017-09-15 21:45:06 +03:00
if ( ( command == null ) || ( command == null ) ) return ; // Safety, should never happen.
2017-08-28 19:27:45 +03:00
2020-09-24 01:34:46 +03:00
// If the device is pending a change, hold.
if ( obj . deviceChanging === true ) { setTimeout ( function ( ) { ChangeAgentCoreInfo ( command ) ; } , 100 ) ; return ; }
obj . deviceChanging = true ;
2017-08-28 19:27:45 +03:00
// Check that the mesh exists
2019-03-08 09:47:27 +03:00
const mesh = parent . meshes [ obj . dbMeshKey ] ;
2020-09-24 01:34:46 +03:00
if ( mesh == null ) { delete obj . deviceChanging ; return ; }
2018-08-22 01:08:15 +03:00
// Get the node and change it if needed
2019-03-08 09:47:27 +03:00
db . Get ( obj . dbNodeKey , function ( err , nodes ) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
2020-09-24 01:34:46 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { delete obj . deviceChanging ; return ; }
2019-03-08 09:47:27 +03:00
const device = nodes [ 0 ] ;
2018-08-22 01:08:15 +03:00
if ( device . agent ) {
2019-01-31 00:43:42 +03:00
var changes = [ ] , change = 0 , log = 0 ;
2018-08-22 01:08:15 +03:00
// Check if anything changes
2019-10-02 00:54:38 +03:00
if ( command . name && ( typeof command . name == 'string' ) && ( command . name != device . name ) ) { change = 1 ; log = 1 ; device . name = command . name ; changes . push ( 'name' ) ; }
2019-01-31 00:43:42 +03:00
if ( ( command . caps != null ) && ( device . agent . core != command . value ) ) { if ( ( command . value == null ) && ( device . agent . core != null ) ) { delete device . agent . core ; } else { device . agent . core = command . value ; } change = 1 ; } // Don't save this as an event to the db.
if ( ( command . caps != null ) && ( ( device . agent . caps & 0xFFFFFFE7 ) != ( command . caps & 0xFFFFFFE7 ) ) ) { device . agent . caps = ( ( device . agent . caps & 24 ) + ( command . caps & 0xFFFFFFE7 ) ) ; change = 1 ; } // Allow Javascript on the agent to change all capabilities except console and javascript support, Don't save this as an event to the db.
2019-10-02 00:54:38 +03:00
if ( ( command . osdesc != null ) && ( typeof command . osdesc == 'string' ) && ( device . osdesc != command . osdesc ) ) { device . osdesc = command . osdesc ; change = 1 ; changes . push ( 'os desc' ) ; } // Don't save this as an event to the db.
2020-10-17 00:46:41 +03:00
if ( ( typeof command . root == 'boolean' ) && ( command . root !== device . agent . root ) ) { change = 1 ; device . agent . root = command . root ; }
2019-03-15 22:30:04 +03:00
if ( device . ip != obj . remoteaddr ) { device . ip = obj . remoteaddr ; change = 1 ; }
2018-08-22 01:08:15 +03:00
if ( command . intelamt ) {
if ( ! device . intelamt ) { device . intelamt = { } ; }
2020-11-02 11:44:07 +03:00
if ( ( command . intelamt . Versions != null ) && ( typeof command . intelamt . Versions == 'object' ) ) {
if ( ( command . intelamt . Versions . AMT != null ) && ( typeof command . intelamt . Versions . AMT == 'string' ) && ( command . intelamt . Versions . AMT . length < 12 ) && ( device . intelamt . ver != command . intelamt . Versions . AMT ) ) { changes . push ( 'AMT version' ) ; device . intelamt . ver = command . intelamt . Versions . AMT ; change = 1 ; log = 1 ; }
2022-06-19 10:06:23 +03:00
if ( ( command . intelamt . Versions . Sku != null ) && ( typeof command . intelamt . Versions . Sku == 'string' ) ) {
const sku = parseInt ( command . intelamt . Versions . Sku ) ;
if ( device . intelamt . sku !== sku ) { device . intelamt . sku = sku ; change = 1 ; log = 1 ; }
}
2020-11-02 11:44:07 +03:00
}
if ( ( command . intelamt . ProvisioningState != null ) && ( typeof command . intelamt . ProvisioningState == 'number' ) && ( device . intelamt . state != command . intelamt . ProvisioningState ) ) { changes . push ( 'AMT state' ) ; device . intelamt . state = command . intelamt . ProvisioningState ; change = 1 ; log = 1 ; }
if ( ( command . intelamt . Flags != null ) && ( typeof command . intelamt . Flags == 'number' ) && ( device . intelamt . flags != command . intelamt . Flags ) ) {
if ( device . intelamt . flags ) { changes . push ( 'AMT flags (' + device . intelamt . flags + ' --> ' + command . intelamt . Flags + ')' ) ; } else { changes . push ( 'AMT flags (' + command . intelamt . Flags + ')' ) ; }
device . intelamt . flags = command . intelamt . Flags ; change = 1 ; log = 1 ;
2018-09-22 02:34:35 +03:00
}
2020-11-02 11:44:07 +03:00
if ( ( command . intelamt . UUID != null ) && ( typeof command . intelamt . UUID == 'string' ) && ( device . intelamt . uuid != command . intelamt . UUID ) ) { changes . push ( 'AMT uuid' ) ; device . intelamt . uuid = command . intelamt . UUID ; change = 1 ; log = 1 ; }
2018-08-22 01:08:15 +03:00
}
2021-01-25 08:51:22 +03:00
if ( command . av != null ) { // Antivirus
2019-08-02 21:35:59 +03:00
if ( ! device . av ) { device . av = [ ] ; }
2021-01-25 08:51:22 +03:00
if ( JSON . stringify ( device . av ) != JSON . stringify ( command . av ) ) { /*changes.push('AV status');*/ device . av = command . av ; change = 1 ; log = 1 ; }
}
if ( command . wsc != null ) { // Windows Security Center
if ( ! device . wsc ) { device . wsc = { } ; }
if ( JSON . stringify ( device . wsc ) != JSON . stringify ( command . wsc ) ) { /*changes.push('Windows Security Center status');*/ device . wsc = command . wsc ; change = 1 ; log = 1 ; }
2019-08-02 21:35:59 +03:00
}
2024-01-04 05:16:41 +03:00
if ( command . defender != null ) { // Defender For Windows Server
if ( ! device . defender ) { device . defender = { } ; }
if ( JSON . stringify ( device . defender ) != JSON . stringify ( command . defender ) ) { /*changes.push('Defender status');*/ device . defender = command . defender ; change = 1 ; log = 1 ; }
2024-04-07 21:12:01 +03:00
}
if ( command . lastbootuptime != null ) { // Last Boot Up Time
if ( ! device . lastbootuptime ) { device . lastbootuptime = "" ; }
if ( device . lastbootuptime != command . lastbootuptime ) { /*changes.push('Last Boot Up Time');*/ device . lastbootuptime = command . lastbootuptime ; change = 1 ; log = 1 ; }
2024-01-04 05:16:41 +03:00
}
2019-08-02 21:35:59 +03:00
2021-01-31 03:15:06 +03:00
// Push Messaging Token
2021-02-23 03:54:30 +03:00
if ( ( command . pmt != null ) && ( typeof command . pmt == 'string' ) && ( device . pmt != command . pmt ) ) {
if ( typeof device . pmt == 'string' ) { db . Remove ( 'pmt_' + device . pmt ) ; }
device . pmt = command . pmt ;
change = 1 ; // Don't save this change as an event to the db, so no log=1.
parent . removePmtFromAllOtherNodes ( device ) ; // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
}
2021-01-31 03:15:06 +03:00
2020-11-02 20:47:02 +03:00
if ( ( command . users != null ) && ( Array . isArray ( command . users ) ) && ( device . users != command . users ) ) { device . users = command . users ; change = 1 ; } // Don't save this to the db.
2019-03-08 09:47:27 +03:00
if ( ( mesh . mtype == 2 ) && ( ! args . wanonly ) ) {
2019-02-21 02:26:27 +03:00
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
if ( device . host != obj . remoteaddr ) { device . host = obj . remoteaddr ; change = 1 ; changes . push ( 'host' ) ; }
2018-08-22 01:08:15 +03:00
// TODO: Check that the agent has an interface that is the same as the one we got this websocket connection on. Only set if we have a match.
}
2017-08-28 19:27:45 +03:00
2024-02-18 04:51:15 +03:00
// Remove old volumes and BitLocker data, this is part of sysinfo.
delete device . volumes ;
2023-10-01 22:50:27 +03:00
2018-09-22 02:34:35 +03:00
// If there are changes, event the new device
2018-08-22 01:08:15 +03:00
if ( change == 1 ) {
2019-04-10 20:41:10 +03:00
// Do some clean up if needed, these values should not be in the database.
if ( device . conn != null ) { delete device . conn ; }
if ( device . pwr != null ) { delete device . pwr ; }
if ( device . agct != null ) { delete device . agct ; }
if ( device . cict != null ) { delete device . cict ; }
2018-09-22 02:34:35 +03:00
// Save to the database
2019-03-08 09:47:27 +03:00
db . Set ( device ) ;
2017-08-28 19:27:45 +03:00
2018-08-22 01:08:15 +03:00
// Event the node change
2019-10-11 21:16:36 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , node : parent . CloneSafeNode ( device ) } ;
2018-09-25 03:47:03 +03:00
if ( changes . length > 0 ) { event . msg = 'Changed device ' + device . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) ; }
2019-07-15 19:48:40 +03:00
if ( ( log == 0 ) || ( ( obj . agentInfo ) && ( obj . agentInfo . capabilities ) && ( obj . agentInfo . capabilities & 0x20 ) ) || ( changes . length == 0 ) ) { event . nolog = 1 ; } // If this is a temporary device, don't log changes
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( device . meshid , [ obj . dbNodeKey ] ) , obj , event ) ;
2017-08-28 19:27:45 +03:00
}
2020-09-24 01:34:46 +03:00
// Device change is done.
delete obj . deviceChanging ;
2018-08-22 01:08:15 +03:00
}
2017-08-28 19:27:45 +03:00
} ) ;
}
2017-08-29 01:06:29 +03:00
// Change the current core information string and event it
function ChangeAgentLocationInfo ( command ) {
2019-04-10 01:51:05 +03:00
if ( obj . agentInfo . capabilities & 0x40 ) return ;
2018-08-22 01:08:15 +03:00
if ( ( command == null ) || ( command == null ) ) { return ; } // Safety, should never happen.
2017-08-29 01:06:29 +03:00
// Check that the mesh exists
2019-03-08 09:47:27 +03:00
const mesh = parent . meshes [ obj . dbMeshKey ] ;
2018-08-22 01:08:15 +03:00
if ( mesh == null ) return ;
2020-09-24 01:34:46 +03:00
// If the device is pending a change, hold.
if ( obj . deviceChanging === true ) { setTimeout ( function ( ) { ChangeAgentLocationInfo ( command ) ; } , 100 ) ; return ; }
obj . deviceChanging = true ;
2018-08-22 01:08:15 +03:00
// Get the node and change it if needed
2019-03-08 09:47:27 +03:00
db . Get ( obj . dbNodeKey , function ( err , nodes ) {
2020-09-24 01:34:46 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { delete obj . deviceChanging ; return ; }
2019-03-08 09:47:27 +03:00
const device = nodes [ 0 ] ;
2018-08-22 01:08:15 +03:00
if ( device . agent ) {
var changes = [ ] , change = 0 ;
// Check if anything changes
if ( ( command . publicip ) && ( device . publicip != command . publicip ) ) { device . publicip = command . publicip ; change = 1 ; changes . push ( 'public ip' ) ; }
if ( ( command . iploc ) && ( device . iploc != command . iploc ) ) { device . iploc = command . iploc ; change = 1 ; changes . push ( 'ip location' ) ; }
// If there are changes, save and event
if ( change == 1 ) {
2019-04-10 20:41:10 +03:00
// Do some clean up if needed, these values should not be in the database.
if ( device . conn != null ) { delete device . conn ; }
if ( device . pwr != null ) { delete device . pwr ; }
if ( device . agct != null ) { delete device . agct ; }
if ( device . cict != null ) { delete device . cict ; }
// Save the device
2019-03-08 09:47:27 +03:00
db . Set ( device ) ;
2017-08-29 01:06:29 +03:00
2018-08-22 01:08:15 +03:00
// Event the node change
2020-09-09 04:59:58 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , node : parent . CloneSafeNode ( device ) , msgid : 59 , msgArgs : [ device . name , mesh . name , changes . join ( ', ' ) ] , msg : 'Changed device ' + device . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) } ;
2018-08-22 01:08:15 +03:00
if ( obj . agentInfo . capabilities & 0x20 ) { event . nolog = 1 ; } // If this is a temporary device, don't log changes
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( device . meshid , [ obj . dbNodeKey ] ) , obj , event ) ;
2017-08-29 01:06:29 +03:00
}
2018-08-22 01:08:15 +03:00
}
2020-09-24 01:34:46 +03:00
// Done changing the device
delete obj . deviceChanging ;
2017-08-29 01:06:29 +03:00
} ) ;
}
2017-08-28 19:27:45 +03:00
// Update the mesh agent tab in the database
function ChangeAgentTag ( tag ) {
2021-01-25 03:09:15 +03:00
if ( ( obj . agentInfo == null ) || ( obj . agentInfo . capabilities & 0x40 ) ) return ;
2020-09-24 01:34:46 +03:00
if ( ( tag != null ) && ( tag . length == 0 ) ) { tag = null ; }
// If the device is pending a change, hold.
2021-01-26 11:09:28 +03:00
if ( obj . deviceChanging === true ) {
2021-02-04 08:59:26 +03:00
var func = function ChangeAgentTagFunc ( ) { ChangeAgentCoreInfo ( ChangeAgentTagFunc . tag ) ; }
func . tag = tag ;
2021-01-26 11:09:28 +03:00
setTimeout ( func , 100 ) ;
return ;
}
2020-09-24 01:34:46 +03:00
obj . deviceChanging = true ;
2017-08-28 19:27:45 +03:00
// Get the node and change it if needed
2019-03-08 09:47:27 +03:00
db . Get ( obj . dbNodeKey , function ( err , nodes ) {
2020-09-24 01:34:46 +03:00
if ( ( nodes == null ) || ( nodes . length != 1 ) ) { delete obj . deviceChanging ; return ; }
2019-03-08 09:47:27 +03:00
const device = nodes [ 0 ] ;
2017-08-28 19:27:45 +03:00
if ( device . agent ) {
2020-09-24 01:34:46 +03:00
// Parse the agent tag
var agentTag = null , serverName = null , serverDesc = null , serverTags = null ;
if ( tag != null ) {
var taglines = tag . split ( '\r\n' ) . join ( '\n' ) . split ( '\r' ) . join ( '\n' ) . split ( '\n' ) ;
for ( var i in taglines ) {
var tagline = taglines [ i ] . trim ( ) ;
if ( tagline . length > 0 ) {
if ( tagline . startsWith ( '~' ) ) {
if ( tagline . startsWith ( '~ServerName:' ) && ( tagline . length > 12 ) && ( serverName == null ) ) { serverName = tagline . substring ( 12 ) . trim ( ) ; }
if ( tagline . startsWith ( '~ServerDesc:' ) && ( tagline . length > 12 ) && ( serverDesc == null ) ) { serverDesc = tagline . substring ( 12 ) . trim ( ) ; }
if ( tagline . startsWith ( '~ServerTags:' ) && ( tagline . length > 12 ) && ( serverTags == null ) ) { serverTags = tagline . substring ( 12 ) . split ( ',' ) ; for ( var j in serverTags ) { serverTags [ j ] = serverTags [ j ] . trim ( ) ; } }
} else { if ( agentTag == null ) { agentTag = tagline ; } }
}
}
}
// Set the agent tag
var changes = false ;
if ( device . agent . tag != agentTag ) { device . agent . tag = agentTag ; if ( ( device . agent . tag == null ) || ( device . agent . tag == '' ) ) { delete device . agent . tag ; } changes = true ; }
if ( domain . agenttag != null ) {
// Set the device's server name
if ( ( serverName != null ) && ( domain . agenttag . servername === 1 ) && ( device . name != serverName ) ) { device . name = serverName ; changes = true ; }
// Set the device's server description
if ( ( serverDesc != null ) && ( domain . agenttag . serverdesc === 1 ) && ( device . desc != serverDesc ) ) { device . desc = serverDesc ; changes = true ; }
// Set the device's server description if there is no description
if ( ( serverDesc != null ) && ( domain . agenttag . serverdesc === 2 ) && ( device . desc != serverDesc ) && ( ( device . desc == null ) || ( device . desc == '' ) ) ) { device . desc = serverDesc ; changes = true ; }
if ( ( serverTags != null ) && ( domain . agenttag . servertags != null ) && ( domain . agenttag . servertags != 0 ) ) {
// Sort the tags
serverTags . sort ( ) ;
// Stringify the tags
var st2 = '' , st1 = serverTags . join ( ',' ) ;
if ( device . tags != null ) { st2 = device . tags . join ( ',' ) ; }
// Set the device's server tags
if ( ( domain . agenttag . servertags === 1 ) && ( st1 != st2 ) ) { device . tags = serverTags ; changes = true ; }
// Set the device's server tags if there are not tags
if ( ( domain . agenttag . servertags === 2 ) && ( st2 == '' ) ) { device . tags = serverTags ; changes = true ; }
// Append to device's server tags
if ( ( domain . agenttag . servertags === 3 ) && ( st1 != st2 ) ) {
if ( device . tags == null ) { device . tags = [ ] ; }
for ( var i in serverTags ) { if ( device . tags . indexOf ( serverTags [ i ] ) == - 1 ) { device . tags . push ( serverTags [ i ] ) ; } }
device . tags . sort ( ) ;
changes = true ;
}
}
}
if ( changes == true ) {
2019-04-10 20:41:10 +03:00
// Do some clean up if needed, these values should not be in the database.
if ( device . conn != null ) { delete device . conn ; }
2020-04-07 07:18:50 +03:00
if ( device . pwr != null ) { delete device . pwr ; }
2019-04-10 20:41:10 +03:00
if ( device . agct != null ) { delete device . agct ; }
if ( device . cict != null ) { delete device . cict ; }
2020-09-24 01:34:46 +03:00
// Update the device
2019-03-08 09:47:27 +03:00
db . Set ( device ) ;
2017-08-28 19:27:45 +03:00
// Event the node change
2019-10-11 21:16:36 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , node : parent . CloneSafeNode ( device ) , nolog : 1 } ;
2019-05-29 03:25:23 +03:00
if ( db . changeStream ) { event . noact = 1 ; } // If DB change stream is active, don't use this event to change the node. Another event will come.
2020-03-18 03:17:04 +03:00
parent . parent . DispatchEvent ( parent . CreateMeshDispatchTargets ( device . meshid , [ obj . dbNodeKey ] ) , obj , event ) ;
2017-08-28 19:27:45 +03:00
}
}
2020-09-24 01:34:46 +03:00
// Done changing the device
delete obj . deviceChanging ;
2017-08-28 19:27:45 +03:00
} ) ;
}
2020-12-08 09:55:29 +03:00
// Check if we need to update this agent, return true if agent binary update required.
2021-01-15 03:17:56 +03:00
// Return 0 is no update needed, 1 update using native system, 2 update using meshcore system
2020-12-08 09:55:29 +03:00
function compareAgentBinaryHash ( agentExeInfo , agentHash ) {
2021-05-28 03:29:22 +03:00
// If this is a temporary agent and the server is set to not update temporary agents, don't update the agent.
if ( ( obj . agentInfo . capabilities & 0x20 ) && ( args . temporaryagentupdate === false ) ) return 0 ;
2021-01-14 23:34:27 +03:00
// If we are testing the agent update system, always return true
2021-01-15 03:17:56 +03:00
if ( ( args . agentupdatetest === true ) || ( args . agentupdatetest === 1 ) ) return 1 ;
if ( args . agentupdatetest === 2 ) return 2 ;
2020-12-08 09:55:29 +03:00
// If the hash matches or is null, no update required.
2021-01-15 03:17:56 +03:00
if ( ( agentExeInfo . hash == agentHash ) || ( agentHash == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' ) ) return 0 ;
2020-12-08 09:55:29 +03:00
// If this is a macOS x86 or ARM agent type and it matched the universal binary, no update required.
2022-03-05 20:35:04 +03:00
if ( ( agentExeInfo . id == 16 ) || ( agentExeInfo . id == 29 ) ) {
2022-03-09 19:49:56 +03:00
if ( domain . meshAgentBinaries && domain . meshAgentBinaries [ 10005 ] ) {
2022-03-05 20:35:04 +03:00
if ( domain . meshAgentBinaries [ 10005 ] . hash == agentHash ) return 0 ;
} else {
if ( parent . parent . meshAgentBinaries [ 10005 ] . hash == agentHash ) return 0 ;
}
}
2021-01-15 03:17:56 +03:00
2020-12-08 09:55:29 +03:00
// No match, update the agent.
2021-08-10 22:23:28 +03:00
if ( args . agentupdatesystem === 2 ) return 2 ; // If set, force a meshcore update.
2021-08-16 23:39:44 +03:00
if ( agentExeInfo . id == 3 ) return 2 ; // Due to a bug in Windows 7 SP1 environement variable exec, we always update 32bit Windows agent using MeshCore for now. Upcoming agent will have a fix for this.
2021-02-09 03:15:37 +03:00
// NOTE: Windows agents with no commit dates may have bad native update system, so use meshcore system instead.
// NOTE: Windows agents with commit date prior to 1612740413000 did not kill all "meshagent.exe" processes and update could fail as a result executable being locked, meshcore system will do this.
if ( ( ( obj . AgentCommitDate == null ) || ( obj . AgentCommitDate < 1612740413000 ) ) && ( ( agentExeInfo . id == 3 ) || ( agentExeInfo . id == 4 ) ) ) return 2 ; // For older Windows agents, use the meshcore update technique.
2021-01-15 07:27:15 +03:00
return 1 ; // By default, use the native update technique.
2020-12-08 09:55:29 +03:00
}
2020-07-08 09:56:08 +03:00
// Request that the core dump file on this agent be uploaded to the server
2020-10-28 02:40:12 +03:00
obj . RequestCoreDump = function ( agenthashhex , corehashhex ) {
2020-07-08 09:56:08 +03:00
if ( agenthashhex . length > 16 ) { agenthashhex = agenthashhex . substring ( 0 , 16 ) ; }
const cookie = parent . parent . encodeCookie ( { a : 'aft' , b : 'coredump' , c : obj . agentInfo . agentId + '-' + agenthashhex + '-' + obj . nodeid + '.dmp' } , parent . parent . loginCookieEncryptionKey ) ;
obj . send ( '{"action":"msg","type":"tunnel","value":"*/' + ( ( ( domain . dns == null ) && ( domain . id != '' ) ) ? ( domain . id + '/' ) : '' ) + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}' ) ;
}
2020-12-31 03:54:02 +03:00
// Return true if we need to ignore the agent hash check
function isIgnoreHashCheck ( ) {
if ( ( args . ignoreagenthashcheck === true ) || ( domain . ignoreagenthashcheck === true ) ) return true ;
// Check site wide exceptions
if ( Array . isArray ( args . ignoreagenthashcheck ) ) {
for ( var i = 0 ; i < args . ignoreagenthashcheck . length ; i ++ ) {
if ( require ( 'ipcheck' ) . match ( obj . remoteaddr , args . ignoreagenthashcheck [ i ] ) ) return true ;
}
}
// Check domain wide exceptions
if ( Array . isArray ( domain . ignoreagenthashcheck ) ) {
for ( var i = 0 ; i < domain . ignoreagenthashcheck . length ; i ++ ) {
if ( require ( 'ipcheck' ) . match ( obj . remoteaddr , domain . ignoreagenthashcheck [ i ] ) ) return true ;
}
}
return false ;
}
2019-06-21 00:27:57 +03:00
// Generate a random Intel AMT password
function checkAmtPassword ( p ) { return ( p . length > 7 ) && ( /\d/ . test ( p ) ) && ( /[a-z]/ . test ( p ) ) && ( /[A-Z]/ . test ( p ) ) && ( /\W/ . test ( p ) ) ; }
function getRandomAmtPassword ( ) { var p ; do { p = Buffer . from ( parent . crypto . randomBytes ( 9 ) , 'binary' ) . toString ( 'base64' ) . split ( '/' ) . join ( '@' ) ; } while ( checkAmtPassword ( p ) == false ) ; return p ; }
2017-08-28 19:27:45 +03:00
return obj ;
2018-08-30 03:40:30 +03:00
} ;