2017-08-28 12:27:45 -04:00
/ * *
2018-01-04 15:15:21 -05:00
* @ description MeshCentral MeshAgent communication module
2017-08-28 12:27:45 -04:00
* @ author Ylian Saint - Hilaire & Bryan Roe
2018-01-04 15:15:21 -05:00
* @ copyright Intel Corporation 2018
* @ license Apache - 2.0
2017-08-28 12:27:45 -04:00
* @ version v0 . 0.1
* /
// Construct a MeshAgent object, called upon connection
module . exports . CreateMeshAgent = function ( parent , db , ws , req , args , domain ) {
var obj = { } ;
obj . parent = parent ;
obj . db = db ;
obj . ws = ws ;
obj . fs = parent . fs ;
obj . args = args ;
obj . nodeid = null ;
obj . meshid = null ;
obj . dbNodeKey = null ;
obj . dbMeshKey = null ;
obj . forge = parent . parent . certificateOperations . forge ;
obj . common = parent . parent . common ;
obj . authenticated = 0 ;
obj . domain = domain ;
obj . receivedCommands = 0 ;
obj . connectTime = null ;
obj . agentCoreCheck = 0 ;
obj . agentInfo ;
obj . agentUpdate = null ;
2018-05-03 14:09:29 -04:00
const agentUpdateBlockSize = 65520 ;
2017-09-01 14:23:22 -04:00
obj . remoteaddr = obj . ws . _socket . remoteAddress ;
2017-10-15 02:22:19 -04:00
obj . useSHA386 = false ;
2017-09-01 14:23:22 -04:00
if ( obj . remoteaddr . startsWith ( '::ffff:' ) ) { obj . remoteaddr = obj . remoteaddr . substring ( 7 ) ; }
2017-10-25 12:58:14 -04:00
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive, 4 minutes
2017-08-28 12:27:45 -04:00
// Send a message to the mesh agent
2017-09-13 14:25:57 -04:00
obj . send = function ( data ) { try { if ( typeof data == 'string' ) { obj . ws . send ( new Buffer ( data , 'binary' ) ) ; } else { obj . ws . send ( data ) ; } } catch ( e ) { } }
2017-08-28 12:27:45 -04:00
// Disconnect this agent
obj . close = function ( arg ) {
2018-07-27 18:50:12 -04:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { obj . ws . close ( ) ; if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Soft disconnect ' + obj . nodeid + ' (' + obj . remoteaddr + ')' ) ; } } catch ( e ) { console . log ( e ) ; } } // Soft close, close the websocket
if ( arg == 2 ) { try { obj . ws . _socket . _parent . end ( ) ; if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Hard disconnect ' + obj . nodeid + ' (' + obj . remoteaddr + ')' ) ; } } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2017-12-13 17:52:57 -05:00
if ( arg == 3 ) { obj . authenticated = - 1 ; } // Don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
2017-08-28 12:27:45 -04:00
if ( obj . parent . wsagents [ obj . dbNodeKey ] == obj ) {
delete obj . parent . wsagents [ obj . dbNodeKey ] ;
obj . parent . parent . ClearConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , 1 ) ;
}
// Other clean up may be needed here
if ( obj . unauth ) { delete obj . unauth ; }
if ( obj . agentUpdate != null ) { obj . fs . close ( obj . agentUpdate . fd ) ; obj . agentUpdate = null ; }
2018-04-13 17:09:25 -04:00
if ( ( obj . agentInfo ) && ( obj . agentInfo . capabilities ) && ( obj . agentInfo . capabilities & 0x20 ) ) { // This is a temporary agent, remote it
2018-04-12 14:15:01 -04:00
// Delete this node including network interface information and events
2018-04-12 21:14:03 -04:00
obj . db . Remove ( obj . dbNodeKey ) ; // Remove node with that id
obj . db . Remove ( 'if' + obj . dbNodeKey ) ; // Remove interface information
obj . db . Remove ( 'nt' + obj . dbNodeKey ) ; // Remove notes
obj . db . RemoveNode ( obj . dbNodeKey ) ; // Remove all entries with node:id
2018-04-12 14:15:01 -04:00
// Event node deletion
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'removenode' , nodeid : obj . dbNodeKey , domain : obj . domain . id , nolog : 1 } )
// Disconnect all connections if needed
var state = obj . parent . parent . GetConnectivityState ( obj . dbNodeKey ) ;
if ( ( state != null ) && ( state . connectivity != null ) ) {
if ( ( state . connectivity & 1 ) != 0 ) { obj . parent . wsagents [ obj . dbNodeKey ] . close ( ) ; } // Disconnect mesh agent
if ( ( state . connectivity & 2 ) != 0 ) { obj . parent . parent . mpsserver . close ( obj . parent . parent . mpsserver . ciraConnections [ obj . dbNodeKey ] ) ; } // Disconnect CIRA connection
}
}
2017-08-28 12:27:45 -04:00
}
// When data is received from the mesh agent web socket
ws . on ( 'message' , function ( msg ) {
if ( msg . length < 2 ) return ;
2017-10-15 02:22:19 -04: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 12:27:45 -04:00
if ( obj . authenticated == 2 ) { // We are authenticated
2018-07-02 17:34:10 -04: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 12:27:45 -04:00
if ( msg . length < 2 ) return ;
var cmdid = obj . common . ReadShort ( msg , 0 ) ;
if ( cmdid == 11 ) { // MeshCommand_CoreModuleHash
if ( msg . length == 4 ) { ChangeAgentCoreInfo ( { caps : 0 } ) ; } // If the agent indicated that no core is running, clear the core information string.
// Mesh core hash, sent by agent with the hash of the current mesh core.
if ( obj . agentCoreCheck == 1000 ) return ; // If we are using a custom core, don't try to update it.
// We need to check if the core is current.
// TODO: Check if we have a mesh specific core. If so, use that.
var agentMeshCoreHash = null ;
2017-10-15 02:22:19 -04:00
if ( msg . length == 52 ) { agentMeshCoreHash = msg . substring ( 4 , 52 ) ; }
2017-12-12 19:04:54 -05:00
if ( ( agentMeshCoreHash != obj . parent . parent . defaultMeshCoreHash ) && ( agentMeshCoreHash != obj . parent . parent . defaultMeshCoreNoMeiHash ) ) {
2017-08-28 12:27:45 -04:00
if ( obj . agentCoreCheck < 5 ) { // This check is in place to avoid a looping core update.
if ( obj . parent . parent . defaultMeshCoreHash == null ) {
// Update no core
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 10, ask mesh agent to clear the core
} else {
// Update new core
2018-01-02 19:52:49 -05:00
if ( obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . amt == true ) {
2017-12-12 19:04:54 -05:00
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) + obj . parent . parent . defaultMeshCoreHash + obj . parent . parent . defaultMeshCore ) ; // Command 10, ask mesh agent to set the core (with MEI support)
} else {
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) + obj . parent . parent . defaultMeshCoreNoMeiHash + obj . parent . parent . defaultMeshCoreNoMei ) ; // Command 10, ask mesh agent to set the core (No MEI)
}
2017-08-28 12:27:45 -04:00
}
obj . agentCoreCheck ++ ;
}
} else {
obj . agentCoreCheck = 0 ;
}
}
else if ( cmdid == 12 ) { // MeshCommand_AgentHash
2017-10-15 02:22:19 -04:00
if ( ( msg . length == 52 ) && ( obj . agentExeInfo != null ) && ( obj . agentExeInfo . update == true ) ) {
2017-08-28 12:27:45 -04:00
var agenthash = obj . common . rstr2hex ( msg . substring ( 4 ) ) . toLowerCase ( ) ;
2017-10-15 02:22:19 -04:00
if ( agenthash != obj . agentExeInfo . hash ) {
2017-08-28 12:27:45 -04:00
// Mesh agent update required
2018-07-27 18:50:12 -04:00
if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Agent update required, NodeID=0x' + obj . nodeid . substring ( 0 , 16 ) + ', ' + obj . agentExeInfo . desc ) ; }
2017-10-15 02:22:19 -04:00
obj . fs . open ( obj . agentExeInfo . path , 'r' , function ( err , fd ) {
2017-08-28 12:27:45 -04:00
if ( err ) { return console . error ( err ) ; }
obj . agentUpdate = { oldHash : agenthash , ptr : 0 , buf : new Buffer ( agentUpdateBlockSize + 4 ) , fd : fd } ;
2017-10-15 02:22:19 -04:00
// We got the agent file open on the server side, tell the agent we are sending an update starting with the SHA384 hash of the result
2017-08-28 12:27:45 -04:00
//console.log("Agent update file open.");
obj . send ( obj . common . ShortToStr ( 13 ) + obj . common . ShortToStr ( 0 ) ) ; // Command 13, start mesh agent download
// 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 ;
var len = - 1 ;
try { len = obj . fs . readSync ( obj . agentUpdate . fd , obj . agentUpdate . buf , 4 , agentUpdateBlockSize , obj . agentUpdate . ptr ) ; } catch ( e ) { }
if ( len == - 1 ) {
// Error reading the agent file, stop here.
obj . fs . close ( obj . agentUpdate . fd ) ;
obj . agentUpdate = null ;
} else {
// Send the first block to the agent
obj . agentUpdate . ptr += len ;
//console.log("Agent update send first block: " + len);
obj . send ( obj . agentUpdate . buf ) ; // Command 14, mesh agent first data block
}
} ) ;
}
}
}
else if ( cmdid == 14 ) { // MeshCommand_AgentBinaryBlock
if ( ( msg . length == 4 ) && ( obj . agentUpdate != null ) ) {
var status = obj . common . ReadShort ( msg , 2 ) ;
if ( status == 1 ) {
var len = - 1 ;
try { len = obj . fs . readSync ( obj . agentUpdate . fd , obj . agentUpdate . buf , 4 , agentUpdateBlockSize , obj . agentUpdate . ptr ) ; } catch ( e ) { }
if ( len == - 1 ) {
// Error reading the agent file, stop here.
obj . fs . close ( obj . agentUpdate . fd ) ;
obj . agentUpdate = null ;
} else {
// Send the next block to the agent
obj . agentUpdate . ptr += len ;
2017-10-25 12:58:14 -04:00
//console.log("Agent update send next block", obj.agentUpdate.ptr, len);
2017-08-28 12:27:45 -04:00
if ( len == agentUpdateBlockSize ) { obj . ws . send ( obj . agentUpdate . buf ) ; } else { obj . ws . send ( obj . agentUpdate . buf . slice ( 0 , len + 4 ) ) ; } // Command 14, mesh agent next data block
if ( len < agentUpdateBlockSize ) {
2017-10-31 19:19:58 -04:00
//console.log("Agent update sent");
2017-10-25 12:58:14 -04:00
obj . send ( obj . common . ShortToStr ( 13 ) + obj . common . ShortToStr ( 0 ) + obj . common . hex2rstr ( obj . agentExeInfo . hash ) ) ; // Command 13, end mesh agent download, send agent SHA384 hash
2017-08-28 12:27:45 -04:00
obj . fs . close ( obj . agentUpdate . fd ) ;
obj . agentUpdate = null ;
}
}
}
}
}
else if ( cmdid == 15 ) { // MeshCommand_AgentTag
2018-01-23 17:15:59 -05: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 12:27:45 -04:00
}
2018-01-23 17:15:59 -05:00
} else if ( obj . authenticated < 2 ) { // We are not authenticated
2017-08-28 12:27:45 -04:00
var cmd = obj . common . ReadShort ( msg , 0 ) ;
if ( cmd == 1 ) {
// Agent authentication request
2017-10-15 02:22:19 -04:00
if ( ( msg . length != 98 ) || ( ( obj . receivedCommands & 1 ) != 0 ) ) return ;
2017-08-28 12:27:45 -04:00
obj . receivedCommands += 1 ; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
2017-10-18 19:28:05 -04:00
// Check that the server hash matches our own web certificate hash (SHA386)
2018-01-02 19:52:49 -05:00
if ( getWebCertHash ( obj . domain ) != msg . substring ( 2 , 50 ) ) { console . log ( 'Agent connected with bad web certificate hash, holding connection (' + obj . remoteaddr + ').' ) ; return ; }
2017-08-28 12:27:45 -04:00
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
2018-01-09 23:13:41 -05:00
obj . agentnonce = msg . substring ( 50 ) ;
2017-11-07 20:05:22 -05:00
if ( obj . useSwarmCert == true ) {
2018-01-09 23:13:41 -05:00
// Perform the hash signature using older swarm server certificate
obj . parent . parent . certificateOperations . acceleratorPerformSignature ( 1 , msg . substring ( 2 ) + obj . nonce , function ( signature ) {
// Send back our certificate + signature
obj . send ( obj . common . ShortToStr ( 2 ) + obj . common . ShortToStr ( obj . parent . swarmCertificateAsn1 . length ) + obj . parent . swarmCertificateAsn1 + signature ) ; // Command 2, certificate + signature
} ) ;
2017-11-07 20:05:22 -05:00
} else {
2018-01-12 14:41:26 -05:00
// Perform the hash signature using the server agent certificate
2018-01-09 23:13:41 -05:00
obj . parent . parent . certificateOperations . acceleratorPerformSignature ( 0 , msg . substring ( 2 ) + obj . nonce , function ( signature ) {
// Send back our certificate + signature
obj . send ( obj . common . ShortToStr ( 2 ) + obj . common . ShortToStr ( obj . parent . agentCertificateAsn1 . length ) + obj . parent . agentCertificateAsn1 + signature ) ; // Command 2, certificate + signature
} ) ;
2017-11-07 20:05:22 -05:00
}
2017-08-28 12:27:45 -04:00
// Check the agent signature if we can
2017-09-15 14:45:06 -04:00
if ( obj . unauthsign != null ) {
2017-11-07 20:05:22 -05:00
if ( processAgentSignature ( obj . unauthsign ) == false ) { console . log ( 'Agent connected with bad signature, holding connection (' + obj . remoteaddr + ').' ) ; return ; } else { completeAgentConnection ( ) ; }
2017-08-28 12:27:45 -04: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
var certlen = obj . common . ReadShort ( msg , 2 ) ;
obj . unauth = { } ;
2018-01-12 14:41:26 -05:00
try { obj . unauth . nodeid = new Buffer ( obj . forge . pki . getPublicKeyFingerprint ( obj . forge . pki . certificateFromAsn1 ( obj . forge . asn1 . fromDer ( msg . substring ( 4 , 4 + certlen ) ) ) . publicKey , { md : obj . forge . md . sha384 . create ( ) } ) . data , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ; } catch ( e ) { return ; }
obj . unauth . nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + new Buffer ( msg . substring ( 4 , 4 + certlen ) , 'binary' ) . toString ( 'base64' ) + '\r\n-----END CERTIFICATE-----' ;
2017-08-28 12:27:45 -04:00
// Check the agent signature if we can
2017-10-18 19:28:05 -04:00
if ( obj . agentnonce == null ) { obj . unauthsign = msg . substring ( 4 + certlen ) ; } else { if ( processAgentSignature ( msg . substring ( 4 + certlen ) ) == false ) { console . log ( 'Agent connected with bad signature, holding connection (' + obj . remoteaddr + ').' ) ; return ; } }
2017-08-28 12:27:45 -04:00
completeAgentConnection ( ) ;
}
else if ( cmd == 3 ) {
// Agent meshid
2017-10-15 02:22:19 -04:00
if ( ( msg . length < 72 ) || ( ( obj . receivedCommands & 4 ) != 0 ) ) return ;
2017-08-28 12:27:45 -04: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 = { } ;
obj . agentInfo . infoVersion = obj . common . ReadInt ( msg , 2 ) ;
obj . agentInfo . agentId = obj . common . ReadInt ( msg , 6 ) ;
obj . agentInfo . agentVersion = obj . common . ReadInt ( msg , 10 ) ;
obj . agentInfo . platformType = obj . common . ReadInt ( msg , 14 ) ;
2017-10-16 23:11:03 -04:00
if ( obj . agentInfo . platformType > 6 || obj . agentInfo . platformType < 1 ) { obj . agentInfo . platformType = 1 ; }
2017-11-07 20:05:22 -05:00
if ( msg . substring ( 50 , 66 ) == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' ) {
obj . meshid = new Buffer ( msg . substring ( 18 , 50 ) , 'binary' ) . toString ( 'hex' ) ; // Older HEX MeshID
} else {
obj . meshid = new Buffer ( msg . substring ( 18 , 66 ) , 'binary' ) . toString ( 'base64' ) . replace ( /\+/g , '@' ) . replace ( /\//g , '$' ) ; // New Base64 MeshID
}
//console.log('MeshID', obj.meshid);
2017-10-15 02:22:19 -04:00
obj . agentInfo . capabilities = obj . common . ReadInt ( msg , 66 ) ;
var computerNameLen = obj . common . ReadShort ( msg , 70 ) ;
obj . agentInfo . computerName = msg . substring ( 72 , 72 + computerNameLen ) ;
2017-08-28 12:27:45 -04:00
obj . dbMeshKey = 'mesh/' + obj . domain . id + '/' + obj . meshid ;
completeAgentConnection ( ) ;
2017-11-07 20:05:22 -05:00
} else if ( cmd == 5 ) {
// ServerID. Agent is telling us what serverid it expects. Useful if we have many server certificates.
if ( ( msg . substring ( 2 , 34 ) == obj . parent . swarmCertificateHash256 ) || ( msg . substring ( 2 , 50 ) == obj . parent . swarmCertificateHash384 ) ) { obj . useSwarmCert = true ; }
2017-08-28 12:27:45 -04:00
}
}
} ) ;
// If error, do nothing
2018-02-07 21:45:14 -05:00
ws . on ( 'error' , function ( err ) { console . log ( 'AGENT WSERR: ' + err ) ; } ) ;
2017-08-28 12:27:45 -04:00
// If the mesh agent web socket is closed, clean up.
2018-07-27 18:50:12 -04:00
ws . on ( 'close' , function ( req ) { if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Agent disconnect ' + obj . nodeid + ' (' + obj . remoteaddr + ')' ) ; } obj . close ( 0 ) ; } ) ;
// obj.ws._socket._parent.on('close', function (req) { if (obj.nodeid != null) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); } });
2017-08-28 12:27:45 -04:00
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
2017-10-15 02:22:19 -04:00
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
2018-01-12 14:41:26 -05:00
obj . nonce = obj . parent . crypto . randomBytes ( 48 ) . toString ( 'binary' ) ;
2018-01-02 19:52:49 -05:00
obj . send ( obj . common . ShortToStr ( 1 ) + getWebCertHash ( obj . domain ) + obj . nonce ) ; // Command 1, hash + nonce
2017-08-28 12:27:45 -04:00
// Once we get all the information about an agent, run this to hook everything up to the server
function completeAgentConnection ( ) {
if ( obj . authenticated = ! 1 || obj . meshid == null ) return ;
// Check that the mesh exists
obj . db . Get ( obj . dbMeshKey , function ( err , meshes ) {
2018-01-02 19:52:49 -05:00
if ( meshes . length == 0 ) { console . log ( 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddr + ', ' + obj . dbMeshKey + ').' ) ; return ; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
2017-08-28 12:27:45 -04:00
var mesh = meshes [ 0 ] ;
2017-09-01 14:23:22 -04:00
if ( mesh . mtype != 2 ) { console . log ( 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddr + ').' ) ; return ; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
2017-08-28 12:27:45 -04:00
// Check that the node exists
obj . db . Get ( obj . dbNodeKey , function ( err , nodes ) {
2017-09-05 20:19:28 -04:00
var device ;
2017-08-28 12:27:45 -04:00
// Mark when we connected to this agent
obj . connectTime = Date . now ( ) ;
if ( nodes . length == 0 ) {
// This node does not exist, create it.
2018-05-21 19:36:51 -04:00
device = { type : 'node' , mtype : mesh . mtype , _id : obj . dbNodeKey , icon : obj . agentInfo . platformType , meshid : obj . dbMeshKey , name : obj . agentInfo . computerName , rname : obj . agentInfo . computerName , domain : domain . id , agent : { ver : obj . agentInfo . agentVersion , id : obj . agentInfo . agentId , caps : obj . agentInfo . capabilities } , host : null } ;
2017-08-28 12:27:45 -04:00
obj . db . Set ( device ) ;
// Event the new node
2018-04-12 21:14:03 -04:00
if ( obj . agentInfo . capabilities & 0x20 ) {
// This is a temporary agent, don't log.
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'addnode' , node : device , domain : domain . id , nolog : 1 } )
} else {
var change = 'Added device ' + obj . agentInfo . computerName + ' to mesh ' + mesh . name ;
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'addnode' , node : device , msg : change , domain : domain . id } )
}
2017-08-28 12:27:45 -04:00
} else {
// Device already exists, look if changes has occured
2017-09-05 20:19:28 -04:00
device = nodes [ 0 ] ;
2017-09-15 14:45:06 -04:00
if ( device . agent == null ) {
2017-08-28 12:27:45 -04:00
device . agent = { ver : obj . agentInfo . agentVersion , id : obj . agentInfo . agentId , caps : obj . agentInfo . capabilities } ; change = 1 ;
} else {
2018-01-02 19:52:49 -05:00
var changes = [ ] , change = 0 , log = 0 ;
2018-05-21 19:36:51 -04:00
if ( device . rname != obj . agentInfo . computerName ) { device . rname = obj . agentInfo . computerName ; change = 1 ; changes . push ( 'computer name' ) ; }
2017-08-28 12:27:45 -04:00
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
2018-01-02 19:52:49 -05:00
if ( device . meshid != obj . dbMeshKey ) { device . meshid = obj . dbMeshKey ; change = 1 ; log = 1 ; changes . push ( 'agent meshid' ) ; } // TODO: If the meshid changes, we need to event a device add/remove on both meshes
2017-08-28 12:27:45 -04:00
if ( change == 1 ) {
obj . db . Set ( device ) ;
2018-04-12 21:14:03 -04:00
// If this is a temporary device, don't log changes
if ( obj . agentInfo . capabilities & 0x20 ) { log = 0 ; }
2017-08-28 12:27:45 -04:00
// Event the node change
2018-01-02 19:52:49 -05:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id } ;
if ( log == 0 ) { event . nolog = 1 ; } else { event . msg = 'Changed device ' + device . name + ' from mesh ' + mesh . name + ': ' + changes . join ( ', ' ) ; }
2017-08-28 12:27:45 -04:00
var device2 = obj . common . Clone ( device ) ;
if ( device2 . intelamt && device2 . intelamt . pass ) delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
event . node = device ;
obj . parent . parent . DispatchEvent ( [ '*' , device . meshid ] , obj , event ) ;
}
}
}
// Check if this agent is already connected
var dupAgent = obj . parent . wsagents [ obj . dbNodeKey ] ;
obj . parent . wsagents [ obj . dbNodeKey ] = obj ;
if ( dupAgent ) {
// Close the duplicate agent
2018-07-27 18:50:12 -04:00
if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Duplicate agent ' + obj . nodeid + ' (' + obj . remoteaddr + ')' ) ; }
2017-12-13 17:52:57 -05:00
dupAgent . close ( 3 ) ;
2017-08-28 12:27:45 -04:00
} else {
// Indicate the agent is connected
obj . parent . parent . SetConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , obj . connectTime , 1 , 1 ) ;
}
// We are done, ready to communicate with this agent
obj . authenticated = 2 ;
// Command 4, inform mesh agent that it's authenticated.
obj . send ( obj . common . ShortToStr ( 4 ) ) ;
// Check the mesh core, if the agent is capable of running one
if ( ( obj . agentInfo . capabilities & 16 ) != 0 ) { obj . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; } // Command 11, ask for mesh core hash.
// Check if we need to make an native update check
2017-10-15 02:22:19 -04:00
obj . agentExeInfo = obj . parent . parent . meshAgentBinaries [ obj . agentInfo . agentId ] ;
if ( ( obj . agentExeInfo != null ) && ( obj . agentExeInfo . update == true ) ) { obj . send ( obj . common . ShortToStr ( 12 ) + obj . common . ShortToStr ( 0 ) ) ; } // Ask the agent for it's executable binary hash
2017-09-05 20:19:28 -04:00
2017-09-06 13:08:01 -04:00
// Check if we already have IP location information for this node
obj . db . Get ( 'iploc_' + obj . remoteaddr , function ( err , iplocs ) {
if ( iplocs . length == 1 ) {
// We have a location in the database for this remote IP
var iploc = nodes [ 0 ] , x = { } ;
2017-12-12 19:04:54 -05: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 ) ;
}
2017-09-05 20:19:28 -04:00
} else {
2017-09-06 13:08:01 -04:00
// Check if we need to ask for the IP location
var doIpLocation = 0 ;
if ( device . iploc == null ) {
doIpLocation = 1 ;
} else {
var loc = device . iploc . split ( ',' ) ;
if ( loc . length < 3 ) {
doIpLocation = 2 ;
} else {
var t = new Date ( ( parseFloat ( loc [ 2 ] ) * 1000 ) ) , now = Date . now ( ) ;
t . setDate ( t . getDate ( ) + 20 ) ;
if ( t < now ) { doIpLocation = 3 ; }
}
}
2017-09-05 20:19:28 -04:00
2017-09-06 13:08:01 -04:00
// If we need to ask for IP location, see if we have the quota to do it.
if ( doIpLocation > 0 ) {
obj . db . getValueOfTheDay ( 'ipLocationRequestLimitor' , 10 , function ( ipLocationLimitor ) {
if ( ipLocationLimitor . value > 0 ) {
ipLocationLimitor . value -- ;
obj . db . Set ( ipLocationLimitor ) ;
obj . send ( JSON . stringify ( { action : 'iplocation' } ) ) ;
}
} ) ;
2017-09-05 20:19:28 -04:00
}
2017-09-06 13:08:01 -04:00
}
} ) ;
2017-08-28 12:27:45 -04:00
} ) ;
} ) ;
}
2018-01-02 19:52:49 -05:00
// Get the web certificate hash for the speficied domain
function getWebCertHash ( domain ) {
2018-01-12 14:41:26 -05:00
var hash = obj . parent . webCertificateHashs [ domain . id ] ;
if ( hash != null ) return hash ;
2018-01-02 19:52:49 -05:00
return obj . parent . webCertificateHash ;
}
2017-08-28 12:27:45 -04:00
// Verify the agent signature
function processAgentSignature ( msg ) {
2018-01-12 14:41:26 -05:00
// Verify the signature. This is the fast way, without using forge.
const verify = obj . parent . crypto . createVerify ( 'SHA384' ) ;
verify . end ( new Buffer ( getWebCertHash ( obj . domain ) + obj . nonce + obj . agentnonce , 'binary' ) ) ;
if ( verify . verify ( obj . unauth . nodeCertPem , new Buffer ( msg , 'binary' ) ) !== true ) { return false ; }
2017-08-28 12:27:45 -04:00
// Connection is a success, clean up
2017-10-15 20:36:06 -04:00
obj . nodeid = obj . unauth . nodeid ;
2017-08-28 12:27:45 -04:00
obj . dbNodeKey = 'node/' + domain . id + '/' + obj . nodeid ;
delete obj . nonce ;
delete obj . agentnonce ;
delete obj . unauth ;
if ( obj . unauthsign ) delete obj . unauthsign ;
2017-09-01 14:23:22 -04:00
obj . parent . parent . debug ( 1 , 'Verified agent connection to ' + obj . nodeid + ' (' + obj . remoteaddr + ').' ) ;
2017-08-28 12:27:45 -04:00
obj . authenticated = 1 ;
return true ;
}
// Process incoming agent JSON data
function processAgentData ( msg ) {
var str = msg . toString ( 'utf8' ) ;
if ( str [ 0 ] == '{' ) {
2018-07-06 13:13:19 -04:00
try { command = JSON . parse ( str ) } catch ( e ) { console . log ( 'Unable to parse agent JSON (' + obj . remoteaddr + '): ' + str ) ; return ; } // If the command can't be parsed, ignore it.
2017-08-28 12:27:45 -04:00
switch ( command . action ) {
case 'msg' :
{
// Route a message.
// If this command has a sessionid, that is the target.
2017-09-15 14:45:06 -04:00
if ( command . sessionid != null ) {
2017-10-23 17:09:58 -04:00
if ( typeof command . sessionid != 'string' ) break ;
2017-08-28 12:27:45 -04:00
var splitsessionid = command . sessionid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splitsessionid [ 0 ] == 'user' ) && ( splitsessionid [ 1 ] == domain . id ) ) {
// Check if this user has rights to get this message
2017-09-15 14:45:06 -04:00
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2017-08-28 12:27:45 -04:00
2017-09-15 14:45:06 -04:00
// See if the session is connected. If so, go ahead and send this message to the target node
2017-08-28 12:27:45 -04:00
var ws = obj . parent . wssessions2 [ command . sessionid ] ;
2017-09-15 14:45:06 -04:00
if ( ws != null ) {
2017-08-28 12:27:45 -04:00
command . nodeid = obj . dbNodeKey ; // Set the nodeid, required for responses.
delete command . sessionid ; // Remove the sessionid, since we are sending to that sessionid, so it's implyed.
ws . send ( JSON . stringify ( command ) ) ;
2017-09-15 14:45:06 -04:00
} else if ( obj . parent . parent . multiServer != null ) {
2017-09-20 17:44:22 -04:00
// See if we can send this to a peer server
var serverid = obj . parent . wsPeerSessions2 [ command . sessionid ] ;
if ( serverid != null ) {
command . fromNodeid = obj . dbNodeKey ;
obj . parent . parent . multiServer . DispatchMessageSingleServer ( command , serverid ) ;
}
2017-08-28 12:27:45 -04:00
}
}
2017-09-15 14:45:06 -04:00
} else if ( command . userid != null ) { // If this command has a userid, that is the target.
2017-10-23 17:09:58 -04:00
if ( typeof command . userid != 'string' ) break ;
2017-08-28 12:27:45 -04:00
var splituserid = command . userid . split ( '/' ) ;
// Check that we are in the same domain and the user has rights over this node.
if ( ( splituserid [ 0 ] == 'user' ) && ( splituserid [ 1 ] == domain . id ) ) {
// Check if this user has rights to get this message
2017-09-15 14:45:06 -04:00
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2017-08-28 12:27:45 -04:00
// See if the session is connected
var sessions = obj . parent . wssessions [ command . userid ] ;
// Go ahead and send this message to the target node
2017-09-15 14:45:06 -04:00
if ( sessions != null ) {
2017-08-28 12:27:45 -04:00
command . nodeid = obj . dbNodeKey ; // Set the nodeid, required for responses.
delete command . userid ; // Remove the userid, since we are sending to that userid, so it's implyed.
for ( var i in sessions ) { sessions [ i ] . send ( JSON . stringify ( command ) ) ; }
}
2018-04-12 14:15:01 -04:00
if ( obj . parent . parent . multiServer != null ) {
// TODO: Add multi-server support
}
2017-08-28 12:27:45 -04:00
}
} else { // Route this command to the mesh
2017-09-15 14:45:06 -04:00
command . nodeid = obj . dbNodeKey ;
var cmdstr = JSON . stringify ( command ) ;
2017-08-28 12:27:45 -04:00
for ( var userid in obj . parent . wssessions ) { // Find all connected users for this mesh and send the message
var user = obj . parent . users [ userid ] ;
2018-06-05 16:28:07 -04:00
if ( ( user != null ) && ( user . links != null ) ) {
2017-08-28 12:27:45 -04:00
var rights = user . links [ obj . dbMeshKey ] ;
2017-09-15 14:45:06 -04:00
if ( rights != null ) { // TODO: Look at what rights are needed for message routing
2017-08-28 12:27:45 -04:00
var sessions = obj . parent . wssessions [ userid ] ;
2017-09-15 14:45:06 -04:00
// Send the message to all users on this server
2018-02-05 14:56:29 -05:00
for ( var i in sessions ) { try { sessions [ i ] . send ( cmdstr ) ; } catch ( e ) { } }
2017-08-28 12:27:45 -04:00
}
}
}
2017-09-15 14:45:06 -04:00
// Send the message to all users of other servers
if ( obj . parent . parent . multiServer != null ) {
delete command . nodeid ;
command . fromNodeid = obj . dbNodeKey ;
command . meshid = obj . dbMeshKey ;
obj . parent . parent . multiServer . DispatchMessage ( command ) ;
}
2017-08-28 12:27:45 -04:00
}
break ;
}
case 'coreinfo' :
{
// Sent by the agent to update agent information
ChangeAgentCoreInfo ( command ) ;
break ;
}
case 'netinfo' :
{
// Sent by the agent to update agent network interface information
delete command . action ;
command . updateTime = Date . now ( ) ;
command . _id = 'if' + obj . dbNodeKey ;
command . type = 'ifinfo' ;
obj . db . Set ( command ) ;
// Event the node interface information change
obj . parent . parent . DispatchEvent ( [ '*' , obj . meshid ] , obj , { action : 'ifchange' , nodeid : obj . dbNodeKey , domain : domain . id , nolog : 1 } ) ;
2017-08-28 15:48:53 -04:00
break ;
}
2017-09-05 20:19:28 -04:00
case 'iplocation' :
2017-08-28 15:48:53 -04:00
{
// Sent by the agent to update location information
2017-08-30 16:52:56 -04:00
if ( ( command . type == 'publicip' ) && ( command . value != null ) && ( typeof command . value == 'object' ) && ( command . value . ip ) && ( command . value . loc ) ) {
2017-08-28 18:06:29 -04:00
var x = { } ;
x . publicip = command . value . ip ;
2017-09-05 20:19:28 -04:00
x . iploc = command . value . loc + ',' + ( Math . floor ( Date . now ( ) / 1000 ) ) ;
2017-08-28 18:06:29 -04:00
ChangeAgentLocationInfo ( x ) ;
2017-09-06 13:08:01 -04:00
command . value . _id = 'iploc_' + command . value . ip ;
command . value . type = 'iploc' ;
command . value . date = Date . now ( ) ;
obj . db . Set ( command . value ) ; // Store the IP to location data in the database
// 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-28 18:06:29 -04:00
}
2017-08-28 12:27:45 -04:00
break ;
}
2017-11-07 20:05:22 -05:00
case 'mc1migration' :
{
if ( command . oldnodeid . length != 64 ) break ;
var oldNodeKey = 'node//' + command . oldnodeid . toLowerCase ( ) ;
obj . db . Get ( oldNodeKey , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var node = nodes [ 0 ] ;
if ( node . meshid == obj . dbMeshKey ) {
// Update the device name & host
2017-11-09 20:18:30 -05:00
var newNode = { name : node . name } ;
if ( node . intelamt != null ) { newNode . intelamt = node . intelamt ; }
ChangeAgentCoreInfo ( newNode ) ;
2017-11-07 20:05:22 -05:00
// Delete this node including network interface information and events
obj . db . Remove ( node . _id ) ;
obj . db . Remove ( 'if' + node . _id ) ;
// Event node deletion
var change = 'Migrated device ' + node . name ;
obj . parent . parent . DispatchEvent ( [ '*' , node . meshid ] , obj , { etype : 'node' , action : 'removenode' , nodeid : node . _id , msg : change , domain : node . domain } )
}
} ) ;
break ;
}
2017-08-28 12:27:45 -04:00
}
}
}
// Change the current core information string and event it
function ChangeAgentCoreInfo ( command ) {
2017-09-15 14:45:06 -04:00
if ( ( command == null ) || ( command == null ) ) return ; // Safety, should never happen.
2017-08-28 12:27:45 -04:00
// Check capabilities value
2017-09-15 14:45:06 -04:00
if ( command . caps == null || command . caps == null ) { command . caps = 0 ; } else { if ( typeof command . caps != 'number' ) command . caps = 0 ; }
2017-08-28 12:27:45 -04:00
// Check that the mesh exists
obj . db . Get ( obj . dbMeshKey , function ( err , meshes ) {
if ( meshes . length != 1 ) return ;
var mesh = meshes [ 0 ] ;
// Get the node and change it if needed
obj . db . Get ( obj . dbNodeKey , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var device = nodes [ 0 ] ;
if ( device . agent ) {
var changes = [ ] , change = 0 ;
// Check if anything changes
2017-11-07 20:05:22 -05:00
if ( command . name && ( command . name != device . name ) ) { change = 1 ; device . name = command . name ; changes . push ( 'name' ) ; }
2017-09-15 14:45:06 -04:00
if ( 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 ; changes . push ( 'agent core' ) ; }
2017-08-28 12:27:45 -04:00
if ( ( device . agent . caps & 0xFFFFFFE7 ) != ( command . caps & 0xFFFFFFE7 ) ) { device . agent . caps = ( ( device . agent . caps & 24 ) + ( command . caps & 0xFFFFFFE7 ) ) ; change = 1 ; changes . push ( 'agent capabilities' ) ; } // Allow Javascript on the agent to change all capabilities except console and javascript support
if ( command . intelamt ) {
if ( ! device . intelamt ) { device . intelamt = { } ; }
2018-05-01 16:44:26 -04:00
if ( ( command . intelamt . ver != null ) && ( device . intelamt . ver != command . intelamt . ver ) ) { device . intelamt . ver = command . intelamt . ver ; change = 1 ; changes . push ( 'AMT version' ) ; }
if ( ( command . intelamt . state != null ) && ( device . intelamt . state != command . intelamt . state ) ) { device . intelamt . state = command . intelamt . state ; change = 1 ; changes . push ( 'AMT state' ) ; }
if ( ( command . intelamt . flags != null ) && ( device . intelamt . flags != command . intelamt . flags ) ) { device . intelamt . flags = command . intelamt . flags ; change = 1 ; changes . push ( 'AMT flags' ) ; }
if ( ( command . intelamt . host != null ) && ( device . intelamt . host != command . intelamt . host ) ) { device . intelamt . host = command . intelamt . host ; change = 1 ; changes . push ( 'AMT host' ) ; }
if ( ( command . intelamt . uuid != null ) && ( device . intelamt . uuid != command . intelamt . uuid ) ) { device . intelamt . uuid = command . intelamt . uuid ; change = 1 ; changes . push ( 'AMT uuid' ) ; }
2017-08-28 12:27:45 -04:00
}
if ( mesh . mtype == 2 ) {
2017-09-01 14:23:22 -04:00
if ( device . host != obj . remoteaddr ) { device . host = obj . remoteaddr ; change = 1 ; changes . push ( 'host' ) ; }
2017-08-28 12:27:45 -04: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.
}
// If there are changes, save and event
if ( change == 1 ) {
obj . db . Set ( device ) ;
// Event the node change
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , msg : 'Changed device ' + device . name + ' from mesh ' + mesh . name + ': ' + changes . join ( ', ' ) } ;
2018-04-12 21:14:03 -04:00
if ( obj . agentInfo . capabilities & 0x20 ) { event . nolog = 1 ; } // If this is a temporary device, don't log changes
2017-08-28 12:27:45 -04:00
var device2 = obj . common . Clone ( device ) ;
if ( device2 . intelamt && device2 . intelamt . pass ) delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
event . node = device ;
obj . parent . parent . DispatchEvent ( [ '*' , device . meshid ] , obj , event ) ;
}
}
} ) ;
} ) ;
}
2017-08-28 18:06:29 -04:00
// Change the current core information string and event it
function ChangeAgentLocationInfo ( command ) {
2017-09-15 14:45:06 -04:00
if ( ( command == null ) || ( command == null ) ) return ; // Safety, should never happen.
2017-08-28 18:06:29 -04:00
// Check that the mesh exists
obj . db . Get ( obj . dbMeshKey , function ( err , meshes ) {
if ( meshes . length != 1 ) return ;
var mesh = meshes [ 0 ] ;
// Get the node and change it if needed
obj . db . Get ( obj . dbNodeKey , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var device = nodes [ 0 ] ;
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 ) {
obj . db . Set ( device ) ;
// Event the node change
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , msg : 'Changed device ' + device . name + ' from mesh ' + mesh . name + ': ' + changes . join ( ', ' ) } ;
2018-04-12 21:14:03 -04:00
if ( obj . agentInfo . capabilities & 0x20 ) { event . nolog = 1 ; } // If this is a temporary device, don't log changes
2017-08-28 18:06:29 -04:00
var device2 = obj . common . Clone ( device ) ;
if ( device2 . intelamt && device2 . intelamt . pass ) delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
event . node = device ;
obj . parent . parent . DispatchEvent ( [ '*' , device . meshid ] , obj , event ) ;
}
}
} ) ;
} ) ;
}
2017-08-28 12:27:45 -04:00
// Update the mesh agent tab in the database
function ChangeAgentTag ( tag ) {
2017-09-15 14:45:06 -04:00
if ( tag . length == 0 ) { tag = null ; }
2017-08-28 12:27:45 -04:00
// Get the node and change it if needed
obj . db . Get ( obj . dbNodeKey , function ( err , nodes ) {
if ( nodes . length != 1 ) return ;
var device = nodes [ 0 ] ;
if ( device . agent ) {
if ( device . agent . tag != tag ) {
device . agent . tag = tag ;
obj . db . Set ( device ) ;
// Event the node change
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , nolog : 1 } ;
var device2 = obj . common . Clone ( device ) ;
if ( device2 . intelamt && device2 . intelamt . pass ) delete device2 . intelamt . pass ; // Remove the Intel AMT password before eventing this.
event . node = device ;
obj . parent . parent . DispatchEvent ( [ '*' , device . meshid ] , obj , event ) ;
}
}
} ) ;
}
return obj ;
}