mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-25 14:45:52 -05:00
e249dc32da
Introduce plugin hook to agent message data, should the plugin need to interact (without additional bandwidth used from those without plugins enabled)
1459 lines
90 KiB
JavaScript
1459 lines
90 KiB
JavaScript
/**
|
|
* @description MeshCentral MeshAgent communication module
|
|
* @author Ylian Saint-Hilaire & Bryan Roe
|
|
* @copyright Intel Corporation 2018-2019
|
|
* @license Apache-2.0
|
|
* @version v0.0.1
|
|
*/
|
|
|
|
/*xjslint node: true */
|
|
/*xjslint plusplus: true */
|
|
/*xjslint maxlen: 256 */
|
|
/*jshint node: true */
|
|
/*jshint strict: false */
|
|
/*jshint esversion: 6 */
|
|
"use strict";
|
|
|
|
// Construct a MeshAgent object, called upon connection
|
|
module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
|
|
const forge = parent.parent.certificateOperations.forge;
|
|
const common = parent.parent.common;
|
|
const agentUpdateBlockSize = 65531;
|
|
parent.agentStats.createMeshAgentCount++;
|
|
|
|
var obj = {};
|
|
obj.domain = domain;
|
|
obj.authenticated = 0;
|
|
obj.receivedCommands = 0;
|
|
obj.agentCoreCheck = 0;
|
|
obj.remoteaddr = (req.ip.startsWith('::ffff:')) ? (req.ip.substring(7)) : req.ip;
|
|
obj.remoteaddrport = obj.remoteaddr + ':' + ws._socket.remotePort;
|
|
obj.nonce = parent.crypto.randomBytes(48).toString('binary');
|
|
//ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes
|
|
if (args.agentidletimeout != 0) { ws._socket.setTimeout(args.agentidletimeout, function () { obj.close(1); }); } // Inactivity timeout of 2:30 minutes, by default agent will WebSocket ping every 2 minutes and server will pong back.
|
|
//obj.nodeid = null;
|
|
//obj.meshid = null;
|
|
//obj.dbNodeKey = null;
|
|
//obj.dbMeshKey = null;
|
|
//obj.connectTime = null;
|
|
//obj.agentInfo = null;
|
|
|
|
// Send a message to the mesh agent
|
|
obj.send = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data), func); } else { ws.send(data, func); } } catch (e) { } };
|
|
obj.sendBinary = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary'), func); } else { ws.send(data, func); } } catch (e) { } };
|
|
|
|
// Disconnect this agent
|
|
obj.close = function (arg) {
|
|
if ((arg == 1) || (arg == null)) { try { ws.close(); if (obj.nodeid != null) { parent.parent.debug('agent', 'Soft disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Soft close, close the websocket
|
|
if (arg == 2) { try { ws._socket._parent.end(); if (obj.nodeid != null) { parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Hard close, close the TCP socket
|
|
// If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
|
|
|
|
// Remove this agent from the webserver list
|
|
if (parent.wsagents[obj.dbNodeKey] == obj) {
|
|
delete parent.wsagents[obj.dbNodeKey];
|
|
parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1);
|
|
}
|
|
|
|
// Get the current mesh
|
|
const mesh = parent.meshes[obj.dbMeshKey];
|
|
|
|
// If this is a temporary or recovery agent, or all devices in this group are temporary, remove the agent (0x20 = Temporary, 0x40 = Recovery)
|
|
if (((obj.agentInfo) && (obj.agentInfo.capabilities) && ((obj.agentInfo.capabilities & 0x20) || (obj.agentInfo.capabilities & 0x40))) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) {
|
|
// Delete this node including network interface information and events
|
|
db.Remove(obj.dbNodeKey); // Remove node with that id
|
|
db.Remove('if' + obj.dbNodeKey); // Remove interface information
|
|
db.Remove('nt' + obj.dbNodeKey); // Remove notes
|
|
db.Remove('lc' + obj.dbNodeKey); // Remove last connect time
|
|
db.Remove('si' + obj.dbNodeKey); // Remove system information
|
|
db.RemoveSMBIOS(obj.dbNodeKey); // Remove SMBios data
|
|
db.RemoveAllNodeEvents(obj.dbNodeKey); // Remove all events for this node
|
|
db.removeAllPowerEventsForNode(obj.dbNodeKey); // Remove all power events for this node
|
|
|
|
// Event node deletion
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, { etype: 'node', action: 'removenode', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
|
|
|
|
// Disconnect all connections if needed
|
|
const state = parent.parent.GetConnectivityState(obj.dbNodeKey);
|
|
if ((state != null) && (state.connectivity != null)) {
|
|
if ((state.connectivity & 1) != 0) { parent.wsagents[obj.dbNodeKey].close(); } // Disconnect mesh agent
|
|
if ((state.connectivity & 2) != 0) { parent.parent.mpsserver.close(parent.parent.mpsserver.ciraConnections[obj.dbNodeKey]); } // Disconnect CIRA connection
|
|
}
|
|
} else {
|
|
// Update the last connect time
|
|
if (obj.authenticated == 2) { db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport, cause: 1 }); }
|
|
}
|
|
|
|
// Set this agent as no longer authenticated
|
|
obj.authenticated = -1;
|
|
|
|
// If we where updating the agent, clean that up.
|
|
if (obj.agentUpdate != null) {
|
|
if (obj.agentUpdate.fd) { try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } }
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
}
|
|
|
|
// Perform timer cleanup
|
|
if (obj.pingtimer) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
|
|
if (obj.pongtimer) { clearInterval(obj.pongtimer); delete obj.pongtimer; }
|
|
|
|
// Perform aggressive cleanup
|
|
if (obj.nonce) { delete obj.nonce; }
|
|
if (obj.nodeid) { delete obj.nodeid; }
|
|
if (obj.unauth) { delete obj.unauth; }
|
|
if (obj.remoteaddr) { delete obj.remoteaddr; }
|
|
if (obj.remoteaddrport) { delete obj.remoteaddrport; }
|
|
if (obj.meshid) { delete obj.meshid; }
|
|
if (obj.dbNodeKey) { delete obj.dbNodeKey; }
|
|
if (obj.dbMeshKey) { delete obj.dbMeshKey; }
|
|
if (obj.connectTime) { delete obj.connectTime; }
|
|
if (obj.agentInfo) { delete obj.agentInfo; }
|
|
if (obj.agentExeInfo) { delete obj.agentExeInfo; }
|
|
ws.removeAllListeners(["message", "close", "error"]);
|
|
};
|
|
|
|
// When data is received from the mesh agent web socket
|
|
ws.on('message', function (msg) {
|
|
if (msg.length < 2) return;
|
|
if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
|
|
|
|
if (obj.authenticated == 2) { // We are authenticated
|
|
if ((obj.agentUpdate == null) && (msg.charCodeAt(0) == 123)) { processAgentData(msg); } // Only process JSON messages if meshagent update is not in progress
|
|
if (msg.length < 2) return;
|
|
const cmdid = 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 we are performing an agent update, don't update the core.
|
|
if (obj.agentUpdate != null) { return; }
|
|
|
|
// If we are using a custom core, don't try to update it.
|
|
if (obj.agentCoreCheck == 1000) {
|
|
obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
|
|
agentCoreIsStable();
|
|
return;
|
|
}
|
|
|
|
// Get the current meshcore hash
|
|
const agentMeshCoreHash = (msg.length == 52) ? msg.substring(4, 52) : null;
|
|
|
|
// If the agent indicates this is a custom core, we are done.
|
|
if ((agentMeshCoreHash != null) && (agentMeshCoreHash == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) {
|
|
obj.agentCoreCheck = 0;
|
|
obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
|
|
agentCoreIsStable();
|
|
return;
|
|
}
|
|
|
|
// We need to check if the core is current. Figure out what core we need.
|
|
var corename = null;
|
|
if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) {
|
|
if (obj.agentCoreCheck == 1001) {
|
|
// If the user asked, use the recovery core.
|
|
corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].rcore;
|
|
} else if (obj.agentInfo.capabilities & 0x40) {
|
|
// If this is a recovery agent, use the agent recovery core.
|
|
corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].arcore;
|
|
} else {
|
|
// This is the normal core for this agent type.
|
|
corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
|
|
}
|
|
}
|
|
|
|
// If we have a core, use it.
|
|
if (corename != null) {
|
|
const meshcorehash = parent.parent.defaultMeshCoresHash[corename];
|
|
if (agentMeshCoreHash != meshcorehash) {
|
|
if ((obj.agentCoreCheck < 5) || (obj.agentCoreCheck == 1001)) {
|
|
if (meshcorehash == null) {
|
|
// Clear the core
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // MeshCommand_CoreModule, ask mesh agent to clear the core
|
|
parent.agentStats.clearingCoreCount++;
|
|
parent.parent.debug('agent', 'Clearing core');
|
|
} else {
|
|
// Update new core
|
|
//obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + meshcorehash + parent.parent.defaultMeshCores[corename]); // MeshCommand_CoreModule, start core update
|
|
//parent.parent.debug('agent', 'Updating code ' + corename);
|
|
|
|
// Update new core with task limiting so not to flood the server. This is a high priority task.
|
|
obj.agentCoreUpdatePending = true;
|
|
parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
|
|
if (obj.authenticated == 2) {
|
|
// Send the updated code.
|
|
delete obj.agentCoreUpdatePending;
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core, function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update
|
|
parent.agentStats.updatingCoreCount++;
|
|
parent.parent.debug('agent', 'Updating core ' + argument.name);
|
|
agentCoreIsStable();
|
|
} else {
|
|
// This agent is probably disconnected, nothing to do.
|
|
parent.parent.taskLimiter.completed(taskid);
|
|
}
|
|
}, { hash: meshcorehash, core: parent.parent.defaultMeshCores[corename], name: corename }, 0);
|
|
}
|
|
obj.agentCoreCheck++;
|
|
}
|
|
} else {
|
|
obj.agentCoreCheck = 0;
|
|
obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
|
|
agentCoreIsStable(); // No updates needed, agent is ready to go.
|
|
}
|
|
}
|
|
|
|
/*
|
|
// TODO: Check if we have a mesh specific core. If so, use that.
|
|
var agentMeshCoreHash = null;
|
|
if (msg.length == 52) { agentMeshCoreHash = msg.substring(4, 52); }
|
|
if ((agentMeshCoreHash != parent.parent.defaultMeshCoreHash) && (agentMeshCoreHash != parent.parent.defaultMeshCoreNoMeiHash)) {
|
|
if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
|
|
if (parent.parent.defaultMeshCoreHash == null) {
|
|
// Update no core
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // Command 10, ask mesh agent to clear the core
|
|
} else {
|
|
// Update new core
|
|
if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].amt == true) {
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + parent.parent.defaultMeshCoreHash + parent.parent.defaultMeshCore); // Command 10, ask mesh agent to set the core (with MEI support)
|
|
} else {
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + parent.parent.defaultMeshCoreNoMeiHash + parent.parent.defaultMeshCoreNoMei); // Command 10, ask mesh agent to set the core (No MEI)
|
|
}
|
|
}
|
|
obj.agentCoreCheck++;
|
|
}
|
|
} else {
|
|
obj.agentCoreCheck = 0;
|
|
}
|
|
*/
|
|
}
|
|
else if (cmdid == 12) { // MeshCommand_AgentHash
|
|
if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
|
|
const agenthash = msg.substring(4);
|
|
if ((agenthash != obj.agentExeInfo.hash) && (agenthash != '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) {
|
|
// Mesh agent update required, do it using task limiter so not to flood the network. Medium priority task.
|
|
parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
|
|
if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; } // If agent disconnection, complete and exit now.
|
|
if (obj.nodeid != null) { parent.parent.debug('agent', 'Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); }
|
|
parent.agentStats.agentBinaryUpdate++;
|
|
if (obj.agentExeInfo.data == null) {
|
|
// Read the agent from disk
|
|
parent.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
|
|
if (obj.agentExeInfo == null) return; // Agent disconnected during this call.
|
|
if (err) { parent.parent.debug('agentupdate', "ERROR: " + err); return console.error(err); }
|
|
obj.agentUpdate = { 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.sendBinary(common.ShortToStr(10) + 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.sendBinary(common.ShortToStr(13) + 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;
|
|
parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) {
|
|
if ((err != null) || (bytesRead == 0)) {
|
|
// Error reading the agent file, stop here.
|
|
try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
parent.parent.debug('agentupdate', "ERROR: Unable to read first block of agent binary from disk.");
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
} else {
|
|
// Send the first block to the agent
|
|
obj.agentUpdate.ptr += bytesRead;
|
|
parent.parent.debug('agentupdate', "Sent first block of " + bytesRead + " bytes from disk.");
|
|
obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// Send the agent from RAM
|
|
obj.agentUpdate = { ptr: 0, buf: Buffer.alloc(agentUpdateBlockSize + 4), taskid: taskid };
|
|
|
|
// MeshCommand_CoreModule, ask mesh agent to clear the core.
|
|
// The new core will only be sent after the agent updates.
|
|
obj.sendBinary(common.ShortToStr(10) + 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
|
|
obj.sendBinary(common.ShortToStr(13) + 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;
|
|
|
|
const len = Math.min(agentUpdateBlockSize, obj.agentExeInfo.data.length - obj.agentUpdate.ptr);
|
|
if (len > 0) {
|
|
// Send the first block
|
|
obj.agentExeInfo.data.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len);
|
|
obj.agentUpdate.ptr += len;
|
|
obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block
|
|
parent.parent.debug('agentupdate', "Sent first block of " + len + " bytes from RAM.");
|
|
} else {
|
|
// Error
|
|
parent.parent.debug('agentupdate', "ERROR: Len of " + len + " is invalid.");
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
}
|
|
}
|
|
}, null, 1);
|
|
|
|
} else {
|
|
// Check the mesh core, if the agent is capable of running one
|
|
if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) {
|
|
obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (cmdid == 14) { // MeshCommand_AgentBinaryBlock
|
|
if ((msg.length == 4) && (obj.agentUpdate != null)) {
|
|
const status = common.ReadShort(msg, 2);
|
|
if (status == 1) {
|
|
if (obj.agentExeInfo.data == null) {
|
|
// Read the agent from disk
|
|
parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) {
|
|
if ((obj.agentExeInfo == null) || (obj.agentUpdate == null)) return; // Agent disconnected during this async call.
|
|
if ((err != null) || (bytesRead < 0)) {
|
|
// Error reading the agent file, stop here.
|
|
parent.parent.debug('agentupdate', "ERROR: Unable to read agent #" + obj.agentExeInfo.id + " binary from disk.");
|
|
try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
} else {
|
|
// Send the next block to the agent
|
|
parent.parent.debug('agentupdate', "Sending disk agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + bytesRead + ".");
|
|
obj.agentUpdate.ptr += bytesRead;
|
|
if (bytesRead == agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, bytesRead + 4)); } // Command 14, mesh agent next data block
|
|
if ((bytesRead < agentUpdateBlockSize) || (obj.agentUpdate.ptr == obj.agentExeInfo.size)) {
|
|
parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from disk, ptr=" + obj.agentUpdate.ptr + ".");
|
|
obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentExeInfo.hash); // Command 13, end mesh agent download, send agent SHA384 hash
|
|
try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// Send the agent from RAM
|
|
const len = Math.min(agentUpdateBlockSize, obj.agentExeInfo.data.length - obj.agentUpdate.ptr);
|
|
if (len > 0) {
|
|
obj.agentExeInfo.data.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len);
|
|
if (len == agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, len + 4)); } // Command 14, mesh agent next data block
|
|
parent.parent.debug('agentupdate', "Sending RAM agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + len + ".");
|
|
obj.agentUpdate.ptr += len;
|
|
}
|
|
|
|
if (obj.agentUpdate.ptr == obj.agentExeInfo.data.length) {
|
|
parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from RAM, ptr=" + obj.agentUpdate.ptr + ".");
|
|
obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentExeInfo.hash); // Command 13, end mesh agent download, send agent SHA384 hash
|
|
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
|
|
delete obj.agentUpdate.buf;
|
|
delete obj.agentUpdate;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (cmdid == 15) { // MeshCommand_AgentTag
|
|
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);
|
|
}
|
|
} else if (obj.authenticated < 2) { // We are not authenticated
|
|
const cmd = common.ReadShort(msg, 0);
|
|
if (cmd == 1) {
|
|
// Agent authentication request
|
|
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
|
|
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
|
|
|
|
if (args.ignoreagenthashcheck === true) {
|
|
// Send the agent web hash back to the agent
|
|
obj.sendBinary(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)
|
|
if ((getWebCertHash(domain) != msg.substring(2, 50)) && (getWebCertFullHash(domain) != msg.substring(2, 50))) {
|
|
parent.agentStats.agentBadWebCertHashCount++;
|
|
console.log('Agent bad web cert hash (Agent:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex').substring(0, 10)) + ' != Server:' + (Buffer.from(getWebCertHash(domain), 'binary').toString('hex').substring(0, 10)) + ' or ' + (new Buffer(getWebCertFullHash(domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').');
|
|
console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
|
|
obj.agentnonce = msg.substring(50, 98);
|
|
|
|
// 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
|
|
parent.parent.certificateOperations.acceleratorPerformSignature(1, msg.substring(2) + obj.nonce, null, function (tag, signature) {
|
|
// Send back our certificate + signature
|
|
obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.swarmCertificateAsn1.length) + parent.swarmCertificateAsn1 + signature); // Command 2, certificate + signature
|
|
});
|
|
} else {
|
|
// Perform the hash signature using the server agent certificate
|
|
parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, null, function (tag, signature) {
|
|
// Send back our certificate + signature
|
|
obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.agentCertificateAsn1.length) + parent.agentCertificateAsn1 + signature); // Command 2, certificate + signature
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check the agent signature if we can
|
|
if (obj.unauthsign != null) {
|
|
if (processAgentSignature(obj.unauthsign) == false) {
|
|
parent.agentStats.agentBadSignature1Count++;
|
|
console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
|
|
} else { completeAgentConnection(); }
|
|
}
|
|
}
|
|
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
|
|
const certlen = common.ReadShort(msg, 2);
|
|
obj.unauth = {};
|
|
try { obj.unauth.nodeid = Buffer.from(forge.pki.getPublicKeyFingerprint(forge.pki.certificateFromAsn1(forge.asn1.fromDer(msg.substring(4, 4 + certlen))).publicKey, { md: forge.md.sha384.create() }).data, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } catch (ex) { console.log(ex); return; }
|
|
obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
|
|
|
|
// Check the agent signature if we can
|
|
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else {
|
|
if (processAgentSignature(msg.substring(4 + certlen)) == false) {
|
|
parent.agentStats.agentBadSignature2Count++;
|
|
console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
|
|
}
|
|
}
|
|
completeAgentConnection();
|
|
}
|
|
else if (cmd == 3) {
|
|
// Agent meshid
|
|
if ((msg.length < 72) || ((obj.receivedCommands & 4) != 0)) return;
|
|
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 = common.ReadInt(msg, 2);
|
|
obj.agentInfo.agentId = common.ReadInt(msg, 6);
|
|
obj.agentInfo.agentVersion = common.ReadInt(msg, 10);
|
|
obj.agentInfo.platformType = common.ReadInt(msg, 14);
|
|
if (obj.agentInfo.platformType > 6 || obj.agentInfo.platformType < 1) { obj.agentInfo.platformType = 1; }
|
|
if (msg.substring(50, 66) == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') {
|
|
obj.meshid = Buffer.from(msg.substring(18, 50), 'binary').toString('hex'); // Older HEX MeshID
|
|
} else {
|
|
obj.meshid = Buffer.from(msg.substring(18, 66), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); // New Base64 MeshID
|
|
}
|
|
//console.log('MeshID', obj.meshid);
|
|
obj.agentInfo.capabilities = common.ReadInt(msg, 66);
|
|
const computerNameLen = common.ReadShort(msg, 70);
|
|
obj.agentInfo.computerName = Buffer.from(msg.substring(72, 72 + computerNameLen), 'binary').toString('utf8');
|
|
obj.dbMeshKey = 'mesh/' + domain.id + '/' + obj.meshid;
|
|
completeAgentConnection();
|
|
} 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.
|
|
} 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) == parent.swarmCertificateHash256) || (msg.substring(2, 50) == parent.swarmCertificateHash384)) { obj.useSwarmCert = true; }
|
|
}
|
|
}
|
|
});
|
|
|
|
// If error, do nothing
|
|
ws.on('error', function (err) { console.log('AGENT WSERR: ' + err); obj.close(0); });
|
|
|
|
// If the mesh agent web socket is closed, clean up.
|
|
ws.on('close', function (req) {
|
|
parent.agentStats.agentClose++;
|
|
if (obj.nodeid != null) {
|
|
const agentId = (obj.agentInfo && obj.agentInfo.agentId) ? obj.agentInfo.agentId : 'Unknown';
|
|
//console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
|
|
parent.parent.debug('agent', 'Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
|
|
|
|
// Log the agent disconnection
|
|
if (parent.wsagentsDisconnections[obj.nodeid] == null) {
|
|
parent.wsagentsDisconnections[obj.nodeid] = 1;
|
|
} else {
|
|
parent.wsagentsDisconnections[obj.nodeid] = ++parent.wsagentsDisconnections[obj.nodeid];
|
|
}
|
|
}
|
|
obj.close(0);
|
|
});
|
|
|
|
// Start authenticate the mesh agent by sending a auth nonce & server TLS cert hash.
|
|
// Send 384 bits SHA384 hash of TLS cert public key + 384 bits nonce
|
|
if (args.ignoreagenthashcheck !== true) {
|
|
obj.sendBinary(common.ShortToStr(1) + getWebCertHash(domain) + obj.nonce); // Command 1, hash + nonce
|
|
}
|
|
|
|
// Return the mesh for this device, in some cases, we may auto-create the mesh.
|
|
function getMeshAutoCreate() {
|
|
var mesh = parent.meshes[obj.dbMeshKey];
|
|
|
|
// If the mesh was not found and we are in LAN mode, check of the domain can be corrected
|
|
if ((args.lanonly == true) && (mesh == null)) {
|
|
var smesh = obj.dbMeshKey.split('/');
|
|
for (var i in parent.parent.config.domains) {
|
|
mesh = parent.meshes['mesh/' + i + '/' + smesh[2]];
|
|
if (mesh != null) {
|
|
obj.domain = domain = parent.parent.config.domains[i];
|
|
obj.meshid = smesh[2];
|
|
obj.dbMeshKey = 'mesh/' + i + '/' + smesh[2];
|
|
obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((mesh == null) && (typeof domain.orphanagentuser == 'string')) {
|
|
const adminUser = parent.users['user/' + domain.id + '/' + domain.orphanagentuser.toLowerCase()];
|
|
if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
|
|
// Mesh name is hex instead of base64
|
|
const meshname = obj.meshid.substring(0, 18);
|
|
|
|
// Create a new mesh for this device
|
|
const links = {};
|
|
links[adminUser._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
|
|
mesh = { type: 'mesh', _id: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', domain: domain.id, links: links };
|
|
db.Set(common.escapeLinksFieldName(mesh));
|
|
parent.meshes[obj.dbMeshKey] = mesh;
|
|
|
|
if (adminUser.links == null) adminUser.links = {};
|
|
adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF };
|
|
db.SetUser(adminUser);
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey, adminUser._id], obj, { etype: 'mesh', username: adminUser.name, meshid: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id });
|
|
}
|
|
} else {
|
|
if ((mesh != null) && (mesh.deleted != null) && (mesh.links)) {
|
|
// Must un-delete this mesh
|
|
var ids = ['*', mesh._id];
|
|
|
|
// See if users still exists, if so, add links to the mesh
|
|
for (var userid in mesh.links) {
|
|
const user = parent.users[userid];
|
|
if (user) {
|
|
if (user.links == null) { user.links = {}; }
|
|
if (user.links[mesh._id] == null) {
|
|
user.links[mesh._id] = { rights: mesh.links[userid].rights };
|
|
ids.push(user._id);
|
|
db.SetUser(user);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send out an event indicating this mesh was "created"
|
|
parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', links: mesh.links, msg: 'Mesh undeleted: ' + mesh._id, domain: domain.id });
|
|
|
|
// Mark the mesh as active
|
|
delete mesh.deleted;
|
|
db.Set(common.escapeLinksFieldName(mesh));
|
|
}
|
|
}
|
|
return mesh;
|
|
}
|
|
|
|
// Send a PING/PONG message
|
|
function sendPing() { obj.send('{"action":"ping"}'); }
|
|
function sendPong() { obj.send('{"action":"pong"}'); }
|
|
|
|
// 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) || obj.pendingCompleteAgentConnection || (obj.agentInfo == null)) { return; }
|
|
obj.pendingCompleteAgentConnection = true;
|
|
|
|
// Setup the agent PING/PONG timers
|
|
if ((typeof args.agentping == 'number') && (obj.pingtimer == null)) { obj.pingtimer = setInterval(sendPing, args.agentping * 1000); }
|
|
else if ((typeof args.agentpong == 'number') && (obj.pongtimer == null)) { obj.pongtimer = setInterval(sendPong, args.agentpong * 1000); }
|
|
|
|
// If this is a recovery agent
|
|
if (obj.agentInfo.capabilities & 0x40) {
|
|
// Inform mesh agent that it's authenticated.
|
|
delete obj.pendingCompleteAgentConnection;
|
|
obj.authenticated = 2;
|
|
obj.sendBinary(common.ShortToStr(4));
|
|
|
|
// Ask for mesh core hash.
|
|
obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0));
|
|
return;
|
|
}
|
|
|
|
// Check if we have too many agent sessions
|
|
if (typeof domain.limits.maxagentsessions == 'number') {
|
|
// Count the number of agent sessions for this domain
|
|
var domainAgentSessionCount = 0;
|
|
for (var i in parent.wsagents) { if (parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } }
|
|
|
|
// Check if we have too many user sessions
|
|
if (domainAgentSessionCount >= domain.limits.maxagentsessions) {
|
|
// Too many, hold the connection.
|
|
parent.agentStats.agentMaxSessionHoldCount++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Check that the mesh exists
|
|
var mesh = parent.meshes[obj.dbMeshKey];
|
|
if (mesh == null) {
|
|
var holdConnection = true;
|
|
if (typeof domain.orphanagentuser == 'string') {
|
|
var adminUser = parent.users['user/' + domain.id + '/' + args.orphanagentuser];
|
|
if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
|
|
// Create a new mesh for this device
|
|
holdConnection = false;
|
|
var links = {};
|
|
links[user._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
|
|
mesh = { type: 'mesh', _id: obj.dbMeshKey, name: obj.meshid, mtype: 2, desc: '', domain: domain.id, links: links };
|
|
db.Set(common.escapeLinksFieldName(mesh));
|
|
parent.meshes[obj.meshid] = mesh;
|
|
parent.parent.AddEventDispatch([obj.meshid], ws);
|
|
|
|
if (adminUser.links == null) user.links = {};
|
|
adminUser.links[obj.meshid] = { rights: 0xFFFFFFFF };
|
|
//adminUser.subscriptions = parent.subscribe(adminUser._id, ws);
|
|
db.SetUser(user);
|
|
parent.parent.DispatchEvent(['*', meshid, user._id], obj, { etype: 'mesh', username: user.name, meshid: obj.meshid, name: obj.meshid, mtype: 2, desc: '', action: 'createmesh', links: links, msg: 'Mesh created: ' + obj.meshid, domain: domain.id });
|
|
}
|
|
}
|
|
|
|
if (holdConnection == true) {
|
|
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
|
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
|
return;
|
|
}
|
|
}
|
|
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.
|
|
*/
|
|
|
|
// Check that the node exists
|
|
db.Get(obj.dbNodeKey, function (err, nodes) {
|
|
if (obj.agentInfo == null) { return; }
|
|
var device, mesh;
|
|
|
|
// See if this node exists in the database
|
|
if ((nodes == null) || (nodes.length == 0)) {
|
|
// This device does not exist, use the meshid given by the device
|
|
|
|
// Check if we already have too many devices for this domain
|
|
if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
|
|
db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) {
|
|
if (ismax == true) {
|
|
// Too many devices in this domain.
|
|
parent.agentStats.maxDomainDevicesReached++;
|
|
} else {
|
|
// We are under the limit, create the new device.
|
|
completeAgentConnection2();
|
|
}
|
|
});
|
|
} else {
|
|
completeAgentConnection2();
|
|
}
|
|
return;
|
|
} else {
|
|
device = nodes[0];
|
|
|
|
// This device exists, meshid given by the device must be ignored, use the server side one.
|
|
if (device.meshid != obj.dbMeshKey) {
|
|
obj.dbMeshKey = device.meshid;
|
|
obj.meshid = device.meshid.split('/')[2];
|
|
}
|
|
|
|
// See if this mesh exists, if it does not we may want to create it.
|
|
mesh = getMeshAutoCreate();
|
|
|
|
// Check if the mesh exists
|
|
if (mesh == null) {
|
|
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
|
parent.agentStats.invalidDomainMesh2Count++;
|
|
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
|
return;
|
|
}
|
|
|
|
// Check if the mesh is the right type
|
|
if (mesh.mtype != 2) {
|
|
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
|
parent.agentStats.invalidMeshType2Count++;
|
|
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
|
|
return;
|
|
}
|
|
|
|
// Mark when this device connected
|
|
obj.connectTime = Date.now();
|
|
db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport, cause: 1 });
|
|
|
|
// Device already exists, look if changes have occured
|
|
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 (mesh.flags && (mesh.flags & 2) && (device.name != obj.agentInfo.computerName)) { device.name = obj.agentInfo.computerName; change = 1; } // We want the server name to be sync'ed to the hostname
|
|
|
|
if (change == 1) {
|
|
// Do some clean up if needed, these values should not be in the database.
|
|
if (device.conn != null) { delete device.conn; }
|
|
if (device.pwr != null) { delete device.pwr; }
|
|
if (device.agct != null) { delete device.agct; }
|
|
if (device.cict != null) { delete device.cict; }
|
|
|
|
// Save the updated device in the database
|
|
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, node: parent.CloneSafeNode(device) };
|
|
if (log == 0) { event.nolog = 1; } else { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
|
|
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
|
|
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
|
|
}
|
|
}
|
|
|
|
completeAgentConnection3(device, mesh);
|
|
});
|
|
}
|
|
|
|
function completeAgentConnection2() {
|
|
// See if this mesh exists, if it does not we may want to create it.
|
|
var mesh = getMeshAutoCreate();
|
|
|
|
// Check if the mesh exists
|
|
if (mesh == null) {
|
|
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
|
parent.agentStats.invalidDomainMeshCount++;
|
|
console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
|
|
return;
|
|
}
|
|
|
|
// Check if the mesh is the right type
|
|
if (mesh.mtype != 2) {
|
|
// If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
|
|
parent.agentStats.invalidMeshTypeCount++;
|
|
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
|
|
return;
|
|
}
|
|
|
|
// Mark when this device connected
|
|
obj.connectTime = Date.now();
|
|
db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport, cause: 1 });
|
|
|
|
// This node does not exist, create it.
|
|
var 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 };
|
|
db.Set(device);
|
|
|
|
// Event the new node
|
|
if (obj.agentInfo.capabilities & 0x20) {
|
|
// This is a temporary agent, don't log.
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 });
|
|
} else {
|
|
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 });
|
|
}
|
|
|
|
completeAgentConnection3(device, mesh);
|
|
}
|
|
|
|
function completeAgentConnection3(device, mesh) {
|
|
// Check if this agent is already connected
|
|
const dupAgent = parent.wsagents[obj.dbNodeKey];
|
|
parent.wsagents[obj.dbNodeKey] = obj;
|
|
if (dupAgent) {
|
|
// Record duplicate agents
|
|
if (parent.duplicateAgentsLog[obj.dbNodeKey] == null) {
|
|
if (dupAgent.remoteaddr == obj.remoteaddr) {
|
|
parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr], count: 1 };
|
|
} else {
|
|
parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr, dupAgent.remoteaddr], count: 1 };
|
|
}
|
|
} else {
|
|
parent.duplicateAgentsLog[obj.dbNodeKey].name = device.name;
|
|
parent.duplicateAgentsLog[obj.dbNodeKey].group = mesh.name;
|
|
parent.duplicateAgentsLog[obj.dbNodeKey].count++;
|
|
if (parent.duplicateAgentsLog[obj.dbNodeKey].ip.indexOf(obj.remoteaddr) == -1) { parent.duplicateAgentsLog[obj.dbNodeKey].ip.push(obj.remoteaddr); }
|
|
}
|
|
|
|
// Close the duplicate agent
|
|
parent.agentStats.duplicateAgentCount++;
|
|
if (obj.nodeid != null) { parent.parent.debug('agent', 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); }
|
|
dupAgent.close(3);
|
|
} else {
|
|
// Indicate the agent is connected
|
|
parent.parent.SetConnectivityState(obj.dbMeshKey, obj.dbNodeKey, obj.connectTime, 1, 1);
|
|
}
|
|
|
|
// We are done, ready to communicate with this agent
|
|
delete obj.pendingCompleteAgentConnection;
|
|
obj.authenticated = 2;
|
|
|
|
// Check how many times this agent disconnected in the last few minutes.
|
|
const disconnectCount = 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;
|
|
}
|
|
|
|
// Command 4, inform mesh agent that it's authenticated.
|
|
obj.sendBinary(common.ShortToStr(4));
|
|
|
|
if (disconnectCount > 4) {
|
|
// Too many disconnections, this agent has issues. Just clear the core.
|
|
obj.sendBinary(common.ShortToStr(10) + 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;
|
|
}
|
|
|
|
// Not sure why, but in rare cases, obj.agentInfo is undefined here.
|
|
if ((obj.agentInfo == null) || (typeof obj.agentInfo.capabilities != 'number')) { return; } // This is an odd case.
|
|
|
|
// Check if we need to make an native update check
|
|
obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
|
|
var corename = null;
|
|
if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) {
|
|
corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
|
|
} else {
|
|
// MeshCommand_CoreModule, ask mesh agent to clear the core
|
|
obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
|
|
}
|
|
|
|
if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
|
|
// Ask the agent for it's executable binary hash
|
|
obj.sendBinary(common.ShortToStr(12) + 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.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
|
|
} else {
|
|
agentCoreIsStable(); // No updates needed, agent is ready to go.
|
|
}
|
|
}
|
|
}
|
|
|
|
// Take a basic Intel AMT policy and add all server information to it, making it ready to send to this agent.
|
|
function completeIntelAmtPolicy(amtPolicy) {
|
|
var r = amtPolicy;
|
|
if (amtPolicy == null) return null;
|
|
if (amtPolicy.type == 2) {
|
|
// CCM - Add server root certificate
|
|
if (parent.parent.certificates.rootex == null) { parent.parent.certificates.rootex = parent.parent.certificates.root.cert.split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('').split('\r').join('').split('\n').join(''); }
|
|
r.rootcert = parent.parent.certificates.rootex;
|
|
if ((amtPolicy.cirasetup == 2) && (parent.parent.mpsserver != null) && (parent.parent.certificates.AmtMpsName != null) && (args.lanonly != true) && (args.mpsport != 0)) {
|
|
// Add server CIRA settings
|
|
r.ciraserver = {
|
|
name: parent.parent.certificates.AmtMpsName,
|
|
port: (typeof args.mpsaliasport == 'number' ? args.mpsaliasport : args.mpsport),
|
|
user: obj.meshid.replace(/\@/g, 'X').replace(/\$/g, 'X').substring(0, 16),
|
|
pass: args.mpspass ? args.mpspass : 'A@xew9rt', // If the MPS password is not set, just use anything. TODO: Use the password as an agent identifier?
|
|
home: ['sdlwerulis3wpj95dfj'] // Use a random FQDN to not have any home network.
|
|
};
|
|
if (Array.isArray(args.ciralocalfqdn)) { r.ciraserver.home = args.ciralocalfqdn; }
|
|
}
|
|
} else if ((amtPolicy.type == 3) && (domain.amtacmactivation.acmmatch)) {
|
|
// ACM - In this mode, don't send much to Intel AMT. Just indicate ACM policy and let the agent try activation when possible.
|
|
r = { type: 3, match: domain.amtacmactivation.acmmatch };
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Send Intel AMT policy
|
|
obj.sendUpdatedIntelAmtPolicy = function (policy) {
|
|
if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT.
|
|
if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; }
|
|
if (policy != null) { try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(policy)) })); } catch (ex) { } }
|
|
}
|
|
}
|
|
|
|
function recoveryAgentCoreIsStable(mesh) {
|
|
parent.agentStats.recoveryCoreIsStableCount++;
|
|
|
|
// Recovery agent is doing ok, lets perform main agent checking.
|
|
//console.log('recoveryAgentCoreIsStable()');
|
|
|
|
// Fetch the the real agent nodeid
|
|
db.Get('da' + obj.dbNodeKey, function (err, nodes, self)
|
|
{
|
|
if ((nodes != null) && (nodes.length == 1))
|
|
{
|
|
self.realNodeKey = nodes[0].raid;
|
|
|
|
// Get agent connection state
|
|
var agentConnected = false;
|
|
var state = parent.parent.GetConnectivityState(self.realNodeKey);
|
|
if (state) { agentConnected = ((state.connectivity & 1) != 0) }
|
|
|
|
self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: self.realNodeKey, agent: agentConnected } }));
|
|
} else
|
|
{
|
|
self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
|
|
}
|
|
}, obj);
|
|
}
|
|
|
|
function agentCoreIsStable() {
|
|
parent.agentStats.coreIsStableCount++;
|
|
|
|
// Check that the mesh exists
|
|
const mesh = parent.meshes[obj.dbMeshKey];
|
|
if (mesh == null) {
|
|
parent.agentStats.meshDoesNotExistCount++;
|
|
// TODO: Mark this agent as part of a mesh that does not exists.
|
|
return; // Probably not worth doing anything else. Hold this agent.
|
|
}
|
|
|
|
// Check if this is a recovery agent
|
|
if (obj.agentInfo.capabilities & 0x40) {
|
|
recoveryAgentCoreIsStable(mesh);
|
|
return;
|
|
}
|
|
|
|
// Fetch the the diagnostic agent nodeid
|
|
db.Get('ra' + obj.dbNodeKey, function (err, nodes) {
|
|
if ((nodes != null) && (nodes.length == 1)) {
|
|
obj.diagnosticNodeKey = nodes[0].daid;
|
|
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.diagnosticNodeKey } }));
|
|
}
|
|
});
|
|
|
|
// Send Intel AMT policy
|
|
if (obj.agentExeInfo && (obj.agentExeInfo.amt == true) && (mesh.amt != null)) { // Only send Intel AMT policy to agents what could have AMT.
|
|
try { obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: completeIntelAmtPolicy(common.Clone(mesh.amt)) })); } catch (ex) { }
|
|
}
|
|
|
|
// Fetch system information
|
|
db.GetHash('si' + obj.dbNodeKey, function (err, results) {
|
|
if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
|
|
});
|
|
|
|
// 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
|
|
db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
|
|
if ((iplocs != null) && (iplocs.length == 1)) {
|
|
// We have a location in the database for this remote IP
|
|
const 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);
|
|
}
|
|
} else {
|
|
// Check if we need to ask for the IP location
|
|
var doIpLocation = 0;
|
|
if (device.iploc == null) {
|
|
doIpLocation = 1;
|
|
} else {
|
|
const 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; }
|
|
}
|
|
}
|
|
|
|
// If we need to ask for IP location, see if we have the quota to do it.
|
|
if (doIpLocation > 0) {
|
|
db.getValueOfTheDay('ipLocationRequestLimitor', 10, function (ipLocationLimitor) {
|
|
if ((ipLocationLimitor != null) && (ipLocationLimitor.value > 0)) {
|
|
ipLocationLimitor.value--;
|
|
db.Set(ipLocationLimitor);
|
|
obj.send(JSON.stringify({ action: 'iplocation' }));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if (parent.parent.pluginHandler != null) {
|
|
parent.parent.pluginHandler.callHook('hook_agentCoreIsStable', obj, parent);
|
|
}
|
|
}
|
|
|
|
// Get the web certificate private key hash for the specified domain
|
|
function getWebCertHash(domain) {
|
|
const hash = parent.webCertificateHashs[domain.id];
|
|
if (hash != null) return hash;
|
|
return parent.webCertificateHash;
|
|
}
|
|
|
|
// Get the web certificate hash for the specified domain
|
|
function getWebCertFullHash(domain) {
|
|
const hash = parent.webCertificateFullHashs[domain.id];
|
|
if (hash != null) return hash;
|
|
return parent.webCertificateFullHash;
|
|
}
|
|
|
|
// Verify the agent signature
|
|
function processAgentSignature(msg) {
|
|
if (args.ignoreagenthashcheck !== true) {
|
|
var verified = false;
|
|
|
|
if (msg.length != 384) {
|
|
// Verify a PKCS7 signature.
|
|
var msgDer = null;
|
|
try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { }
|
|
if (msgDer != null) {
|
|
try {
|
|
var p7 = forge.pkcs7.messageFromAsn1(msgDer);
|
|
var sig = p7.rawCapture.signature;
|
|
|
|
// Verify with key hash
|
|
var buf = Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary');
|
|
var verifier = 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(domain) + obj.nonce + obj.agentnonce, 'binary');
|
|
verifier = parent.crypto.createVerify('RSA-SHA384');
|
|
verifier.update(buf);
|
|
verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
|
|
}
|
|
if (verified == false) {
|
|
// Not a valid signature
|
|
parent.agentStats.invalidPkcsSignatureCount++;
|
|
return false;
|
|
}
|
|
} catch (ex) { };
|
|
}
|
|
}
|
|
|
|
if (verified == false) {
|
|
// Verify the RSA signature. This is the fast way, without using forge.
|
|
const verify = parent.crypto.createVerify('SHA384');
|
|
verify.end(Buffer.from(getWebCertHash(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 = parent.crypto.createVerify('SHA384');
|
|
verify2.end(Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the full cert hash
|
|
if (verify2.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
|
|
parent.agentStats.invalidRsaSignatureCount++;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connection is a success, clean up
|
|
obj.nodeid = obj.unauth.nodeid;
|
|
obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
|
|
delete obj.nonce;
|
|
delete obj.agentnonce;
|
|
delete obj.unauth;
|
|
delete obj.receivedCommands;
|
|
if (obj.unauthsign) delete obj.unauthsign;
|
|
parent.agentStats.verifiedAgentConnectionCount++;
|
|
parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').');
|
|
obj.authenticated = 1;
|
|
return true;
|
|
}
|
|
|
|
// Process incoming agent JSON data
|
|
function processAgentData(msg) {
|
|
var i, str = msg.toString('utf8'), command = null;
|
|
if (str[0] == '{') {
|
|
try { command = JSON.parse(str); } catch (ex) { parent.agentStats.invalidJsonCount++; console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex); return; } // If the command can't be parsed, ignore it.
|
|
if (typeof command != 'object') { return; }
|
|
switch (command.action) {
|
|
case 'msg':
|
|
{
|
|
// Route a message
|
|
parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey);
|
|
break;
|
|
}
|
|
case 'coreinfo':
|
|
{
|
|
// Sent by the agent to update agent information
|
|
ChangeAgentCoreInfo(command);
|
|
break;
|
|
}
|
|
case 'smbios':
|
|
{
|
|
// Store the RAW SMBios table of this computer
|
|
// We store SMBIOS information as a string because we don't want the MongoDB to attempt to store all of the sub-documents seperatly.
|
|
// If an agent sends an insanely large SMBIOS table, don't store it.
|
|
try {
|
|
var smbiosstr = JSON.stringify(command.value);
|
|
if (smbiosstr.length < 65535) { db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: smbiosstr }); }
|
|
} catch (ex) { }
|
|
|
|
// Event the node interface information change (This is a lot of traffic, probably don't need this).
|
|
//parent.parent.DispatchEvent(['*', obj.meshid], obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
|
|
|
|
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';
|
|
db.Set(command);
|
|
|
|
// Event the node interface information change
|
|
parent.parent.DispatchEvent(['*', obj.meshid], obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
|
|
|
|
break;
|
|
}
|
|
case 'iplocation':
|
|
{
|
|
// Sent by the agent to update location information
|
|
if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) {
|
|
var x = {};
|
|
x.publicip = command.value.ip;
|
|
x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000));
|
|
ChangeAgentLocationInfo(x);
|
|
command.value._id = 'iploc_' + command.value.ip;
|
|
command.value.type = 'iploc';
|
|
command.value.date = Date.now();
|
|
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' }
|
|
}
|
|
break;
|
|
}
|
|
case 'mc1migration':
|
|
{
|
|
if (command.oldnodeid.length != 64) break;
|
|
const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase();
|
|
db.Get(oldNodeKey, function (err, nodes) {
|
|
if ((nodes != null) && (nodes.length != 1)) return;
|
|
const node = nodes[0];
|
|
if (node.meshid == obj.dbMeshKey) {
|
|
// Update the device name & host
|
|
const newNode = { "name": node.name };
|
|
if (node.intelamt != null) { newNode.intelamt = node.intelamt; }
|
|
ChangeAgentCoreInfo(newNode);
|
|
|
|
// Delete this node including network interface information and events
|
|
db.Remove(node._id);
|
|
db.Remove('if' + node._id);
|
|
|
|
// Event node deletion
|
|
const change = 'Migrated device ' + node.name;
|
|
parent.parent.DispatchEvent(['*', node.meshid], obj, { etype: 'node', action: 'removenode', nodeid: node._id, msg: change, domain: node.domain });
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case 'openUrl':
|
|
{
|
|
// Sent by the agent to return the status of a open URL action.
|
|
// Nothing is done right now.
|
|
break;
|
|
}
|
|
case 'log':
|
|
{
|
|
// Log a value in the event log
|
|
if ((typeof command.msg == 'string') && (command.msg.length < 4096)) {
|
|
var event = { etype: 'node', action: 'agentlog', nodeid: obj.dbNodeKey, domain: domain.id, msg: command.msg };
|
|
var targets = ['*', obj.dbMeshKey];
|
|
if (typeof command.userid == 'string') {
|
|
var loguser = parent.users[command.userid];
|
|
if (loguser) { event.userid = command.userid; event.username = loguser.name; targets.push(command.userid); }
|
|
}
|
|
if ((typeof command.sessionid == 'string') && (command.sessionid.length < 500)) { event.sessionid = command.sessionid; }
|
|
parent.parent.DispatchEvent(targets, obj, event);
|
|
}
|
|
break;
|
|
}
|
|
case 'ping': { sendPong(); break; }
|
|
case 'pong': { break; }
|
|
case 'getScript':
|
|
{
|
|
// Used by the agent to get configuration scripts.
|
|
if (command.type == 1) {
|
|
parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) {
|
|
obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() }));
|
|
});
|
|
} else if (command.type == 2) {
|
|
parent.getCiraCleanupScript(function (script) {
|
|
obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() }));
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case 'acmactivate':
|
|
{
|
|
if (obj.agentInfo.capabilities & 0x20) return; // If this is a temporary device, don't do ACM activation.
|
|
|
|
// Get the current Intel AMT policy
|
|
var mesh = parent.meshes[obj.dbMeshKey];
|
|
if ((mesh == null) || (mesh.amt == null) || (mesh.amt.type != 3) || (domain.amtacmactivation == null) || (domain.amtacmactivation.acmmatch == null) || (mesh.amt.password == null)) break; // If this is not the right policy, ignore this.
|
|
|
|
// Get the Intel AMT admin password, randomize if needed.
|
|
var amtpassword = ((mesh.amt.password == '') ? getRandomAmtPassword() : mesh.amt.password);
|
|
if (checkAmtPassword(amtpassword) == false) return; // Invalid Intel AMT password, this should never happen.
|
|
|
|
// Agent is asking the server to sign an Intel AMT ACM activation request
|
|
var signResponse = parent.parent.certificateOperations.signAcmRequest(domain, command, 'admin', amtpassword, obj.remoteaddrport, obj.dbNodeKey, obj.dbMeshKey, obj.agentInfo.computerName, obj.agentInfo.agentId); // TODO: Place account credentials!!!
|
|
if ((signResponse != null) && (signResponse.error == null)) {
|
|
// Log this activation event
|
|
var event = { etype: 'node', action: 'amtactivate', nodeid: obj.dbNodeKey, domain: domain.id, msg: 'Device requested Intel AMT ACM activation, FQDN: ' + command.fqdn, ip: obj.remoteaddrport };
|
|
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, event);
|
|
|
|
// Update the device Intel AMT information
|
|
ChangeAgentCoreInfo({ "intelamt": { user: 'admin', pass: amtpassword, uuid: command.uuid, realm: command.realm } });
|
|
|
|
// Send the activation response
|
|
obj.send(JSON.stringify(signResponse));
|
|
}
|
|
break;
|
|
}
|
|
case 'diagnostic':
|
|
{
|
|
if (typeof command.value == 'object') {
|
|
switch (command.value.command) {
|
|
case 'register': {
|
|
// Only main agent can do this
|
|
if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64))
|
|
{
|
|
// Store links to diagnostic agent id
|
|
var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value);
|
|
db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent
|
|
db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent
|
|
}
|
|
break;
|
|
}
|
|
case 'query': {
|
|
// Only the diagnostic agent can do
|
|
if ((obj.agentInfo.capabilities & 0x40) != 0) {
|
|
// Return nodeid of main agent + connection status
|
|
db.Get('da' + obj.dbNodeKey, function (err, nodes) {
|
|
if (nodes.length == 1) {
|
|
obj.realNodeKey = nodes[0].raid;
|
|
|
|
// Get agent connection state
|
|
var agentConnected = false;
|
|
var state = parent.parent.GetConnectivityState(obj.realNodeKey);
|
|
if (state) { agentConnected = ((state.connectivity & 1) != 0) }
|
|
|
|
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } }));
|
|
} else {
|
|
obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
case 'log': {
|
|
if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256)) {
|
|
// If this is a diagnostic agent, log the event in the log of the main agent
|
|
var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, domain: domain.id, msg: command.value.value };
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, event);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'sysinfo': {
|
|
if ((typeof command.data == 'object') && (typeof command.data.hash == 'string')) {
|
|
command.data._id = 'si' + obj.dbNodeKey;
|
|
command.data.type = 'sysinfo';
|
|
command.data.domain = domain.id;
|
|
command.data.time = Date.now();
|
|
db.Set(command.data); // Update system information in the database.
|
|
|
|
// Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed
|
|
var event = { etype: 'node', action: 'sysinfohash', nodeid: obj.dbNodeKey, domain: domain.id, hash: command.data.hash, nolog: 1 };
|
|
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, event);
|
|
}
|
|
break;
|
|
}
|
|
case 'sysinfocheck': {
|
|
// Check system information update
|
|
db.GetHash('si' + obj.dbNodeKey, function (err, results) {
|
|
if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
|
|
});
|
|
break;
|
|
}
|
|
case 'plugin': {
|
|
if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
|
|
try {
|
|
var pluginHandler = require('./pluginHandler.js').pluginHandler(parent.parent);
|
|
pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
|
|
} catch (e) {
|
|
console.log('Error loading plugin handler (' + e + ')');
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
parent.agentStats.unknownAgentActionCount++;
|
|
console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + command.action + '.');
|
|
break;
|
|
}
|
|
}
|
|
if (parent.parent.pluginHandler != null) {
|
|
parent.parent.pluginHandler.callHook('hook_processAgentData', command, obj, parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Change the current core information string and event it
|
|
function ChangeAgentCoreInfo(command) {
|
|
if (obj.agentInfo.capabilities & 0x40) return;
|
|
if ((command == null) || (command == null)) return; // Safety, should never happen.
|
|
|
|
// Check that the mesh exists
|
|
const mesh = parent.meshes[obj.dbMeshKey];
|
|
if (mesh == null) return;
|
|
|
|
// Get the node and change it if needed
|
|
db.Get(obj.dbNodeKey, function (err, nodes) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
|
|
if ((nodes == null) || (nodes.length != 1)) return;
|
|
const device = nodes[0];
|
|
if (device.agent) {
|
|
var changes = [], change = 0, log = 0;
|
|
|
|
//if (command.users) { console.log(command.users); }
|
|
|
|
// Check if anything changes
|
|
if (command.name && (typeof command.name == 'string') && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); }
|
|
if ((command.caps != null) && (device.agent.core != command.value)) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; } // Don't save this as an event to the db.
|
|
if ((command.caps != null) && ((device.agent.caps & 0xFFFFFFE7) != (command.caps & 0xFFFFFFE7))) { device.agent.caps = ((device.agent.caps & 24) + (command.caps & 0xFFFFFFE7)); change = 1; } // Allow Javascript on the agent to change all capabilities except console and javascript support, Don't save this as an event to the db.
|
|
if ((command.osdesc != null) && (typeof command.osdesc == 'string') && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; changes.push('os desc'); } // Don't save this as an event to the db.
|
|
if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
|
|
if (command.intelamt) {
|
|
if (!device.intelamt) { device.intelamt = {}; }
|
|
if ((command.intelamt.ver != null) && (typeof command.intelamt.ver == 'string') && (command.intelamt.ver.length < 12) && (device.intelamt.ver != command.intelamt.ver)) { changes.push('AMT version'); device.intelamt.ver = command.intelamt.ver; change = 1; log = 1; }
|
|
if ((command.intelamt.sku != null) && (typeof command.intelamt.sku == 'number') && (device.intelamt.sku !== command.intelamt.sku)) { changes.push('AMT SKU'); device.intelamt.sku = command.intelamt.sku; change = 1; log = 1; }
|
|
if ((command.intelamt.state != null) && (typeof command.intelamt.state == 'number') && (device.intelamt.state != command.intelamt.state)) { changes.push('AMT state'); device.intelamt.state = command.intelamt.state; change = 1; log = 1; }
|
|
if ((command.intelamt.flags != null) && (typeof command.intelamt.flags == 'number') && (device.intelamt.flags != command.intelamt.flags)) {
|
|
if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.flags + ')'); }
|
|
device.intelamt.flags = command.intelamt.flags; change = 1; log = 1;
|
|
}
|
|
if ((command.intelamt.realm != null) && (typeof command.intelamt.realm == 'string') && (device.intelamt.realm != command.intelamt.realm)) { changes.push('AMT realm'); device.intelamt.realm = command.intelamt.realm; change = 1; log = 1; }
|
|
if ((command.intelamt.host != null) && (typeof command.intelamt.host == 'string') && (device.intelamt.host != command.intelamt.host)) { changes.push('AMT host'); device.intelamt.host = command.intelamt.host; change = 1; log = 1; }
|
|
if ((command.intelamt.uuid != null) && (typeof command.intelamt.uuid == 'string') && (device.intelamt.uuid != command.intelamt.uuid)) { changes.push('AMT uuid'); device.intelamt.uuid = command.intelamt.uuid; change = 1; log = 1; }
|
|
if ((command.intelamt.user != null) && (typeof command.intelamt.user == 'string') && (device.intelamt.user != command.intelamt.user)) { changes.push('AMT user'); device.intelamt.user = command.intelamt.user; change = 1; log = 1; }
|
|
if ((command.intelamt.pass != null) && (typeof command.intelamt.pass == 'string') && (device.intelamt.pass != command.intelamt.pass)) { changes.push('AMT pass'); device.intelamt.pass = command.intelamt.pass; change = 1; log = 1; }
|
|
}
|
|
if (command.av) {
|
|
if (!device.av) { device.av = []; }
|
|
if ((command.av != null) && (JSON.stringify(device.av) != JSON.stringify(command.av))) { /*changes.push('AV status');*/ device.av = command.av; change = 1; log = 1; }
|
|
}
|
|
|
|
if ((command.users != null) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db.
|
|
if ((mesh.mtype == 2) && (!args.wanonly)) {
|
|
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
|
|
if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
|
|
// 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, event the new device
|
|
if (change == 1) {
|
|
// Do some clean up if needed, these values should not be in the database.
|
|
if (device.conn != null) { delete device.conn; }
|
|
if (device.pwr != null) { delete device.pwr; }
|
|
if (device.agct != null) { delete device.agct; }
|
|
if (device.cict != null) { delete device.cict; }
|
|
|
|
// Save to the database
|
|
db.Set(device);
|
|
|
|
// Event the node change
|
|
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
|
|
if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
|
|
if ((log == 0) || ((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || (changes.length == 0)) { event.nolog = 1; } // If this is a temporary device, don't log changes
|
|
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
|
|
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Change the current core information string and event it
|
|
function ChangeAgentLocationInfo(command) {
|
|
if (obj.agentInfo.capabilities & 0x40) return;
|
|
if ((command == null) || (command == null)) { return; } // Safety, should never happen.
|
|
|
|
// Check that the mesh exists
|
|
const mesh = parent.meshes[obj.dbMeshKey];
|
|
if (mesh == null) return;
|
|
|
|
// Get the node and change it if needed
|
|
db.Get(obj.dbNodeKey, function (err, nodes) {
|
|
if ((nodes == null) || (nodes.length != 1)) { return; }
|
|
const 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) {
|
|
// Do some clean up if needed, these values should not be in the database.
|
|
if (device.conn != null) { delete device.conn; }
|
|
if (device.pwr != null) { delete device.pwr; }
|
|
if (device.agct != null) { delete device.agct; }
|
|
if (device.cict != null) { delete device.cict; }
|
|
|
|
// Save the device
|
|
db.Set(device);
|
|
|
|
// Event the node change
|
|
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), msg: 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', ') };
|
|
if (obj.agentInfo.capabilities & 0x20) { event.nolog = 1; } // If this is a temporary device, don't log changes
|
|
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
|
|
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update the mesh agent tab in the database
|
|
function ChangeAgentTag(tag) {
|
|
if (obj.agentInfo.capabilities & 0x40) return;
|
|
if (tag.length == 0) { tag = null; }
|
|
// Get the node and change it if needed
|
|
db.Get(obj.dbNodeKey, function (err, nodes) {
|
|
if ((nodes == null) || (nodes.length != 1)) return;
|
|
const device = nodes[0];
|
|
if (device.agent) {
|
|
if (device.agent.tag != tag) {
|
|
// Do some clean up if needed, these values should not be in the database.
|
|
if (device.conn != null) { delete device.conn; }
|
|
if (device.pwr != null) { delete device.pwr; }
|
|
if (device.agct != null) { delete device.agct; }
|
|
if (device.cict != null) { delete device.cict; }
|
|
|
|
// Set the new tag
|
|
device.agent.tag = tag;
|
|
db.Set(device);
|
|
|
|
// Event the node change
|
|
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), nolog: 1 };
|
|
if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
|
|
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Generate a random Intel AMT password
|
|
function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
|
|
function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
|
|
|
|
return obj;
|
|
};
|