meshagent.js 138 KB

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