MeshCentral/meshagent.js

1163 lines
71 KiB
JavaScript
Raw Normal View History

2017-08-28 12:27:45 -04:00
/**
2018-01-04 15:15:21 -05:00
* @description MeshCentral MeshAgent communication module
2017-08-28 12:27:45 -04:00
* @author Ylian Saint-Hilaire & Bryan Roe
2019-01-03 19:22:15 -05:00
* @copyright Intel Corporation 2018-2019
2018-01-04 15:15:21 -05:00
* @license Apache-2.0
2017-08-28 12:27:45 -04:00
* @version v0.0.1
*/
2018-08-29 20:40:30 -04:00
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict";
2018-08-27 15:24:15 -04:00
2017-08-28 12:27:45 -04:00
// Construct a MeshAgent object, called upon connection
module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
2019-03-08 01:47:27 -05:00
const forge = parent.parent.certificateOperations.forge;
const common = parent.parent.common;
const agentUpdateBlockSize = 65531;
2019-03-08 01:47:27 -05:00
2017-08-28 12:27:45 -04:00
var obj = {};
obj.domain = domain;
2019-03-08 01:47:27 -05:00
obj.authenticated = 0;
2017-08-28 12:27:45 -04:00
obj.receivedCommands = 0;
obj.agentCoreCheck = 0;
2019-03-08 01:47:27 -05:00
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');
2017-10-25 12:58:14 -04:00
ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes
2019-03-08 01:47:27 -05:00
//obj.nodeid = null;
//obj.meshid = null;
//obj.dbNodeKey = null;
//obj.dbMeshKey = null;
//obj.connectTime = null;
//obj.agentInfo = null;
2017-08-28 12:27:45 -04:00
// Send a message to the mesh agent
2019-03-08 01:47:27 -05:00
obj.send = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary'), func); } else { ws.send(data, func); } } catch (e) { } };
2017-08-28 12:27:45 -04:00
// Disconnect this agent
obj.close = function (arg) {
2019-02-25 20:30:19 -05:00
obj.authenticated = -1;
2019-03-08 01:47:27 -05:00
if ((arg == 1) || (arg == null)) { try { ws.close(); if (obj.nodeid != null) { parent.parent.debug(1, '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(1, 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Hard close, close the TCP socket
2019-02-25 20:30:19 -05:00
// If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
// Remove this agent from the webserver list
2019-03-08 01:47:27 -05:00
if (parent.wsagents[obj.dbNodeKey] == obj) {
delete parent.wsagents[obj.dbNodeKey];
parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1);
2017-08-28 12:27:45 -04:00
}
// Get the current mesh
2019-03-08 01:47:27 -05:00
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))) {
2018-04-12 14:15:01 -04:00
// Delete this node including network interface information and events
2019-03-08 01:47:27 -05:00
db.Remove(obj.dbNodeKey); // Remove node with that id
db.Remove('if' + obj.dbNodeKey); // Remove interface information
db.Remove('nt' + obj.dbNodeKey); // Remove notes
db.Remove('lc' + obj.dbNodeKey); // Remove last connect time
db.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
2018-04-12 14:15:01 -04:00
// Event node deletion
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, { etype: 'node', action: 'removenode', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
2018-04-12 14:15:01 -04:00
// Disconnect all connections if needed
2019-03-08 01:47:27 -05:00
const state = parent.parent.GetConnectivityState(obj.dbNodeKey);
2018-04-12 14:15:01 -04:00
if ((state != null) && (state.connectivity != null)) {
2019-03-08 01:47:27 -05:00
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
2018-04-12 14:15:01 -04:00
}
2018-09-25 14:51:40 -04:00
} else {
// Update the last connect time
2019-03-08 01:47:27 -05:00
if (obj.authenticated == 2) { db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport }); }
2018-04-12 14:15:01 -04:00
}
// 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;
}
// 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"]);
2018-08-29 20:40:30 -04:00
};
2017-08-28 12:27:45 -04:00
// When data is received from the mesh agent web socket
ws.on('message', function (msg) {
if (msg.length < 2) return;
if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
2017-08-28 12:27:45 -04:00
if (obj.authenticated == 2) { // We are authenticated
if ((obj.agentUpdate == null) && (msg.charCodeAt(0) == 123)) { processAgentData(msg); } // Only process JSON messages if meshagent update is not in progress
2017-08-28 12:27:45 -04:00
if (msg.length < 2) return;
2019-03-08 01:47:27 -05:00
const cmdid = common.ReadShort(msg, 0);
2017-08-28 12:27:45 -04:00
if (cmdid == 11) { // MeshCommand_CoreModuleHash
2018-09-21 19:34:35 -04: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 12:27:45 -04:00
// Mesh core hash, sent by agent with the hash of the current mesh core.
// If we are using a custom core, don't try to update it.
if (obj.agentCoreCheck == 1000) {
2019-03-08 01:47:27 -05:00
obj.send(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;
}
2018-12-29 18:24:33 -05:00
// Get the current meshcore hash
const agentMeshCoreHash = (msg.length == 52) ? msg.substring(4, 52) : null;
2019-03-08 01:47:27 -05:00
// 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;
2019-03-08 01:47:27 -05:00
obj.send(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;
}
2019-03-08 01:47:27 -05:00
// We need to check if the core is current. Figure out what core we need.
var corename;
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.
2018-12-29 18:24:33 -05:00
if (corename != null) {
2019-03-08 01:47:27 -05:00
const meshcorehash = parent.parent.defaultMeshCoresHash[corename];
2018-12-29 18:24:33 -05:00
if (agentMeshCoreHash != meshcorehash) {
2019-01-21 17:05:50 -05:00
if ((obj.agentCoreCheck < 5) || (obj.agentCoreCheck == 1001)) {
2018-12-29 18:24:33 -05:00
if (meshcorehash == null) {
2019-01-17 18:07:34 -05:00
// Clear the core
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(10) + common.ShortToStr(0)); // MeshCommand_CoreModule, ask mesh agent to clear the core
parent.parent.debug(1, 'Clearing core');
2018-12-29 18:24:33 -05:00
} else {
// Update new core
2019-03-08 01:47:27 -05:00
//obj.send(common.ShortToStr(10) + common.ShortToStr(0) + meshcorehash + parent.parent.defaultMeshCores[corename]); // MeshCommand_CoreModule, start core update
//parent.parent.debug(1, 'Updating code ' + corename);
2019-02-08 17:17:35 -05:00
// Update new core with task limiting so not to flood the server. This is a high priority task.
obj.agentCoreUpdatePending = true;
2019-03-08 01:47:27 -05:00
parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
2019-02-25 20:30:19 -05:00
if (obj.authenticated == 2) {
// Send the updated code.
2019-03-08 01:47:27 -05:00
delete obj.agentCoreUpdatePending;
obj.send(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core, function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update
parent.parent.debug(1, 'Updating code ' + argument.name);
2019-02-25 20:30:19 -05:00
agentCoreIsStable();
} else {
// This agent is probably disconnected, nothing to do.
2019-03-08 01:47:27 -05:00
parent.parent.taskLimiter.completed(taskid);
2019-02-25 20:30:19 -05:00
}
2019-03-08 01:47:27 -05:00
}, { hash: meshcorehash, core: parent.parent.defaultMeshCores[corename], name: corename }, 0);
2018-12-29 18:24:33 -05:00
}
obj.agentCoreCheck++;
}
} else {
obj.agentCoreCheck = 0;
2019-03-08 01:47:27 -05:00
obj.send(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.
2018-12-29 18:24:33 -05:00
}
}
/*
2017-08-28 12:27:45 -04:00
// 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); }
2019-03-08 01:47:27 -05:00
if ((agentMeshCoreHash != parent.parent.defaultMeshCoreHash) && (agentMeshCoreHash != parent.parent.defaultMeshCoreNoMeiHash)) {
2017-08-28 12:27:45 -04:00
if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
2019-03-08 01:47:27 -05:00
if (parent.parent.defaultMeshCoreHash == null) {
2017-08-28 12:27:45 -04:00
// Update no core
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(10) + common.ShortToStr(0)); // Command 10, ask mesh agent to clear the core
2017-08-28 12:27:45 -04:00
} else {
// Update new core
2019-03-08 01:47:27 -05:00
if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].amt == true) {
obj.send(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 {
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(10) + common.ShortToStr(0) + parent.parent.defaultMeshCoreNoMeiHash + parent.parent.defaultMeshCoreNoMei); // Command 10, ask mesh agent to set the core (No MEI)
}
2017-08-28 12:27:45 -04:00
}
obj.agentCoreCheck++;
}
} else {
obj.agentCoreCheck = 0;
}
2018-12-29 18:24:33 -05:00
*/
2017-08-28 12:27:45 -04:00
}
else if (cmdid == 12) { // MeshCommand_AgentHash
if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
2019-03-08 01:47:27 -05:00
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')) {
2019-02-08 17:17:35 -05:00
// Mesh agent update required, do it using task limiter so not to flood the network. Medium priority task.
2019-03-08 01:47:27 -05:00
parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; } // If agent disconnection, complete and exit now.
if (obj.nodeid != null) { parent.parent.debug(1, 'Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); }
if (obj.agentExeInfo.data == null) {
// Read the agent from disk
2019-03-08 01:47:27 -05:00
parent.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
if (obj.agentExeInfo == null) return; // Agent disconnected during this call.
if (err) { return console.error(err); }
2019-03-08 01:47:27 -05:00
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.
2019-03-08 01:47:27 -05:00
obj.send(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.");
2019-03-08 01:47:27 -05:00
obj.send(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;
2019-03-08 01:47:27 -05:00
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
delete obj.agentUpdate;
} else {
// Send the first block to the agent
obj.agentUpdate.ptr += bytesRead;
//console.log("Agent update send first block: " + bytesRead);
obj.send(obj.agentUpdate.buf); // Command 14, mesh agent first data block
}
});
});
} else {
// Send the agent from RAM
2019-03-08 01:47:27 -05:00
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.
2019-03-08 01:47:27 -05:00
obj.send(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
2019-03-08 01:47:27 -05:00
obj.send(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;
2019-03-08 01:47:27 -05:00
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.send(obj.agentUpdate.buf); // Command 14, mesh agent first data block
} else {
// Error
2019-03-08 01:47:27 -05:00
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
delete obj.agentUpdate;
}
}
2019-02-08 17:17:35 -05:00
}, null, 1);
2019-01-21 17:05:50 -05:00
} else {
// Check the mesh core, if the agent is capable of running one
2019-03-08 01:47:27 -05:00
if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) {
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
}
2017-08-28 12:27:45 -04:00
}
}
}
else if (cmdid == 14) { // MeshCommand_AgentBinaryBlock
if ((msg.length == 4) && (obj.agentUpdate != null)) {
2019-03-08 01:47:27 -05:00
const status = common.ReadShort(msg, 2);
2017-08-28 12:27:45 -04:00
if (status == 1) {
if (obj.agentExeInfo.data == null) {
// Read the agent from disk
2019-03-08 01:47:27 -05:00
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.
2019-03-08 01:47:27 -05:00
if ((err != null) || (bytesRead < 0)) {
// Error reading the agent file, stop here.
try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
delete obj.agentUpdate;
} else {
// Send the next block to the agent
obj.agentUpdate.ptr += bytesRead;
//console.log("Agent update send next block", obj.agentUpdate.ptr, bytesRead);
if (bytesRead == agentUpdateBlockSize) { obj.send(obj.agentUpdate.buf); } else { obj.send(obj.agentUpdate.buf.slice(0, bytesRead + 4)); } // Command 14, mesh agent next data block
if (bytesRead < agentUpdateBlockSize) {
//console.log("Agent update sent from disk.");
obj.send(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;
}
}
2019-03-08 01:47:27 -05:00
});
2017-08-28 12:27:45 -04:00
} else {
// Send the agent from RAM
2019-03-08 01:47:27 -05:00
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.send(obj.agentUpdate.buf); } else { obj.send(obj.agentUpdate.buf.slice(0, len + 4)); } // Command 14, mesh agent next data block
obj.agentUpdate.ptr += len;
}
2017-08-28 12:27:45 -04:00
if (obj.agentUpdate.ptr == obj.agentExeInfo.data.length) {
//console.log("Agent update sent from RAM.");
2019-03-08 01:47:27 -05:00
obj.send(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;
2017-08-28 12:27:45 -04:00
}
}
}
}
}
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);
2017-08-28 12:27:45 -04:00
}
} else if (obj.authenticated < 2) { // We are not authenticated
2019-03-08 01:47:27 -05:00
const cmd = common.ReadShort(msg, 0);
2017-08-28 12:27:45 -04:00
if (cmd == 1) {
// Agent authentication request
if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
2017-08-28 12:27:45 -04:00
obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
2019-03-08 01:47:27 -05:00
if (args.ignoreagenthashcheck === true) {
// Send the agent web hash back to the agent
2019-03-08 01:47:27 -05:00
obj.send(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-03-08 01:47:27 -05:00
if ((getWebCertHash(domain) != msg.substring(2, 50)) && (getWebCertFullHash(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(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;
}
}
2017-08-28 12:27:45 -04:00
// 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
2019-03-08 01:47:27 -05:00
parent.parent.certificateOperations.acceleratorPerformSignature(1, msg.substring(2) + obj.nonce, obj, function (obj2, signature) {
// Send back our certificate + signature
obj2.send(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
2019-03-08 01:47:27 -05:00
parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, obj, function (obj2, signature) {
// Send back our certificate + signature
obj2.send(common.ShortToStr(2) + common.ShortToStr(parent.agentCertificateAsn1.length) + parent.agentCertificateAsn1 + signature); // Command 2, certificate + signature
});
}
}
2017-08-28 12:27:45 -04:00
// Check the agent signature if we can
if (obj.unauthsign != null) {
2018-11-01 18:01:21 -04:00
if (processAgentSignature(obj.unauthsign) == false) { console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; } else { completeAgentConnection(); }
2017-08-28 12:27:45 -04:00
}
}
else if (cmd == 2) {
// Agent certificate
if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return;
obj.receivedCommands += 2; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Decode the certificate
2019-03-08 01:47:27 -05:00
const certlen = common.ReadShort(msg, 2);
2017-08-28 12:27:45 -04:00
obj.unauth = {};
2019-03-08 01:47:27 -05:00
try { obj.unauth.nodeid = Buffer.from(forge.pki.getPublicKeyFingerprint(forge.pki.certificateFromAsn1(forge.asn1.fromDer(msg.substring(4, 4 + certlen))).publicKey, { md: forge.md.sha384.create() }).data, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } catch (ex) { console.log(ex); return; }
obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
2017-08-28 12:27:45 -04:00
// Check the agent signature if we can
2018-11-01 18:01:21 -04:00
if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { if (processAgentSignature(msg.substring(4 + certlen)) == false) { console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; } }
2017-08-28 12:27:45 -04:00
completeAgentConnection();
}
else if (cmd == 3) {
// Agent meshid
if ((msg.length < 72) || ((obj.receivedCommands & 4) != 0)) return;
2017-08-28 12:27:45 -04:00
obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
// Set the meshid
obj.agentInfo = {};
2019-03-08 01:47:27 -05:00
obj.agentInfo.infoVersion = common.ReadInt(msg, 2);
obj.agentInfo.agentId = common.ReadInt(msg, 6);
obj.agentInfo.agentVersion = common.ReadInt(msg, 10);
obj.agentInfo.platformType = common.ReadInt(msg, 14);
2017-10-16 23:11:03 -04:00
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);
2019-03-08 01:47:27 -05:00
obj.agentInfo.capabilities = common.ReadInt(msg, 66);
const computerNameLen = common.ReadShort(msg, 70);
obj.agentInfo.computerName = msg.substring(72, 72 + computerNameLen);
2019-03-08 01:47:27 -05:00
obj.dbMeshKey = 'mesh/' + domain.id + '/' + obj.meshid;
2017-08-28 12:27:45 -04:00
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.
2019-03-08 01:47:27 -05:00
if ((msg.substring(2, 34) == parent.swarmCertificateHash256) || (msg.substring(2, 50) == parent.swarmCertificateHash384)) { obj.useSwarmCert = true; }
2017-08-28 12:27:45 -04:00
}
}
});
// If error, do nothing
ws.on('error', function (err) { console.log('AGENT WSERR: ' + err); obj.close(0); });
2017-08-28 12:27:45 -04:00
// If the mesh agent web socket is closed, clean up.
2019-01-22 20:47:51 -05:00
ws.on('close', function (req) {
if (obj.nodeid != null) {
2019-03-08 01:47:27 -05:00
const agentId = (obj.agentInfo && obj.agentInfo.agentId) ? obj.agentInfo.agentId : 'Unknown';
2019-02-12 16:37:01 -05:00
//console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
2019-03-08 01:47:27 -05:00
parent.parent.debug(1, 'Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
// Log the agent disconnection
2019-03-08 01:47:27 -05:00
if (parent.wsagentsDisconnections[obj.nodeid] == null) {
parent.wsagentsDisconnections[obj.nodeid] = 1;
} else {
2019-03-08 01:47:27 -05:00
parent.wsagentsDisconnections[obj.nodeid] = ++parent.wsagentsDisconnections[obj.nodeid];
}
2019-01-22 20:47:51 -05:00
}
obj.close(0);
});
2019-03-08 01:47:27 -05:00
// ws._socket._parent.on('close', function (req) { if (obj.nodeid != null) { parent.parent.debug(1, 'Agent TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } });
2017-08-28 12:27:45 -04:00
// 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
2019-03-08 01:47:27 -05:00
if (args.ignoreagenthashcheck !== true) {
obj.send(common.ShortToStr(1) + getWebCertHash(domain) + obj.nonce); // Command 1, hash + nonce
}
2017-08-28 12:27:45 -04:00
// Return the mesh for this device, in some cases, we may auto-create the mesh.
function getMeshAutoCreate() {
2019-03-08 01:47:27 -05:00
const mesh = parent.meshes[obj.dbMeshKey];
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
2019-03-08 01:47:27 -05:00
const meshname = obj.meshid.substring(0, 18);
// Create a new mesh for this device
2019-03-08 01:47:27 -05:00
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 };
2019-03-08 01:47:27 -05:00
db.Set(common.escapeLinksFieldName(mesh));
parent.meshes[obj.dbMeshKey] = mesh;
2019-02-14 18:53:22 -05:00
if (adminUser.links == null) adminUser.links = {};
adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF };
2019-03-08 01:47:27 -05:00
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 });
}
2019-02-18 17:32:55 -05:00
} else {
if ((mesh != null) && (mesh.deleted != null) && (mesh.links)) {
2019-02-18 17:32:55 -05:00
// 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) {
2019-03-08 01:47:27 -05:00
const user = parent.users[userid];
2019-02-18 17:32:55 -05:00
if (user) {
if (user.links == null) { user.links = {}; }
if (user.links[mesh._id] == null) {
user.links[mesh._id] = { rights: mesh.links[userid].rights };
ids.push(user._id);
2019-03-08 01:47:27 -05:00
db.SetUser(user);
2019-02-18 17:32:55 -05:00
}
}
}
// Send out an event indicating this mesh was "created"
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', links: mesh.links, msg: 'Mesh undeleted: ' + mesh._id, domain: domain.id });
2019-02-18 17:32:55 -05:00
// Mark the mesh as active
delete mesh.deleted;
2019-03-08 01:47:27 -05:00
db.Set(common.escapeLinksFieldName(mesh));
2019-02-18 17:32:55 -05:00
}
}
return mesh;
}
2017-08-28 12:27:45 -04:00
// Once we get all the information about an agent, run this to hook everything up to the server
function completeAgentConnection() {
if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection) return;
obj.pendingCompleteAgentConnection = true;
// Check if we have too many agent sessions
if (typeof domain.limits.maxagentsessions == 'number') {
// Count the number of agent sessions for this domain
var domainAgentSessionCount = 0;
2019-03-08 01:47:27 -05:00
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) { return; } // Too many, hold the connection.
}
/*
2017-08-28 12:27:45 -04:00
// Check that the mesh exists
2019-03-08 01:47:27 -05:00
var mesh = parent.meshes[obj.dbMeshKey];
if (mesh == null) {
var holdConnection = true;
2019-03-08 01:47:27 -05:00
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 };
2019-03-08 01:47:27 -05:00
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 };
2019-03-08 01:47:27 -05:00
//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;
}
}
2018-11-01 18:01:21 -04:00
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
2019-03-08 01:47:27 -05:00
db.Get(obj.dbNodeKey, function (err, nodes) {
var device;
// See if this node exists in the database
if (nodes.length == 0) {
// This device does not exist, use the meshid given by the device
// See if this mesh exists, if it does not we may want to create it.
2019-03-08 01:47:27 -05:00
const 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.
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.
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
return;
2019-02-18 17:32:55 -05:00
}
// Mark when this device connected
obj.connectTime = Date.now();
2019-03-08 01:47:27 -05:00
db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport });
// 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 };
2019-03-08 01:47:27 -05:00
db.Set(device);
// Event the new node
if (obj.agentInfo.capabilities & 0x20) {
// This is a temporary agent, don't log.
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', obj.dbMeshKey], obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 });
2017-08-28 12:27:45 -04:00
} else {
2019-03-08 01:47:27 -05:00
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 12:27:45 -04:00
}
} 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.
2019-03-08 01:47:27 -05:00
const 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.
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.
console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
return;
}
// Mark when this device connected
obj.connectTime = Date.now();
2019-03-08 01:47:27 -05:00
db.Set({ _id: 'lc' + obj.dbNodeKey, type: 'lastconnect', domain: domain.id, time: obj.connectTime, addr: obj.remoteaddrport });
// Device already exists, look if changes has occured
2018-08-29 20:40:30 -04: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 (change == 1) {
2019-03-08 01:47:27 -05:00
db.Set(device);
2018-08-29 20:40:30 -04:00
// 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 };
if (log == 0) { event.nolog = 1; } else { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
2019-03-08 01:47:27 -05:00
const device2 = common.Clone(device);
2018-08-29 20:40:30 -04:00
if (device2.intelamt && device2.intelamt.pass) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
event.node = device;
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
}
}
2017-08-28 12:27:45 -04:00
// Check if this agent is already connected
2019-03-08 01:47:27 -05:00
const dupAgent = parent.wsagents[obj.dbNodeKey];
parent.wsagents[obj.dbNodeKey] = obj;
if (dupAgent) {
// Close the duplicate agent
2019-03-08 01:47:27 -05:00
if (obj.nodeid != null) { parent.parent.debug(1, 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); }
dupAgent.close(3);
} else {
// Indicate the agent is connected
2019-03-08 01:47:27 -05:00
parent.parent.SetConnectivityState(obj.dbMeshKey, obj.dbNodeKey, obj.connectTime, 1, 1);
}
2017-09-05 20:19:28 -04:00
// 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.
2019-03-08 01:47:27 -05:00
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.
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(4));
if (disconnectCount > 4) {
// Too many disconnections, this agent has issues. Just clear the core.
2019-03-08 01:47:27 -05:00
obj.send(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;
}
if ((obj.agentInfo.capabilities & 64) != 0) {
// This is a recovery agent
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
2019-01-21 17:05:50 -05:00
} else {
// Check if we need to make an native update check
2019-03-08 01:47:27 -05:00
obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
const corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
if (corename == null) { obj.send(common.ShortToStr(10) + 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
2019-03-08 01:47:27 -05:00
obj.send(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)) {
2019-03-08 01:47:27 -05:00
obj.send(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
} else {
agentCoreIsStable(); // No updates needed, agent is ready to go.
}
}
2019-01-21 17:05:50 -05:00
}
});
}
2019-01-21 17:05:50 -05:00
function recoveryAgentCoreIsStable() {
// Recovery agent is doing ok, lets perform main agent checking.
// TODO
console.log('recoveryAgentCoreIsStable()');
// Close the recovery agent connection when done.
//obj.close(1);
}
obj.sendUpdatedIntelAmtPolicy = function() {
2019-03-08 01:47:27 -05:00
const mesh = parent.meshes[obj.dbMeshKey];
if (mesh != null) {
// Send Intel AMT policy
2019-03-08 01:47:27 -05:00
const amtPolicy = (mesh.amt != null) ? mesh.amt : null;
obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: amtPolicy }));
}
}
function agentCoreIsStable() {
// Check that the mesh exists
2019-03-08 01:47:27 -05:00
const mesh = parent.meshes[obj.dbMeshKey];
if (mesh == null) {
// TODO: Mark this agent as part of a mesh that does not exists.
return; // Probably not worth doing anything else. Hold this agent.
}
// Send Intel AMT policy
2019-03-08 01:47:27 -05:00
const amtPolicy = (mesh.amt != null) ? mesh.amt : null;
obj.send(JSON.stringify({ action: 'amtPolicy', amtPolicy: amtPolicy }));
// Do this if IP location is enabled on this domain TODO: Set IP location per device group?
if (domain.iplocation == true) {
// Check if we already have IP location information for this node
2019-03-08 01:47:27 -05:00
db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
if (iplocs.length == 1) {
// We have a location in the database for this remote IP
2019-03-08 01:47:27 -05:00
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;
2017-09-05 20:19:28 -04:00
} else {
2019-03-08 01:47:27 -05:00
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; }
}
}
2017-09-05 20:19:28 -04:00
// If we need to ask for IP location, see if we have the quota to do it.
if (doIpLocation > 0) {
2019-03-08 01:47:27 -05:00
db.getValueOfTheDay('ipLocationRequestLimitor', 10, function (ipLocationLimitor) {
if (ipLocationLimitor.value > 0) {
ipLocationLimitor.value--;
2019-03-08 01:47:27 -05:00
db.Set(ipLocationLimitor);
obj.send(JSON.stringify({ action: 'iplocation' }));
}
});
}
}
});
}
2017-08-28 12:27:45 -04:00
}
2018-01-02 19:52:49 -05:00
// Get the web certificate private key hash for the specified domain
2018-01-02 19:52:49 -05:00
function getWebCertHash(domain) {
2019-03-08 01:47:27 -05:00
const hash = parent.webCertificateHashs[domain.id];
if (hash != null) return hash;
2019-03-08 01:47:27 -05:00
return parent.webCertificateHash;
2018-01-02 19:52:49 -05:00
}
// Get the web certificate hash for the specified domain
function getWebCertFullHash(domain) {
2019-03-08 01:47:27 -05:00
const hash = parent.webCertificateFullHashs[domain.id];
if (hash != null) return hash;
2019-03-08 01:47:27 -05:00
return parent.webCertificateFullHash;
}
2017-08-28 12:27:45 -04:00
// Verify the agent signature
function processAgentSignature(msg) {
2019-03-08 01:47:27 -05:00
if (args.ignoreagenthashcheck !== true) {
2019-01-11 13:02:54 -05:00
var verified = false;
if (msg.length != 384) {
// Verify a PKCS7 signature.
var msgDer = null;
2019-03-08 01:47:27 -05:00
try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { }
2019-01-11 13:02:54 -05:00
if (msgDer != null) {
try {
2019-03-08 01:47:27 -05:00
var p7 = forge.pkcs7.messageFromAsn1(msgDer);
2019-01-11 13:02:54 -05:00
var sig = p7.rawCapture.signature;
// Verify with key hash
2019-03-08 01:47:27 -05:00
var buf = Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary');
var verifier = parent.crypto.createVerify('RSA-SHA384');
2019-01-11 13:02:54 -05:00
verifier.update(buf);
verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
if (verified == false) {
// Verify with full hash
2019-03-08 01:47:27 -05:00
buf = Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary');
verifier = parent.crypto.createVerify('RSA-SHA384');
2019-01-11 13:02:54 -05:00
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.
2019-03-08 01:47:27 -05:00
const verify = parent.crypto.createVerify('SHA384');
verify.end(Buffer.from(getWebCertHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash
2019-01-11 13:02:54 -05:00
if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
2019-03-08 01:47:27 -05:00
const verify2 = parent.crypto.createVerify('SHA384');
verify2.end(Buffer.from(getWebCertFullHash(domain) + obj.nonce + obj.agentnonce, 'binary')); // Test using the full cert hash
2019-01-11 13:02:54 -05:00
if (verify2.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { return false; }
}
}
}
2017-08-28 12:27:45 -04:00
// Connection is a success, clean up
obj.nodeid = obj.unauth.nodeid;
2017-08-28 12:27:45 -04:00
obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
delete obj.nonce;
delete obj.agentnonce;
delete obj.unauth;
2019-03-08 01:47:27 -05:00
delete obj.receivedCommands;
2017-08-28 12:27:45 -04:00
if (obj.unauthsign) delete obj.unauthsign;
2019-03-08 01:47:27 -05:00
parent.parent.debug(1, 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').');
2017-08-28 12:27:45 -04:00
obj.authenticated = 1;
return true;
}
// Process incoming agent JSON data
function processAgentData(msg) {
2019-03-08 01:47:27 -05:00
var i, str = msg.toString('utf8'), command = null;
2017-08-28 12:27:45 -04:00
if (str[0] == '{') {
2018-11-01 18:01:21 -04: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 15:24:15 -04:00
if (typeof command != 'object') { return; }
2017-08-28 12:27:45 -04:00
switch (command.action) {
case 'msg':
{
// Route a message.
// If this command has a sessionid, that is the target.
if (command.sessionid != null) {
2017-10-23 17:09:58 -04:00
if (typeof command.sessionid != 'string') break;
2017-08-28 12:27:45 -04:00
var splitsessionid = command.sessionid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splitsessionid[0] == 'user') && (splitsessionid[1] == domain.id)) {
// Check if this user has rights to get this message
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2018-08-29 20:40:30 -04:00
// See if the session is connected. If so, go ahead and send this message to the target node
2019-03-08 01:47:27 -05:00
var ws = parent.wssessions2[command.sessionid];
if (ws != null) {
2017-08-28 12:27:45 -04:00
command.nodeid = obj.dbNodeKey; // Set the nodeid, required for responses.
delete command.sessionid; // Remove the sessionid, since we are sending to that sessionid, so it's implyed.
try { ws.send(JSON.stringify(command)); } catch (ex) { }
2019-03-08 01:47:27 -05:00
} else if (parent.parent.multiServer != null) {
// See if we can send this to a peer server
2019-03-08 01:47:27 -05:00
var serverid = parent.wsPeerSessions2[command.sessionid];
if (serverid != null) {
command.fromNodeid = obj.dbNodeKey;
2019-03-08 01:47:27 -05:00
parent.parent.multiServer.DispatchMessageSingleServer(command, serverid);
}
2017-08-28 12:27:45 -04:00
}
}
} else if (command.userid != null) { // If this command has a userid, that is the target.
2017-10-23 17:09:58 -04:00
if (typeof command.userid != 'string') break;
2017-08-28 12:27:45 -04:00
var splituserid = command.userid.split('/');
// Check that we are in the same domain and the user has rights over this node.
if ((splituserid[0] == 'user') && (splituserid[1] == domain.id)) {
// Check if this user has rights to get this message
//if (mesh.links[user._id] == null || ((mesh.links[user._id].rights & 16) == 0)) return; // TODO!!!!!!!!!!!!!!!!!!!!!
2017-08-28 12:27:45 -04:00
// See if the session is connected
2019-03-08 01:47:27 -05:00
var sessions = parent.wssessions[command.userid];
2017-08-28 12:27:45 -04:00
// Go ahead and send this message to the target node
if (sessions != null) {
2017-08-28 12:27:45 -04:00
command.nodeid = obj.dbNodeKey; // Set the nodeid, required for responses.
delete command.userid; // Remove the userid, since we are sending to that userid, so it's implyed.
2018-08-29 20:40:30 -04:00
for (i in sessions) { sessions[i].send(JSON.stringify(command)); }
2017-08-28 12:27:45 -04:00
}
2018-04-12 14:15:01 -04:00
2019-03-08 01:47:27 -05:00
if (parent.parent.multiServer != null) {
2018-04-12 14:15:01 -04:00
// TODO: Add multi-server support
}
2017-08-28 12:27:45 -04:00
}
} else { // Route this command to the mesh
command.nodeid = obj.dbNodeKey;
var cmdstr = JSON.stringify(command);
2019-03-08 01:47:27 -05:00
for (var userid in parent.wssessions) { // Find all connected users for this mesh and send the message
var user = parent.users[userid];
2018-06-05 16:28:07 -04:00
if ((user != null) && (user.links != null)) {
2017-08-28 12:27:45 -04:00
var rights = user.links[obj.dbMeshKey];
if (rights != null) { // TODO: Look at what rights are needed for message routing
2019-03-08 01:47:27 -05:00
var xsessions = parent.wssessions[userid];
// Send the message to all users on this server
2018-08-29 20:40:30 -04:00
for (i in xsessions) { try { xsessions[i].send(cmdstr); } catch (e) { } }
2017-08-28 12:27:45 -04:00
}
}
}
// Send the message to all users of other servers
2019-03-08 01:47:27 -05:00
if (parent.parent.multiServer != null) {
delete command.nodeid;
command.fromNodeid = obj.dbNodeKey;
command.meshid = obj.dbMeshKey;
2019-03-08 01:47:27 -05:00
parent.parent.multiServer.DispatchMessage(command);
}
2017-08-28 12:27:45 -04:00
}
break;
}
case 'coreinfo':
{
// Sent by the agent to update agent information
ChangeAgentCoreInfo(command);
2018-09-27 19:17:05 -04:00
break;
}
case 'smbios':
{
// Store the RAW SMBios table of this computer
2019-02-25 16:14:04 -05:00
// 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);
2019-03-08 01:47:27 -05:00
if (smbiosstr.length < 65535) { db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: smbiosstr }); }
2019-02-25 16:14:04 -05:00
} catch (ex) { }
2018-09-27 19:17:05 -04:00
// Event the node interface information change (This is a lot of traffic, probably don't need this).
2019-03-08 01:47:27 -05:00
//parent.parent.DispatchEvent(['*', obj.meshid], obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
2018-09-27 19:17:05 -04:00
2017-08-28 12:27:45 -04: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';
2019-03-08 01:47:27 -05:00
db.Set(command);
2017-08-28 12:27:45 -04:00
// Event the node interface information change
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', obj.meshid], obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
2017-08-28 12:27:45 -04:00
break;
}
2017-09-05 20:19:28 -04:00
case 'iplocation':
{
// Sent by the agent to update location information
2017-08-30 16:52:56 -04:00
if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) {
2017-08-28 18:06:29 -04:00
var x = {};
x.publicip = command.value.ip;
2018-08-29 20:40:30 -04:00
x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000));
2017-08-28 18:06:29 -04:00
ChangeAgentLocationInfo(x);
command.value._id = 'iploc_' + command.value.ip;
command.value.type = 'iploc';
command.value.date = Date.now();
2019-03-08 01:47:27 -05:00
db.Set(command.value); // Store the IP to location data in the database
// Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' }
2017-08-28 18:06:29 -04:00
}
2017-08-28 12:27:45 -04:00
break;
}
case 'mc1migration':
{
if (command.oldnodeid.length != 64) break;
2019-03-08 01:47:27 -05:00
const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase();
db.Get(oldNodeKey, function (err, nodes) {
if (nodes.length != 1) return;
2019-03-08 01:47:27 -05:00
const node = nodes[0];
if (node.meshid == obj.dbMeshKey) {
// Update the device name & host
2019-03-08 01:47:27 -05:00
const newNode = { "name": node.name };
if (node.intelamt != null) { newNode.intelamt = node.intelamt; }
ChangeAgentCoreInfo(newNode);
// Delete this node including network interface information and events
2019-03-08 01:47:27 -05:00
db.Remove(node._id);
db.Remove('if' + node._id);
// Event node deletion
2019-03-08 01:47:27 -05:00
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 'getScript':
{
// Used by the agent to get configuration scripts.
if (command.type == 1) {
2019-03-08 01:47:27 -05:00
parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) {
obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() }));
});
} else if (command.type == 2) {
2019-03-08 01:47:27 -05:00
parent.getCiraCleanupScript(function (script) {
obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() }));
});
}
break;
}
2019-01-04 20:59:13 -05:00
default: {
console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + command.action + '.');
break;
}
2017-08-28 12:27:45 -04:00
}
}
}
// Change the current core information string and event it
function ChangeAgentCoreInfo(command) {
if ((command == null) || (command == null)) return; // Safety, should never happen.
2017-08-28 12:27:45 -04:00
// Check that the mesh exists
2019-03-08 01:47:27 -05:00
const mesh = parent.meshes[obj.dbMeshKey];
if (mesh == null) return;
// Get the node and change it if needed
2019-03-08 01:47:27 -05:00
db.Get(obj.dbNodeKey, function (err, nodes) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
if (nodes.length != 1) return;
2019-03-08 01:47:27 -05:00
const device = nodes[0];
if (device.agent) {
var changes = [], change = 0, log = 0;
2018-09-21 19:34:35 -04:00
//if (command.users) { console.log(command.users); }
// Check if anything changes
if (command.name && (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) && (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 (command.intelamt) {
if (!device.intelamt) { device.intelamt = {}; }
if ((command.intelamt.ver != null) && (device.intelamt.ver != command.intelamt.ver)) { changes.push('AMT version'); device.intelamt.ver = command.intelamt.ver; change = 1; log = 1; }
if ((command.intelamt.state != null) && (device.intelamt.state != command.intelamt.state)) { changes.push('AMT state'); device.intelamt.state = command.intelamt.state; change = 1; log = 1; }
2018-09-21 19:34:35 -04:00
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; log = 1;
2018-09-21 19:34:35 -04:00
}
if ((command.intelamt.host != null) && (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) && (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) && (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) && (device.intelamt.pass != command.intelamt.pass)) { changes.push('AMT pass'); device.intelamt.pass = command.intelamt.pass; 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.
2019-03-08 01:47:27 -05:00
if ((mesh.mtype == 2) && (!args.wanonly)) {
2019-02-20 18:26:27 -05:00
// In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
// 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 12:27:45 -04:00
2018-09-21 19:34:35 -04:00
// If there are changes, event the new device
if (change == 1) {
2018-09-21 19:34:35 -04:00
// Save to the database
2019-03-08 01:47:27 -05:00
db.Set(device);
2017-08-28 12:27:45 -04:00
// Event the node change
2018-09-21 19:34:35 -04:00
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id };
if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
if ((log == 0) || (obj.agentInfo.capabilities & 0x20) || (changes.length == 0)) { event.nolog = 1; } // If this is a temporary device, don't log changes
2019-03-08 01:47:27 -05:00
var device2 = common.Clone(device);
if (device2.intelamt && device2.intelamt.pass) { delete device2.intelamt.pass; } // Remove the Intel AMT password before eventing this.
event.node = device;
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
2017-08-28 12:27:45 -04:00
}
}
2017-08-28 12:27:45 -04:00
});
}
2017-08-28 18:06:29 -04:00
// Change the current core information string and event it
function ChangeAgentLocationInfo(command) {
if ((command == null) || (command == null)) { return; } // Safety, should never happen.
2017-08-28 18:06:29 -04:00
// Check that the mesh exists
2019-03-08 01:47:27 -05:00
const mesh = parent.meshes[obj.dbMeshKey];
if (mesh == null) return;
// Get the node and change it if needed
2019-03-08 01:47:27 -05:00
db.Get(obj.dbNodeKey, function (err, nodes) {
if (nodes.length != 1) { return; }
2019-03-08 01:47:27 -05:00
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) {
2019-03-08 01:47:27 -05:00
db.Set(device);
2017-08-28 18:06:29 -04:00
// Event the node change
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, 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
2019-03-08 01:47:27 -05:00
var device2 = common.Clone(device);
if (device2.intelamt && device2.intelamt.pass) { delete device2.intelamt.pass; } // Remove the Intel AMT password before eventing this.
event.node = device;
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
2017-08-28 18:06:29 -04:00
}
}
2017-08-28 18:06:29 -04:00
});
}
2017-08-28 12:27:45 -04:00
// Update the mesh agent tab in the database
function ChangeAgentTag(tag) {
if (tag.length == 0) { tag = null; }
2017-08-28 12:27:45 -04:00
// Get the node and change it if needed
2019-03-08 01:47:27 -05:00
db.Get(obj.dbNodeKey, function (err, nodes) {
2017-08-28 12:27:45 -04:00
if (nodes.length != 1) return;
2019-03-08 01:47:27 -05:00
const device = nodes[0];
2017-08-28 12:27:45 -04:00
if (device.agent) {
if (device.agent.tag != tag) {
device.agent.tag = tag;
2019-03-08 01:47:27 -05:00
db.Set(device);
2017-08-28 12:27:45 -04:00
// Event the node change
var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 };
2019-03-08 01:47:27 -05:00
var device2 = common.Clone(device);
2017-08-28 12:27:45 -04:00
if (device2.intelamt && device2.intelamt.pass) delete device2.intelamt.pass; // Remove the Intel AMT password before eventing this.
event.node = device;
2019-03-08 01:47:27 -05:00
parent.parent.DispatchEvent(['*', device.meshid], obj, event);
2017-08-28 12:27:45 -04:00
}
}
});
}
return obj;
2018-08-29 20:40:30 -04:00
};