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
2019-01-04 03:22:15 +03:00
* @ copyright Intel Corporation 2018 - 2019
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
2018-08-21 21:02:35 +03:00
var AgentConnectCount = 0 ;
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 ) {
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 ;
2018-08-30 03:40:30 +03:00
obj . agentInfo = null ;
2017-08-28 19:27:45 +03:00
obj . agentUpdate = null ;
2018-05-03 21:09:29 +03:00
const agentUpdateBlockSize = 65520 ;
2018-11-02 01:01:21 +03:00
obj . remoteaddr = req . ip ;
2017-09-01 21:23:22 +03:00
if ( obj . remoteaddr . startsWith ( '::ffff:' ) ) { obj . remoteaddr = obj . remoteaddr . substring ( 7 ) ; }
2018-11-02 01:01:21 +03:00
obj . remoteaddrport = obj . remoteaddr + ':' + obj . ws . _socket . remotePort ;
obj . agentConnectCount = ++ AgentConnectCount ;
2017-10-25 19:58:14 +03:00
ws . _socket . setKeepAlive ( true , 240000 ) ; // Set TCP keep alive, 4 minutes
2017-08-28 19:27:45 +03:00
// Send a message to the mesh agent
2019-01-29 02:47:54 +03:00
obj . send = function ( data , func ) { try { if ( typeof data == 'string' ) { obj . ws . send ( Buffer . from ( data , 'binary' ) , func ) ; } else { obj . ws . send ( data , func ) ; } } catch ( e ) { } } ;
2017-08-28 19:27:45 +03:00
// Disconnect this agent
obj . close = function ( arg ) {
2018-11-02 01:01:21 +03:00
if ( ( arg == 1 ) || ( arg == null ) ) { try { obj . ws . close ( ) ; if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Soft disconnect ' + obj . nodeid + ' (' + obj . remoteaddrport + ')' ) ; } } 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 . remoteaddrport + ')' ) ; } } catch ( e ) { console . log ( e ) ; } } // Hard close, close the TCP socket
2017-12-14 01:52:57 +03:00
if ( arg == 3 ) { obj . authenticated = - 1 ; } // Don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
2017-08-28 19:27:45 +03:00
if ( obj . parent . wsagents [ obj . dbNodeKey ] == obj ) {
delete obj . parent . wsagents [ obj . dbNodeKey ] ;
obj . parent . parent . ClearConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , 1 ) ;
}
2018-09-27 00:58:55 +03:00
// Get the current mesh
var mesh = obj . parent . meshes [ obj . dbMeshKey ] ;
2017-08-28 19:27:45 +03:00
// Other clean up may be needed here
if ( obj . unauth ) { delete obj . unauth ; }
2019-01-29 03:53:11 +03:00
if ( obj . agentUpdate != null ) {
obj . fs . close ( obj . agentUpdate . fd ) ;
obj . parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
obj . agentUpdate = null ;
}
2018-09-27 00:58:55 +03:00
if ( ( ( obj . agentInfo ) && ( obj . agentInfo . capabilities ) && ( obj . agentInfo . capabilities & 0x20 ) ) || ( ( mesh ) && ( mesh . flags ) && ( mesh . flags & 1 ) ) ) { // This is a temporary agent, remote it
2018-04-12 21:15:01 +03:00
// Delete this node including network interface information and events
2018-04-13 04:14:03 +03: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
2018-09-25 21:51:40 +03:00
obj . db . Remove ( 'lc' + obj . dbNodeKey ) ; // Remove last connect time
2018-09-28 02:17:05 +03:00
obj . db . Remove ( 'sm' + obj . dbNodeKey ) ; // Remove SMBios data
2018-04-13 04:14:03 +03:00
obj . db . RemoveNode ( obj . dbNodeKey ) ; // Remove all entries with node:id
2018-04-12 21:15:01 +03:00
// Event node deletion
2018-08-30 03:40:30 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'removenode' , nodeid : obj . dbNodeKey , domain : obj . domain . id , nolog : 1 } ) ;
2018-04-12 21:15:01 +03:00
// 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
}
2018-09-25 21:51:40 +03:00
} else {
// Update the last connect time
obj . db . Set ( { _id : 'lc' + obj . dbNodeKey , type : 'lastconnect' , domain : domain . id , time : obj . connectTime } ) ;
2018-04-12 21:15:01 +03:00
}
2018-08-21 21:02:35 +03:00
delete obj . nodeid ;
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 ) {
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 ;
var cmdid = obj . common . ReadShort ( msg , 0 ) ;
if ( cmdid == 11 ) { // MeshCommand_CoreModuleHash
2018-09-22 02:34:35 +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.
if ( obj . agentCoreCheck == 1000 ) return ; // If we are using a custom core, don't try to update it.
2018-12-30 02:24:33 +03:00
// Get the current meshcore hash
const agentMeshCoreHash = ( msg . length == 52 ) ? msg . substring ( 4 , 52 ) : null ;
// We need to check if the core is current. First, figure out what core we need.
2019-01-22 01:05:50 +03:00
var corename = obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core ;
if ( obj . agentCoreCheck == 1001 ) { corename = obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . rcore ; } // Use the recovery core.
2018-12-30 02:24:33 +03:00
if ( corename != null ) {
const meshcorehash = obj . parent . parent . defaultMeshCoresHash [ corename ] ;
if ( agentMeshCoreHash != meshcorehash ) {
2019-01-22 01:05:50 +03:00
if ( ( obj . agentCoreCheck < 5 ) || ( obj . agentCoreCheck == 1001 ) ) {
2018-12-30 02:24:33 +03:00
if ( meshcorehash == null ) {
2019-01-18 02:07:34 +03:00
// Clear the core
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreModule, ask mesh agent to clear the core
2018-12-30 02:24:33 +03:00
obj . parent . parent . debug ( 1 , 'Clearing core' ) ;
} else {
// Update new core
2019-01-29 02:47:54 +03:00
//obj.send(obj.common.ShortToStr(10) + obj.common.ShortToStr(0) + meshcorehash + obj.parent.parent.defaultMeshCores[corename]); // MeshCommand_CoreModule, start core update
//obj.parent.parent.debug(1, 'Updating code ' + corename);
// Update new core with task limiting so not to flood the server.
obj . parent . parent . taskLimiter . launch ( function ( argument , taskid , taskLimiterQueue ) {
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) + argument . hash + argument . core , function ( ) { obj . parent . parent . taskLimiter . completed ( taskid ) ; } ) ; // MeshCommand_CoreModule, start core update
obj . parent . parent . debug ( 1 , 'Updating code ' + argument . name ) ;
} , { hash : meshcorehash , core : obj . parent . parent . defaultMeshCores [ corename ] , name : corename } ) ;
2018-12-30 02:24:33 +03:00
}
obj . agentCoreCheck ++ ;
}
} else {
obj . agentCoreCheck = 0 ;
2019-01-18 02:07:34 +03:00
obj . send ( obj . common . ShortToStr ( 16 ) + obj . common . ShortToStr ( 0 ) ) ; // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
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 ) ; }
2017-12-13 03:04:54 +03:00
if ( ( agentMeshCoreHash != obj . parent . parent . defaultMeshCoreHash ) && ( agentMeshCoreHash != obj . 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.
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-03 03:52:49 +03:00
if ( obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . amt == true ) {
2017-12-13 03:04:54 +03: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 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 ) ) {
2017-08-28 19:27:45 +03:00
var agenthash = obj . common . rstr2hex ( msg . substring ( 4 ) ) . toLowerCase ( ) ;
2019-01-23 02:40:08 +03:00
if ( ( agenthash != obj . agentExeInfo . hash ) && ( agenthash != '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' ) ) {
2019-01-29 03:53:11 +03:00
// Mesh agent update required, do it using task limiter so not to flood the network.
obj . parent . parent . taskLimiter . launch ( function ( argument , taskid , taskLimiterQueue ) {
if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Agent update required, NodeID=0x' + obj . nodeid . substring ( 0 , 16 ) + ', ' + obj . agentExeInfo . desc ) ; }
obj . fs . open ( obj . agentExeInfo . path , 'r' , function ( err , fd ) {
if ( err ) { return console . error ( err ) ; }
obj . agentUpdate = { oldHash : agenthash , ptr : 0 , buf : Buffer . alloc ( agentUpdateBlockSize + 4 ) , fd : fd , taskid : taskid } ;
// MeshCommand_CoreModule, ask mesh agent to clear the core.
// The new core will only be sent after the agent updates.
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ;
// 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
//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 . parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
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
}
} ) ;
} , null ) ;
2019-01-22 01:05:50 +03:00
} else {
// Check the mesh core, if the agent is capable of running one
if ( ( ( obj . agentInfo . capabilities & 16 ) != 0 ) && ( obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core != null ) ) {
obj . send ( obj . common . ShortToStr ( 11 ) + obj . 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 ) ) {
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 ) ;
2019-01-29 03:53:11 +03:00
obj . parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2017-08-28 19:27:45 +03:00
obj . agentUpdate = null ;
} else {
// Send the next block to the agent
obj . agentUpdate . ptr += len ;
2017-10-25 19:58:14 +03:00
//console.log("Agent update send next block", obj.agentUpdate.ptr, len);
2018-09-25 03:47:03 +03:00
if ( len == agentUpdateBlockSize ) { obj . send ( obj . agentUpdate . buf ) ; } else { obj . send ( obj . agentUpdate . buf . slice ( 0 , len + 4 ) ) ; } // Command 14, mesh agent next data block
2017-08-28 19:27:45 +03:00
if ( len < agentUpdateBlockSize ) {
2017-11-01 02:19:58 +03:00
//console.log("Agent update sent");
2017-10-25 19:58:14 +03: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 19:27:45 +03:00
obj . fs . close ( obj . agentUpdate . fd ) ;
2019-01-29 03:53:11 +03:00
obj . parent . parent . taskLimiter . completed ( obj . agentUpdate . taskid ) ; // Indicate this task complete
2017-08-28 19:27:45 +03:00
obj . agentUpdate = null ;
}
}
}
}
}
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
2017-08-28 19:27:45 +03:00
var cmd = obj . common . ReadShort ( msg , 0 ) ;
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.
2018-12-15 23:34:55 +03:00
if ( obj . args . ignoreagenthashcheck === true ) {
// Send the agent web hash back to the agent
obj . send ( obj . common . ShortToStr ( 1 ) + msg . substring ( 2 , 50 ) + obj . nonce ) ; // Command 1, hash + nonce. Use the web hash given by the agent.
} else {
// Check that the server hash matches our own web certificate hash (SHA384)
2019-01-03 05:34:50 +03:00
if ( ( getWebCertHash ( obj . domain ) != msg . substring ( 2 , 50 ) ) && ( getWebCertFullHash ( obj . domain ) != msg . substring ( 2 , 50 ) ) ) { console . log ( 'Agent bad web cert hash (Agent:' + ( Buffer . from ( msg . substring ( 2 , 50 ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' != Server:' + ( Buffer . from ( getWebCertHash ( obj . domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + ' or ' + ( new Buffer ( getWebCertFullHash ( obj . domain ) , 'binary' ) . toString ( 'hex' ) . substring ( 0 , 10 ) ) + '), holding connection (' + obj . remoteaddrport + ').' ) ; return ; }
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
obj . parent . parent . certificateOperations . acceleratorPerformSignature ( 1 , msg . substring ( 2 ) + obj . nonce , obj , function ( obj2 , signature ) {
// Send back our certificate + signature
obj2 . send ( obj2 . common . ShortToStr ( 2 ) + obj2 . common . ShortToStr ( obj2 . parent . swarmCertificateAsn1 . length ) + obj2 . parent . swarmCertificateAsn1 + signature ) ; // Command 2, certificate + signature
} ) ;
} else {
// Perform the hash signature using the server agent certificate
obj . parent . parent . certificateOperations . acceleratorPerformSignature ( 0 , msg . substring ( 2 ) + obj . nonce , obj , function ( obj2 , signature ) {
// Send back our certificate + signature
2018-09-11 02:14:51 +03:00
obj2 . send ( obj2 . common . ShortToStr ( 2 ) + obj2 . common . ShortToStr ( obj2 . parent . agentCertificateAsn1 . length ) + obj2 . 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 ) {
2018-11-02 01:01:21 +03:00
if ( processAgentSignature ( obj . unauthsign ) == false ) { 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
var certlen = obj . common . ReadShort ( msg , 2 ) ;
obj . unauth = { } ;
2019-01-11 21:02:54 +03:00
try { obj . unauth . nodeid = Buffer . from ( 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 ( ex ) { console . log ( 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
2018-11-02 01:01:21 +03: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 . remoteaddrport + ').' ) ; return ; } }
2017-08-28 19:27:45 +03:00
completeAgentConnection ( ) ;
}
else if ( cmd == 3 ) {
// Agent meshid
2017-10-15 09:22:19 +03:00
if ( ( msg . length < 72 ) || ( ( 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 = { } ;
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-17 06:11:03 +03:00
if ( obj . agentInfo . platformType > 6 || 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);
2017-10-15 09:22:19 +03: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 19:27:45 +03:00
obj . dbMeshKey = 'mesh/' + obj . domain . id + '/' + obj . meshid ;
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.
if ( ( msg . substring ( 2 , 34 ) == obj . parent . swarmCertificateHash256 ) || ( msg . substring ( 2 , 50 ) == obj . parent . swarmCertificateHash384 ) ) { obj . useSwarmCert = true ; }
2017-08-28 19:27:45 +03:00
}
}
} ) ;
// If error, do nothing
2018-02-08 05:45:14 +03:00
ws . on ( 'error' , function ( err ) { console . log ( 'AGENT WSERR: ' + err ) ; } ) ;
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 ) {
if ( obj . nodeid != null ) {
//console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + obj.agentInfo.agentId);
obj . parent . parent . debug ( 1 , 'Agent disconnect ' + obj . nodeid + ' (' + obj . remoteaddrport + ') id=' + obj . agentInfo . agentId ) ;
2019-01-23 06:34:55 +03:00
// Log the agent disconnection
if ( obj . parent . wsagentsDisconnections [ obj . nodeid ] == null ) {
obj . parent . wsagentsDisconnections [ obj . nodeid ] = 1 ;
} else {
obj . parent . wsagentsDisconnections [ obj . nodeid ] = ++ obj . parent . wsagentsDisconnections [ obj . nodeid ] ;
}
2019-01-23 04:47:51 +03:00
}
obj . close ( 0 ) ;
} ) ;
2018-11-02 01:01:21 +03:00
// obj.ws._socket._parent.on('close', function (req) { if (obj.nodeid != null) { obj.parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } });
2017-08-28 19:27:45 +03:00
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
2017-10-15 09:22:19 +03:00
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
2018-01-12 22:41:26 +03:00
obj . nonce = obj . parent . crypto . randomBytes ( 48 ) . toString ( 'binary' ) ;
2018-12-15 23:34:55 +03:00
if ( obj . args . ignoreagenthashcheck !== true ) {
obj . send ( obj . common . ShortToStr ( 1 ) + getWebCertHash ( obj . domain ) + obj . nonce ) ; // Command 1, hash + nonce
}
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 ( ) {
2018-08-22 01:08:15 +03:00
if ( ( obj . authenticated != 1 ) || ( obj . meshid == null ) || obj . pendingCompleteAgentConnection ) return ;
2018-08-21 21:02:35 +03:00
obj . pendingCompleteAgentConnection = true ;
2017-08-28 19:27:45 +03:00
// Check that the mesh exists
2018-08-22 01:08:15 +03:00
var mesh = obj . parent . meshes [ obj . dbMeshKey ] ;
2018-11-02 01:01:21 +03:00
if ( mesh == null ) { console . log ( 'Agent connected with invalid domain/mesh, holding connection (' + obj . remoteaddrport + ', ' + 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.
if ( mesh . mtype != 2 ) { console . log ( 'Agent connected with invalid mesh type, holding connection (' + obj . remoteaddrport + ').' ) ; return ; } // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours.
2018-08-22 01:08:15 +03:00
// Check that the node exists
obj . db . Get ( obj . dbNodeKey , function ( err , nodes ) {
var device ;
// Mark when we connected to this agent
obj . connectTime = Date . now ( ) ;
2018-09-25 03:47:03 +03:00
obj . db . Set ( { _id : 'lc' + obj . dbNodeKey , type : 'lastconnect' , domain : domain . id , time : obj . connectTime } ) ;
// See if this node exists in the database
2018-08-22 01:08:15 +03:00
if ( nodes . length == 0 ) {
// This node does not exist, create it.
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 } ;
obj . db . Set ( device ) ;
// Event the new node
if ( obj . agentInfo . capabilities & 0x20 ) {
// This is a temporary agent, don't log.
2018-08-30 03:40:30 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'addnode' , node : device , domain : domain . id , nolog : 1 } ) ;
2017-08-28 19:27:45 +03:00
} else {
2018-08-30 03:40:30 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , obj . dbMeshKey ] , obj , { etype : 'node' , action : 'addnode' , node : device , msg : ( 'Added device ' + obj . agentInfo . computerName + ' to mesh ' + mesh . name ) , domain : domain . id } ) ;
2017-08-28 19:27:45 +03:00
}
2018-08-22 01:08:15 +03:00
} else {
// Device already exists, look if changes has occured
device = nodes [ 0 ] ;
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
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
if ( change == 1 ) {
obj . db . Set ( device ) ;
// If this is a temporary device, don't log changes
if ( obj . agentInfo . capabilities & 0x20 ) { log = 0 ; }
// Event the node change
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id } ;
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 ( ', ' ) ; }
2018-08-30 03:40:30 +03: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 ) ;
2018-08-22 01:08:15 +03:00
}
}
2017-08-28 19:27:45 +03:00
2018-08-22 01:08:15 +03:00
// 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-11-02 01:01:21 +03:00
if ( obj . nodeid != null ) { obj . parent . parent . debug ( 1 , 'Duplicate agent ' + obj . nodeid + ' (' + obj . remoteaddrport + ')' ) ; }
2018-08-22 01:08:15 +03:00
dupAgent . close ( 3 ) ;
} else {
// Indicate the agent is connected
obj . parent . parent . SetConnectivityState ( obj . dbMeshKey , obj . dbNodeKey , obj . connectTime , 1 , 1 ) ;
}
2017-09-06 03:19:28 +03:00
2018-08-22 01:08:15 +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
// Check how many times this agent disconnected in the last few minutes.
var disconnectCount = obj . parent . wsagentsDisconnections [ obj . nodeid ] ;
if ( disconnectCount > 6 ) {
console . log ( 'Agent in big trouble: NodeId=' + obj . nodeid + ', IP=' + obj . remoteaddrport + ', Agent=' + obj . agentInfo . agentId + '.' ) ;
// TODO: Log or do something to recover?
return ;
}
2018-08-22 01:08:15 +03:00
// Command 4, inform mesh agent that it's authenticated.
obj . send ( obj . common . ShortToStr ( 4 ) ) ;
2019-01-23 06:34:55 +03:00
if ( disconnectCount > 4 ) {
// Too many disconnections, this agent has issues. Just clear the core.
obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ;
console . log ( 'Agent in trouble: NodeId=' + obj . nodeid + ', IP=' + obj . remoteaddrport + ', Agent=' + obj . agentInfo . agentId + '.' ) ;
// TODO: Log or do something to recover?
return ;
}
2018-08-22 01:08:15 +03:00
// Check if we need to make an native update check
obj . agentExeInfo = obj . parent . parent . meshAgentBinaries [ obj . agentInfo . agentId ] ;
2019-01-22 01:05:50 +03:00
const corename = obj . parent . parent . meshAgentsArchitectureNumbers [ obj . agentInfo . agentId ] . core ;
if ( corename == null ) { obj . send ( obj . common . ShortToStr ( 10 ) + obj . common . ShortToStr ( 0 ) ) ; } // MeshCommand_CoreModule, ask mesh agent to clear the core
if ( ( obj . agentExeInfo != null ) && ( obj . agentExeInfo . update == true ) ) {
// Ask the agent for it's executable binary hash
obj . send ( obj . common . ShortToStr ( 12 ) + obj . common . ShortToStr ( 0 ) ) ;
} else {
// Check the mesh core, if the agent is capable of running one
if ( ( ( obj . agentInfo . capabilities & 16 ) != 0 ) && ( corename != null ) ) { obj . send ( obj . common . ShortToStr ( 11 ) + obj . common . ShortToStr ( 0 ) ) ; } // Command 11, ask for mesh core hash.
}
// 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
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 = { } ;
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-06 03:19:28 +03:00
} else {
2019-01-22 01:05:50 +03:00
// Check if we need to ask for the IP location
var doIpLocation = 0 ;
if ( device . iploc == null ) {
doIpLocation = 1 ;
2017-09-06 20:08:01 +03:00
} else {
2019-01-22 01:05:50 +03:00
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-06 20:08:01 +03:00
}
2017-09-06 03:19:28 +03:00
2019-01-22 01:05:50 +03: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-06 20:08:01 +03:00
}
2019-01-22 01:05:50 +03:00
} ) ;
}
2019-01-23 06:34:55 +03:00
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 ) {
2018-01-12 22:41:26 +03:00
var hash = obj . parent . webCertificateHashs [ domain . id ] ;
if ( hash != null ) return hash ;
2018-01-03 03:52:49 +03:00
return obj . parent . webCertificateHash ;
}
2018-12-01 03:42:58 +03:00
// Get the web certificate hash for the specified domain
function getWebCertFullHash ( domain ) {
var hash = obj . parent . webCertificateFullHashs [ domain . id ] ;
if ( hash != null ) return hash ;
return obj . parent . webCertificateFullHash ;
}
2017-08-28 19:27:45 +03:00
// Verify the agent signature
function processAgentSignature ( msg ) {
2018-12-15 23:34:55 +03:00
if ( obj . args . ignoreagenthashcheck !== true ) {
2019-01-11 21:02:54 +03:00
var verified = false ;
if ( msg . length != 384 ) {
// Verify a PKCS7 signature.
var msgDer = null ;
try { msgDer = obj . forge . asn1 . fromDer ( obj . forge . util . createBuffer ( msg , 'binary' ) ) ; } catch ( ex ) { }
if ( msgDer != null ) {
try {
var p7 = obj . forge . pkcs7 . messageFromAsn1 ( msgDer ) ;
var sig = p7 . rawCapture . signature ;
// Verify with key hash
var buf = Buffer . from ( getWebCertHash ( obj . domain ) + obj . nonce + obj . agentnonce , 'binary' ) ;
var verifier = obj . parent . crypto . createVerify ( 'RSA-SHA384' ) ;
verifier . update ( buf ) ;
verified = verifier . verify ( obj . unauth . nodeCertPem , sig , 'binary' ) ;
if ( verified == false ) {
// Verify with full hash
buf = Buffer . from ( getWebCertFullHash ( obj . domain ) + obj . nonce + obj . agentnonce , 'binary' ) ;
verifier = obj . parent . crypto . createVerify ( 'RSA-SHA384' ) ;
verifier . update ( buf ) ;
verified = verifier . verify ( obj . unauth . nodeCertPem , sig , 'binary' ) ;
}
if ( verified == false ) { return false ; } // Not a valid signature
} catch ( ex ) { } ;
}
}
if ( verified == false ) {
// Verify the RSA signature. This is the fast way, without using forge.
const verify = obj . parent . crypto . createVerify ( 'SHA384' ) ;
verify . end ( Buffer . from ( getWebCertHash ( obj . domain ) + obj . nonce + obj . agentnonce , 'binary' ) ) ; // Test using the private key hash
if ( verify . verify ( obj . unauth . nodeCertPem , Buffer . from ( msg , 'binary' ) ) !== true ) {
const verify2 = obj . parent . crypto . createVerify ( 'SHA384' ) ;
verify2 . end ( Buffer . from ( getWebCertFullHash ( obj . domain ) + obj . nonce + obj . agentnonce , 'binary' ) ) ; // Test using the full cert hash
if ( verify2 . verify ( obj . unauth . nodeCertPem , Buffer . from ( msg , 'binary' ) ) !== true ) { return false ; }
}
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 ;
if ( obj . unauthsign ) delete obj . unauthsign ;
2018-11-02 01:01:21 +03:00
obj . parent . parent . debug ( 1 , '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 ) {
2018-08-30 03:40:30 +03:00
var i ;
2018-08-27 22:24:15 +03:00
var str = msg . toString ( 'utf8' ) , command = null ;
2017-08-28 19:27:45 +03:00
if ( str [ 0 ] == '{' ) {
2018-11-02 01:01:21 +03:00
try { command = JSON . parse ( str ) ; } catch ( ex ) { console . log ( 'Unable to parse agent JSON (' + obj . remoteaddrport + '): ' + str , ex ) ; return ; } // If the command can't be parsed, ignore it.
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' :
{
// Route a message.
// If this command has a sessionid, that is the target.
2017-09-15 21:45:06 +03:00
if ( command . sessionid != null ) {
2017-10-24 00:09:58 +03:00
if ( typeof command . sessionid != 'string' ) break ;
2017-08-28 19:27:45 +03: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 21:45:06 +03:00
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2018-08-30 03:40:30 +03:00
2017-09-15 21:45:06 +03:00
// See if the session is connected. If so, go ahead and send this message to the target node
2017-08-28 19:27:45 +03:00
var ws = obj . parent . wssessions2 [ command . sessionid ] ;
2017-09-15 21:45:06 +03:00
if ( ws != null ) {
2017-08-28 19:27:45 +03: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.
2018-09-25 03:47:03 +03:00
try { ws . send ( JSON . stringify ( command ) ) ; } catch ( ex ) { }
2017-09-15 21:45:06 +03:00
} else if ( obj . parent . parent . multiServer != null ) {
2017-09-21 00:44:22 +03: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 19:27:45 +03:00
}
}
2017-09-15 21:45:06 +03:00
} else if ( command . userid != null ) { // If this command has a userid, that is the target.
2017-10-24 00:09:58 +03:00
if ( typeof command . userid != 'string' ) break ;
2017-08-28 19:27:45 +03: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 21:45:06 +03:00
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2017-08-28 19:27:45 +03: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 21:45:06 +03:00
if ( sessions != null ) {
2017-08-28 19:27:45 +03: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.
2018-08-30 03:40:30 +03:00
for ( i in sessions ) { sessions [ i ] . send ( JSON . stringify ( command ) ) ; }
2017-08-28 19:27:45 +03:00
}
2018-04-12 21:15:01 +03:00
if ( obj . parent . parent . multiServer != null ) {
// TODO: Add multi-server support
}
2017-08-28 19:27:45 +03:00
}
} else { // Route this command to the mesh
2017-09-15 21:45:06 +03:00
command . nodeid = obj . dbNodeKey ;
var cmdstr = JSON . stringify ( command ) ;
2017-08-28 19:27:45 +03: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 23:28:07 +03:00
if ( ( user != null ) && ( user . links != null ) ) {
2017-08-28 19:27:45 +03:00
var rights = user . links [ obj . dbMeshKey ] ;
2017-09-15 21:45:06 +03:00
if ( rights != null ) { // TODO: Look at what rights are needed for message routing
2018-08-30 03:40:30 +03:00
var xsessions = obj . parent . wssessions [ userid ] ;
2017-09-15 21:45:06 +03:00
// Send the message to all users on this server
2018-08-30 03:40:30 +03:00
for ( i in xsessions ) { try { xsessions [ i ] . send ( cmdstr ) ; } catch ( e ) { } }
2017-08-28 19:27:45 +03:00
}
}
}
2017-09-15 21:45:06 +03: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 19:27:45 +03:00
}
break ;
}
case 'coreinfo' :
{
// Sent by the agent to update agent information
ChangeAgentCoreInfo ( command ) ;
2018-09-28 02:17:05 +03:00
break ;
}
case 'smbios' :
{
// The RAW SMBios table of this computer
obj . db . Set ( { _id : 'sm' + obj . dbNodeKey , type : 'smbios' , domain : domain . id , time : Date . now ( ) , smbios : command . value } ) ;
// Event the node interface information change (This is a lot of traffic, probably don't need this).
//obj.parent.parent.DispatchEvent(['*', obj.meshid], obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
2017-08-28 19:27:45 +03:00
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 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 ( ) ;
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-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 ;
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
2018-09-22 02:34:35 +03:00
var 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
obj . db . Remove ( node . _id ) ;
obj . db . Remove ( 'if' + node . _id ) ;
// Event node deletion
var change = 'Migrated device ' + node . name ;
2018-08-30 03:40:30 +03:00
obj . parent . parent . DispatchEvent ( [ '*' , node . meshid ] , 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-01-05 04:59:13 +03:00
default : {
console . log ( 'Unknown agent action (' + obj . remoteaddrport + '): ' + command . action + '.' ) ;
break ;
}
2017-08-28 19:27:45 +03:00
}
}
}
// Change the current core information string and event it
function ChangeAgentCoreInfo ( command ) {
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
// Check that the mesh exists
2018-08-22 01:08:15 +03:00
var mesh = obj . parent . meshes [ obj . dbMeshKey ] ;
if ( mesh == null ) return ;
// Get the node and change it if needed
2018-11-25 23:59:42 +03:00
obj . 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.
2018-08-22 01:08:15 +03:00
if ( nodes . length != 1 ) return ;
var device = nodes [ 0 ] ;
if ( device . agent ) {
var changes = [ ] , change = 0 ;
2018-09-22 02:34:35 +03:00
//if (command.users) { console.log(command.users); }
2018-08-22 01:08:15 +03:00
// Check if anything changes
if ( command . name && ( command . name != device . name ) ) { change = 1 ; device . name = command . name ; changes . push ( 'name' ) ; }
2018-09-27 00:58:55 +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 ; changes . push ( 'agent core' ) ; }
2018-09-22 02:34:35 +03:00
if ( ( command . caps != null ) && ( ( 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 . osdesc != null ) && ( device . osdesc != command . osdesc ) ) { device . osdesc = command . osdesc ; change = 1 ; changes . push ( 'os desc' ) ; }
2018-08-22 01:08:15 +03:00
if ( command . intelamt ) {
if ( ! device . intelamt ) { device . intelamt = { } ; }
2018-09-22 02:34:35 +03:00
if ( ( command . intelamt . ver != null ) && ( device . intelamt . ver != command . intelamt . ver ) ) { changes . push ( 'AMT version' ) ; device . intelamt . ver = command . intelamt . ver ; change = 1 ; }
if ( ( command . intelamt . state != null ) && ( device . intelamt . state != command . intelamt . state ) ) { changes . push ( 'AMT state' ) ; device . intelamt . state = command . intelamt . state ; change = 1 ; }
if ( ( command . intelamt . flags != null ) && ( 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 ;
}
if ( ( command . intelamt . host != null ) && ( device . intelamt . host != command . intelamt . host ) ) { changes . push ( 'AMT host' ) ; device . intelamt . host = command . intelamt . host ; change = 1 ; }
if ( ( command . intelamt . uuid != null ) && ( device . intelamt . uuid != command . intelamt . uuid ) ) { changes . push ( 'AMT uuid' ) ; device . intelamt . uuid = command . intelamt . uuid ; change = 1 ; }
2018-08-22 01:08:15 +03:00
}
2018-09-22 02:34:35 +03:00
if ( ( command . users != null ) && ( device . users != command . users ) ) { device . users = command . users ; change = 1 ; } // Would be nice not to save this to the db.
2018-08-22 01:08:15 +03:00
if ( mesh . mtype == 2 ) {
if ( device . host != obj . remoteaddr ) { device . host = obj . remoteaddr ; change = 1 ; changes . push ( 'host' ) ; }
// 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
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 ) {
2018-09-22 02:34:35 +03:00
// Save to the database
2018-08-22 01:08:15 +03:00
obj . db . Set ( device ) ;
2017-08-28 19:27:45 +03:00
2018-08-22 01:08:15 +03:00
// Event the node change
2018-09-22 02:34:35 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id } ;
2018-09-25 03:47:03 +03:00
if ( changes . length > 0 ) { event . msg = 'Changed device ' + device . name + ' from group ' + mesh . name + ': ' + changes . join ( ', ' ) ; }
2018-09-22 02:34:35 +03:00
if ( ( obj . agentInfo . capabilities & 0x20 ) || ( changes . length == 0 ) ) { event . nolog = 1 ; } // If this is a temporary device, don't log changes
2018-08-22 01:08:15 +03: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 19:27:45 +03:00
}
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 ) {
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
2018-08-22 01:08:15 +03:00
var mesh = obj . parent . meshes [ obj . dbMeshKey ] ;
if ( mesh == null ) return ;
// 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 ) ;
2017-08-29 01:06:29 +03:00
2018-08-22 01:08:15 +03:00
// Event the node change
2018-09-25 03:47:03 +03:00
var event = { etype : 'node' , action : 'changenode' , nodeid : obj . dbNodeKey , domain : domain . id , 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
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-29 01:06:29 +03:00
}
2018-08-22 01:08:15 +03:00
}
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 ) {
2017-09-15 21:45:06 +03:00
if ( tag . length == 0 ) { tag = null ; }
2017-08-28 19:27:45 +03: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 ;
2018-08-30 03:40:30 +03:00
} ;