EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
meshagent.js
Go to the documentation of this file.
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
18module.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(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");
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);
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 'coreinfo':
1228 {
1229 // Sent by the agent to update agent information
1230 ChangeAgentCoreInfo(command);
1231
1232 if ((obj.agentCoreUpdate === true) && (obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
1233 // Agent update. The recovery core was loaded in the agent, send a command to update the agent
1234 parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { // Medium priority task
1235 // If agent disconnection, complete and exit now.
1236 if ((obj.authenticated != 2) || (obj.agentExeInfo == null)) { parent.parent.taskLimiter.completed(taskid); return; }
1237
1238 // Agent update. The recovery core was loaded in the agent, send a command to update the agent
1239 obj.agentCoreUpdateTaskId = taskid;
1240 const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
1241 var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex };
1242 parent.parent.debug('agentupdate', "Sending agent update url: " + cmd.url);
1243
1244 // Add the hash
1245 if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
1246
1247 // Add server TLS cert hash
1248 if (isIgnoreHashCheck() == false) {
1249 const tlsCertHash = parent.webCertificateFullHashs[domain.id];
1250 if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
1251 }
1252
1253 // Send the agent update command
1254 obj.send(JSON.stringify(cmd));
1255 }, null, 1);
1256 }
1257 break;
1258 }
1259 case 'smbios':
1260 {
1261 // SMBIOS information must never be saved when NeDB is in use. NeDB will currupt that database.
1262 if (db.SetSMBIOS == null) break;
1263
1264 // See if we need to save SMBIOS information
1265 if (domain.smbios === true) {
1266 // Store the RAW SMBios table of this computer
1267 // Perform sanity checks before storing
1268 try {
1269 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]; } }
1270 db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: command.value });
1271 } catch (ex) { }
1272 }
1273
1274 // Event the node interface information change (This is a lot of traffic, probably don't need this).
1275 //parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
1276
1277 break;
1278 }
1279 case 'netinfo':
1280 {
1281 // Check if network information is present
1282 if ((command.netif2 == null) && (command.netif == null)) return;
1283
1284 // Escape any field names that have special characters
1285 if (command.netif2 != null) {
1286 for (var i in command.netif2) {
1287 var esc = common.escapeFieldName(i);
1288 if (esc !== i) { command.netif2[esc] = command.netif2[i]; delete command.netif2[i]; }
1289 }
1290 }
1291
1292 // Sent by the agent to update agent network interface information
1293 delete command.action;
1294 command.updateTime = Date.now();
1295 command._id = 'if' + obj.dbNodeKey;
1296 command.domain = domain.id;
1297 command.type = 'ifinfo';
1298 db.Set(command);
1299
1300 // Event the node interface information change
1301 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
1302
1303 break;
1304 }
1305 case 'iplocation':
1306 {
1307 // Sent by the agent to update location information
1308 if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) {
1309 var x = {};
1310 x.publicip = command.value.ip;
1311 x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000));
1312 ChangeAgentLocationInfo(x);
1313 command.value._id = 'iploc_' + command.value.ip;
1314 command.value.type = 'iploc';
1315 command.value.date = Date.now();
1316 db.Set(command.value); // Store the IP to location data in the database
1317 // Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' }
1318 }
1319 break;
1320 }
1321 case 'mc1migration':
1322 {
1323 if (command.oldnodeid.length != 64) break;
1324 const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase();
1325 db.Get(oldNodeKey, function (err, nodes) {
1326 if ((nodes == null) || (nodes.length != 1)) return;
1327 const node = nodes[0];
1328 if (node.meshid == obj.dbMeshKey) {
1329 // Update the device name & host
1330 const newNode = { "name": node.name };
1331 if (node.intelamt != null) { newNode.intelamt = node.intelamt; }
1332 ChangeAgentCoreInfo(newNode);
1333
1334 // Delete this node including network interface information and events
1335 db.Remove(node._id);
1336 db.Remove('if' + node._id);
1337
1338 // Event node deletion
1339 const change = 'Migrated device ' + node.name;
1340 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: node._id, msg: change, domain: node.domain });
1341 }
1342 });
1343 break;
1344 }
1345 case 'openUrl':
1346 {
1347 // Sent by the agent to return the status of a open URL action.
1348 // Nothing is done right now.
1349 break;
1350 }
1351 case 'log':
1352 {
1353 // Log a value in the event log
1354 if ((typeof command.msg == 'string') && (command.msg.length < 4096)) {
1355 var event = { etype: 'node', action: 'agentlog', nodeid: obj.dbNodeKey, domain: domain.id, msg: command.msg };
1356 if (typeof command.msgid == 'number') { event.msgid = command.msgid; }
1357 if (typeof command.guestname == 'string') { event.guestname = command.guestname; }
1358 if (Array.isArray(command.msgArgs)) { event.msgArgs = command.msgArgs; }
1359 if (typeof command.remoteaddr == 'string') { event.remoteaddr = command.remoteaddr; }
1360 var targets = parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]);
1361 if (typeof command.userid == 'string') {
1362 var loguser = parent.users[command.userid];
1363 if (loguser) { event.userid = command.userid; event.username = loguser.name; targets.push(command.userid); }
1364 }
1365 if (typeof command.xuserid == 'string') {
1366 var xloguser = parent.users[command.xuserid];
1367 if (xloguser) { targets.push(command.xuserid); }
1368 }
1369 if ((typeof command.sessionid == 'string') && (command.sessionid.length < 500)) { event.sessionid = command.sessionid; }
1370 parent.parent.DispatchEvent(targets, obj, event);
1371
1372 // If this is a help request, see if we need to email notify anyone
1373 if (event.msgid == 98) {
1374 // Get the node and change it if needed
1375 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.
1376 if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
1377 const device = nodes[0];
1378 if (typeof device.name == 'string') { parent.parent.NotifyUserOfDeviceHelpRequest(domain, device.meshid, device._id, device.name, command.msgArgs[0], command.msgArgs[1]); }
1379 });
1380 }
1381 }
1382 break;
1383 }
1384 case 'ping': { sendPong(); break; }
1385 case 'pong': { break; }
1386 case 'getScript':
1387 {
1388 // Used by the agent to get configuration scripts.
1389 if (command.type == 1) {
1390 parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) {
1391 obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() }));
1392 });
1393 } else if (command.type == 2) {
1394 parent.getCiraCleanupScript(function (script) {
1395 obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() }));
1396 });
1397 }
1398 break;
1399 }
1400 case 'diagnostic':
1401 {
1402 if (typeof command.value == 'object') {
1403 switch (command.value.command) {
1404 case 'register': {
1405 // Only main agent can do this
1406 if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64)) {
1407 // Store links to diagnostic agent id
1408 var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value);
1409 db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent
1410 db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent
1411 }
1412 break;
1413 }
1414 case 'query': {
1415 // Only the diagnostic agent can do
1416 if ((obj.agentInfo.capabilities & 0x40) != 0) {
1417 // Return nodeid of main agent + connection status
1418 db.Get('da' + obj.dbNodeKey, function (err, nodes) {
1419 if ((nodes != null) && (nodes.length == 1)) {
1420 obj.realNodeKey = nodes[0].raid;
1421
1422 // Get agent connection state
1423 var agentConnected = false;
1424 var state = parent.parent.GetConnectivityState(obj.realNodeKey);
1425 if (state) { agentConnected = ((state.connectivity & 1) != 0) }
1426
1427 obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } }));
1428 } else {
1429 obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
1430 }
1431 });
1432 }
1433 break;
1434 }
1435 case 'log': {
1436 if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256)) {
1437 // If this is a diagnostic agent, log the event in the log of the main agent
1438 var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, snodeid: obj.dbNodeKey, domain: domain.id, msg: command.value.value };
1439 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
1440 }
1441 break;
1442 }
1443 }
1444 }
1445 break;
1446 }
1447 case 'sysinfo': {
1448 if ((typeof command.data == 'object') && (typeof command.data.hash == 'string')) {
1449 // Validate command.data.
1450 if (common.validateObjectForMongo(command.data, 1024) == false) break;
1451
1452 // Save to database
1453 command.data._id = 'si' + obj.dbNodeKey;
1454 command.data.type = 'sysinfo';
1455 command.data.domain = domain.id;
1456 command.data.time = Date.now();
1457 db.Set(command.data); // Update system information in the database.
1458
1459 // Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed
1460 var event = { etype: 'node', action: 'sysinfohash', nodeid: obj.dbNodeKey, domain: domain.id, hash: command.data.hash, nolog: 1 };
1461 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
1462 }
1463 break;
1464 }
1465 case 'sysinfocheck': {
1466 // Check system information update
1467 db.GetHash('si' + obj.dbNodeKey, function (err, results) {
1468 if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
1469 });
1470 break;
1471 }
1472 case 'sessions': {
1473 // This is a list of sessions provided by the agent
1474 if (obj.sessions == null) { obj.sessions = {}; }
1475 if (typeof command.value != null) {
1476 if (command.type == 'kvm') { obj.sessions.kvm = command.value; }
1477 else if (command.type == 'terminal') { obj.sessions.terminal = command.value; }
1478 else if (command.type == 'files') { obj.sessions.files = command.value; }
1479 else if (command.type == 'help') { obj.sessions.help = command.value; }
1480 else if (command.type == 'tcp') { obj.sessions.tcp = command.value; }
1481 else if (command.type == 'udp') { obj.sessions.udp = command.value; }
1482 else if (command.type == 'msg') { obj.sessions.msg = command.value; }
1483 else if (command.type == 'app') { obj.sessions.app = command.value; }
1484 }
1485
1486 // Any "help" session must have an associated app, if not, remove it.
1487 if (obj.sessions.help != null) {
1488 for (var i in obj.sessions.help) { if (obj.sessions.help[i] == null) { delete obj.sessions.help[i]; } }
1489 if (Object.keys(obj.sessions.help).length == 0) { delete obj.sessions.help; }
1490 }
1491
1492 // Inform everyone of updated sessions
1493 obj.updateSessions();
1494 break;
1495 }
1496 case 'battery': {
1497 // Device battery and power state
1498 if (obj.sessions == null) { obj.sessions = {}; }
1499 if (obj.sessions.battery == null) { obj.sessions.battery = {}; }
1500 if ((command.state == 'ac') || (command.state == 'dc')) { obj.sessions.battery.state = command.state; } else { delete obj.sessions.battery.state; }
1501 if ((typeof command.level == 'number') && (command.level >= 0) && (command.level <= 100)) { obj.sessions.battery.level = command.level; } else { delete obj.sessions.battery.level; }
1502 obj.updateSessions();
1503 break;
1504 }
1505 case 'getcoredump': {
1506 // Check if we requested a core dump file in the last minute, if so, ignore this.
1507 if ((parent.lastCoreDumpRequest != null) && ((Date.now() - parent.lastCoreDumpRequest) < 60000)) break;
1508
1509 // Indicates if the agent has a coredump available
1510 if ((command.exists === true) && (typeof command.agenthashhex == 'string') && (command.agenthashhex.length == 96)) {
1511 // Check if we already have this exact dump file
1512 const coreDumpFile = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps', obj.agentInfo.agentId + '-' + command.agenthashhex + '-' + obj.nodeid + '.dmp');
1513 parent.fs.stat(coreDumpFile, function (err, stats) {
1514 if (stats != null) return;
1515 obj.coreDumpPresent = true;
1516
1517 // Check how many files are in the coredumps folder
1518 const coreDumpPath = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps');
1519 parent.fs.readdir(coreDumpPath, function (err, files) {
1520 if ((files != null) && (files.length >= 20)) return; // Don't get more than 20 core dump files.
1521
1522 // Get the core dump uploaded to the server.
1523 parent.lastCoreDumpRequest = Date.now();
1524 obj.RequestCoreDump(command.agenthashhex, command.corehashhex);
1525 });
1526 });
1527 }
1528 break;
1529 }
1530 case 'tunnelCloseStats': {
1531 // TODO: This this extra stats from the tunnel, you can merge this into the tunnel event in the database.
1532 //console.log(command);
1533
1534 // Validate input
1535 if ((command.sent == null) || (typeof command.sent != 'string')) return;
1536 if ((command.sentActual == null) || (typeof command.sentActual != 'string')) return;
1537 if ((command.sentActual == null) || (typeof command.sentActual != 'number')) return;
1538
1539 // Event the session closed compression data.
1540 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 + '.' };
1541 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
1542 break;
1543 }
1544 case 'lmsinfo': {
1545 // Agents send the LMS port bindings
1546 // Example: {"action":"lmsinfo","value":{"ports":["623","16992"]}}
1547 break;
1548 }
1549 case 'plugin': {
1550 if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
1551 try {
1552 parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
1553 } catch (e) {
1554 parent.parent.debug('agent', 'Error loading plugin handler (' + e + ')');
1555 console.log('Error loading plugin handler (' + e + ')');
1556 }
1557 break;
1558 }
1559 case 'meshToolInfo': {
1560 // Return information about a MeshCentral tool. Current tools are 'MeshCentralRouter' and 'MeshCentralAssistant'
1561 // Information includes file hash and download location URL
1562 if (typeof command.name != 'string') break;
1563 var info = parent.parent.meshToolsBinaries[command.name];
1564 if ((command.hash != null) && (info.hash == command.hash)) return;
1565
1566 // To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
1567 var xdomain = (domain.dns == null) ? domain.id : '';
1568 if (xdomain != '') xdomain += '/';
1569
1570 // Build the response
1571 const responseCmd = { action: 'meshToolInfo', name: command.name, tag: command.tag, sessionid: command.sessionid, hash: info.hash, size: info.size, url: info.url };
1572 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.
1573 if (command.cookie === true) { responseCmd.url += ('&auth=' + parent.parent.encodeCookie({ download: info.dlname }, parent.parent.loginCookieEncryptionKey)); }
1574 if (command.pipe === true) { responseCmd.pipe = true; }
1575 if (parent.webCertificateHashs[domain.id] != null) { responseCmd.serverhash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex'); }
1576 try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { }
1577 break;
1578 }
1579 case 'agentupdate': {
1580 if ((obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
1581 var func = function agentUpdateFunc(argument, taskid, taskLimiterQueue) { // Medium priority task
1582 // If agent disconnection, complete and exit now.
1583 if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; }
1584
1585 // Agent is requesting an agent update
1586 obj.agentCoreUpdateTaskId = taskid;
1587 const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
1588 var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex, sessionid: agentUpdateFunc.sessionid };
1589 parent.parent.debug('agentupdate', "Sending user requested agent update url: " + cmd.url);
1590
1591 // Add the hash
1592 if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
1593
1594 // Add server TLS cert hash
1595 if (isIgnoreHashCheck() == false) {
1596 const tlsCertHash = parent.webCertificateFullHashs[domain.id];
1597 if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
1598 }
1599
1600 // Send the agent update command
1601 obj.send(JSON.stringify(cmd));
1602 }
1603 func.sessionid = command.sessionid;
1604
1605 // Agent update. The recovery core was loaded in the agent, send a command to update the agent
1606 parent.parent.taskLimiter.launch(func, null, 1);
1607 }
1608 break;
1609 }
1610 case 'agentupdatedownloaded': {
1611 if (obj.agentCoreUpdateTaskId != null) {
1612 // Indicate this udpate task is complete
1613 parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId);
1614 delete obj.agentCoreUpdateTaskId;
1615 }
1616 break;
1617 }
1618 case 'errorlog': { // This is the agent error log
1619 if ((!Array.isArray(command.log)) || (command.log.length == 0) || (parent.parent.agentErrorLog == null)) break;
1620 var lastLogEntry = command.log[command.log.length - 1];
1621 if ((lastLogEntry != null) && (typeof lastLogEntry == 'object') && (typeof lastLogEntry.t == 'number')) {
1622 parent.fs.write(parent.parent.agentErrorLog, obj.dbNodeKey + ', ' + Date.now() + ', ' + str + '\r\n', function (err) { });
1623 db.Set({ _id: 'al' + obj.dbNodeKey, lastEvent: lastLogEntry.t });
1624 }
1625 break;
1626 }
1627 case '2faauth': {
1628 // Validate input
1629 if ((typeof command.url != 'string') || (typeof command.approved != 'boolean') || (command.url.startsWith('2fa://') == false)) return;
1630
1631 // parse the URL
1632 var url = null;
1633 try { url = require('url').parse(command.url); } catch (ex) { }
1634 if (url == null) return;
1635
1636 // Decode the cookie
1637 var urlSplit = url.query.split('&c=');
1638 if (urlSplit.length != 2) return;
1639 const authCookie = parent.parent.decodeCookie(urlSplit[1], null, 1);
1640 if ((authCookie == null) || (typeof authCookie.c != 'string') || (('code=' + authCookie.c) != urlSplit[0])) return;
1641 if ((typeof authCookie.n != 'string') || (authCookie.n != obj.dbNodeKey) || (typeof authCookie.u != 'string')) return;
1642
1643 // Fetch the user
1644 const user = parent.users[authCookie.u];
1645 if (user == null) return;
1646
1647 // Add this device as the authentication push notification device for this user
1648 if (authCookie.a == 'addAuth') {
1649 // Do nothing if authentication is not approved.
1650 // We do not want to indicate that the remote user responded to this.
1651 if (command.approved !== true) return;
1652
1653 // Change the user
1654 user.otpdev = obj.dbNodeKey;
1655 parent.db.SetUser(user);
1656
1657 // Notify change
1658 var targets = ['*', 'server-users', user._id];
1659 if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
1660 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 };
1661 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.
1662 parent.parent.DispatchEvent(targets, obj, event);
1663 }
1664
1665 // Complete 2FA checking
1666 if (authCookie.a == 'checkAuth') {
1667 if (typeof authCookie.s != 'string') return;
1668 // Notify 2FA response
1669 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 });
1670 }
1671
1672 break;
1673 }
1674 case 'getUserImage': {
1675 // Validate input
1676 if (typeof command.userid != 'string') {
1677 // Send back the default image if required
1678 if ((command.default) || (command.sentDefault)) {
1679 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) { }
1680 obj.send(JSON.stringify(command));
1681 }
1682 return;
1683 }
1684 var useridsplit = command.userid.split('/');
1685 if ((useridsplit.length != 3) || (useridsplit[1] != domain.id)) return;
1686
1687 // Add the user's real name if present
1688 var u = parent.users[command.userid];
1689 if (u == null) return;
1690 if (u.name) { command.name = u.name; }
1691 if (u.realname) { command.realname = u.realname; }
1692
1693 // An agent can only request images of accounts with rights to the device.
1694 if (parent.GetNodeRights(command.userid, obj.dbMeshKey, obj.dbNodeKey) != 0) {
1695 parent.db.Get('im' + command.userid, function (err, images) {
1696 if ((err == null) && (images != null) && (images.length == 1)) {
1697 // Send back the account image
1698 command.image = images[0].image;
1699 } else {
1700 // Send back the default image if required
1701 if ((command.default) || (command.sentDefault)) {
1702 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) { }
1703 }
1704 }
1705 obj.send(JSON.stringify(command));
1706 });
1707 }
1708 break;
1709 }
1710 case 'getServerImage': {
1711 if (command.agent === 'assistant') {
1712 // Return server title and image for MeshCentral Assistant
1713 if ((domain.assistantcustomization != null) && (typeof domain.assistantcustomization == 'object')) {
1714 var ok = false;
1715 if (typeof domain.assistantcustomization.title == 'string') { ok = true; command.title = domain.assistantcustomization.title; }
1716 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); } }
1717 if (ok) { obj.send(JSON.stringify(command)); }
1718 }
1719 }
1720 if (command.agent === 'android') {
1721 // Return server title and image for MeshCentral Assistant
1722 if ((domain.androidcustomization != null) && (typeof domain.androidcustomization == 'object')) {
1723 var ok = false;
1724 if (typeof domain.androidcustomization.title == 'string') { ok = true; command.title = domain.androidcustomization.title; }
1725 if (typeof domain.androidcustomization.subtitle == 'string') { ok = true; command.subtitle = domain.androidcustomization.subtitle; }
1726 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); } }
1727 if (ok) { obj.send(JSON.stringify(command)); }
1728 }
1729 }
1730 break;
1731 }
1732 case 'guestShare': {
1733 if ((command.flags == null) || (command.flags == 0)) {
1734 // Stop any current self-share, this is allowed even if self guest sharing is not allows so to clear any old shares.
1735 removeGuestSharing(function () {
1736 delete obj.guestSharing;
1737 obj.send(JSON.stringify({ action: 'guestShare', flags: command.flags, url: null, viewOnly: false }));
1738 });
1739 } else {
1740 // Add a new self-share, this will replace any share for this device
1741 if ((domain.agentselfguestsharing == null) || (domain.agentselfguestsharing == false) || (typeof command.flags != 'number')) return; // Check if agent self-sharing is allowed, this is off by default.
1742 if ((command.flags & 2) == 0) { command.viewOnly = false; } // Only allow "view only" if desktop is shared.
1743 addGuestSharing(command.flags, command.viewOnly, function (share) {
1744 obj.guestSharing = true;
1745 obj.send(JSON.stringify({ action: 'guestShare', url: share.url, flags: share.flags, viewOnly: share.viewOnly }));
1746 })
1747 }
1748 break;
1749 }
1750 case 'amtconfig': {
1751 // Sent by the agent when the agent needs a Intel AMT APF connection to the server
1752 const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey);
1753 try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { }
1754 break;
1755 }
1756 case 'script-task': {
1757 // These command are for running regular batch jobs on the remote device
1758 if (parent.parent.taskManager != null) { parent.parent.taskManager.agentAction(command, obj); }
1759 break;
1760 }
1761 default: {
1762 parent.agentStats.unknownAgentActionCount++;
1763 parent.parent.debug('agent', 'Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
1764 console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
1765 break;
1766 }
1767 }
1768 if (parent.parent.pluginHandler != null) {
1769 parent.parent.pluginHandler.callHook('hook_processAgentData', command, obj, parent);
1770 }
1771 }
1772 }
1773
1774 function addGuestSharing(flags, viewOnly, func) {
1775 // Create cookie
1776 const publicid = 'AS:' + obj.dbNodeKey;
1777 const extrakey = getRandomAmtPassword();
1778 const cookie = { a: 6, pid: publicid, k: extrakey }; // New style sharing cookie
1779 const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
1780 if (inviteCookie == null) return;
1781
1782 // Create the server url
1783 var serverName = parent.getWebServerName(domain, req);
1784 var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
1785 var xdomain = (domain.dns == null) ? domain.id : '';
1786 if (xdomain != '') xdomain += '/';
1787 var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie;
1788 if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; }
1789
1790 // Create a device sharing database entry
1791 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 };
1792
1793 // Add expire time
1794 if ((typeof domain.agentselfguestsharing == 'object') && (typeof domain.agentselfguestsharing.expire == 'number') && (domain.agentselfguestsharing.expire > 0)) {
1795 shareEntry.startTime = Date.now();
1796 shareEntry.expireTime = Date.now() + (60000 * domain.agentselfguestsharing.expire);
1797 }
1798
1799 if (viewOnly === true) { shareEntry.viewOnly = true; }
1800 parent.db.Set(shareEntry);
1801
1802 // Send out an event that we added a device share
1803 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey);
1804 var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added device share with unlimited time', msgid: 131, msgArgs: ['Agent'], domain: domain.id };
1805 parent.parent.DispatchEvent(targets, obj, event);
1806
1807 // Send device share update
1808 parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
1809 if (err != null) return;
1810
1811 // Check device sharing
1812 var now = Date.now();
1813 for (var i = 0; i < docs.length; i++) {
1814 const doc = docs[i];
1815 if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else {
1816 // This share is ok, remove extra data we don't need to send.
1817 delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
1818 }
1819 }
1820
1821 // Send device share update
1822 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
1823 parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 });
1824
1825 // Callback
1826 if (func) { func({ url: url, flags: flags, viewOnly: viewOnly }); }
1827 });
1828 }
1829
1830 function removeGuestSharing(func) {
1831 var publicid = 'AS:' + obj.dbNodeKey;
1832 parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
1833 if (err != null) return;
1834
1835 // Remove device sharing
1836 var now = Date.now(), removedExact = null, removed = false, okDocs = [];
1837 for (var i = 0; i < docs.length; i++) {
1838 const doc = docs[i];
1839 if (doc.publicid == publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; }
1840 else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else {
1841 // This share is ok, remove extra data we don't need to send.
1842 delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
1843 okDocs.push(doc);
1844 }
1845 }
1846
1847 // Event device share removal
1848 if (removedExact != null) {
1849 // Send out an event that we removed a device share
1850 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, ['server-shareremove']);
1851 var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['Agent'], domain: domain.id, publicid: publicid };
1852 parent.parent.DispatchEvent(targets, obj, event);
1853 }
1854
1855 // If we removed any shares, send device share update
1856 if (removed == true) {
1857 var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
1858 parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
1859 }
1860
1861 // Call back when done
1862 if (func) func(removed);
1863 });
1864 }
1865
1866 // Notify update of sessions
1867 obj.updateSessions = function () {
1868 // Perform some clean up
1869 for (var i in obj.sessions) { if (Object.keys(obj.sessions[i]).length == 0) { delete obj.sessions[i]; } }
1870 if (Object.keys(obj.sessions).length == 0) { delete obj.sessions; }
1871
1872 // Event the new sessions, this will notify everyone that agent sessions have changed
1873 var event = { etype: 'node', action: 'devicesessions', nodeid: obj.dbNodeKey, domain: domain.id, sessions: obj.sessions, nolog: 1 };
1874 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
1875 }
1876
1877 // Change the current core information string and event it
1878 function ChangeAgentCoreInfo(command) {
1879 if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
1880 if ((command == null) || (command == null)) return; // Safety, should never happen.
1881
1882 // If the device is pending a change, hold.
1883 if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; }
1884 obj.deviceChanging = true;
1885
1886 // Check that the mesh exists
1887 const mesh = parent.meshes[obj.dbMeshKey];
1888 if (mesh == null) { delete obj.deviceChanging; return; }
1889
1890 // Get the node and change it if needed
1891 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.
1892 if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
1893 const device = nodes[0];
1894 if (device.agent) {
1895 var changes = [], change = 0, log = 0;
1896
1897 // Check if anything changes
1898 if (command.name && (typeof command.name == 'string') && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); }
1899 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.
1900 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.
1901 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.
1902 if ((typeof command.root == 'boolean') && (command.root !== device.agent.root)) { change = 1; device.agent.root = command.root; }
1903 if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
1904 if (command.intelamt) {
1905 if (!device.intelamt) { device.intelamt = {}; }
1906 if ((command.intelamt.Versions != null) && (typeof command.intelamt.Versions == 'object')) {
1907 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; }
1908 if ((command.intelamt.Versions.Sku != null) && (typeof command.intelamt.Versions.Sku == 'string')) {
1909 const sku = parseInt(command.intelamt.Versions.Sku);
1910 if (device.intelamt.sku !== sku) { device.intelamt.sku = sku; change = 1; log = 1; }
1911 }
1912 }
1913 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; }
1914 if ((command.intelamt.Flags != null) && (typeof command.intelamt.Flags == 'number') && (device.intelamt.flags != command.intelamt.Flags)) {
1915 if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.Flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.Flags + ')'); }
1916 device.intelamt.flags = command.intelamt.Flags; change = 1; log = 1;
1917 }
1918 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; }
1919 }
1920 if (command.av != null) { // Antivirus
1921 if (!device.av) { device.av = []; }
1922 if (JSON.stringify(device.av) != JSON.stringify(command.av)) { /*changes.push('AV status');*/ device.av = command.av; change = 1; log = 1; }
1923 }
1924 if (command.wsc != null) { // Windows Security Center
1925 if (!device.wsc) { device.wsc = {}; }
1926 if (JSON.stringify(device.wsc) != JSON.stringify(command.wsc)) { /*changes.push('Windows Security Center status');*/ device.wsc = command.wsc; change = 1; log = 1; }
1927 }
1928 if (command.defender != null) { // Defender For Windows Server
1929 if (!device.defender) { device.defender = {}; }
1930 if (JSON.stringify(device.defender) != JSON.stringify(command.defender)) { /*changes.push('Defender status');*/ device.defender = command.defender; change = 1; log = 1; }
1931 }
1932 if (command.lastbootuptime != null) { // Last Boot Up Time
1933 if (!device.lastbootuptime) { device.lastbootuptime = ""; }
1934 if (device.lastbootuptime != command.lastbootuptime) { /*changes.push('Last Boot Up Time');*/ device.lastbootuptime = command.lastbootuptime; change = 1; log = 1; }
1935 }
1936
1937 // Push Messaging Token
1938 if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) {
1939 if (typeof device.pmt == 'string') { db.Remove('pmt_' + device.pmt); }
1940 device.pmt = command.pmt;
1941 change = 1; // Don't save this change as an event to the db, so no log=1.
1942 parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
1943 }
1944
1945 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.
1946 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.
1947 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.
1948 if ((mesh.mtype == 2) && (!args.wanonly)) {
1949 // In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
1950 if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
1951 // 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.
1952 }
1953
1954 // Remove old volumes and BitLocker data, this is part of sysinfo.
1955 delete device.volumes;
1956
1957 // If there are changes, event the new device
1958 if (change == 1) {
1959 // Do some clean up if needed, these values should not be in the database.
1960 if (device.conn != null) { delete device.conn; }
1961 if (device.pwr != null) { delete device.pwr; }
1962 if (device.agct != null) { delete device.agct; }
1963 if (device.cict != null) { delete device.cict; }
1964
1965 // Save to the database
1966 db.Set(device);
1967
1968 // Event the node change
1969 var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
1970 if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
1971 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
1972 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.
1973 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
1974 }
1975
1976 // Device change is done.
1977 delete obj.deviceChanging;
1978 }
1979 });
1980 }
1981
1982 // Change the current core information string and event it
1983 function ChangeAgentLocationInfo(command) {
1984 if (obj.agentInfo.capabilities & 0x40) return;
1985 if ((command == null) || (command == null)) { return; } // Safety, should never happen.
1986
1987 // Check that the mesh exists
1988 const mesh = parent.meshes[obj.dbMeshKey];
1989 if (mesh == null) return;
1990
1991 // If the device is pending a change, hold.
1992 if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentLocationInfo(command); }, 100); return; }
1993 obj.deviceChanging = true;
1994
1995 // Get the node and change it if needed
1996 db.Get(obj.dbNodeKey, function (err, nodes) {
1997 if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
1998 const device = nodes[0];
1999 if (device.agent) {
2000 var changes = [], change = 0;
2001
2002 // Check if anything changes
2003 if ((command.publicip) && (device.publicip != command.publicip)) { device.publicip = command.publicip; change = 1; changes.push('public ip'); }
2004 if ((command.iploc) && (device.iploc != command.iploc)) { device.iploc = command.iploc; change = 1; changes.push('ip location'); }
2005
2006 // If there are changes, save and event
2007 if (change == 1) {
2008 // Do some clean up if needed, these values should not be in the database.
2009 if (device.conn != null) { delete device.conn; }
2010 if (device.pwr != null) { delete device.pwr; }
2011 if (device.agct != null) { delete device.agct; }
2012 if (device.cict != null) { delete device.cict; }
2013
2014 // Save the device
2015 db.Set(device);
2016
2017 // Event the node change
2018 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(', ') };
2019 if (obj.agentInfo.capabilities & 0x20) { event.nolog = 1; } // If this is a temporary device, don't log changes
2020 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.
2021 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
2022 }
2023 }
2024
2025 // Done changing the device
2026 delete obj.deviceChanging;
2027 });
2028 }
2029
2030 // Update the mesh agent tab in the database
2031 function ChangeAgentTag(tag) {
2032 if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
2033 if ((tag != null) && (tag.length == 0)) { tag = null; }
2034
2035 // If the device is pending a change, hold.
2036 if (obj.deviceChanging === true) {
2037 var func = function ChangeAgentTagFunc() { ChangeAgentCoreInfo(ChangeAgentTagFunc.tag); }
2038 func.tag = tag;
2039 setTimeout(func, 100);
2040 return;
2041 }
2042 obj.deviceChanging = true;
2043
2044 // Get the node and change it if needed
2045 db.Get(obj.dbNodeKey, function (err, nodes) {
2046 if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
2047 const device = nodes[0];
2048 if (device.agent) {
2049 // Parse the agent tag
2050 var agentTag = null, serverName = null, serverDesc = null, serverTags = null;
2051 if (tag != null) {
2052 var taglines = tag.split('\r\n').join('\n').split('\r').join('\n').split('\n');
2053 for (var i in taglines) {
2054 var tagline = taglines[i].trim();
2055 if (tagline.length > 0) {
2056 if (tagline.startsWith('~')) {
2057 if (tagline.startsWith('~ServerName:') && (tagline.length > 12) && (serverName == null)) { serverName = tagline.substring(12).trim(); }
2058 if (tagline.startsWith('~ServerDesc:') && (tagline.length > 12) && (serverDesc == null)) { serverDesc = tagline.substring(12).trim(); }
2059 if (tagline.startsWith('~ServerTags:') && (tagline.length > 12) && (serverTags == null)) { serverTags = tagline.substring(12).split(','); for (var j in serverTags) { serverTags[j] = serverTags[j].trim(); } }
2060 } else { if (agentTag == null) { agentTag = tagline; } }
2061 }
2062 }
2063 }
2064
2065 // Set the agent tag
2066 var changes = false;
2067 if (device.agent.tag != agentTag) { device.agent.tag = agentTag; if ((device.agent.tag == null) || (device.agent.tag == '')) { delete device.agent.tag; } changes = true; }
2068 if (domain.agenttag != null) {
2069 // Set the device's server name
2070 if ((serverName != null) && (domain.agenttag.servername === 1) && (device.name != serverName)) { device.name = serverName; changes = true; }
2071
2072 // Set the device's server description
2073 if ((serverDesc != null) && (domain.agenttag.serverdesc === 1) && (device.desc != serverDesc)) { device.desc = serverDesc; changes = true; }
2074
2075 // Set the device's server description if there is no description
2076 if ((serverDesc != null) && (domain.agenttag.serverdesc === 2) && (device.desc != serverDesc) && ((device.desc == null) || (device.desc == ''))) { device.desc = serverDesc; changes = true; }
2077
2078 if ((serverTags != null) && (domain.agenttag.servertags != null) && (domain.agenttag.servertags != 0)) {
2079 // Sort the tags
2080 serverTags.sort();
2081
2082 // Stringify the tags
2083 var st2 = '', st1 = serverTags.join(',');
2084 if (device.tags != null) { st2 = device.tags.join(','); }
2085
2086 // Set the device's server tags
2087 if ((domain.agenttag.servertags === 1) && (st1 != st2)) { device.tags = serverTags; changes = true; }
2088
2089 // Set the device's server tags if there are not tags
2090 if ((domain.agenttag.servertags === 2) && (st2 == '')) { device.tags = serverTags; changes = true; }
2091
2092 // Append to device's server tags
2093 if ((domain.agenttag.servertags === 3) && (st1 != st2)) {
2094 if (device.tags == null) { device.tags = []; }
2095 for (var i in serverTags) { if (device.tags.indexOf(serverTags[i]) == -1) { device.tags.push(serverTags[i]); } }
2096 device.tags.sort();
2097 changes = true;
2098 }
2099 }
2100 }
2101
2102 if (changes == true) {
2103 // Do some clean up if needed, these values should not be in the database.
2104 if (device.conn != null) { delete device.conn; }
2105 if (device.pwr != null) { delete device.pwr; }
2106 if (device.agct != null) { delete device.agct; }
2107 if (device.cict != null) { delete device.cict; }
2108
2109 // Update the device
2110 db.Set(device);
2111
2112 // Event the node change
2113 var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), nolog: 1 };
2114 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.
2115 parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
2116 }
2117 }
2118
2119 // Done changing the device
2120 delete obj.deviceChanging;
2121 });
2122 }
2123
2124 // Check if we need to update this agent, return true if agent binary update required.
2125 // Return 0 is no update needed, 1 update using native system, 2 update using meshcore system
2126 function compareAgentBinaryHash(agentExeInfo, agentHash) {
2127 // If this is a temporary agent and the server is set to not update temporary agents, don't update the agent.
2128 if ((obj.agentInfo.capabilities & 0x20) && (args.temporaryagentupdate === false)) return 0;
2129 // If we are testing the agent update system, always return true
2130 if ((args.agentupdatetest === true) || (args.agentupdatetest === 1)) return 1;
2131 if (args.agentupdatetest === 2) return 2;
2132 // If the hash matches or is null, no update required.
2133 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;
2134 // If this is a macOS x86 or ARM agent type and it matched the universal binary, no update required.
2135 if ((agentExeInfo.id == 16) || (agentExeInfo.id == 29)) {
2136 if (domain.meshAgentBinaries && domain.meshAgentBinaries[10005]) {
2137 if (domain.meshAgentBinaries[10005].hash == agentHash) return 0;
2138 } else {
2139 if (parent.parent.meshAgentBinaries[10005].hash == agentHash) return 0;
2140 }
2141 }
2142
2143 // No match, update the agent.
2144 if (args.agentupdatesystem === 2) return 2; // If set, force a meshcore update.
2145 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.
2146 // NOTE: Windows agents with no commit dates may have bad native update system, so use meshcore system instead.
2147 // 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.
2148 if (((obj.AgentCommitDate == null) || (obj.AgentCommitDate < 1612740413000)) && ((agentExeInfo.id == 3) || (agentExeInfo.id == 4))) return 2; // For older Windows agents, use the meshcore update technique.
2149 return 1; // By default, use the native update technique.
2150 }
2151
2152 // Request that the core dump file on this agent be uploaded to the server
2153 obj.RequestCoreDump = function (agenthashhex, corehashhex) {
2154 if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); }
2155 const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey);
2156 obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}');
2157 }
2158
2159 // Return true if we need to ignore the agent hash check
2160 function isIgnoreHashCheck() {
2161 if ((args.ignoreagenthashcheck === true) || (domain.ignoreagenthashcheck === true)) return true;
2162
2163 // Check site wide exceptions
2164 if (Array.isArray(args.ignoreagenthashcheck)) {
2165 for (var i = 0; i < args.ignoreagenthashcheck.length; i++) {
2166 if (require('ipcheck').match(obj.remoteaddr, args.ignoreagenthashcheck[i])) return true;
2167 }
2168 }
2169
2170 // Check domain wide exceptions
2171 if (Array.isArray(domain.ignoreagenthashcheck)) {
2172 for (var i = 0; i < domain.ignoreagenthashcheck.length; i++) {
2173 if (require('ipcheck').match(obj.remoteaddr, domain.ignoreagenthashcheck[i])) return true;
2174 }
2175 }
2176
2177 return false;
2178 }
2179
2180 // Generate a random Intel AMT password
2181 function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
2182 function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
2183
2184 return obj;
2185};