| 1 | /** |
| 2 | * @description MeshCentral MeshAgent communication module |
| 3 | * @author Ylian Saint-Hilaire & Bryan Roe |
| 4 | * @copyright Intel Corporation 2018-2022 |
| 5 | * @license Apache-2.0 |
| 6 | * @version v0.0.1 |
| 7 | */ |
| 8 | |
| 9 | /*xjslint node: true */ |
| 10 | /*xjslint plusplus: true */ |
| 11 | /*xjslint maxlen: 256 */ |
| 12 | /*jshint node: true */ |
| 13 | /*jshint strict: false */ |
| 14 | /*jshint esversion: 6 */ |
| 15 | "use strict"; |
| 16 | |
| 17 | // Construct a MeshAgent object, called upon connection |
| 18 | module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { |
| 19 | const forge = parent.parent.certificateOperations.forge; |
| 20 | const common = parent.parent.common; |
| 21 | parent.agentStats.createMeshAgentCount++; |
| 22 | parent.parent.debug('agent', 'New agent at ' + req.clientIp + ':' + ws._socket.remotePort); |
| 23 | |
| 24 | var obj = {}; |
| 25 | obj.domain = domain; |
| 26 | obj.authenticated = 0; |
| 27 | obj.receivedCommands = 0; |
| 28 | obj.agentCoreCheck = 0; |
| 29 | obj.remoteaddr = req.clientIp; |
| 30 | obj.remoteaddrport = obj.remoteaddr + ':' + ws._socket.remotePort; |
| 31 | obj.nonce = parent.crypto.randomBytes(48).toString('binary'); |
| 32 | //ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes |
| 33 | 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. |
| 34 | //obj.nodeid = null; |
| 35 | //obj.meshid = null; |
| 36 | //obj.dbNodeKey = null; |
| 37 | //obj.dbMeshKey = null; |
| 38 | //obj.connectTime = null; |
| 39 | //obj.agentInfo = null; |
| 40 | |
| 41 | ws._socket.bytesReadEx = 0; |
| 42 | ws._socket.bytesWrittenEx = 0; |
| 43 | |
| 44 | // Perform data accounting |
| 45 | function dataAccounting() { |
| 46 | parent.trafficStats.AgentCtrlIn += (ws._socket.bytesRead - ws._socket.bytesReadEx); |
| 47 | parent.trafficStats.AgentCtrlOut += (ws._socket.bytesWritten - ws._socket.bytesWrittenEx); |
| 48 | ws._socket.bytesReadEx = ws._socket.bytesRead; |
| 49 | ws._socket.bytesWrittenEx = ws._socket.bytesWritten; |
| 50 | } |
| 51 | |
| 52 | // Send a message to the mesh agent |
| 53 | obj.send = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data), func); } else { ws.send(data, func); } } catch (e) { } }; |
| 54 | obj.sendBinary = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary'), func); } else { ws.send(data, func); } } catch (e) { } }; |
| 55 | |
| 56 | // Disconnect this agent |
| 57 | obj.close = function (arg) { |
| 58 | dataAccounting(); |
| 59 | |
| 60 | 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 |
| 61 | if (arg == 2) { |
| 62 | try { |
| 63 | if (ws._socket._parent != null) |
| 64 | ws._socket._parent.end(); |
| 65 | else |
| 66 | ws._socket.end(); |
| 67 | |
| 68 | if (obj.nodeid != null) { |
| 69 | parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); |
| 70 | } |
| 71 | } catch (e) { console.log(e); } |
| 72 | } |
| 73 | // If arg == 2, hard close, close the TCP socket |
| 74 | // If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent). |
| 75 | |
| 76 | // Stop any current self-share |
| 77 | if (obj.guestSharing === true) { removeGuestSharing(); } |
| 78 | |
| 79 | // Remove this agent from the webserver list |
| 80 | if (parent.wsagents[obj.dbNodeKey] == obj) { |
| 81 | delete parent.wsagents[obj.dbNodeKey]; |
| 82 | parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1, null, { remoteaddrport: obj.remoteaddrport, name: obj.name }); |
| 83 | } |
| 84 | |
| 85 | // Remove this agent from the list of agents with bad web certificates |
| 86 | if (obj.badWebCert) { delete parent.wsagentsWithBadWebCerts[obj.badWebCert]; } |
| 87 | |
| 88 | // Get the current mesh |
| 89 | const mesh = parent.meshes[obj.dbMeshKey]; |
| 90 | |
| 91 | // If this is a temporary or recovery agent, or all devices in this group are temporary, remove the agent (0x20 = Temporary, 0x40 = Recovery) |
| 92 | if (((obj.agentInfo) && (obj.agentInfo.capabilities) && ((obj.agentInfo.capabilities & 0x20) || (obj.agentInfo.capabilities & 0x40))) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) { |
| 93 | // Delete this node including network interface information and events |
| 94 | db.Remove(obj.dbNodeKey); // Remove node with that id |
| 95 | db.Remove('if' + obj.dbNodeKey); // Remove interface information |
| 96 | db.Remove('nt' + obj.dbNodeKey); // Remove notes |
| 97 | db.Remove('lc' + obj.dbNodeKey); // Remove last connect time |
| 98 | db.Remove('si' + obj.dbNodeKey); // Remove system information |
| 99 | db.Remove('al' + obj.dbNodeKey); // Remove error log last time |
| 100 | if (db.RemoveSMBIOS) { db.RemoveSMBIOS(obj.dbNodeKey); } // Remove SMBios data |
| 101 | db.RemoveAllNodeEvents(domain.id, obj.dbNodeKey); // Remove all events for this node |
| 102 | db.removeAllPowerEventsForNode(obj.dbNodeKey); // Remove all power events for this node |
| 103 | |
| 104 | // Event node deletion |
| 105 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 }); |
| 106 | |
| 107 | // Disconnect all connections if needed |
| 108 | const state = parent.parent.GetConnectivityState(obj.dbNodeKey); |
| 109 | if ((state != null) && (state.connectivity != null)) { |
| 110 | if ((state.connectivity & 1) != 0) { parent.wsagents[obj.dbNodeKey].close(); } // Disconnect mesh agent |
| 111 | if ((state.connectivity & 2) != 0) { parent.parent.mpsserver.closeAllForNode(obj.dbNodeKey); } // Disconnect CIRA connection |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Set this agent as no longer authenticated |
| 116 | obj.authenticated = -1; |
| 117 | |
| 118 | // If we where updating the agent using native method, clean that up. |
| 119 | if (obj.agentUpdate != null) { |
| 120 | if (obj.agentUpdate.fd) { try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } } |
| 121 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 122 | delete obj.agentUpdate.buf; |
| 123 | delete obj.agentUpdate; |
| 124 | } |
| 125 | |
| 126 | // If we where updating the agent meshcore method, clean that up. |
| 127 | if (obj.agentCoreUpdateTaskId != null) { |
| 128 | parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId); |
| 129 | delete obj.agentCoreUpdateTaskId; |
| 130 | } |
| 131 | |
| 132 | // Perform timer cleanup |
| 133 | if (obj.pingtimer) { clearInterval(obj.pingtimer); delete obj.pingtimer; } |
| 134 | if (obj.pongtimer) { clearInterval(obj.pongtimer); delete obj.pongtimer; } |
| 135 | |
| 136 | // Perform aggressive cleanup |
| 137 | delete obj.name; |
| 138 | delete obj.nonce; |
| 139 | delete obj.nodeid; |
| 140 | delete obj.unauth; |
| 141 | delete obj.remoteaddr; |
| 142 | delete obj.remoteaddrport; |
| 143 | delete obj.meshid; |
| 144 | delete obj.connectTime; |
| 145 | delete obj.agentInfo; |
| 146 | delete obj.agentExeInfo; |
| 147 | ws.removeAllListeners(['message', 'close', 'error']); |
| 148 | }; |
| 149 | |
| 150 | // When data is received from the mesh agent web socket |
| 151 | ws.on('message', function (msg) { |
| 152 | dataAccounting(); |
| 153 | if (msg.length < 2) return; |
| 154 | if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string |
| 155 | if (obj.authenticated == 2) { // We are authenticated |
| 156 | if ((obj.agentUpdate == null) && (msg.charCodeAt(0) == 123)) { processAgentData(msg); } // Only process JSON messages if meshagent update is not in progress |
| 157 | if (msg.length < 2) return; |
| 158 | const cmdid = common.ReadShort(msg, 0); |
| 159 | if (cmdid == 11) { // MeshCommand_CoreModuleHash |
| 160 | if (msg.length == 4) { ChangeAgentCoreInfo({ 'caps': 0 }); } // If the agent indicated that no core is running, clear the core information string. |
| 161 | // Mesh core hash, sent by agent with the hash of the current mesh core. |
| 162 | |
| 163 | // If we are performing an agent update, don't update the core. |
| 164 | if (obj.agentUpdate != null) { return; } |
| 165 | |
| 166 | // If we are using a custom core, don't try to update it. |
| 167 | if (obj.agentCoreCheck == 1000) { |
| 168 | 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. |
| 169 | agentCoreIsStable(); |
| 170 | return; |
| 171 | } |
| 172 | |
| 173 | // Get the current meshcore hash |
| 174 | const agentMeshCoreHash = (msg.length == 52) ? msg.substring(4, 52) : null; |
| 175 | |
| 176 | // If the agent indicates this is a custom core, we are done. |
| 177 | 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')) { |
| 178 | obj.agentCoreCheck = 0; |
| 179 | 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. |
| 180 | agentCoreIsStable(); |
| 181 | return; |
| 182 | } |
| 183 | |
| 184 | // We need to check if the core is current. Figure out what core we need. |
| 185 | var corename = null; |
| 186 | if ((obj.agentInfo != null) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null)) { |
| 187 | if ((obj.agentCoreCheck == 1001) || (obj.agentCoreUpdate == true)) { |
| 188 | // If the user asked, use the recovery core. |
| 189 | corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].rcore; |
| 190 | } else if (obj.agentCoreCheck == 1011) { |
| 191 | // If the user asked, use the tiny core. |
| 192 | corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].tcore; |
| 193 | } else if (obj.agentInfo.capabilities & 0x40) { |
| 194 | // If this is a recovery agent, use the agent recovery core. |
| 195 | corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].arcore; |
| 196 | } else { |
| 197 | // This is the normal core for this agent type. |
| 198 | corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | // If we have a core, use it. |
| 203 | if (corename != null) { |
| 204 | const meshcorehash = parent.parent.defaultMeshCoresHash[corename]; |
| 205 | if (agentMeshCoreHash != meshcorehash) { |
| 206 | if ((obj.agentCoreCheck < 5) || (obj.agentCoreCheck == 1001) || (obj.agentCoreCheck == 1011) || (obj.agentCoreUpdate == true)) { |
| 207 | if (meshcorehash == null) { |
| 208 | // Clear the core |
| 209 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // MeshCommand_CoreModule, ask mesh agent to clear the core |
| 210 | parent.agentStats.clearingCoreCount++; |
| 211 | parent.parent.debug('agent', "Clearing core for agent " + obj.nodeid); |
| 212 | } else { |
| 213 | // Setup task limiter options, this system limits how many tasks can run at the same time to spread the server load. |
| 214 | var taskLimiterOptions = { hash: meshcorehash, core: parent.parent.defaultMeshCores[corename], name: corename }; |
| 215 | |
| 216 | // If the agent supports compression, sent the core compressed. |
| 217 | if ((obj.agentInfo.capabilities & 0x100) && (parent.parent.defaultMeshCoresDeflate[corename])) { |
| 218 | args.core = parent.parent.defaultMeshCoresDeflate[corename]; |
| 219 | } |
| 220 | |
| 221 | // Update new core with task limiting so not to flood the server. This is a high priority task. |
| 222 | obj.agentCoreUpdatePending = true; |
| 223 | parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { |
| 224 | if (obj.authenticated == 2) { |
| 225 | // Send the updated core. |
| 226 | delete obj.agentCoreUpdatePending; |
| 227 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core.toString('binary'), function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update |
| 228 | parent.agentStats.updatingCoreCount++; |
| 229 | parent.parent.debug('agent', "Updating core " + argument.name + " for agent " + obj.nodeid); |
| 230 | } else { |
| 231 | // This agent is probably disconnected, nothing to do. |
| 232 | parent.parent.taskLimiter.completed(taskid); |
| 233 | } |
| 234 | }, taskLimiterOptions, 0); |
| 235 | } |
| 236 | obj.agentCoreCheck++; |
| 237 | } |
| 238 | } else { |
| 239 | obj.agentCoreCheck = 0; |
| 240 | 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. |
| 241 | agentCoreIsStable(); // No updates needed, agent is ready to go. |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | /* |
| 246 | // TODO: Check if we have a mesh specific core. If so, use that. |
| 247 | var agentMeshCoreHash = null; |
| 248 | if (msg.length == 52) { agentMeshCoreHash = msg.substring(4, 52); } |
| 249 | if ((agentMeshCoreHash != parent.parent.defaultMeshCoreHash) && (agentMeshCoreHash != parent.parent.defaultMeshCoreNoMeiHash)) { |
| 250 | if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update. |
| 251 | if (parent.parent.defaultMeshCoreHash == null) { |
| 252 | // Update no core |
| 253 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // Command 10, ask mesh agent to clear the core |
| 254 | } else { |
| 255 | // Update new core |
| 256 | if ((parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].amt == true)) { |
| 257 | 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) |
| 258 | } else { |
| 259 | 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) |
| 260 | } |
| 261 | } |
| 262 | obj.agentCoreCheck++; |
| 263 | } |
| 264 | } else { |
| 265 | obj.agentCoreCheck = 0; |
| 266 | } |
| 267 | */ |
| 268 | } |
| 269 | else if (cmdid == 12) { // MeshCommand_AgentHash |
| 270 | if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { |
| 271 | const agenthash = msg.substring(4); |
| 272 | const agentUpdateMethod = compareAgentBinaryHash(obj.agentExeInfo, agenthash); |
| 273 | if (agentUpdateMethod === 2) { // Use meshcore agent update system |
| 274 | // Send the recovery core to the agent, if the agent is capable of running one |
| 275 | if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) { |
| 276 | parent.agentStats.agentMeshCoreBinaryUpdate++; |
| 277 | obj.agentCoreUpdate = true; |
| 278 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // Ask to clear the core |
| 279 | obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Ask for meshcore hash |
| 280 | } |
| 281 | } else if (agentUpdateMethod === 1) { // Use native agent update system |
| 282 | // Mesh agent update required, do it using task limiter so not to flood the network. Medium priority task. |
| 283 | parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { |
| 284 | if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; } // If agent disconnection, complete and exit now. |
| 285 | if (obj.nodeid != null) { parent.parent.debug('agent', "Agent update required, NodeID=0x" + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); } |
| 286 | parent.agentStats.agentBinaryUpdate++; |
| 287 | if ((obj.agentExeInfo.data == null) && (((obj.agentInfo.capabilities & 0x100) == 0) || (obj.agentExeInfo.zdata == null))) { |
| 288 | // Read the agent from disk |
| 289 | parent.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) { |
| 290 | if (obj.agentExeInfo == null) return; // Agent disconnected during this call. |
| 291 | if (err) { parent.parent.debug('agentupdate', "ERROR: " + err); return console.error(err); } |
| 292 | obj.agentUpdate = { ptr: 0, buf: Buffer.alloc(parent.parent.agentUpdateBlockSize + 4), fd: fd, taskid: taskid }; |
| 293 | |
| 294 | // MeshCommand_CoreModule, ask mesh agent to clear the core. |
| 295 | // The new core will only be sent after the agent updates. |
| 296 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); |
| 297 | |
| 298 | // We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result |
| 299 | //console.log("Agent update file open."); |
| 300 | obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0)); // Command 13, start mesh agent download |
| 301 | |
| 302 | // Send the first mesh agent update data block |
| 303 | obj.agentUpdate.buf[0] = 0; |
| 304 | obj.agentUpdate.buf[1] = 14; |
| 305 | obj.agentUpdate.buf[2] = 0; |
| 306 | obj.agentUpdate.buf[3] = 1; |
| 307 | parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, parent.parent.agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) { |
| 308 | if (obj.agentUpdate == null) return; |
| 309 | if ((err != null) || (bytesRead == 0)) { |
| 310 | // Error reading the agent file, stop here. |
| 311 | try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } |
| 312 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 313 | parent.parent.debug('agentupdate', "ERROR: Unable to read first block of agent binary from disk."); |
| 314 | delete obj.agentUpdate.buf; |
| 315 | delete obj.agentUpdate; |
| 316 | } else { |
| 317 | // Send the first block to the agent |
| 318 | obj.agentUpdate.ptr += bytesRead; |
| 319 | parent.parent.debug('agentupdate', "Sent first block of " + bytesRead + " bytes from disk."); |
| 320 | obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block |
| 321 | } |
| 322 | }); |
| 323 | }); |
| 324 | } else { |
| 325 | // Send the agent from RAM |
| 326 | obj.agentUpdate = { ptr: 0, buf: Buffer.alloc(parent.parent.agentUpdateBlockSize + 4), taskid: taskid }; |
| 327 | |
| 328 | // MeshCommand_CoreModule, ask mesh agent to clear the core. |
| 329 | // The new core will only be sent after the agent updates. |
| 330 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); |
| 331 | |
| 332 | // We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result |
| 333 | obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0)); // Command 13, start mesh agent download |
| 334 | |
| 335 | // Send the first mesh agent update data block |
| 336 | obj.agentUpdate.buf[0] = 0; |
| 337 | obj.agentUpdate.buf[1] = 14; |
| 338 | obj.agentUpdate.buf[2] = 0; |
| 339 | obj.agentUpdate.buf[3] = 1; |
| 340 | |
| 341 | // If agent supports compression, send the compressed agent if possible. |
| 342 | if ((obj.agentInfo.capabilities & 0x100) && (obj.agentExeInfo.zdata != null)) { |
| 343 | // Send compressed data |
| 344 | obj.agentUpdate.agentUpdateData = obj.agentExeInfo.zdata; |
| 345 | obj.agentUpdate.agentUpdateHash = obj.agentExeInfo.zhash; |
| 346 | } else { |
| 347 | // Send uncompressed data |
| 348 | obj.agentUpdate.agentUpdateData = obj.agentExeInfo.data; |
| 349 | obj.agentUpdate.agentUpdateHash = obj.agentExeInfo.hash; |
| 350 | } |
| 351 | |
| 352 | const len = Math.min(parent.parent.agentUpdateBlockSize, obj.agentUpdate.agentUpdateData.length - obj.agentUpdate.ptr); |
| 353 | if (len > 0) { |
| 354 | // Send the first block |
| 355 | obj.agentUpdate.agentUpdateData.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len); |
| 356 | obj.agentUpdate.ptr += len; |
| 357 | obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block |
| 358 | parent.parent.debug('agentupdate', "Sent first block of " + len + " bytes from RAM."); |
| 359 | } else { |
| 360 | // Error |
| 361 | parent.parent.debug('agentupdate', "ERROR: Len of " + len + " is invalid."); |
| 362 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 363 | delete obj.agentUpdate.buf; |
| 364 | delete obj.agentUpdate; |
| 365 | } |
| 366 | } |
| 367 | }, null, 1); |
| 368 | |
| 369 | } else { |
| 370 | // Check the mesh core, if the agent is capable of running one |
| 371 | if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) { |
| 372 | obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash. |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | } |
| 377 | else if (cmdid == 14) { // MeshCommand_AgentBinaryBlock |
| 378 | if ((msg.length == 4) && (obj.agentUpdate != null)) { |
| 379 | const status = common.ReadShort(msg, 2); |
| 380 | if (status == 1) { |
| 381 | if (obj.agentExeInfo.data == null) { |
| 382 | // Read the agent from disk |
| 383 | parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, parent.parent.agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) { |
| 384 | if ((obj.agentExeInfo == null) || (obj.agentUpdate == null)) return; // Agent disconnected during this async call. |
| 385 | if ((err != null) || (bytesRead < 0)) { |
| 386 | // Error reading the agent file, stop here. |
| 387 | parent.parent.debug('agentupdate', "ERROR: Unable to read agent #" + obj.agentExeInfo.id + " binary from disk."); |
| 388 | try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } |
| 389 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 390 | delete obj.agentUpdate.buf; |
| 391 | delete obj.agentUpdate; |
| 392 | } else { |
| 393 | // Send the next block to the agent |
| 394 | parent.parent.debug('agentupdate', "Sending disk agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + bytesRead + "."); |
| 395 | obj.agentUpdate.ptr += bytesRead; |
| 396 | 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 |
| 397 | if ((bytesRead < parent.parent.agentUpdateBlockSize) || (obj.agentUpdate.ptr == obj.agentExeInfo.size)) { |
| 398 | parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from disk, ptr=" + obj.agentUpdate.ptr + "."); |
| 399 | obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentExeInfo.hash); // Command 13, end mesh agent download, send agent SHA384 hash |
| 400 | try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } |
| 401 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 402 | delete obj.agentUpdate.buf; |
| 403 | delete obj.agentUpdate; |
| 404 | } |
| 405 | } |
| 406 | }); |
| 407 | } else { |
| 408 | // Send the agent from RAM |
| 409 | const len = Math.min(parent.parent.agentUpdateBlockSize, obj.agentUpdate.agentUpdateData.length - obj.agentUpdate.ptr); |
| 410 | if (len > 0) { |
| 411 | obj.agentUpdate.agentUpdateData.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len); |
| 412 | 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 |
| 413 | parent.parent.debug('agentupdate', "Sending RAM agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + len + "."); |
| 414 | obj.agentUpdate.ptr += len; |
| 415 | } |
| 416 | |
| 417 | if (obj.agentUpdate.ptr == obj.agentUpdate.agentUpdateData.length) { |
| 418 | parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from RAM, ptr=" + obj.agentUpdate.ptr + "."); |
| 419 | obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentUpdate.agentUpdateHash); // Command 13, end mesh agent download, send agent SHA384 hash |
| 420 | parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete |
| 421 | delete obj.agentUpdate.buf; |
| 422 | delete obj.agentUpdate; |
| 423 | } |
| 424 | } |
| 425 | } |
| 426 | } |
| 427 | } |
| 428 | else if (cmdid == 15) { // MeshCommand_AgentTag |
| 429 | var tag = msg.substring(2); |
| 430 | while (tag.charCodeAt(tag.length - 1) == 0) { tag = tag.substring(0, tag.length - 1); } // Remove end-of-line zeros. |
| 431 | ChangeAgentTag(tag); |
| 432 | } |
| 433 | } else if (obj.authenticated < 2) { // We are not authenticated |
| 434 | // Check if this is a un-authenticated JSON |
| 435 | if (msg.charCodeAt(0) == 123) { |
| 436 | var str = msg.toString('utf8'), command = null; |
| 437 | if (str[0] == '{') { |
| 438 | try { command = JSON.parse(str); } catch (ex) { } // If the command can't be parsed, ignore it. |
| 439 | if ((command != null) && (command.action === 'agentName') && (typeof command.value == 'string') && (command.value.length > 0) && (command.value.length < 256)) { obj.agentName = command.value; } |
| 440 | } |
| 441 | return; |
| 442 | } |
| 443 | const cmd = common.ReadShort(msg, 0); |
| 444 | if (cmd == 1) { |
| 445 | // Agent authentication request |
| 446 | if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return; |
| 447 | obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. |
| 448 | |
| 449 | if (isIgnoreHashCheck()) { |
| 450 | // Send the agent web hash back to the agent |
| 451 | // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce |
| 452 | obj.sendBinary(common.ShortToStr(1) + msg.substring(2, 50) + obj.nonce); // Command 1, hash + nonce. Use the web hash given by the agent. |
| 453 | } else { |
| 454 | // Check that the server hash matches our own web certificate hash (SHA384) |
| 455 | obj.agentSeenCerthash = msg.substring(2, 50); |
| 456 | if ((getWebCertHash(domain) != obj.agentSeenCerthash) && (getWebCertFullHash(domain) != obj.agentSeenCerthash) && (parent.defaultWebCertificateHash != obj.agentSeenCerthash) && (parent.defaultWebCertificateFullHash != obj.agentSeenCerthash)) { |
| 457 | if (parent.parent.supportsProxyCertificatesRequest !== false) { |
| 458 | obj.badWebCert = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64'); |
| 459 | parent.wsagentsWithBadWebCerts[obj.badWebCert] = obj; // Add this agent to the list of of agents with bad web certificates. |
| 460 | parent.parent.updateProxyCertificates(false); |
| 461 | } |
| 462 | parent.agentStats.agentBadWebCertHashCount++; |
| 463 | parent.setAgentIssue(obj, "BadWebCertHash: " + Buffer.from(msg.substring(2, 50), 'binary').toString('hex')); |
| 464 | parent.parent.debug('agent', '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 ' + (Buffer.from(getWebCertFullHash(domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); |
| 465 | parent.parent.debug('agent', 'Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.'); |
| 466 | 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 ' + (Buffer.from(getWebCertFullHash(domain), 'binary').toString('hex').substring(0, 10)) + '), holding connection (' + obj.remoteaddrport + ').'); |
| 467 | console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.'); |
| 468 | delete obj.agentSeenCerthash; |
| 469 | return; |
| 470 | } else { |
| 471 | // The hash matched one of the acceptable values, send the agent web hash back to the agent |
| 472 | // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce |
| 473 | // Command 1, hash + nonce. Use the web hash given by the agent. |
| 474 | obj.sendBinary(common.ShortToStr(1) + obj.agentSeenCerthash + obj.nonce); |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | // Use our server private key to sign the ServerHash + AgentNonce + ServerNonce |
| 479 | obj.agentnonce = msg.substring(50, 98); |
| 480 | |
| 481 | // Check if we got the agent auth confirmation |
| 482 | if ((obj.receivedCommands & 8) == 0) { |
| 483 | // If we did not get an indication that the agent already validated this server, send the server signature. |
| 484 | if (obj.useSwarmCert == true) { |
| 485 | // Perform the hash signature using older swarm server certificate |
| 486 | parent.parent.certificateOperations.acceleratorPerformSignature(1, msg.substring(2) + obj.nonce, null, function (tag, signature) { |
| 487 | // Send back our certificate + signature |
| 488 | obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.swarmCertificateAsn1.length) + parent.swarmCertificateAsn1 + signature); // Command 2, certificate + signature |
| 489 | }); |
| 490 | } else { |
| 491 | // Perform the hash signature using the server agent certificate |
| 492 | parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, null, function (tag, signature) { |
| 493 | // Send back our certificate + signature |
| 494 | obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.agentCertificateAsn1.length) + parent.agentCertificateAsn1 + signature); // Command 2, certificate + signature |
| 495 | }); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | // Check the agent signature if we can |
| 500 | if (obj.unauthsign != null) { |
| 501 | if (processAgentSignature(obj.unauthsign) == false) { |
| 502 | parent.agentStats.agentBadSignature1Count++; |
| 503 | parent.setAgentIssue(obj, "BadSignature1"); |
| 504 | parent.parent.debug('agent', 'Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); |
| 505 | console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; |
| 506 | } else { completeAgentConnection(); } |
| 507 | } |
| 508 | } |
| 509 | else if (cmd == 2) { |
| 510 | // Agent certificate |
| 511 | if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return; |
| 512 | obj.receivedCommands += 2; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. |
| 513 | |
| 514 | // Decode the certificate |
| 515 | const certlen = common.ReadShort(msg, 2); |
| 516 | obj.unauth = {}; |
| 517 | 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); parent.parent.debug('agent', ex); return; } |
| 518 | obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----'; |
| 519 | |
| 520 | // Check the agent signature if we can |
| 521 | if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { |
| 522 | if (processAgentSignature(msg.substring(4 + certlen)) == false) { |
| 523 | parent.agentStats.agentBadSignature2Count++; |
| 524 | parent.setAgentIssue(obj, "BadSignature2"); |
| 525 | parent.parent.debug('agent', 'Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); |
| 526 | console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return; |
| 527 | } |
| 528 | } |
| 529 | completeAgentConnection(); |
| 530 | } |
| 531 | else if (cmd == 3) { |
| 532 | // Agent meshid |
| 533 | if ((msg.length < 70) || ((obj.receivedCommands & 4) != 0)) return; |
| 534 | obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. |
| 535 | |
| 536 | // Set the meshid |
| 537 | obj.agentInfo = {}; |
| 538 | obj.agentInfo.infoVersion = common.ReadInt(msg, 2); |
| 539 | obj.agentInfo.agentId = common.ReadInt(msg, 6); |
| 540 | obj.agentInfo.agentVersion = common.ReadInt(msg, 10); |
| 541 | obj.agentInfo.platformType = common.ReadInt(msg, 14); |
| 542 | if (obj.agentInfo.platformType > 8 || obj.agentInfo.platformType < 1) { obj.agentInfo.platformType = 1; } |
| 543 | if (msg.substring(50, 66) == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') { |
| 544 | obj.meshid = Buffer.from(msg.substring(18, 50), 'binary').toString('hex'); // Older HEX MeshID |
| 545 | } else { |
| 546 | obj.meshid = Buffer.from(msg.substring(18, 66), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); // New Base64 MeshID |
| 547 | } |
| 548 | //console.log('MeshID', obj.meshid); |
| 549 | obj.agentInfo.capabilities = common.ReadInt(msg, 66); |
| 550 | if (msg.length > 70) { |
| 551 | const computerNameLen = common.ReadShort(msg, 70); |
| 552 | obj.agentInfo.computerName = Buffer.from(msg.substring(72, 72 + computerNameLen), 'binary').toString('utf8'); |
| 553 | //console.log('computerName', msg.length, computerNameLen, obj.agentInfo.computerName); |
| 554 | } else { |
| 555 | obj.agentInfo.computerName = ''; |
| 556 | //console.log('computerName-none'); |
| 557 | } |
| 558 | |
| 559 | obj.dbMeshKey = 'mesh/' + domain.id + '/' + obj.meshid; |
| 560 | completeAgentConnection(); |
| 561 | } else if (cmd == 4) { |
| 562 | if ((msg.length < 2) || ((obj.receivedCommands & 8) != 0)) return; |
| 563 | obj.receivedCommands += 8; // Agent can't send the same command twice on the same connection ever. Block DOS attack path. |
| 564 | // Agent already authenticated the server, wants to skip the server signature - which is great for server performance. |
| 565 | } else if (cmd == 5) { |
| 566 | // ServerID. Agent is telling us what serverid it expects. Useful if we have many server certificates. |
| 567 | if ((msg.substring(2, 34) == parent.swarmCertificateHash256) || (msg.substring(2, 50) == parent.swarmCertificateHash384)) { obj.useSwarmCert = true; } |
| 568 | } else if (cmd == 30) { |
| 569 | // Agent Commit Date. This is future proofing. Can be used to change server behavior depending on the date range of the agent. |
| 570 | try { obj.AgentCommitDate = Date.parse(msg.substring(2)) } catch (ex) { } |
| 571 | //console.log('Connected Agent Commit Date: ' + msg.substring(2) + ", " + Date.parse(msg.substring(2))); |
| 572 | } |
| 573 | } |
| 574 | }); |
| 575 | |
| 576 | // If error, do nothing |
| 577 | ws.on('error', function (err) { parent.parent.debug('agent', 'AGENT WSERR: ' + err); console.log('AGENT WSERR: ' + err); obj.close(0); }); |
| 578 | |
| 579 | // If the mesh agent web socket is closed, clean up. |
| 580 | ws.on('close', function (req) { |
| 581 | parent.agentStats.agentClose++; |
| 582 | if (obj.nodeid != null) { |
| 583 | const agentId = (obj.agentInfo && obj.agentInfo.agentId) ? obj.agentInfo.agentId : 'Unknown'; |
| 584 | //console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId); |
| 585 | parent.parent.debug('agent', 'Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId); |
| 586 | |
| 587 | // Log the agent disconnection if we are not testing agent update |
| 588 | if (args.agentupdatetest == null) { |
| 589 | if (parent.wsagentsDisconnections[obj.nodeid] == null) { |
| 590 | parent.wsagentsDisconnections[obj.nodeid] = 1; |
| 591 | } else { |
| 592 | parent.wsagentsDisconnections[obj.nodeid] = ++parent.wsagentsDisconnections[obj.nodeid]; |
| 593 | } |
| 594 | } |
| 595 | } |
| 596 | obj.close(0); |
| 597 | }); |
| 598 | |
| 599 | // Return the mesh for this device, in some cases, we may auto-create the mesh. |
| 600 | function getMeshAutoCreate() { |
| 601 | var mesh = parent.meshes[obj.dbMeshKey]; |
| 602 | |
| 603 | // If the mesh was not found and we are in LAN mode, check of the domain can be corrected |
| 604 | if ((args.lanonly == true) && (mesh == null)) { |
| 605 | var smesh = obj.dbMeshKey.split('/'); |
| 606 | for (var i in parent.parent.config.domains) { |
| 607 | mesh = parent.meshes['mesh/' + i + '/' + smesh[2]]; |
| 608 | if (mesh != null) { |
| 609 | obj.domain = domain = parent.parent.config.domains[i]; |
| 610 | obj.meshid = smesh[2]; |
| 611 | obj.dbMeshKey = 'mesh/' + i + '/' + smesh[2]; |
| 612 | obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid; |
| 613 | break; |
| 614 | } |
| 615 | } |
| 616 | } |
| 617 | |
| 618 | if ((mesh == null) && (typeof domain.orphanagentuser == 'string')) { |
| 619 | const adminUser = parent.users['user/' + domain.id + '/' + domain.orphanagentuser]; |
| 620 | if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) { |
| 621 | // Mesh name is hex instead of base64 |
| 622 | const meshname = obj.meshid.substring(0, 18); |
| 623 | |
| 624 | // Create a new mesh for this device |
| 625 | const links = {}; |
| 626 | links[adminUser._id] = { name: adminUser.name, rights: 0xFFFFFFFF }; |
| 627 | mesh = { type: 'mesh', _id: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', domain: domain.id, links: links }; |
| 628 | db.Set(mesh); |
| 629 | parent.meshes[obj.dbMeshKey] = mesh; |
| 630 | |
| 631 | if (adminUser.links == null) adminUser.links = {}; |
| 632 | adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF }; |
| 633 | db.SetUser(adminUser); |
| 634 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [adminUser._id, obj.dbNodeKey]), obj, { etype: 'mesh', username: adminUser.name, meshid: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', action: 'createmesh', links: links, msgid: 55, msgArgs: [obj.meshid], msg: "Created device group: " + obj.meshid, domain: domain.id }); |
| 635 | } |
| 636 | } else { |
| 637 | if ((mesh != null) && (mesh.deleted != null) && (mesh.links)) { |
| 638 | // Must un-delete this mesh |
| 639 | var ids = parent.CreateMeshDispatchTargets(mesh._id, [obj.dbNodeKey]); |
| 640 | |
| 641 | // See if users still exists, if so, add links to the mesh |
| 642 | for (var userid in mesh.links) { |
| 643 | const user = parent.users[userid]; |
| 644 | if (user) { |
| 645 | if (user.links == null) { user.links = {}; } |
| 646 | if (user.links[mesh._id] == null) { |
| 647 | user.links[mesh._id] = { rights: mesh.links[userid].rights }; |
| 648 | ids.push(user._id); |
| 649 | db.SetUser(user); |
| 650 | } |
| 651 | } |
| 652 | } |
| 653 | |
| 654 | // Send out an event indicating this mesh was "created" |
| 655 | parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', links: mesh.links, msgid: 56, msgArgs: [mesh._id], msg: "Device group undeleted: " + mesh._id, domain: domain.id }); |
| 656 | |
| 657 | // Mark the mesh as active |
| 658 | delete mesh.deleted; |
| 659 | db.Set(mesh); |
| 660 | } |
| 661 | } |
| 662 | return mesh; |
| 663 | } |
| 664 | |
| 665 | // Send a PING/PONG message |
| 666 | function sendPing() { obj.send('{"action":"ping"}'); } |
| 667 | function sendPong() { obj.send('{"action":"pong"}'); } |
| 668 | |
| 669 | // Once we get all the information about an agent, run this to hook everything up to the server |
| 670 | function completeAgentConnection() { |
| 671 | if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection || (obj.agentInfo == null)) { return; } |
| 672 | obj.pendingCompleteAgentConnection = true; |
| 673 | |
| 674 | // Setup the agent PING/PONG timers |
| 675 | if ((typeof args.agentping == 'number') && (obj.pingtimer == null)) { obj.pingtimer = setInterval(sendPing, args.agentping * 1000); } |
| 676 | else if ((typeof args.agentpong == 'number') && (obj.pongtimer == null)) { obj.pongtimer = setInterval(sendPong, args.agentpong * 1000); } |
| 677 | |
| 678 | // If this is a recovery agent |
| 679 | if (obj.agentInfo.capabilities & 0x40) { |
| 680 | // Inform mesh agent that it's authenticated. |
| 681 | delete obj.pendingCompleteAgentConnection; |
| 682 | obj.authenticated = 2; |
| 683 | obj.sendBinary(common.ShortToStr(4)); |
| 684 | |
| 685 | // Ask for mesh core hash. |
| 686 | obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); |
| 687 | return; |
| 688 | } |
| 689 | |
| 690 | // Check if we have too many agent sessions |
| 691 | if (typeof domain.limits.maxagentsessions == 'number') { |
| 692 | // Count the number of agent sessions for this domain |
| 693 | var domainAgentSessionCount = 0; |
| 694 | for (var i in parent.wsagents) { if (parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } } |
| 695 | |
| 696 | // Check if we have too many user sessions |
| 697 | if (domainAgentSessionCount >= domain.limits.maxagentsessions) { |
| 698 | // Too many, hold the connection. |
| 699 | parent.agentStats.agentMaxSessionHoldCount++; |
| 700 | return; |
| 701 | } |
| 702 | } |
| 703 | |
| 704 | /* |
| 705 | // Check that the mesh exists |
| 706 | var mesh = parent.meshes[obj.dbMeshKey]; |
| 707 | if (mesh == null) { |
| 708 | var holdConnection = true; |
| 709 | if (typeof domain.orphanagentuser == 'string') { |
| 710 | var adminUser = parent.users['user/' + domain.id + '/' + args.orphanagentuser]; |
| 711 | if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) { |
| 712 | // Create a new mesh for this device |
| 713 | holdConnection = false; |
| 714 | var links = {}; |
| 715 | links[user._id] = { name: adminUser.name, rights: 0xFFFFFFFF }; |
| 716 | mesh = { type: 'mesh', _id: obj.dbMeshKey, name: obj.meshid, mtype: 2, desc: '', domain: domain.id, links: links }; |
| 717 | db.Set(mesh); |
| 718 | parent.meshes[obj.meshid] = mesh; |
| 719 | parent.parent.AddEventDispatch(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), ws); |
| 720 | |
| 721 | if (adminUser.links == null) user.links = {}; |
| 722 | adminUser.links[obj.meshid] = { rights: 0xFFFFFFFF }; |
| 723 | //adminUser.subscriptions = parent.subscribe(adminUser._id, ws); |
| 724 | db.SetUser(user); |
| 725 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(meshid, [user._id, obj.dbNodeKey]), 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 }); |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | if (holdConnection == true) { |
| 730 | // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 731 | parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 732 | console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 733 | return; |
| 734 | } |
| 735 | } |
| 736 | if (mesh.mtype != 2) { // If we disconnect, the agnet will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 737 | parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 738 | console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 739 | return; |
| 740 | } |
| 741 | */ |
| 742 | |
| 743 | // Check that the node exists |
| 744 | db.Get(obj.dbNodeKey, function (err, nodes) { |
| 745 | if (obj.agentInfo == null) { return; } |
| 746 | var device, mesh; |
| 747 | |
| 748 | // See if this node exists in the database |
| 749 | if ((nodes == null) || (nodes.length == 0)) { |
| 750 | // This device does not exist, use the meshid given by the device |
| 751 | |
| 752 | // Check if we already have too many devices for this domain |
| 753 | if (domain.limits && (typeof domain.limits.maxdevices == 'number')) { |
| 754 | db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) { |
| 755 | if (ismax == true) { |
| 756 | // Too many devices in this domain. |
| 757 | parent.agentStats.maxDomainDevicesReached++; |
| 758 | } else { |
| 759 | // We are under the limit, create the new device. |
| 760 | completeAgentConnection2(); |
| 761 | } |
| 762 | }); |
| 763 | } else { |
| 764 | completeAgentConnection2(); |
| 765 | } |
| 766 | return; |
| 767 | } else { |
| 768 | device = nodes[0]; |
| 769 | obj.name = device.name; |
| 770 | |
| 771 | // This device exists, meshid given by the device must be ignored, use the server side one. |
| 772 | if ((device.meshid != null) && (device.meshid != obj.dbMeshKey)) { |
| 773 | obj.dbMeshKey = device.meshid; |
| 774 | obj.meshid = device.meshid.split('/')[2]; |
| 775 | } |
| 776 | |
| 777 | // See if this mesh exists, if it does not we may want to create it. |
| 778 | mesh = getMeshAutoCreate(); |
| 779 | |
| 780 | // Check if the mesh exists |
| 781 | if (mesh == null) { |
| 782 | // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 783 | parent.agentStats.invalidDomainMesh2Count++; |
| 784 | parent.setAgentIssue(obj, "invalidDomainMesh2"); |
| 785 | parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 786 | console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 787 | return; |
| 788 | } |
| 789 | |
| 790 | // Check if the mesh is the right type |
| 791 | if (mesh.mtype != 2) { |
| 792 | // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 793 | parent.agentStats.invalidMeshType2Count++; |
| 794 | parent.setAgentIssue(obj, "invalidMeshType2"); |
| 795 | parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 796 | console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 797 | return; |
| 798 | } |
| 799 | |
| 800 | // Mark when this device connected |
| 801 | obj.connectTime = Date.now(); |
| 802 | |
| 803 | // Device already exists, look if changes have occured |
| 804 | var changes = [], change = 0, log = 0; |
| 805 | if (device.agent == null) { device.agent = { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }; change = 1; } |
| 806 | if (device.rname != obj.agentInfo.computerName) { device.rname = obj.agentInfo.computerName; change = 1; changes.push('computer name'); } |
| 807 | if (device.agent.ver != obj.agentInfo.agentVersion) { device.agent.ver = obj.agentInfo.agentVersion; change = 1; changes.push('agent version'); } |
| 808 | if (device.agent.id != obj.agentInfo.agentId) { device.agent.id = obj.agentInfo.agentId; change = 1; changes.push('agent type'); } |
| 809 | 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 |
| 810 | // We want the server name to be sync'ed to the hostname or the --agentName |
| 811 | // (flag 16 allows to override the name until next connection) |
| 812 | if (mesh.flags && (mesh.flags & 2)) { |
| 813 | var preferredName = (mesh.flags & 8) && obj.agentName || obj.agentInfo.computerName; |
| 814 | if (device.name != preferredName) {device.name = preferredName; change = 1; } |
| 815 | } |
| 816 | if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; } |
| 817 | |
| 818 | if (change == 1) { |
| 819 | // Do some clean up if needed, these values should not be in the database. |
| 820 | if (device.conn != null) { delete device.conn; } |
| 821 | if (device.pwr != null) { delete device.pwr; } |
| 822 | if (device.agct != null) { delete device.agct; } |
| 823 | if (device.cict != null) { delete device.cict; } |
| 824 | |
| 825 | // Save the updated device in the database |
| 826 | db.Set(device); |
| 827 | |
| 828 | // If this is a temporary device, don't log changes |
| 829 | if (obj.agentInfo.capabilities & 0x20) { log = 0; } |
| 830 | |
| 831 | // Event the node change |
| 832 | var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) }; |
| 833 | if (log == 0) { event.nolog = 1; } else { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); } |
| 834 | 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. |
| 835 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); |
| 836 | } |
| 837 | } |
| 838 | |
| 839 | completeAgentConnection3(device, mesh); |
| 840 | }); |
| 841 | } |
| 842 | |
| 843 | function completeAgentConnection2() { |
| 844 | // See if this mesh exists, if it does not we may want to create it. |
| 845 | var mesh = getMeshAutoCreate(); |
| 846 | |
| 847 | // Check if the mesh exists |
| 848 | if (mesh == null) { |
| 849 | // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 850 | parent.agentStats.invalidDomainMeshCount++; |
| 851 | parent.setAgentIssue(obj, "invalidDomainMesh"); |
| 852 | parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 853 | console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').'); |
| 854 | return; |
| 855 | } |
| 856 | |
| 857 | // Check if the mesh is the right type |
| 858 | if (mesh.mtype != 2) { |
| 859 | // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours. |
| 860 | parent.agentStats.invalidMeshTypeCount++; |
| 861 | parent.setAgentIssue(obj, "invalidMeshType"); |
| 862 | parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 863 | console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').'); |
| 864 | return; |
| 865 | } |
| 866 | |
| 867 | // Mark when this device connected |
| 868 | obj.connectTime = Date.now(); |
| 869 | |
| 870 | // This node does not exist, create it. |
| 871 | var agentName = obj.agentName ? obj.agentName : obj.agentInfo.computerName; |
| 872 | 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, firstconnect: obj.connectTime }; |
| 873 | db.Set(device); |
| 874 | |
| 875 | // Event the new node |
| 876 | if (obj.agentInfo.capabilities & 0x20) { |
| 877 | // This is a temporary agent, don't log. |
| 878 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 }); |
| 879 | } else { |
| 880 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'addnode', node: device, msgid: 57, msgArgs: [obj.agentInfo.computerName, mesh.name], msg: ('Added device ' + obj.agentInfo.computerName + ' to device group ' + mesh.name), domain: domain.id }); |
| 881 | } |
| 882 | |
| 883 | completeAgentConnection3(device, mesh); |
| 884 | } |
| 885 | |
| 886 | function completeAgentConnection3(device, mesh) { |
| 887 | // Check if this agent is already connected |
| 888 | const dupAgent = parent.wsagents[obj.dbNodeKey]; |
| 889 | parent.wsagents[obj.dbNodeKey] = obj; |
| 890 | if (dupAgent) { |
| 891 | // Record duplicate agents |
| 892 | if (parent.duplicateAgentsLog[obj.dbNodeKey] == null) { |
| 893 | if (dupAgent.remoteaddr == obj.remoteaddr) { |
| 894 | parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr], count: 1 }; |
| 895 | } else { |
| 896 | parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr, dupAgent.remoteaddr], count: 1 }; |
| 897 | } |
| 898 | } else { |
| 899 | parent.duplicateAgentsLog[obj.dbNodeKey].name = device.name; |
| 900 | parent.duplicateAgentsLog[obj.dbNodeKey].group = mesh.name; |
| 901 | parent.duplicateAgentsLog[obj.dbNodeKey].count++; |
| 902 | if (parent.duplicateAgentsLog[obj.dbNodeKey].ip.indexOf(obj.remoteaddr) == -1) { parent.duplicateAgentsLog[obj.dbNodeKey].ip.push(obj.remoteaddr); } |
| 903 | } |
| 904 | |
| 905 | // Close the duplicate agent |
| 906 | parent.agentStats.duplicateAgentCount++; |
| 907 | parent.setAgentIssue(obj, 'duplicateAgent'); |
| 908 | if (obj.nodeid != null) { parent.parent.debug('agent', 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } |
| 909 | dupAgent.close(3); |
| 910 | } else { |
| 911 | // Indicate the agent is connected |
| 912 | parent.parent.SetConnectivityState(obj.dbMeshKey, obj.dbNodeKey, obj.connectTime, 1, 1, null, { remoteaddrport: obj.remoteaddrport, name: device.name }); |
| 913 | } |
| 914 | |
| 915 | // We are done, ready to communicate with this agent |
| 916 | delete obj.pendingCompleteAgentConnection; |
| 917 | obj.authenticated = 2; |
| 918 | |
| 919 | // Check how many times this agent disconnected in the last few minutes. |
| 920 | const disconnectCount = parent.wsagentsDisconnections[obj.nodeid]; |
| 921 | if (disconnectCount > 6) { |
| 922 | parent.parent.debug('agent', 'Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); |
| 923 | console.log('Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); |
| 924 | parent.agentStats.agentInBigTrouble++; |
| 925 | // TODO: Log or do something to recover? |
| 926 | return; |
| 927 | } |
| 928 | |
| 929 | // Command 4, inform mesh agent that it's authenticated. |
| 930 | obj.sendBinary(common.ShortToStr(4)); |
| 931 | |
| 932 | // Not sure why, but in rare cases, obj.agentInfo is undefined here. |
| 933 | if ((obj.agentInfo == null) || (typeof obj.agentInfo.capabilities != 'number')) { return; } // This is an odd case. |
| 934 | obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId]; |
| 935 | if (domain.meshAgentBinaries && domain.meshAgentBinaries[obj.agentInfo.agentId]) { obj.agentExeInfo = domain.meshAgentBinaries[obj.agentInfo.agentId]; } |
| 936 | |
| 937 | // Check if this agent is reconnecting too often. |
| 938 | if (disconnectCount > 4) { |
| 939 | // Too many disconnections, this agent has issues. Just clear the core. |
| 940 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); |
| 941 | parent.parent.debug('agent', 'Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); |
| 942 | parent.agentStats.agentInTrouble++; |
| 943 | //console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.'); |
| 944 | // TODO: Log or do something to recover? |
| 945 | return; |
| 946 | } |
| 947 | |
| 948 | // Check if we need to make an native update check |
| 949 | var corename = null; |
| 950 | if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) { |
| 951 | corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core; |
| 952 | } else { |
| 953 | // MeshCommand_CoreModule, ask mesh agent to clear the core |
| 954 | obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); |
| 955 | } |
| 956 | |
| 957 | if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) { |
| 958 | // Ask the agent for it's executable binary hash |
| 959 | obj.sendBinary(common.ShortToStr(12) + common.ShortToStr(0)); |
| 960 | } else { |
| 961 | // Check the mesh core, if the agent is capable of running one |
| 962 | if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) { |
| 963 | obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash. |
| 964 | } else { |
| 965 | agentCoreIsStable(); // No updates needed, agent is ready to go. |
| 966 | } |
| 967 | } |
| 968 | } |
| 969 | |
| 970 | // Indicate to the agent that we want to check Intel AMT configuration |
| 971 | // This may trigger a CIRA-LMS tunnel from the agent so the server can inspect the device. |
| 972 | obj.sendUpdatedIntelAmtPolicy = function (policy) { |
| 973 | if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT. |
| 974 | if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; } |
| 975 | if ((policy != null) && (policy.type != 0)) { |
| 976 | const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey); |
| 977 | try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { } |
| 978 | } |
| 979 | } |
| 980 | } |
| 981 | |
| 982 | function recoveryAgentCoreIsStable(mesh) { |
| 983 | parent.agentStats.recoveryCoreIsStableCount++; |
| 984 | |
| 985 | // Recovery agent is doing ok, lets perform main agent checking. |
| 986 | //console.log('recoveryAgentCoreIsStable()'); |
| 987 | |
| 988 | // Fetch the the real agent nodeid |
| 989 | db.Get('da' + obj.dbNodeKey, function (err, nodes, self) { |
| 990 | if ((nodes != null) && (nodes.length == 1)) { |
| 991 | self.realNodeKey = nodes[0].raid; |
| 992 | |
| 993 | // Get agent connection state |
| 994 | var agentConnected = false; |
| 995 | var state = parent.parent.GetConnectivityState(self.realNodeKey); |
| 996 | if (state) { agentConnected = ((state.connectivity & 1) != 0) } |
| 997 | |
| 998 | self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: self.realNodeKey, agent: agentConnected } })); |
| 999 | } else { |
| 1000 | self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } })); |
| 1001 | } |
| 1002 | }, obj); |
| 1003 | } |
| 1004 | |
| 1005 | function agentCoreIsStable() { |
| 1006 | parent.agentStats.coreIsStableCount++; |
| 1007 | |
| 1008 | // Check that the mesh exists |
| 1009 | const mesh = parent.meshes[obj.dbMeshKey]; |
| 1010 | if (mesh == null) { |
| 1011 | parent.agentStats.meshDoesNotExistCount++; |
| 1012 | parent.setAgentIssue(obj, "meshDoesNotExist"); |
| 1013 | // TODO: Mark this agent as part of a mesh that does not exists. |
| 1014 | return; // Probably not worth doing anything else. Hold this agent. |
| 1015 | } |
| 1016 | |
| 1017 | // Check if this is a recovery agent |
| 1018 | if (obj.agentInfo.capabilities & 0x40) { |
| 1019 | recoveryAgentCoreIsStable(mesh); |
| 1020 | return; |
| 1021 | } |
| 1022 | |
| 1023 | // Fetch the the diagnostic agent nodeid |
| 1024 | db.Get('ra' + obj.dbNodeKey, function (err, nodes) { |
| 1025 | if ((nodes != null) && (nodes.length == 1)) { |
| 1026 | obj.diagnosticNodeKey = nodes[0].daid; |
| 1027 | obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.diagnosticNodeKey } })); |
| 1028 | } |
| 1029 | }); |
| 1030 | |
| 1031 | // Indicate that we want to check the Intel AMT configuration |
| 1032 | // This may trigger a CIRA-LMS tunnel to the server for further processing |
| 1033 | obj.sendUpdatedIntelAmtPolicy(); |
| 1034 | |
| 1035 | // Fetch system information |
| 1036 | db.GetHash('si' + obj.dbNodeKey, function (err, results) { |
| 1037 | if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); } |
| 1038 | }); |
| 1039 | |
| 1040 | // Agent error log dump |
| 1041 | if (parent.parent.agentErrorLog != null) { |
| 1042 | db.Get('al' + obj.dbNodeKey, function (err, docs) { // Agent Log |
| 1043 | if ((docs != null) && (docs.length == 1) && (typeof docs[0].lastEvent)) { |
| 1044 | obj.send('{"action":"errorlog","startTime":' + docs[0].lastEvent + '}'); // Ask all events after a given time |
| 1045 | } else { |
| 1046 | obj.send('{"action":"errorlog"}'); // Ask all |
| 1047 | } |
| 1048 | }); |
| 1049 | } |
| 1050 | |
| 1051 | // Set agent core dump |
| 1052 | if ((parent.parent.config.settings != null) && ((parent.parent.config.settings.agentcoredump === true) || (parent.parent.config.settings.agentcoredump === false))) { |
| 1053 | obj.send(JSON.stringify({ action: 'coredump', value: parent.parent.config.settings.agentcoredump })); |
| 1054 | if (parent.parent.config.settings.agentcoredump === true) { |
| 1055 | // Check if we requested a core dump file in the last minute, if not, ask if one is present. |
| 1056 | if ((parent.lastCoreDumpRequest == null) || ((Date.now() - parent.lastCoreDumpRequest) >= 60000)) { obj.send(JSON.stringify({ action: 'getcoredump' })); } |
| 1057 | } |
| 1058 | } |
| 1059 | |
| 1060 | // Do this if IP location is enabled on this domain TODO: Set IP location per device group? |
| 1061 | if (domain.iplocation == true) { |
| 1062 | // Check if we already have IP location information for this node |
| 1063 | db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) { |
| 1064 | if ((iplocs != null) && (iplocs.length == 1)) { |
| 1065 | // We have a location in the database for this remote IP |
| 1066 | const iploc = iplocs[0], x = {}; |
| 1067 | if ((iploc != null) && (iploc.ip != null) && (iploc.loc != null)) { |
| 1068 | x.publicip = iploc.ip; |
| 1069 | x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000)); |
| 1070 | ChangeAgentLocationInfo(x); |
| 1071 | } |
| 1072 | } else { |
| 1073 | // Check if we need to ask for the IP location |
| 1074 | var doIpLocation = 0; |
| 1075 | if (obj.iploc == null) { |
| 1076 | doIpLocation = 1; |
| 1077 | } else { |
| 1078 | const loc = obj.iploc.split(','); |
| 1079 | if (loc.length < 3) { |
| 1080 | doIpLocation = 2; |
| 1081 | } else { |
| 1082 | var t = new Date((parseFloat(loc[2]) * 1000)), now = Date.now(); |
| 1083 | t.setDate(t.getDate() + 20); |
| 1084 | if (t < now) { doIpLocation = 3; } |
| 1085 | } |
| 1086 | } |
| 1087 | |
| 1088 | // If we need to ask for IP location, see if we have the quota to do it. |
| 1089 | if (doIpLocation > 0) { |
| 1090 | db.getValueOfTheDay('ipLocationRequestLimitor', 1000, function (ipLocationLimitor) { |
| 1091 | if ((ipLocationLimitor != null) && (ipLocationLimitor.value > 0)) { |
| 1092 | ipLocationLimitor.value--; |
| 1093 | db.Set(ipLocationLimitor); |
| 1094 | obj.send(JSON.stringify({ action: 'iplocation' })); |
| 1095 | } |
| 1096 | }); |
| 1097 | } |
| 1098 | } |
| 1099 | }); |
| 1100 | } |
| 1101 | |
| 1102 | // Indicate server information to the agent. |
| 1103 | var serverInfo = { action: 'serverInfo' }; |
| 1104 | if ((typeof domain.terminal == 'object') && (typeof domain.terminal.launchcommand == 'object')) { |
| 1105 | // Send terminal starting command |
| 1106 | serverInfo.termlaunchcommand = {}; |
| 1107 | if (typeof domain.terminal.launchcommand.linux == 'string') { serverInfo.termlaunchcommand.linux = domain.terminal.launchcommand.linux; } |
| 1108 | if (typeof domain.terminal.launchcommand.darwin == 'string') { serverInfo.termlaunchcommand.darwin = domain.terminal.launchcommand.darwin; } |
| 1109 | if (typeof domain.terminal.launchcommand.freebsd == 'string') { serverInfo.termlaunchcommand.freebsd = domain.terminal.launchcommand.freebsd; } |
| 1110 | } |
| 1111 | // Enable agent self guest sharing if allowed |
| 1112 | if (domain.agentselfguestsharing) { serverInfo.agentSelfGuestSharing = true; } |
| 1113 | obj.send(JSON.stringify(serverInfo)); |
| 1114 | |
| 1115 | // Plug in handler |
| 1116 | if (parent.parent.pluginHandler != null) { |
| 1117 | parent.parent.pluginHandler.callHook('hook_agentCoreIsStable', obj, parent); |
| 1118 | } |
| 1119 | } |
| 1120 | |
| 1121 | // Get the web certificate private key hash for the specified domain |
| 1122 | function getWebCertHash(domain) { |
| 1123 | const hash = parent.webCertificateHashs[domain.id]; |
| 1124 | if (hash != null) return hash; |
| 1125 | return parent.webCertificateHash; |
| 1126 | } |
| 1127 | |
| 1128 | // Get the web certificate hash for the specified domain |
| 1129 | function getWebCertFullHash(domain) { |
| 1130 | const hash = parent.webCertificateFullHashs[domain.id]; |
| 1131 | if (hash != null) return hash; |
| 1132 | return parent.webCertificateFullHash; |
| 1133 | } |
| 1134 | |
| 1135 | // Verify the agent signature |
| 1136 | function processAgentSignature(msg) { |
| 1137 | if (isIgnoreHashCheck() == false) { |
| 1138 | var verified = false; |
| 1139 | |
| 1140 | // This agent did not report a valid TLS certificate hash, fail now. |
| 1141 | if (obj.agentSeenCerthash == null) return false; |
| 1142 | |
| 1143 | // Raw RSA signatures have an exact length of 256 or 384. PKCS7 is larger. |
| 1144 | if ((msg.length != 384) && (msg.length != 256)) { |
| 1145 | // Verify a PKCS7 signature. |
| 1146 | var msgDer = null; |
| 1147 | try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { } |
| 1148 | if (msgDer != null) { |
| 1149 | try { |
| 1150 | const p7 = forge.pkcs7.messageFromAsn1(msgDer); |
| 1151 | const sig = p7.rawCapture.signature; |
| 1152 | |
| 1153 | // Verify with key hash |
| 1154 | var buf = Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary'); |
| 1155 | var verifier = parent.crypto.createVerify('RSA-SHA384'); |
| 1156 | verifier.update(buf); |
| 1157 | verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary'); |
| 1158 | if (verified !== true) { |
| 1159 | // Not a valid signature |
| 1160 | parent.agentStats.invalidPkcsSignatureCount++; |
| 1161 | parent.setAgentIssue(obj, "invalidPkcsSignature"); |
| 1162 | return false; |
| 1163 | } |
| 1164 | } catch (ex) { }; |
| 1165 | } |
| 1166 | } |
| 1167 | |
| 1168 | if (verified == false) { |
| 1169 | // Verify the RSA signature. This is the fast way, without using forge. |
| 1170 | const verify = parent.crypto.createVerify('SHA384'); |
| 1171 | verify.end(Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash |
| 1172 | if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) { |
| 1173 | parent.agentStats.invalidRsaSignatureCount++; |
| 1174 | parent.setAgentIssue(obj, "invalidRsaSignature"); |
| 1175 | return false; |
| 1176 | } |
| 1177 | } |
| 1178 | } |
| 1179 | |
| 1180 | // Connection is a success, clean up |
| 1181 | obj.nodeid = obj.unauth.nodeid; |
| 1182 | obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid; |
| 1183 | delete obj.nonce; |
| 1184 | delete obj.agentnonce; |
| 1185 | delete obj.unauth; |
| 1186 | delete obj.receivedCommands; |
| 1187 | delete obj.agentSeenCerthash; |
| 1188 | if (obj.unauthsign) delete obj.unauthsign; |
| 1189 | parent.agentStats.verifiedAgentConnectionCount++; |
| 1190 | parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').'); |
| 1191 | obj.authenticated = 1; |
| 1192 | return true; |
| 1193 | } |
| 1194 | |
| 1195 | // Process incoming agent JSON data |
| 1196 | function processAgentData(msg) { |
| 1197 | if (obj.agentInfo == null) return; |
| 1198 | var i, str = msg.toString('utf8'), command = null; |
| 1199 | if (str[0] == '{') { |
| 1200 | try { command = JSON.parse(str); } catch (ex) { |
| 1201 | // If the command can't be parsed, ignore it. |
| 1202 | parent.agentStats.invalidJsonCount++; |
| 1203 | parent.setAgentIssue(obj, "invalidJson (" + str.length + "): " + str); |
| 1204 | parent.parent.debug('agent', 'Unable to parse agent JSON (' + obj.remoteaddrport + ')'); |
| 1205 | console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex); |
| 1206 | return; |
| 1207 | } |
| 1208 | if (typeof command != 'object') { return; } |
| 1209 | switch (command.action) { |
| 1210 | case 'msg': |
| 1211 | { |
| 1212 | // If the same console command is processed many times, kick out this agent. |
| 1213 | // This is a safety mesure to guard against the agent DOS'ing the server. |
| 1214 | if (command.type == 'console') { |
| 1215 | if (obj.consoleKickValue == command.value) { |
| 1216 | if (obj.consoleKickCount) { obj.consoleKickCount++; } else { obj.consoleKickCount = 1; } |
| 1217 | if (obj.consoleKickCount > 30) { obj.close(); return; } // 30 identical console messages received, kick out this agent. |
| 1218 | } else { |
| 1219 | obj.consoleKickValue = command.value; |
| 1220 | } |
| 1221 | } |
| 1222 | |
| 1223 | // Route a message |
| 1224 | parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey); |
| 1225 | break; |
| 1226 | } |
| 1227 | case 'software': |
| 1228 | { |
| 1229 | // Todo - save software into database for offline access but send to web clients for now |
| 1230 | parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey); |
| 1231 | break; |
| 1232 | } |
| 1233 | case 'coreinfo': |
| 1234 | { |
| 1235 | // Sent by the agent to update agent information |
| 1236 | ChangeAgentCoreInfo(command); |
| 1237 | |
| 1238 | if ((obj.agentCoreUpdate === true) && (obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) { |
| 1239 | // Agent update. The recovery core was loaded in the agent, send a command to update the agent |
| 1240 | parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { // Medium priority task |
| 1241 | // If agent disconnection, complete and exit now. |
| 1242 | if ((obj.authenticated != 2) || (obj.agentExeInfo == null)) { parent.parent.taskLimiter.completed(taskid); return; } |
| 1243 | |
| 1244 | // Agent update. The recovery core was loaded in the agent, send a command to update the agent |
| 1245 | obj.agentCoreUpdateTaskId = taskid; |
| 1246 | const getme = new URL(obj.agentExeInfo.url); |
| 1247 | const url = '*' + getme.pathname + getme.search; |
| 1248 | var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex }; |
| 1249 | parent.parent.debug('agentupdate', "Sending agent update url: " + cmd.url); |
| 1250 | |
| 1251 | // Add the hash |
| 1252 | if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; } |
| 1253 | |
| 1254 | // Add server TLS cert hash |
| 1255 | if (isIgnoreHashCheck() == false) { |
| 1256 | const tlsCertHash = parent.webCertificateFullHashs[domain.id]; |
| 1257 | if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); } |
| 1258 | } |
| 1259 | |
| 1260 | // Send the agent update command |
| 1261 | obj.send(JSON.stringify(cmd)); |
| 1262 | }, null, 1); |
| 1263 | } |
| 1264 | break; |
| 1265 | } |
| 1266 | case 'smbios': |
| 1267 | { |
| 1268 | // SMBIOS information must never be saved when NeDB is in use. NeDB will currupt that database. |
| 1269 | if (db.SetSMBIOS == null) break; |
| 1270 | |
| 1271 | // See if we need to save SMBIOS information |
| 1272 | if (domain.smbios === true) { |
| 1273 | // Store the RAW SMBios table of this computer |
| 1274 | // Perform sanity checks before storing |
| 1275 | try { |
| 1276 | 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]; } } |
| 1277 | db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: command.value }); |
| 1278 | } catch (ex) { } |
| 1279 | } |
| 1280 | |
| 1281 | // Event the node interface information change (This is a lot of traffic, probably don't need this). |
| 1282 | //parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 }); |
| 1283 | |
| 1284 | break; |
| 1285 | } |
| 1286 | case 'netinfo': |
| 1287 | { |
| 1288 | // Check if network information is present |
| 1289 | if ((command.netif2 == null) && (command.netif == null)) return; |
| 1290 | |
| 1291 | // Escape any field names that have special characters |
| 1292 | if (command.netif2 != null) { |
| 1293 | for (var i in command.netif2) { |
| 1294 | var esc = common.escapeFieldName(i); |
| 1295 | if (esc !== i) { command.netif2[esc] = command.netif2[i]; delete command.netif2[i]; } |
| 1296 | } |
| 1297 | } |
| 1298 | |
| 1299 | // Sent by the agent to update agent network interface information |
| 1300 | delete command.action; |
| 1301 | command.updateTime = Date.now(); |
| 1302 | command._id = 'if' + obj.dbNodeKey; |
| 1303 | command.domain = domain.id; |
| 1304 | command.type = 'ifinfo'; |
| 1305 | db.Set(command); |
| 1306 | |
| 1307 | // Event the node interface information change |
| 1308 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 }); |
| 1309 | |
| 1310 | break; |
| 1311 | } |
| 1312 | case 'iplocation': |
| 1313 | { |
| 1314 | // Sent by the agent to update location information |
| 1315 | if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) { |
| 1316 | var x = {}; |
| 1317 | x.publicip = command.value.ip; |
| 1318 | x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000)); |
| 1319 | ChangeAgentLocationInfo(x); |
| 1320 | command.value._id = 'iploc_' + command.value.ip; |
| 1321 | command.value.type = 'iploc'; |
| 1322 | command.value.date = Date.now(); |
| 1323 | db.Set(command.value); // Store the IP to location data in the database |
| 1324 | // Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' } |
| 1325 | } |
| 1326 | break; |
| 1327 | } |
| 1328 | case 'mc1migration': |
| 1329 | { |
| 1330 | if (command.oldnodeid.length != 64) break; |
| 1331 | const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase(); |
| 1332 | db.Get(oldNodeKey, function (err, nodes) { |
| 1333 | if ((nodes == null) || (nodes.length != 1)) return; |
| 1334 | const node = nodes[0]; |
| 1335 | if (node.meshid == obj.dbMeshKey) { |
| 1336 | // Update the device name & host |
| 1337 | const newNode = { "name": node.name }; |
| 1338 | if (node.intelamt != null) { newNode.intelamt = node.intelamt; } |
| 1339 | ChangeAgentCoreInfo(newNode); |
| 1340 | |
| 1341 | // Delete this node including network interface information and events |
| 1342 | db.Remove(node._id); |
| 1343 | db.Remove('if' + node._id); |
| 1344 | |
| 1345 | // Event node deletion |
| 1346 | const change = 'Migrated device ' + node.name; |
| 1347 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: node._id, msg: change, domain: node.domain }); |
| 1348 | } |
| 1349 | }); |
| 1350 | break; |
| 1351 | } |
| 1352 | case 'openUrl': |
| 1353 | { |
| 1354 | // Sent by the agent to return the status of a open URL action. |
| 1355 | // Nothing is done right now. |
| 1356 | break; |
| 1357 | } |
| 1358 | case 'log': |
| 1359 | { |
| 1360 | // Log a value in the event log |
| 1361 | if ((typeof command.msg == 'string') && (command.msg.length < 4096)) { |
| 1362 | var event = { etype: 'node', action: 'agentlog', nodeid: obj.dbNodeKey, domain: domain.id, msg: command.msg }; |
| 1363 | if (typeof command.msgid == 'number') { event.msgid = command.msgid; } |
| 1364 | if (typeof command.guestname == 'string') { event.guestname = command.guestname; } |
| 1365 | if (Array.isArray(command.msgArgs)) { event.msgArgs = command.msgArgs; } |
| 1366 | if (typeof command.remoteaddr == 'string') { event.remoteaddr = command.remoteaddr; } |
| 1367 | var targets = parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]); |
| 1368 | if (typeof command.userid == 'string') { |
| 1369 | var loguser = parent.users[command.userid]; |
| 1370 | if (loguser) { event.userid = command.userid; event.username = loguser.name; targets.push(command.userid); } |
| 1371 | } |
| 1372 | if (typeof command.xuserid == 'string') { |
| 1373 | var xloguser = parent.users[command.xuserid]; |
| 1374 | if (xloguser) { targets.push(command.xuserid); } |
| 1375 | } |
| 1376 | if ((typeof command.sessionid == 'string') && (command.sessionid.length < 500)) { event.sessionid = command.sessionid; } |
| 1377 | parent.parent.DispatchEvent(targets, obj, event); |
| 1378 | |
| 1379 | // If this is a help request, see if we need to email notify anyone |
| 1380 | if (event.msgid == 98) { |
| 1381 | // Get the node and change it if needed |
| 1382 | 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. |
| 1383 | if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } |
| 1384 | const device = nodes[0]; |
| 1385 | if (typeof device.name == 'string') { parent.parent.NotifyUserOfDeviceHelpRequest(domain, device.meshid, device._id, device.name, command.msgArgs[0], command.msgArgs[1]); } |
| 1386 | }); |
| 1387 | } |
| 1388 | } |
| 1389 | break; |
| 1390 | } |
| 1391 | case 'ping': { sendPong(); break; } |
| 1392 | case 'pong': { break; } |
| 1393 | case 'getScript': |
| 1394 | { |
| 1395 | // Used by the agent to get configuration scripts. |
| 1396 | if (command.type == 1) { |
| 1397 | parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) { |
| 1398 | obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() })); |
| 1399 | }); |
| 1400 | } else if (command.type == 2) { |
| 1401 | parent.getCiraCleanupScript(function (script) { |
| 1402 | obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() })); |
| 1403 | }); |
| 1404 | } |
| 1405 | break; |
| 1406 | } |
| 1407 | case 'diagnostic': |
| 1408 | { |
| 1409 | if (typeof command.value == 'object') { |
| 1410 | switch (command.value.command) { |
| 1411 | case 'register': { |
| 1412 | // Only main agent can do this |
| 1413 | if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64)) { |
| 1414 | // Store links to diagnostic agent id |
| 1415 | var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value); |
| 1416 | db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent |
| 1417 | db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent |
| 1418 | } |
| 1419 | break; |
| 1420 | } |
| 1421 | case 'query': { |
| 1422 | // Only the diagnostic agent can do |
| 1423 | if ((obj.agentInfo.capabilities & 0x40) != 0) { |
| 1424 | // Return nodeid of main agent + connection status |
| 1425 | db.Get('da' + obj.dbNodeKey, function (err, nodes) { |
| 1426 | if ((nodes != null) && (nodes.length == 1)) { |
| 1427 | obj.realNodeKey = nodes[0].raid; |
| 1428 | |
| 1429 | // Get agent connection state |
| 1430 | var agentConnected = false; |
| 1431 | var state = parent.parent.GetConnectivityState(obj.realNodeKey); |
| 1432 | if (state) { agentConnected = ((state.connectivity & 1) != 0) } |
| 1433 | |
| 1434 | obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } })); |
| 1435 | } else { |
| 1436 | obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } })); |
| 1437 | } |
| 1438 | }); |
| 1439 | } |
| 1440 | break; |
| 1441 | } |
| 1442 | case 'log': { |
| 1443 | if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256)) { |
| 1444 | // If this is a diagnostic agent, log the event in the log of the main agent |
| 1445 | var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, snodeid: obj.dbNodeKey, domain: domain.id, msg: command.value.value }; |
| 1446 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event); |
| 1447 | } |
| 1448 | break; |
| 1449 | } |
| 1450 | } |
| 1451 | } |
| 1452 | break; |
| 1453 | } |
| 1454 | case 'sysinfo': { |
| 1455 | if ((typeof command.data == 'object') && (typeof command.data.hash == 'string')) { |
| 1456 | // Validate command.data. |
| 1457 | if (common.validateObjectForMongo(command.data, 1024) == false) break; |
| 1458 | |
| 1459 | // Save to database |
| 1460 | command.data._id = 'si' + obj.dbNodeKey; |
| 1461 | command.data.type = 'sysinfo'; |
| 1462 | command.data.domain = domain.id; |
| 1463 | command.data.time = Date.now(); |
| 1464 | db.Set(command.data); // Update system information in the database. |
| 1465 | |
| 1466 | // Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed |
| 1467 | var event = { etype: 'node', action: 'sysinfohash', nodeid: obj.dbNodeKey, domain: domain.id, hash: command.data.hash, nolog: 1 }; |
| 1468 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event); |
| 1469 | } |
| 1470 | break; |
| 1471 | } |
| 1472 | case 'sysinfocheck': { |
| 1473 | // Check system information update |
| 1474 | db.GetHash('si' + obj.dbNodeKey, function (err, results) { |
| 1475 | if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); } |
| 1476 | }); |
| 1477 | break; |
| 1478 | } |
| 1479 | case 'sessions': { |
| 1480 | // This is a list of sessions provided by the agent |
| 1481 | if (obj.sessions == null) { obj.sessions = {}; } |
| 1482 | if (typeof command.value != null) { |
| 1483 | if (command.type == 'kvm') { obj.sessions.kvm = command.value; } |
| 1484 | else if (command.type == 'terminal') { obj.sessions.terminal = command.value; } |
| 1485 | else if (command.type == 'files') { obj.sessions.files = command.value; } |
| 1486 | else if (command.type == 'help') { obj.sessions.help = command.value; } |
| 1487 | else if (command.type == 'tcp') { obj.sessions.tcp = command.value; } |
| 1488 | else if (command.type == 'udp') { obj.sessions.udp = command.value; } |
| 1489 | else if (command.type == 'msg') { obj.sessions.msg = command.value; } |
| 1490 | else if (command.type == 'app') { obj.sessions.app = command.value; } |
| 1491 | } |
| 1492 | |
| 1493 | // Any "help" session must have an associated app, if not, remove it. |
| 1494 | if (obj.sessions.help != null) { |
| 1495 | for (var i in obj.sessions.help) { if (obj.sessions.help[i] == null) { delete obj.sessions.help[i]; } } |
| 1496 | if (Object.keys(obj.sessions.help).length == 0) { delete obj.sessions.help; } |
| 1497 | } |
| 1498 | |
| 1499 | // Inform everyone of updated sessions |
| 1500 | obj.updateSessions(); |
| 1501 | break; |
| 1502 | } |
| 1503 | case 'battery': { |
| 1504 | // Device battery and power state |
| 1505 | if (obj.sessions == null) { obj.sessions = {}; } |
| 1506 | if (obj.sessions.battery == null) { obj.sessions.battery = {}; } |
| 1507 | if ((command.state == 'ac') || (command.state == 'dc')) { obj.sessions.battery.state = command.state; } else { delete obj.sessions.battery.state; } |
| 1508 | if ((typeof command.level == 'number') && (command.level >= 0) && (command.level <= 100)) { obj.sessions.battery.level = command.level; } else { delete obj.sessions.battery.level; } |
| 1509 | obj.updateSessions(); |
| 1510 | break; |
| 1511 | } |
| 1512 | case 'getcoredump': { |
| 1513 | // Check if we requested a core dump file in the last minute, if so, ignore this. |
| 1514 | if ((parent.lastCoreDumpRequest != null) && ((Date.now() - parent.lastCoreDumpRequest) < 60000)) break; |
| 1515 | |
| 1516 | // Indicates if the agent has a coredump available |
| 1517 | if ((command.exists === true) && (typeof command.agenthashhex == 'string') && (command.agenthashhex.length == 96)) { |
| 1518 | // Check if we already have this exact dump file |
| 1519 | const coreDumpFile = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps', obj.agentInfo.agentId + '-' + command.agenthashhex + '-' + obj.nodeid + '.dmp'); |
| 1520 | parent.fs.stat(coreDumpFile, function (err, stats) { |
| 1521 | if (stats != null) return; |
| 1522 | obj.coreDumpPresent = true; |
| 1523 | |
| 1524 | // Check how many files are in the coredumps folder |
| 1525 | const coreDumpPath = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps'); |
| 1526 | parent.fs.readdir(coreDumpPath, function (err, files) { |
| 1527 | if ((files != null) && (files.length >= 20)) return; // Don't get more than 20 core dump files. |
| 1528 | |
| 1529 | // Get the core dump uploaded to the server. |
| 1530 | parent.lastCoreDumpRequest = Date.now(); |
| 1531 | obj.RequestCoreDump(command.agenthashhex, command.corehashhex); |
| 1532 | }); |
| 1533 | }); |
| 1534 | } |
| 1535 | break; |
| 1536 | } |
| 1537 | case 'tunnelCloseStats': { |
| 1538 | // TODO: This this extra stats from the tunnel, you can merge this into the tunnel event in the database. |
| 1539 | //console.log(command); |
| 1540 | |
| 1541 | // Validate input |
| 1542 | if ((command.sent == null) || (typeof command.sent != 'string')) return; |
| 1543 | if ((command.sentActual == null) || (typeof command.sentActual != 'string')) return; |
| 1544 | if ((command.sentActual == null) || (typeof command.sentActual != 'number')) return; |
| 1545 | |
| 1546 | // Event the session closed compression data. |
| 1547 | var event = { etype: 'node', action: 'sessioncompression', nodeid: obj.dbNodeKey, domain: domain.id, sent: parseInt(command.sent), sentActual: parseInt(command.sentActual), msgid: 54, msgArgs: [command.sentRatio, parseInt(command.sent), parseInt(command.sentActual)], msg: 'Agent closed session with ' + command.sentRatio + '% agent to server compression. Sent: ' + command.sent + ', Compressed: ' + command.sentActual + '.' }; |
| 1548 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event); |
| 1549 | break; |
| 1550 | } |
| 1551 | case 'lmsinfo': { |
| 1552 | // Agents send the LMS port bindings |
| 1553 | // Example: {"action":"lmsinfo","value":{"ports":["623","16992"]}} |
| 1554 | break; |
| 1555 | } |
| 1556 | case 'plugin': { |
| 1557 | if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break; |
| 1558 | try { |
| 1559 | parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent); |
| 1560 | } catch (e) { |
| 1561 | parent.parent.debug('agent', 'Error loading plugin handler (' + e + ')'); |
| 1562 | console.log('Error loading plugin handler (' + e + ')'); |
| 1563 | } |
| 1564 | break; |
| 1565 | } |
| 1566 | case 'meshToolInfo': { |
| 1567 | // Return information about a MeshCentral tool. Current tools are 'MeshCentralRouter' and 'MeshCentralAssistant' |
| 1568 | // Information includes file hash and download location URL |
| 1569 | if (typeof command.name != 'string') break; |
| 1570 | var info = parent.parent.meshToolsBinaries[command.name]; |
| 1571 | if ((command.hash != null) && (info.hash == command.hash)) return; |
| 1572 | |
| 1573 | // To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly. |
| 1574 | var xdomain = (domain.dns == null) ? domain.id : ''; |
| 1575 | if (xdomain != '') xdomain += '/'; |
| 1576 | |
| 1577 | // Build the response |
| 1578 | const responseCmd = { action: 'meshToolInfo', name: command.name, tag: command.tag, sessionid: command.sessionid, hash: info.hash, size: info.size, url: info.url }; |
| 1579 | if ((command.name == 'MeshCentralAssistant') && (command.msh == true)) { responseCmd.url = '*/' + xdomain + 'meshagents?id=10006'; } // If this is Assistant and the MSH needs to be included in the executable, change the URL. |
| 1580 | if (command.cookie === true) { responseCmd.url += ('&auth=' + parent.parent.encodeCookie({ download: info.dlname }, parent.parent.loginCookieEncryptionKey)); } |
| 1581 | if (command.pipe === true) { responseCmd.pipe = true; } |
| 1582 | if (parent.webCertificateHashs[domain.id] != null) { responseCmd.serverhash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex'); } |
| 1583 | try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { } |
| 1584 | break; |
| 1585 | } |
| 1586 | case 'agentupdate': { |
| 1587 | if ((obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) { |
| 1588 | var func = function agentUpdateFunc(argument, taskid, taskLimiterQueue) { // Medium priority task |
| 1589 | // If agent disconnection, complete and exit now. |
| 1590 | if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; } |
| 1591 | |
| 1592 | // Agent is requesting an agent update |
| 1593 | obj.agentCoreUpdateTaskId = taskid; |
| 1594 | const getme = new URL(obj.agentExeInfo.url); |
| 1595 | const url = '*' + getme.pathname + getme.search; |
| 1596 | var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex, sessionid: agentUpdateFunc.sessionid }; |
| 1597 | parent.parent.debug('agentupdate', "Sending user requested agent update url: " + cmd.url); |
| 1598 | |
| 1599 | // Add the hash |
| 1600 | if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; } |
| 1601 | |
| 1602 | // Add server TLS cert hash |
| 1603 | if (isIgnoreHashCheck() == false) { |
| 1604 | const tlsCertHash = parent.webCertificateFullHashs[domain.id]; |
| 1605 | if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); } |
| 1606 | } |
| 1607 | |
| 1608 | // Send the agent update command |
| 1609 | obj.send(JSON.stringify(cmd)); |
| 1610 | } |
| 1611 | func.sessionid = command.sessionid; |
| 1612 | |
| 1613 | // Agent update. The recovery core was loaded in the agent, send a command to update the agent |
| 1614 | parent.parent.taskLimiter.launch(func, null, 1); |
| 1615 | } |
| 1616 | break; |
| 1617 | } |
| 1618 | case 'agentupdatedownloaded': { |
| 1619 | if (obj.agentCoreUpdateTaskId != null) { |
| 1620 | // Indicate this udpate task is complete |
| 1621 | parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId); |
| 1622 | delete obj.agentCoreUpdateTaskId; |
| 1623 | } |
| 1624 | break; |
| 1625 | } |
| 1626 | case 'errorlog': { // This is the agent error log |
| 1627 | if ((!Array.isArray(command.log)) || (command.log.length == 0) || (parent.parent.agentErrorLog == null)) break; |
| 1628 | var lastLogEntry = command.log[command.log.length - 1]; |
| 1629 | if ((lastLogEntry != null) && (typeof lastLogEntry == 'object') && (typeof lastLogEntry.t == 'number')) { |
| 1630 | parent.fs.write(parent.parent.agentErrorLog, obj.dbNodeKey + ', ' + Date.now() + ', ' + str + '\r\n', function (err) { }); |
| 1631 | db.Set({ _id: 'al' + obj.dbNodeKey, lastEvent: lastLogEntry.t }); |
| 1632 | } |
| 1633 | break; |
| 1634 | } |
| 1635 | case '2faauth': { |
| 1636 | // Validate input |
| 1637 | if ((typeof command.url != 'string') || (typeof command.approved != 'boolean') || (command.url.startsWith('2fa://') == false)) return; |
| 1638 | |
| 1639 | // parse the URL |
| 1640 | var url = null; |
| 1641 | try { url = new URL(command.url); } catch (ex) { } |
| 1642 | if (url == null) return; |
| 1643 | |
| 1644 | // Decode the cookie |
| 1645 | var urlSplit = url.search.slice(1).split('&c='); |
| 1646 | if (urlSplit.length != 2) return; |
| 1647 | const authCookie = parent.parent.decodeCookie(urlSplit[1], null, 1); |
| 1648 | if ((authCookie == null) || (typeof authCookie.c != 'string') || (('code=' + authCookie.c) != urlSplit[0])) return; |
| 1649 | if ((typeof authCookie.n != 'string') || (authCookie.n != obj.dbNodeKey) || (typeof authCookie.u != 'string')) return; |
| 1650 | |
| 1651 | // Fetch the user |
| 1652 | const user = parent.users[authCookie.u]; |
| 1653 | if (user == null) return; |
| 1654 | |
| 1655 | // Add this device as the authentication push notification device for this user |
| 1656 | if (authCookie.a == 'addAuth') { |
| 1657 | // Do nothing if authentication is not approved. |
| 1658 | // We do not want to indicate that the remote user responded to this. |
| 1659 | if (command.approved !== true) return; |
| 1660 | |
| 1661 | // Change the user |
| 1662 | user.otpdev = obj.dbNodeKey; |
| 1663 | parent.db.SetUser(user); |
| 1664 | |
| 1665 | // Notify change |
| 1666 | var targets = ['*', 'server-users', user._id]; |
| 1667 | if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } } |
| 1668 | var event = { etype: 'user', userid: user._id, username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msgid: 113, msg: "Added push notification authentication device", domain: domain.id }; |
| 1669 | if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the user. Another event will come. |
| 1670 | parent.parent.DispatchEvent(targets, obj, event); |
| 1671 | } |
| 1672 | |
| 1673 | // Complete 2FA checking |
| 1674 | if (authCookie.a == 'checkAuth') { |
| 1675 | if (typeof authCookie.s != 'string') return; |
| 1676 | // Notify 2FA response |
| 1677 | parent.parent.DispatchEvent(['2fadev-' + authCookie.s], obj, { etype: '2fadev', action: '2faresponse', domain: domain.id, nodeid: obj.dbNodeKey, code: authCookie.a, userid: user._id, approved: command.approved, sessionid: authCookie.s, nolog: 1 }); |
| 1678 | } |
| 1679 | |
| 1680 | break; |
| 1681 | } |
| 1682 | case 'getUserImage': { |
| 1683 | // Validate input |
| 1684 | if (typeof command.userid != 'string') { |
| 1685 | // Send back the default image if required |
| 1686 | if ((command.default) || (command.sentDefault)) { |
| 1687 | try { command.image = 'data:image/png;base64,' + Buffer.from(parent.fs.readFileSync(parent.parent.path.join(__dirname, 'public', 'images', 'user-128.png')), 'binary').toString('base64'); } catch (ex) { } |
| 1688 | obj.send(JSON.stringify(command)); |
| 1689 | } |
| 1690 | return; |
| 1691 | } |
| 1692 | var useridsplit = command.userid.split('/'); |
| 1693 | if ((useridsplit.length != 3) || (useridsplit[1] != domain.id)) return; |
| 1694 | |
| 1695 | // Add the user's real name if present |
| 1696 | var u = parent.users[command.userid]; |
| 1697 | if (u == null) return; |
| 1698 | if (u.name) { command.name = u.name; } |
| 1699 | if (u.realname) { command.realname = u.realname; } |
| 1700 | |
| 1701 | // An agent can only request images of accounts with rights to the device. |
| 1702 | if (parent.GetNodeRights(command.userid, obj.dbMeshKey, obj.dbNodeKey) != 0) { |
| 1703 | parent.db.Get('im' + command.userid, function (err, images) { |
| 1704 | if ((err == null) && (images != null) && (images.length == 1)) { |
| 1705 | // Send back the account image |
| 1706 | command.image = images[0].image; |
| 1707 | } else { |
| 1708 | // Send back the default image if required |
| 1709 | if ((command.default) || (command.sentDefault)) { |
| 1710 | try { command.image = 'data:image/png;base64,' + Buffer.from(parent.fs.readFileSync(parent.parent.path.join(__dirname, 'public', 'images', 'user-128.png')), 'binary').toString('base64'); } catch (ex) { } |
| 1711 | } |
| 1712 | } |
| 1713 | obj.send(JSON.stringify(command)); |
| 1714 | }); |
| 1715 | } |
| 1716 | break; |
| 1717 | } |
| 1718 | case 'getServerImage': { |
| 1719 | if (command.agent === 'assistant') { |
| 1720 | // Return server title and image for MeshCentral Assistant |
| 1721 | if ((domain.assistantcustomization != null) && (typeof domain.assistantcustomization == 'object')) { |
| 1722 | var ok = false; |
| 1723 | if (typeof domain.assistantcustomization.title == 'string') { ok = true; command.title = domain.assistantcustomization.title; } |
| 1724 | if (typeof domain.assistantcustomization.image == 'string') { try { command.image = 'data:image/jpeg;base64,' + Buffer.from(parent.fs.readFileSync(parent.parent.getConfigFilePath(domain.assistantcustomization.image)), 'binary').toString('base64'); ok = true; } catch (ex) { console.log(ex); } } |
| 1725 | if (ok) { obj.send(JSON.stringify(command)); } |
| 1726 | } |
| 1727 | } |
| 1728 | if (command.agent === 'android') { |
| 1729 | // Return server title and image for MeshCentral Assistant |
| 1730 | if ((domain.androidcustomization != null) && (typeof domain.androidcustomization == 'object')) { |
| 1731 | var ok = false; |
| 1732 | if (typeof domain.androidcustomization.title == 'string') { ok = true; command.title = domain.androidcustomization.title; } |
| 1733 | if (typeof domain.androidcustomization.subtitle == 'string') { ok = true; command.subtitle = domain.androidcustomization.subtitle; } |
| 1734 | if (typeof domain.androidcustomization.image == 'string') { try { command.image = 'data:image/jpeg;base64,' + Buffer.from(parent.fs.readFileSync(parent.parent.getConfigFilePath(domain.androidcustomization.image)), 'binary').toString('base64'); ok = true; } catch (ex) { console.log(ex); } } |
| 1735 | if (ok) { obj.send(JSON.stringify(command)); } |
| 1736 | } |
| 1737 | } |
| 1738 | break; |
| 1739 | } |
| 1740 | case 'guestShare': { |
| 1741 | if ((command.flags == null) || (command.flags == 0)) { |
| 1742 | // Stop any current self-share, this is allowed even if self guest sharing is not allows so to clear any old shares. |
| 1743 | removeGuestSharing(function () { |
| 1744 | delete obj.guestSharing; |
| 1745 | obj.send(JSON.stringify({ action: 'guestShare', flags: command.flags, url: null, viewOnly: false })); |
| 1746 | }); |
| 1747 | } else { |
| 1748 | // Add a new self-share, this will replace any share for this device |
| 1749 | if ((domain.agentselfguestsharing == null) || (domain.agentselfguestsharing == false) || (typeof command.flags != 'number')) return; // Check if agent self-sharing is allowed, this is off by default. |
| 1750 | if ((command.flags & 2) == 0) { command.viewOnly = false; } // Only allow "view only" if desktop is shared. |
| 1751 | addGuestSharing(command.flags, command.viewOnly, function (share) { |
| 1752 | obj.guestSharing = true; |
| 1753 | obj.send(JSON.stringify({ action: 'guestShare', url: share.url, flags: share.flags, viewOnly: share.viewOnly })); |
| 1754 | }) |
| 1755 | } |
| 1756 | break; |
| 1757 | } |
| 1758 | case 'amtconfig': { |
| 1759 | // Sent by the agent when the agent needs a Intel AMT APF connection to the server |
| 1760 | const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey); |
| 1761 | try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { } |
| 1762 | break; |
| 1763 | } |
| 1764 | case 'script-task': { |
| 1765 | // These command are for running regular batch jobs on the remote device |
| 1766 | if (parent.parent.taskManager != null) { parent.parent.taskManager.agentAction(command, obj); } |
| 1767 | break; |
| 1768 | } |
| 1769 | default: { |
| 1770 | parent.agentStats.unknownAgentActionCount++; |
| 1771 | parent.parent.debug('agent', 'Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.'); |
| 1772 | console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.'); |
| 1773 | break; |
| 1774 | } |
| 1775 | } |
| 1776 | if (parent.parent.pluginHandler != null) { |
| 1777 | parent.parent.pluginHandler.callHook('hook_processAgentData', command, obj, parent); |
| 1778 | } |
| 1779 | } |
| 1780 | } |
| 1781 | |
| 1782 | function addGuestSharing(flags, viewOnly, func) { |
| 1783 | // Create cookie |
| 1784 | const publicid = 'AS:' + obj.dbNodeKey; |
| 1785 | const extrakey = getRandomAmtPassword(); |
| 1786 | const cookie = { a: 6, pid: publicid, k: extrakey }; // New style sharing cookie |
| 1787 | const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey); |
| 1788 | if (inviteCookie == null) return; |
| 1789 | |
| 1790 | // Create the server url |
| 1791 | var serverName = parent.getWebServerName(domain, req); |
| 1792 | var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified |
| 1793 | var xdomain = (domain.dns == null) ? domain.id : ''; |
| 1794 | if (xdomain != '') xdomain += '/'; |
| 1795 | var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie; |
| 1796 | if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; } |
| 1797 | |
| 1798 | // Create a device sharing database entry |
| 1799 | var shareEntry = { _id: 'deviceshare-' + publicid, type: 'deviceshare', nodeid: obj.dbNodeKey, p: flags, domain: domain.id, publicid: publicid, guestName: 'Agent', consent: 0x7F, url: url, extrakey: extrakey }; |
| 1800 | |
| 1801 | // Add expire time |
| 1802 | if ((typeof domain.agentselfguestsharing == 'object') && (typeof domain.agentselfguestsharing.expire == 'number') && (domain.agentselfguestsharing.expire > 0)) { |
| 1803 | shareEntry.startTime = Date.now(); |
| 1804 | shareEntry.expireTime = Date.now() + (60000 * domain.agentselfguestsharing.expire); |
| 1805 | } |
| 1806 | |
| 1807 | if (viewOnly === true) { shareEntry.viewOnly = true; } |
| 1808 | parent.db.Set(shareEntry); |
| 1809 | |
| 1810 | // Send out an event that we added a device share |
| 1811 | var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey); |
| 1812 | var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added device share with unlimited time', msgid: 131, msgArgs: ['Agent'], domain: domain.id }; |
| 1813 | parent.parent.DispatchEvent(targets, obj, event); |
| 1814 | |
| 1815 | // Send device share update |
| 1816 | parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) { |
| 1817 | if (err != null) return; |
| 1818 | |
| 1819 | // Check device sharing |
| 1820 | var now = Date.now(); |
| 1821 | for (var i = 0; i < docs.length; i++) { |
| 1822 | const doc = docs[i]; |
| 1823 | if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else { |
| 1824 | // This share is ok, remove extra data we don't need to send. |
| 1825 | delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; |
| 1826 | } |
| 1827 | } |
| 1828 | |
| 1829 | // Send device share update |
| 1830 | var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []); |
| 1831 | parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 }); |
| 1832 | |
| 1833 | // Callback |
| 1834 | if (func) { func({ url: url, flags: flags, viewOnly: viewOnly }); } |
| 1835 | }); |
| 1836 | } |
| 1837 | |
| 1838 | function removeGuestSharing(func) { |
| 1839 | var publicid = 'AS:' + obj.dbNodeKey; |
| 1840 | parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) { |
| 1841 | if (err != null) return; |
| 1842 | |
| 1843 | // Remove device sharing |
| 1844 | var now = Date.now(), removedExact = null, removed = false, okDocs = []; |
| 1845 | for (var i = 0; i < docs.length; i++) { |
| 1846 | const doc = docs[i]; |
| 1847 | if (doc.publicid == publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; } |
| 1848 | else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else { |
| 1849 | // This share is ok, remove extra data we don't need to send. |
| 1850 | delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type; |
| 1851 | okDocs.push(doc); |
| 1852 | } |
| 1853 | } |
| 1854 | |
| 1855 | // Event device share removal |
| 1856 | if (removedExact != null) { |
| 1857 | // Send out an event that we removed a device share |
| 1858 | var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, ['server-shareremove']); |
| 1859 | var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['Agent'], domain: domain.id, publicid: publicid }; |
| 1860 | parent.parent.DispatchEvent(targets, obj, event); |
| 1861 | } |
| 1862 | |
| 1863 | // If we removed any shares, send device share update |
| 1864 | if (removed == true) { |
| 1865 | var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []); |
| 1866 | parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 }); |
| 1867 | } |
| 1868 | |
| 1869 | // Call back when done |
| 1870 | if (func) func(removed); |
| 1871 | }); |
| 1872 | } |
| 1873 | |
| 1874 | // Notify update of sessions |
| 1875 | obj.updateSessions = function () { |
| 1876 | // Perform some clean up |
| 1877 | for (var i in obj.sessions) { if (Object.keys(obj.sessions[i]).length == 0) { delete obj.sessions[i]; } } |
| 1878 | if (Object.keys(obj.sessions).length == 0) { delete obj.sessions; } |
| 1879 | |
| 1880 | // Event the new sessions, this will notify everyone that agent sessions have changed |
| 1881 | var event = { etype: 'node', action: 'devicesessions', nodeid: obj.dbNodeKey, domain: domain.id, sessions: obj.sessions, nolog: 1 }; |
| 1882 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event); |
| 1883 | } |
| 1884 | |
| 1885 | // Change the current core information string and event it |
| 1886 | function ChangeAgentCoreInfo(command) { |
| 1887 | if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return; |
| 1888 | if ((command == null) || (command == null)) return; // Safety, should never happen. |
| 1889 | |
| 1890 | // If the device is pending a change, hold. |
| 1891 | if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; } |
| 1892 | obj.deviceChanging = true; |
| 1893 | |
| 1894 | // Check that the mesh exists |
| 1895 | const mesh = parent.meshes[obj.dbMeshKey]; |
| 1896 | if (mesh == null) { delete obj.deviceChanging; return; } |
| 1897 | |
| 1898 | // Get the node and change it if needed |
| 1899 | 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. |
| 1900 | if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } |
| 1901 | const device = nodes[0]; |
| 1902 | if (device.agent) { |
| 1903 | var changes = [], change = 0, log = 0; |
| 1904 | |
| 1905 | // Check if anything changes |
| 1906 | if (command.name && (typeof command.name == 'string') && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); } |
| 1907 | 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. |
| 1908 | 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. |
| 1909 | 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. |
| 1910 | if ((typeof command.root == 'boolean') && (command.root !== device.agent.root)) { change = 1; device.agent.root = command.root; } |
| 1911 | if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; } |
| 1912 | if (command.intelamt) { |
| 1913 | if (!device.intelamt) { device.intelamt = {}; } |
| 1914 | if ((command.intelamt.Versions != null) && (typeof command.intelamt.Versions == 'object')) { |
| 1915 | if ((command.intelamt.Versions.AMT != null) && (typeof command.intelamt.Versions.AMT == 'string') && (command.intelamt.Versions.AMT.length < 12) && (device.intelamt.ver != command.intelamt.Versions.AMT)) { changes.push('AMT version'); device.intelamt.ver = command.intelamt.Versions.AMT; change = 1; log = 1; } |
| 1916 | if ((command.intelamt.Versions.Sku != null) && (typeof command.intelamt.Versions.Sku == 'string')) { |
| 1917 | const sku = parseInt(command.intelamt.Versions.Sku); |
| 1918 | if (device.intelamt.sku !== sku) { device.intelamt.sku = sku; change = 1; log = 1; } |
| 1919 | } |
| 1920 | } |
| 1921 | if ((command.intelamt.ProvisioningState != null) && (typeof command.intelamt.ProvisioningState == 'number') && (device.intelamt.state != command.intelamt.ProvisioningState)) { changes.push('AMT state'); device.intelamt.state = command.intelamt.ProvisioningState; change = 1; log = 1; } |
| 1922 | if ((command.intelamt.Flags != null) && (typeof command.intelamt.Flags == 'number') && (device.intelamt.flags != command.intelamt.Flags)) { |
| 1923 | if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.Flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.Flags + ')'); } |
| 1924 | device.intelamt.flags = command.intelamt.Flags; change = 1; log = 1; |
| 1925 | } |
| 1926 | 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; } |
| 1927 | } |
| 1928 | if (command.av != null) { // Antivirus |
| 1929 | if (!device.av) { device.av = []; } |
| 1930 | if (JSON.stringify(device.av) != JSON.stringify(command.av)) { /*changes.push('AV status');*/ device.av = command.av; change = 1; log = 1; } |
| 1931 | } |
| 1932 | if (command.wsc != null) { // Windows Security Center |
| 1933 | if (!device.wsc) { device.wsc = {}; } |
| 1934 | if (JSON.stringify(device.wsc) != JSON.stringify(command.wsc)) { /*changes.push('Windows Security Center status');*/ device.wsc = command.wsc; change = 1; log = 1; } |
| 1935 | } |
| 1936 | if (command.lsc != null) { // Linux Security Center |
| 1937 | if (!device.lsc) { device.lsc = {}; } |
| 1938 | if (JSON.stringify(device.lsc) != JSON.stringify(command.lsc)) { /*changes.push('Linux Security Center status');*/ device.lsc = command.lsc; change = 1; log = 1; } |
| 1939 | } |
| 1940 | if (command.defender != null) { // Defender For Windows Server |
| 1941 | if (!device.defender) { device.defender = {}; } |
| 1942 | if (JSON.stringify(device.defender) != JSON.stringify(command.defender)) { /*changes.push('Defender status');*/ device.defender = command.defender; change = 1; log = 1; } |
| 1943 | } |
| 1944 | if (command.lastbootuptime != null) { // Last Boot Up Time |
| 1945 | if (!device.lastbootuptime) { device.lastbootuptime = ""; } |
| 1946 | if (device.lastbootuptime != command.lastbootuptime) { /*changes.push('Last Boot Up Time');*/ device.lastbootuptime = command.lastbootuptime; change = 1; log = 1; } |
| 1947 | } |
| 1948 | if (command.idletime != null) { // Idle Time |
| 1949 | if (!device.idletime) { device.idletime = 0; } |
| 1950 | if (parseInt(device.idletime) != parseInt(command.idletime)) { /*changes.push('Idle Time');*/ device.idletime = parseInt(command.idletime); change = 1; } // Don't log idle time changes, this is too volatile. |
| 1951 | } |
| 1952 | |
| 1953 | // Push Messaging Token |
| 1954 | if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) { |
| 1955 | if (typeof device.pmt == 'string') { db.Remove('pmt_' + device.pmt); } |
| 1956 | device.pmt = command.pmt; |
| 1957 | change = 1; // Don't save this change as an event to the db, so no log=1. |
| 1958 | parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included. |
| 1959 | } |
| 1960 | |
| 1961 | if ((command.users != null) && (Array.isArray(command.users)) && (device.users != command.users)) { device.users = command.users; change = 1; } // Don't save this to the db. |
| 1962 | if ((command.lusers != null) && (Array.isArray(command.lusers)) && (device.lusers != command.lusers)) { device.lusers = command.lusers; change = 1; } // Don't save this to the db. |
| 1963 | if ((command.upnusers != null) && (Array.isArray(command.upnusers)) && (device.upnusers != command.upnusers)) { device.upnusers = command.upnusers; change = 1; } // Don't save this to the db. |
| 1964 | if ((mesh.mtype == 2) && (!args.wanonly)) { |
| 1965 | // In WAN mode, the hostname of a computer is not important. Don't log hostname changes. |
| 1966 | if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); } |
| 1967 | // 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. |
| 1968 | } |
| 1969 | |
| 1970 | // Remove old volumes and BitLocker data, this is part of sysinfo. |
| 1971 | delete device.volumes; |
| 1972 | |
| 1973 | // If there are changes, event the new device |
| 1974 | if (change == 1) { |
| 1975 | // Do some clean up if needed, these values should not be in the database. |
| 1976 | if (device.conn != null) { delete device.conn; } |
| 1977 | if (device.pwr != null) { delete device.pwr; } |
| 1978 | if (device.agct != null) { delete device.agct; } |
| 1979 | if (device.cict != null) { delete device.cict; } |
| 1980 | |
| 1981 | // Save to the database |
| 1982 | db.Set(device); |
| 1983 | |
| 1984 | // Event the node change |
| 1985 | var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) }; |
| 1986 | if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); } |
| 1987 | 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 |
| 1988 | 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. |
| 1989 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); |
| 1990 | } |
| 1991 | |
| 1992 | // Device change is done. |
| 1993 | delete obj.deviceChanging; |
| 1994 | } |
| 1995 | }); |
| 1996 | } |
| 1997 | |
| 1998 | // Change the current core information string and event it |
| 1999 | function ChangeAgentLocationInfo(command) { |
| 2000 | if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return; |
| 2001 | if ((command == null) || (command == null)) { return; } // Safety, should never happen. |
| 2002 | |
| 2003 | // Check that the mesh exists |
| 2004 | const mesh = parent.meshes[obj.dbMeshKey]; |
| 2005 | if (mesh == null) return; |
| 2006 | |
| 2007 | // If the device is pending a change, hold. |
| 2008 | if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentLocationInfo(command); }, 100); return; } |
| 2009 | obj.deviceChanging = true; |
| 2010 | |
| 2011 | // Get the node and change it if needed |
| 2012 | db.Get(obj.dbNodeKey, function (err, nodes) { |
| 2013 | if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } |
| 2014 | const device = nodes[0]; |
| 2015 | if (device.agent) { |
| 2016 | var changes = [], change = 0; |
| 2017 | |
| 2018 | // Check if anything changes |
| 2019 | if ((command.publicip) && (device.publicip != command.publicip)) { device.publicip = command.publicip; change = 1; changes.push('public ip'); } |
| 2020 | if ((command.iploc) && (device.iploc != command.iploc)) { device.iploc = command.iploc; change = 1; changes.push('ip location'); } |
| 2021 | |
| 2022 | // If there are changes, save and event |
| 2023 | if (change == 1) { |
| 2024 | // Do some clean up if needed, these values should not be in the database. |
| 2025 | if (device.conn != null) { delete device.conn; } |
| 2026 | if (device.pwr != null) { delete device.pwr; } |
| 2027 | if (device.agct != null) { delete device.agct; } |
| 2028 | if (device.cict != null) { delete device.cict; } |
| 2029 | |
| 2030 | // Save the device |
| 2031 | db.Set(device); |
| 2032 | |
| 2033 | // Event the node change |
| 2034 | var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), msgid: 59, msgArgs: [device.name, mesh.name, changes.join(', ')], msg: 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', ') }; |
| 2035 | if (obj.agentInfo.capabilities & 0x20) { event.nolog = 1; } // If this is a temporary device, don't log changes |
| 2036 | 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. |
| 2037 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); |
| 2038 | } |
| 2039 | } |
| 2040 | |
| 2041 | // Done changing the device |
| 2042 | delete obj.deviceChanging; |
| 2043 | }); |
| 2044 | } |
| 2045 | |
| 2046 | // Update the mesh agent tab in the database |
| 2047 | function ChangeAgentTag(tag) { |
| 2048 | if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return; |
| 2049 | if ((tag != null) && (tag.length == 0)) { tag = null; } |
| 2050 | |
| 2051 | // If the device is pending a change, hold. |
| 2052 | if (obj.deviceChanging === true) { |
| 2053 | var func = function ChangeAgentTagFunc() { ChangeAgentCoreInfo(ChangeAgentTagFunc.tag); } |
| 2054 | func.tag = tag; |
| 2055 | setTimeout(func, 100); |
| 2056 | return; |
| 2057 | } |
| 2058 | obj.deviceChanging = true; |
| 2059 | |
| 2060 | // Get the node and change it if needed |
| 2061 | db.Get(obj.dbNodeKey, function (err, nodes) { |
| 2062 | if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; } |
| 2063 | const device = nodes[0]; |
| 2064 | if (device.agent) { |
| 2065 | // Parse the agent tag |
| 2066 | var agentTag = null, serverName = null, serverDesc = null, serverTags = null; |
| 2067 | if (tag != null) { |
| 2068 | var taglines = tag.split('\r\n').join('\n').split('\r').join('\n').split('\n'); |
| 2069 | for (var i in taglines) { |
| 2070 | var tagline = taglines[i].trim(); |
| 2071 | if (tagline.length > 0) { |
| 2072 | if (tagline.startsWith('~')) { |
| 2073 | if (tagline.startsWith('~ServerName:') && (tagline.length > 12) && (serverName == null)) { serverName = tagline.substring(12).trim(); } |
| 2074 | if (tagline.startsWith('~ServerDesc:') && (tagline.length > 12) && (serverDesc == null)) { serverDesc = tagline.substring(12).trim(); } |
| 2075 | if (tagline.startsWith('~ServerTags:') && (tagline.length > 12) && (serverTags == null)) { serverTags = tagline.substring(12).split(','); for (var j in serverTags) { serverTags[j] = serverTags[j].trim(); } } |
| 2076 | } else { if (agentTag == null) { agentTag = tagline; } } |
| 2077 | } |
| 2078 | } |
| 2079 | } |
| 2080 | |
| 2081 | // Set the agent tag |
| 2082 | var changes = false; |
| 2083 | if (device.agent.tag != agentTag) { device.agent.tag = agentTag; if ((device.agent.tag == null) || (device.agent.tag == '')) { delete device.agent.tag; } changes = true; } |
| 2084 | if (domain.agenttag != null) { |
| 2085 | // Set the device's server name |
| 2086 | if ((serverName != null) && (domain.agenttag.servername === 1) && (device.name != serverName)) { device.name = serverName; changes = true; } |
| 2087 | |
| 2088 | // Set the device's server description |
| 2089 | if ((serverDesc != null) && (domain.agenttag.serverdesc === 1) && (device.desc != serverDesc)) { device.desc = serverDesc; changes = true; } |
| 2090 | |
| 2091 | // Set the device's server description if there is no description |
| 2092 | if ((serverDesc != null) && (domain.agenttag.serverdesc === 2) && (device.desc != serverDesc) && ((device.desc == null) || (device.desc == ''))) { device.desc = serverDesc; changes = true; } |
| 2093 | |
| 2094 | if ((serverTags != null) && (domain.agenttag.servertags != null) && (domain.agenttag.servertags != 0)) { |
| 2095 | // Sort the tags |
| 2096 | serverTags.sort(); |
| 2097 | |
| 2098 | // Stringify the tags |
| 2099 | var st2 = '', st1 = serverTags.join(','); |
| 2100 | if (device.tags != null) { st2 = device.tags.join(','); } |
| 2101 | |
| 2102 | // Set the device's server tags |
| 2103 | if ((domain.agenttag.servertags === 1) && (st1 != st2)) { device.tags = serverTags; changes = true; } |
| 2104 | |
| 2105 | // Set the device's server tags if there are not tags |
| 2106 | if ((domain.agenttag.servertags === 2) && (st2 == '')) { device.tags = serverTags; changes = true; } |
| 2107 | |
| 2108 | // Append to device's server tags |
| 2109 | if ((domain.agenttag.servertags === 3) && (st1 != st2)) { |
| 2110 | if (device.tags == null) { device.tags = []; } |
| 2111 | for (var i in serverTags) { if (device.tags.indexOf(serverTags[i]) == -1) { device.tags.push(serverTags[i]); } } |
| 2112 | device.tags.sort(); |
| 2113 | changes = true; |
| 2114 | } |
| 2115 | } |
| 2116 | } |
| 2117 | |
| 2118 | if (changes == true) { |
| 2119 | // Do some clean up if needed, these values should not be in the database. |
| 2120 | if (device.conn != null) { delete device.conn; } |
| 2121 | if (device.pwr != null) { delete device.pwr; } |
| 2122 | if (device.agct != null) { delete device.agct; } |
| 2123 | if (device.cict != null) { delete device.cict; } |
| 2124 | |
| 2125 | // Update the device |
| 2126 | db.Set(device); |
| 2127 | |
| 2128 | // Event the node change |
| 2129 | var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), nolog: 1 }; |
| 2130 | 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. |
| 2131 | parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event); |
| 2132 | } |
| 2133 | } |
| 2134 | |
| 2135 | // Done changing the device |
| 2136 | delete obj.deviceChanging; |
| 2137 | }); |
| 2138 | } |
| 2139 | |
| 2140 | // Check if we need to update this agent, return true if agent binary update required. |
| 2141 | // Return 0 is no update needed, 1 update using native system, 2 update using meshcore system |
| 2142 | function compareAgentBinaryHash(agentExeInfo, agentHash) { |
| 2143 | // If this is a temporary agent and the server is set to not update temporary agents, don't update the agent. |
| 2144 | if ((obj.agentInfo.capabilities & 0x20) && (args.temporaryagentupdate === false)) return 0; |
| 2145 | // If we are testing the agent update system, always return true |
| 2146 | if ((args.agentupdatetest === true) || (args.agentupdatetest === 1)) return 1; |
| 2147 | if (args.agentupdatetest === 2) return 2; |
| 2148 | // If the hash matches or is null, no update required. |
| 2149 | if ((agentExeInfo.hash == agentHash) || (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')) return 0; |
| 2150 | // If this is a macOS x86 or ARM agent type and it matched the universal binary, no update required. |
| 2151 | if ((agentExeInfo.id == 16) || (agentExeInfo.id == 29)) { |
| 2152 | if (domain.meshAgentBinaries && domain.meshAgentBinaries[10005]) { |
| 2153 | if (domain.meshAgentBinaries[10005].hash == agentHash) return 0; |
| 2154 | } else { |
| 2155 | if (parent.parent.meshAgentBinaries[10005].hash == agentHash) return 0; |
| 2156 | } |
| 2157 | } |
| 2158 | |
| 2159 | // No match, update the agent. |
| 2160 | if (args.agentupdatesystem === 2) return 2; // If set, force a meshcore update. |
| 2161 | if (agentExeInfo.id == 3) return 2; // Due to a bug in Windows 7 SP1 environement variable exec, we always update 32bit Windows agent using MeshCore for now. Upcoming agent will have a fix for this. |
| 2162 | // NOTE: Windows agents with no commit dates may have bad native update system, so use meshcore system instead. |
| 2163 | // NOTE: Windows agents with commit date prior to 1612740413000 did not kill all "meshagent.exe" processes and update could fail as a result executable being locked, meshcore system will do this. |
| 2164 | if (((obj.AgentCommitDate == null) || (obj.AgentCommitDate < 1612740413000)) && ((agentExeInfo.id == 3) || (agentExeInfo.id == 4))) return 2; // For older Windows agents, use the meshcore update technique. |
| 2165 | return 1; // By default, use the native update technique. |
| 2166 | } |
| 2167 | |
| 2168 | // Request that the core dump file on this agent be uploaded to the server |
| 2169 | obj.RequestCoreDump = function (agenthashhex, corehashhex) { |
| 2170 | if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); } |
| 2171 | const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey); |
| 2172 | obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}'); |
| 2173 | } |
| 2174 | |
| 2175 | // Return true if we need to ignore the agent hash check |
| 2176 | function isIgnoreHashCheck() { |
| 2177 | if ((args.ignoreagenthashcheck === true) || (domain.ignoreagenthashcheck === true)) return true; |
| 2178 | |
| 2179 | // Check site wide exceptions |
| 2180 | if (Array.isArray(args.ignoreagenthashcheck)) { |
| 2181 | for (var i = 0; i < args.ignoreagenthashcheck.length; i++) { |
| 2182 | if (require('ipcheck').match(obj.remoteaddr, args.ignoreagenthashcheck[i])) return true; |
| 2183 | } |
| 2184 | } |
| 2185 | |
| 2186 | // Check domain wide exceptions |
| 2187 | if (Array.isArray(domain.ignoreagenthashcheck)) { |
| 2188 | for (var i = 0; i < domain.ignoreagenthashcheck.length; i++) { |
| 2189 | if (require('ipcheck').match(obj.remoteaddr, domain.ignoreagenthashcheck[i])) return true; |
| 2190 | } |
| 2191 | } |
| 2192 | |
| 2193 | return false; |
| 2194 | } |
| 2195 | |
| 2196 | // Generate a random Intel AMT password |
| 2197 | function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } |
| 2198 | function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } |
| 2199 | |
| 2200 | return obj; |
| 2201 | }; |