mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1478 lines
		
	
	
	
		
			92 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1478 lines
		
	
	
	
		
			92 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
* @description MeshCentral MeshAgent communication module
 | 
						|
* @author Ylian Saint-Hilaire & Bryan Roe
 | 
						|
* @copyright Intel Corporation 2018-2020
 | 
						|
* @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;
 | 
						|
    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);
 | 
						|
        }
 | 
						|
 | 
						|
        // Remove this agent from the list of agents with bad web certificates
 | 
						|
        if (obj.badWebCert) { delete parent.wsagentsWithBadWebCerts[obj.badWebCert]; }
 | 
						|
 | 
						|
        // 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(parent.CreateMeshDispatchTargets(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] != null) && (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(parent.parent.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, parent.parent.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(parent.parent.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(parent.parent.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, parent.parent.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 == parent.parent.agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, bytesRead + 4)); } // Command 14, mesh agent next data block
 | 
						|
                                    if ((bytesRead < parent.parent.agentUpdateBlockSize) || (obj.agentUpdate.ptr == obj.agentExeInfo.size)) {
 | 
						|
                                        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(parent.parent.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 == parent.parent.agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, len + 4)); } // Command 14, mesh agent next data block
 | 
						|
                                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
 | 
						|
            // Check if this is a un-authenticated JSON
 | 
						|
            if (msg.charCodeAt(0) == 123) {
 | 
						|
                var str = msg.toString('utf8'), command = null;
 | 
						|
                if (str[0] == '{') {
 | 
						|
                    try { command = JSON.parse(str); } catch (ex) { } // If the command can't be parsed, ignore it.
 | 
						|
                    if ((command != null) && (command.action === 'agentName') && (typeof command.value == 'string') && (command.value.length > 0) && (command.value.length < 256)) { obj.agentName = command.value; }
 | 
						|
                }
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            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))) {
 | 
						|
                        if (parent.parent.supportsProxyCertificatesRequest !== false) {
 | 
						|
                            obj.badWebCert = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64');
 | 
						|
                            parent.wsagentsWithBadWebCerts[obj.badWebCert] = obj; // Add this agent to the list of of agents with bad web certificates.
 | 
						|
                            parent.parent.updateProxyCertificates(false);
 | 
						|
                        }
 | 
						|
                        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 < 70) || ((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);
 | 
						|
                if (msg.length > 70) {
 | 
						|
                    const computerNameLen = common.ReadShort(msg, 70);
 | 
						|
                    obj.agentInfo.computerName = Buffer.from(msg.substring(72, 72 + computerNameLen), 'binary').toString('utf8');
 | 
						|
                    //console.log('computerName', msg.length, computerNameLen, obj.agentInfo.computerName);
 | 
						|
                } else {
 | 
						|
                    obj.agentInfo.computerName = '';
 | 
						|
                    //console.log('computerName-none');
 | 
						|
                }
 | 
						|
                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(parent.CreateMeshDispatchTargets(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 = parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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 agentName = obj.agentName ? obj.agentName : obj.agentInfo.computerName;
 | 
						|
        var device = { type: 'node', mtype: mesh.mtype, _id: obj.dbNodeKey, icon: obj.agentInfo.platformType, meshid: obj.dbMeshKey, name: agentName, rname: obj.agentInfo.computerName, domain: domain.id, agent: { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }, host: null };
 | 
						|
        db.Set(device);
 | 
						|
 | 
						|
        // Event the new node
 | 
						|
        if (obj.agentInfo.capabilities & 0x20) {
 | 
						|
            // This is a temporary agent, don't log.
 | 
						|
            parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey), obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 });
 | 
						|
        } else {
 | 
						|
            parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(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
 | 
						|
                        // Perform sanity checks before storing
 | 
						|
                        try {
 | 
						|
                            for (var i in command.value) { var k = parseInt(i); if ((k != i) || (i > 255) || (typeof command.value[i] != 'object') || (command.value[i].length == null) || (command.value[i].length > 1024) || (command.value[i].length < 0)) { delete command.value[i]; } }
 | 
						|
                            db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: command.value });
 | 
						|
                        } catch (ex) { }
 | 
						|
 | 
						|
                        // Event the node interface information change (This is a lot of traffic, probably don't need this).
 | 
						|
                        //parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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 = parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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 { 
 | 
						|
                        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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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(parent.CreateMeshDispatchTargets(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;
 | 
						|
};
 |