meshagent.js 137 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178
  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. if (mesh.flags && (mesh.flags & 2) && (device.name != obj.agentInfo.computerName)) { device.name = obj.agentInfo.computerName; change = 1; } // We want the server name to be sync'ed to the hostname
  732. if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
  733. if (change == 1) {
  734. // Do some clean up if needed, these values should not be in the database.
  735. if (device.conn != null) { delete device.conn; }
  736. if (device.pwr != null) { delete device.pwr; }
  737. if (device.agct != null) { delete device.agct; }
  738. if (device.cict != null) { delete device.cict; }
  739. // Save the updated device in the database
  740. db.Set(device);
  741. // If this is a temporary device, don't log changes
  742. if (obj.agentInfo.capabilities & 0x20) { log = 0; }
  743. // Event the node change
  744. var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
  745. if (log == 0) { event.nolog = 1; } else { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
  746. 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.
  747. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
  748. }
  749. }
  750. completeAgentConnection3(device, mesh);
  751. });
  752. }
  753. function completeAgentConnection2() {
  754. // See if this mesh exists, if it does not we may want to create it.
  755. var mesh = getMeshAutoCreate();
  756. // Check if the mesh exists
  757. if (mesh == null) {
  758. // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
  759. parent.agentStats.invalidDomainMeshCount++;
  760. parent.setAgentIssue(obj, "invalidDomainMesh");
  761. parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
  762. console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
  763. return;
  764. }
  765. // Check if the mesh is the right type
  766. if (mesh.mtype != 2) {
  767. // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
  768. parent.agentStats.invalidMeshTypeCount++;
  769. parent.setAgentIssue(obj, "invalidMeshType");
  770. parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
  771. console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
  772. return;
  773. }
  774. // Mark when this device connected
  775. obj.connectTime = Date.now();
  776. // This node does not exist, create it.
  777. var agentName = obj.agentName ? obj.agentName : obj.agentInfo.computerName;
  778. 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 };
  779. db.Set(device);
  780. // Event the new node
  781. if (obj.agentInfo.capabilities & 0x20) {
  782. // This is a temporary agent, don't log.
  783. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 });
  784. } else {
  785. 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 });
  786. }
  787. completeAgentConnection3(device, mesh);
  788. }
  789. function completeAgentConnection3(device, mesh) {
  790. // Check if this agent is already connected
  791. const dupAgent = parent.wsagents[obj.dbNodeKey];
  792. parent.wsagents[obj.dbNodeKey] = obj;
  793. if (dupAgent) {
  794. // Record duplicate agents
  795. if (parent.duplicateAgentsLog[obj.dbNodeKey] == null) {
  796. if (dupAgent.remoteaddr == obj.remoteaddr) {
  797. parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr], count: 1 };
  798. } else {
  799. parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr, dupAgent.remoteaddr], count: 1 };
  800. }
  801. } else {
  802. parent.duplicateAgentsLog[obj.dbNodeKey].name = device.name;
  803. parent.duplicateAgentsLog[obj.dbNodeKey].group = mesh.name;
  804. parent.duplicateAgentsLog[obj.dbNodeKey].count++;
  805. if (parent.duplicateAgentsLog[obj.dbNodeKey].ip.indexOf(obj.remoteaddr) == -1) { parent.duplicateAgentsLog[obj.dbNodeKey].ip.push(obj.remoteaddr); }
  806. }
  807. // Close the duplicate agent
  808. parent.agentStats.duplicateAgentCount++;
  809. parent.setAgentIssue(obj, 'duplicateAgent');
  810. if (obj.nodeid != null) { parent.parent.debug('agent', 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); }
  811. dupAgent.close(3);
  812. } else {
  813. // Indicate the agent is connected
  814. parent.parent.SetConnectivityState(obj.dbMeshKey, obj.dbNodeKey, obj.connectTime, 1, 1, null, { remoteaddrport: obj.remoteaddrport, name: device.name });
  815. }
  816. // We are done, ready to communicate with this agent
  817. delete obj.pendingCompleteAgentConnection;
  818. obj.authenticated = 2;
  819. // Check how many times this agent disconnected in the last few minutes.
  820. const disconnectCount = parent.wsagentsDisconnections[obj.nodeid];
  821. if (disconnectCount > 6) {
  822. parent.parent.debug('agent', 'Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
  823. console.log('Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
  824. parent.agentStats.agentInBigTrouble++;
  825. // TODO: Log or do something to recover?
  826. return;
  827. }
  828. // Command 4, inform mesh agent that it's authenticated.
  829. obj.sendBinary(common.ShortToStr(4));
  830. // Not sure why, but in rare cases, obj.agentInfo is undefined here.
  831. if ((obj.agentInfo == null) || (typeof obj.agentInfo.capabilities != 'number')) { return; } // This is an odd case.
  832. obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
  833. if (domain.meshAgentBinaries && domain.meshAgentBinaries[obj.agentInfo.agentId]) { obj.agentExeInfo = domain.meshAgentBinaries[obj.agentInfo.agentId]; }
  834. // Check if this agent is reconnecting too often.
  835. if (disconnectCount > 4) {
  836. // Too many disconnections, this agent has issues. Just clear the core.
  837. obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
  838. parent.parent.debug('agent', 'Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
  839. parent.agentStats.agentInTrouble++;
  840. //console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
  841. // TODO: Log or do something to recover?
  842. return;
  843. }
  844. // Check if we need to make an native update check
  845. var corename = null;
  846. if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) {
  847. corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
  848. } else {
  849. // MeshCommand_CoreModule, ask mesh agent to clear the core
  850. obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
  851. }
  852. if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
  853. // Ask the agent for it's executable binary hash
  854. obj.sendBinary(common.ShortToStr(12) + common.ShortToStr(0));
  855. } else {
  856. // Check the mesh core, if the agent is capable of running one
  857. if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) {
  858. obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
  859. } else {
  860. agentCoreIsStable(); // No updates needed, agent is ready to go.
  861. }
  862. }
  863. }
  864. // Indicate to the agent that we want to check Intel AMT configuration
  865. // This may trigger a CIRA-LMS tunnel from the agent so the server can inspect the device.
  866. obj.sendUpdatedIntelAmtPolicy = function (policy) {
  867. if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT.
  868. if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; }
  869. if ((policy != null) && (policy.type != 0)) {
  870. const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey);
  871. try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { }
  872. }
  873. }
  874. }
  875. function recoveryAgentCoreIsStable(mesh) {
  876. parent.agentStats.recoveryCoreIsStableCount++;
  877. // Recovery agent is doing ok, lets perform main agent checking.
  878. //console.log('recoveryAgentCoreIsStable()');
  879. // Fetch the the real agent nodeid
  880. db.Get('da' + obj.dbNodeKey, function (err, nodes, self) {
  881. if ((nodes != null) && (nodes.length == 1)) {
  882. self.realNodeKey = nodes[0].raid;
  883. // Get agent connection state
  884. var agentConnected = false;
  885. var state = parent.parent.GetConnectivityState(self.realNodeKey);
  886. if (state) { agentConnected = ((state.connectivity & 1) != 0) }
  887. self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: self.realNodeKey, agent: agentConnected } }));
  888. } else {
  889. self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
  890. }
  891. }, obj);
  892. }
  893. function agentCoreIsStable() {
  894. parent.agentStats.coreIsStableCount++;
  895. // Check that the mesh exists
  896. const mesh = parent.meshes[obj.dbMeshKey];
  897. if (mesh == null) {
  898. parent.agentStats.meshDoesNotExistCount++;
  899. parent.setAgentIssue(obj, "meshDoesNotExist");
  900. // TODO: Mark this agent as part of a mesh that does not exists.
  901. return; // Probably not worth doing anything else. Hold this agent.
  902. }
  903. // Check if this is a recovery agent
  904. if (obj.agentInfo.capabilities & 0x40) {
  905. recoveryAgentCoreIsStable(mesh);
  906. return;
  907. }
  908. // Fetch the the diagnostic agent nodeid
  909. db.Get('ra' + obj.dbNodeKey, function (err, nodes) {
  910. if ((nodes != null) && (nodes.length == 1)) {
  911. obj.diagnosticNodeKey = nodes[0].daid;
  912. obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.diagnosticNodeKey } }));
  913. }
  914. });
  915. // Indicate that we want to check the Intel AMT configuration
  916. // This may trigger a CIRA-LMS tunnel to the server for further processing
  917. obj.sendUpdatedIntelAmtPolicy();
  918. // Fetch system information
  919. db.GetHash('si' + obj.dbNodeKey, function (err, results) {
  920. if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
  921. });
  922. // Agent error log dump
  923. if (parent.parent.agentErrorLog != null) {
  924. db.Get('al' + obj.dbNodeKey, function (err, docs) { // Agent Log
  925. if ((docs != null) && (docs.length == 1) && (typeof docs[0].lastEvent)) {
  926. obj.send('{"action":"errorlog","startTime":' + docs[0].lastEvent + '}'); // Ask all events after a given time
  927. } else {
  928. obj.send('{"action":"errorlog"}'); // Ask all
  929. }
  930. });
  931. }
  932. // Set agent core dump
  933. if ((parent.parent.config.settings != null) && ((parent.parent.config.settings.agentcoredump === true) || (parent.parent.config.settings.agentcoredump === false))) {
  934. obj.send(JSON.stringify({ action: 'coredump', value: parent.parent.config.settings.agentcoredump }));
  935. if (parent.parent.config.settings.agentcoredump === true) {
  936. // Check if we requested a core dump file in the last minute, if not, ask if one is present.
  937. if ((parent.lastCoreDumpRequest == null) || ((Date.now() - parent.lastCoreDumpRequest) >= 60000)) { obj.send(JSON.stringify({ action: 'getcoredump' })); }
  938. }
  939. }
  940. // Do this if IP location is enabled on this domain TODO: Set IP location per device group?
  941. if (domain.iplocation == true) {
  942. // Check if we already have IP location information for this node
  943. db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
  944. if ((iplocs != null) && (iplocs.length == 1)) {
  945. // We have a location in the database for this remote IP
  946. const iploc = iplocs[0], x = {};
  947. if ((iploc != null) && (iploc.ip != null) && (iploc.loc != null)) {
  948. x.publicip = iploc.ip;
  949. x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000));
  950. ChangeAgentLocationInfo(x);
  951. }
  952. } else {
  953. // Check if we need to ask for the IP location
  954. var doIpLocation = 0;
  955. if (obj.iploc == null) {
  956. doIpLocation = 1;
  957. } else {
  958. const loc = obj.iploc.split(',');
  959. if (loc.length < 3) {
  960. doIpLocation = 2;
  961. } else {
  962. var t = new Date((parseFloat(loc[2]) * 1000)), now = Date.now();
  963. t.setDate(t.getDate() + 20);
  964. if (t < now) { doIpLocation = 3; }
  965. }
  966. }
  967. // If we need to ask for IP location, see if we have the quota to do it.
  968. if (doIpLocation > 0) {
  969. db.getValueOfTheDay('ipLocationRequestLimitor', 10, function (ipLocationLimitor) {
  970. if ((ipLocationLimitor != null) && (ipLocationLimitor.value > 0)) {
  971. ipLocationLimitor.value--;
  972. db.Set(ipLocationLimitor);
  973. obj.send(JSON.stringify({ action: 'iplocation' }));
  974. }
  975. });
  976. }
  977. }
  978. });
  979. }
  980. // Indicate server information to the agent.
  981. var serverInfo = { action: 'serverInfo' };
  982. if ((typeof domain.terminal == 'object') && (typeof domain.terminal.launchcommand == 'object')) {
  983. // Send terminal starting command
  984. serverInfo.termlaunchcommand = {};
  985. if (typeof domain.terminal.launchcommand.linux == 'string') { serverInfo.termlaunchcommand.linux = domain.terminal.launchcommand.linux; }
  986. if (typeof domain.terminal.launchcommand.darwin == 'string') { serverInfo.termlaunchcommand.darwin = domain.terminal.launchcommand.darwin; }
  987. if (typeof domain.terminal.launchcommand.freebsd == 'string') { serverInfo.termlaunchcommand.freebsd = domain.terminal.launchcommand.freebsd; }
  988. }
  989. // Enable agent self guest sharing if allowed
  990. if (domain.agentselfguestsharing) { serverInfo.agentSelfGuestSharing = true; }
  991. obj.send(JSON.stringify(serverInfo));
  992. // Plug in handler
  993. if (parent.parent.pluginHandler != null) {
  994. parent.parent.pluginHandler.callHook('hook_agentCoreIsStable', obj, parent);
  995. }
  996. }
  997. // Get the web certificate private key hash for the specified domain
  998. function getWebCertHash(domain) {
  999. const hash = parent.webCertificateHashs[domain.id];
  1000. if (hash != null) return hash;
  1001. return parent.webCertificateHash;
  1002. }
  1003. // Get the web certificate hash for the specified domain
  1004. function getWebCertFullHash(domain) {
  1005. const hash = parent.webCertificateFullHashs[domain.id];
  1006. if (hash != null) return hash;
  1007. return parent.webCertificateFullHash;
  1008. }
  1009. // Verify the agent signature
  1010. function processAgentSignature(msg) {
  1011. if (isIgnoreHashCheck() == false) {
  1012. var verified = false;
  1013. // This agent did not report a valid TLS certificate hash, fail now.
  1014. if (obj.agentSeenCerthash == null) return false;
  1015. // Raw RSA signatures have an exact length of 256 or 384. PKCS7 is larger.
  1016. if ((msg.length != 384) && (msg.length != 256)) {
  1017. // Verify a PKCS7 signature.
  1018. var msgDer = null;
  1019. try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { }
  1020. if (msgDer != null) {
  1021. try {
  1022. const p7 = forge.pkcs7.messageFromAsn1(msgDer);
  1023. const sig = p7.rawCapture.signature;
  1024. // Verify with key hash
  1025. var buf = Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary');
  1026. var verifier = parent.crypto.createVerify('RSA-SHA384');
  1027. verifier.update(buf);
  1028. verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
  1029. if (verified !== true) {
  1030. // Not a valid signature
  1031. parent.agentStats.invalidPkcsSignatureCount++;
  1032. parent.setAgentIssue(obj, "invalidPkcsSignature");
  1033. return false;
  1034. }
  1035. } catch (ex) { };
  1036. }
  1037. }
  1038. if (verified == false) {
  1039. // Verify the RSA signature. This is the fast way, without using forge.
  1040. const verify = parent.crypto.createVerify('SHA384');
  1041. verify.end(Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash
  1042. if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
  1043. parent.agentStats.invalidRsaSignatureCount++;
  1044. parent.setAgentIssue(obj, "invalidRsaSignature");
  1045. return false;
  1046. }
  1047. }
  1048. }
  1049. // Connection is a success, clean up
  1050. obj.nodeid = obj.unauth.nodeid;
  1051. obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
  1052. delete obj.nonce;
  1053. delete obj.agentnonce;
  1054. delete obj.unauth;
  1055. delete obj.receivedCommands;
  1056. delete obj.agentSeenCerthash;
  1057. if (obj.unauthsign) delete obj.unauthsign;
  1058. parent.agentStats.verifiedAgentConnectionCount++;
  1059. parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').');
  1060. obj.authenticated = 1;
  1061. return true;
  1062. }
  1063. // Process incoming agent JSON data
  1064. function processAgentData(msg) {
  1065. if (obj.agentInfo == null) return;
  1066. var i, str = msg.toString('utf8'), command = null;
  1067. if (str[0] == '{') {
  1068. try { command = JSON.parse(str); } catch (ex) {
  1069. // If the command can't be parsed, ignore it.
  1070. parent.agentStats.invalidJsonCount++;
  1071. parent.setAgentIssue(obj, "invalidJson (" + str.length + "): " + str);
  1072. parent.parent.debug('agent', 'Unable to parse agent JSON (' + obj.remoteaddrport + ')');
  1073. console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex);
  1074. return;
  1075. }
  1076. if (typeof command != 'object') { return; }
  1077. switch (command.action) {
  1078. case 'msg':
  1079. {
  1080. // If the same console command is processed many times, kick out this agent.
  1081. // This is a safety mesure to guard against the agent DOS'ing the server.
  1082. if (command.type == 'console') {
  1083. if (obj.consoleKickValue == command.value) {
  1084. if (obj.consoleKickCount) { obj.consoleKickCount++; } else { obj.consoleKickCount = 1; }
  1085. if (obj.consoleKickCount > 30) { obj.close(); return; } // 30 identical console messages received, kick out this agent.
  1086. } else {
  1087. obj.consoleKickValue = command.value;
  1088. }
  1089. }
  1090. // Route a message
  1091. parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey);
  1092. break;
  1093. }
  1094. case 'coreinfo':
  1095. {
  1096. // Sent by the agent to update agent information
  1097. ChangeAgentCoreInfo(command);
  1098. if ((obj.agentCoreUpdate === true) && (obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
  1099. // Agent update. The recovery core was loaded in the agent, send a command to update the agent
  1100. parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { // Medium priority task
  1101. // If agent disconnection, complete and exit now.
  1102. if ((obj.authenticated != 2) || (obj.agentExeInfo == null)) { parent.parent.taskLimiter.completed(taskid); return; }
  1103. // Agent update. The recovery core was loaded in the agent, send a command to update the agent
  1104. obj.agentCoreUpdateTaskId = taskid;
  1105. const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
  1106. var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex };
  1107. parent.parent.debug('agentupdate', "Sending agent update url: " + cmd.url);
  1108. // Add the hash
  1109. if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
  1110. // Add server TLS cert hash
  1111. if (isIgnoreHashCheck() == false) {
  1112. const tlsCertHash = parent.webCertificateFullHashs[domain.id];
  1113. if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
  1114. }
  1115. // Send the agent update command
  1116. obj.send(JSON.stringify(cmd));
  1117. }, null, 1);
  1118. }
  1119. break;
  1120. }
  1121. case 'smbios':
  1122. {
  1123. // SMBIOS information must never be saved when NeDB is in use. NeDB will currupt that database.
  1124. if (db.SetSMBIOS == null) break;
  1125. // See if we need to save SMBIOS information
  1126. if (domain.smbios === true) {
  1127. // Store the RAW SMBios table of this computer
  1128. // Perform sanity checks before storing
  1129. try {
  1130. 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]; } }
  1131. db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: command.value });
  1132. } catch (ex) { }
  1133. }
  1134. // Event the node interface information change (This is a lot of traffic, probably don't need this).
  1135. //parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
  1136. break;
  1137. }
  1138. case 'netinfo':
  1139. {
  1140. // Check if network information is present
  1141. if ((command.netif2 == null) && (command.netif == null)) return;
  1142. // Escape any field names that have special characters
  1143. if (command.netif2 != null) {
  1144. for (var i in command.netif2) {
  1145. var esc = common.escapeFieldName(i);
  1146. if (esc !== i) { command.netif2[esc] = command.netif2[i]; delete command.netif2[i]; }
  1147. }
  1148. }
  1149. // Sent by the agent to update agent network interface information
  1150. delete command.action;
  1151. command.updateTime = Date.now();
  1152. command._id = 'if' + obj.dbNodeKey;
  1153. command.domain = domain.id;
  1154. command.type = 'ifinfo';
  1155. db.Set(command);
  1156. // Event the node interface information change
  1157. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
  1158. break;
  1159. }
  1160. case 'iplocation':
  1161. {
  1162. // Sent by the agent to update location information
  1163. if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) {
  1164. var x = {};
  1165. x.publicip = command.value.ip;
  1166. x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000));
  1167. ChangeAgentLocationInfo(x);
  1168. command.value._id = 'iploc_' + command.value.ip;
  1169. command.value.type = 'iploc';
  1170. command.value.date = Date.now();
  1171. db.Set(command.value); // Store the IP to location data in the database
  1172. // Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' }
  1173. }
  1174. break;
  1175. }
  1176. case 'mc1migration':
  1177. {
  1178. if (command.oldnodeid.length != 64) break;
  1179. const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase();
  1180. db.Get(oldNodeKey, function (err, nodes) {
  1181. if ((nodes == null) || (nodes.length != 1)) return;
  1182. const node = nodes[0];
  1183. if (node.meshid == obj.dbMeshKey) {
  1184. // Update the device name & host
  1185. const newNode = { "name": node.name };
  1186. if (node.intelamt != null) { newNode.intelamt = node.intelamt; }
  1187. ChangeAgentCoreInfo(newNode);
  1188. // Delete this node including network interface information and events
  1189. db.Remove(node._id);
  1190. db.Remove('if' + node._id);
  1191. // Event node deletion
  1192. const change = 'Migrated device ' + node.name;
  1193. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: node._id, msg: change, domain: node.domain });
  1194. }
  1195. });
  1196. break;
  1197. }
  1198. case 'openUrl':
  1199. {
  1200. // Sent by the agent to return the status of a open URL action.
  1201. // Nothing is done right now.
  1202. break;
  1203. }
  1204. case 'log':
  1205. {
  1206. // Log a value in the event log
  1207. if ((typeof command.msg == 'string') && (command.msg.length < 4096)) {
  1208. var event = { etype: 'node', action: 'agentlog', nodeid: obj.dbNodeKey, domain: domain.id, msg: command.msg };
  1209. if (typeof command.msgid == 'number') { event.msgid = command.msgid; }
  1210. if (typeof command.guestname == 'string') { event.guestname = command.guestname; }
  1211. if (Array.isArray(command.msgArgs)) { event.msgArgs = command.msgArgs; }
  1212. if (typeof command.remoteaddr == 'string') { event.remoteaddr = command.remoteaddr; }
  1213. var targets = parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]);
  1214. if (typeof command.userid == 'string') {
  1215. var loguser = parent.users[command.userid];
  1216. if (loguser) { event.userid = command.userid; event.username = loguser.name; targets.push(command.userid); }
  1217. }
  1218. if (typeof command.xuserid == 'string') {
  1219. var xloguser = parent.users[command.xuserid];
  1220. if (xloguser) { targets.push(command.xuserid); }
  1221. }
  1222. if ((typeof command.sessionid == 'string') && (command.sessionid.length < 500)) { event.sessionid = command.sessionid; }
  1223. parent.parent.DispatchEvent(targets, obj, event);
  1224. // If this is a help request, see if we need to email notify anyone
  1225. if (event.msgid == 98) {
  1226. // Get the node and change it if needed
  1227. 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.
  1228. if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
  1229. const device = nodes[0];
  1230. if (typeof device.name == 'string') { parent.parent.NotifyUserOfDeviceHelpRequest(domain, device.meshid, device._id, device.name, command.msgArgs[0], command.msgArgs[1]); }
  1231. });
  1232. }
  1233. }
  1234. break;
  1235. }
  1236. case 'ping': { sendPong(); break; }
  1237. case 'pong': { break; }
  1238. case 'getScript':
  1239. {
  1240. // Used by the agent to get configuration scripts.
  1241. if (command.type == 1) {
  1242. parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) {
  1243. obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() }));
  1244. });
  1245. } else if (command.type == 2) {
  1246. parent.getCiraCleanupScript(function (script) {
  1247. obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() }));
  1248. });
  1249. }
  1250. break;
  1251. }
  1252. case 'diagnostic':
  1253. {
  1254. if (typeof command.value == 'object') {
  1255. switch (command.value.command) {
  1256. case 'register': {
  1257. // Only main agent can do this
  1258. if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64)) {
  1259. // Store links to diagnostic agent id
  1260. var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value);
  1261. db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent
  1262. db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent
  1263. }
  1264. break;
  1265. }
  1266. case 'query': {
  1267. // Only the diagnostic agent can do
  1268. if ((obj.agentInfo.capabilities & 0x40) != 0) {
  1269. // Return nodeid of main agent + connection status
  1270. db.Get('da' + obj.dbNodeKey, function (err, nodes) {
  1271. if ((nodes != null) && (nodes.length == 1)) {
  1272. obj.realNodeKey = nodes[0].raid;
  1273. // Get agent connection state
  1274. var agentConnected = false;
  1275. var state = parent.parent.GetConnectivityState(obj.realNodeKey);
  1276. if (state) { agentConnected = ((state.connectivity & 1) != 0) }
  1277. obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } }));
  1278. } else {
  1279. obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
  1280. }
  1281. });
  1282. }
  1283. break;
  1284. }
  1285. case 'log': {
  1286. if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256)) {
  1287. // If this is a diagnostic agent, log the event in the log of the main agent
  1288. var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, snodeid: obj.dbNodeKey, domain: domain.id, msg: command.value.value };
  1289. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
  1290. }
  1291. break;
  1292. }
  1293. }
  1294. }
  1295. break;
  1296. }
  1297. case 'sysinfo': {
  1298. if ((typeof command.data == 'object') && (typeof command.data.hash == 'string')) {
  1299. // Validate command.data.
  1300. if (common.validateObjectForMongo(command.data, 1024) == false) break;
  1301. // Save to database
  1302. command.data._id = 'si' + obj.dbNodeKey;
  1303. command.data.type = 'sysinfo';
  1304. command.data.domain = domain.id;
  1305. command.data.time = Date.now();
  1306. db.Set(command.data); // Update system information in the database.
  1307. // Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed
  1308. var event = { etype: 'node', action: 'sysinfohash', nodeid: obj.dbNodeKey, domain: domain.id, hash: command.data.hash, nolog: 1 };
  1309. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
  1310. }
  1311. break;
  1312. }
  1313. case 'sysinfocheck': {
  1314. // Check system information update
  1315. db.GetHash('si' + obj.dbNodeKey, function (err, results) {
  1316. if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
  1317. });
  1318. break;
  1319. }
  1320. case 'sessions': {
  1321. // This is a list of sessions provided by the agent
  1322. if (obj.sessions == null) { obj.sessions = {}; }
  1323. if (typeof command.value != null) {
  1324. if (command.type == 'kvm') { obj.sessions.kvm = command.value; }
  1325. else if (command.type == 'terminal') { obj.sessions.terminal = command.value; }
  1326. else if (command.type == 'files') { obj.sessions.files = command.value; }
  1327. else if (command.type == 'help') { obj.sessions.help = command.value; }
  1328. else if (command.type == 'tcp') { obj.sessions.tcp = command.value; }
  1329. else if (command.type == 'udp') { obj.sessions.udp = command.value; }
  1330. else if (command.type == 'msg') { obj.sessions.msg = command.value; }
  1331. else if (command.type == 'app') { obj.sessions.app = command.value; }
  1332. }
  1333. // Any "help" session must have an associated app, if not, remove it.
  1334. if (obj.sessions.help != null) {
  1335. for (var i in obj.sessions.help) { if (obj.sessions.help[i] == null) { delete obj.sessions.help[i]; } }
  1336. if (Object.keys(obj.sessions.help).length == 0) { delete obj.sessions.help; }
  1337. }
  1338. // Inform everyone of updated sessions
  1339. obj.updateSessions();
  1340. break;
  1341. }
  1342. case 'battery': {
  1343. // Device battery and power state
  1344. if (obj.sessions == null) { obj.sessions = {}; }
  1345. if (obj.sessions.battery == null) { obj.sessions.battery = {}; }
  1346. if ((command.state == 'ac') || (command.state == 'dc')) { obj.sessions.battery.state = command.state; } else { delete obj.sessions.battery.state; }
  1347. if ((typeof command.level == 'number') && (command.level >= 0) && (command.level <= 100)) { obj.sessions.battery.level = command.level; } else { delete obj.sessions.battery.level; }
  1348. obj.updateSessions();
  1349. break;
  1350. }
  1351. case 'getcoredump': {
  1352. // Check if we requested a core dump file in the last minute, if so, ignore this.
  1353. if ((parent.lastCoreDumpRequest != null) && ((Date.now() - parent.lastCoreDumpRequest) < 60000)) break;
  1354. // Indicates if the agent has a coredump available
  1355. if ((command.exists === true) && (typeof command.agenthashhex == 'string') && (command.agenthashhex.length == 96)) {
  1356. // Check if we already have this exact dump file
  1357. const coreDumpFile = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps', obj.agentInfo.agentId + '-' + command.agenthashhex + '-' + obj.nodeid + '.dmp');
  1358. parent.fs.stat(coreDumpFile, function (err, stats) {
  1359. if (stats != null) return;
  1360. obj.coreDumpPresent = true;
  1361. // Check how many files are in the coredumps folder
  1362. const coreDumpPath = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps');
  1363. parent.fs.readdir(coreDumpPath, function (err, files) {
  1364. if ((files != null) && (files.length >= 20)) return; // Don't get more than 20 core dump files.
  1365. // Get the core dump uploaded to the server.
  1366. parent.lastCoreDumpRequest = Date.now();
  1367. obj.RequestCoreDump(command.agenthashhex, command.corehashhex);
  1368. });
  1369. });
  1370. }
  1371. break;
  1372. }
  1373. case 'tunnelCloseStats': {
  1374. // TODO: This this extra stats from the tunnel, you can merge this into the tunnel event in the database.
  1375. //console.log(command);
  1376. // Validate input
  1377. if ((command.sent == null) || (typeof command.sent != 'string')) return;
  1378. if ((command.sentActual == null) || (typeof command.sentActual != 'string')) return;
  1379. if ((command.sentActual == null) || (typeof command.sentActual != 'number')) return;
  1380. // Event the session closed compression data.
  1381. 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 + '.' };
  1382. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
  1383. break;
  1384. }
  1385. case 'lmsinfo': {
  1386. // Agents send the LMS port bindings
  1387. // Example: {"action":"lmsinfo","value":{"ports":["623","16992"]}}
  1388. break;
  1389. }
  1390. case 'plugin': {
  1391. if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
  1392. try {
  1393. parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
  1394. } catch (e) {
  1395. parent.parent.debug('agent', 'Error loading plugin handler (' + e + ')');
  1396. console.log('Error loading plugin handler (' + e + ')');
  1397. }
  1398. break;
  1399. }
  1400. case 'meshToolInfo': {
  1401. // Return information about a MeshCentral tool. Current tools are 'MeshCentralRouter' and 'MeshCentralAssistant'
  1402. // Information includes file hash and download location URL
  1403. if (typeof command.name != 'string') break;
  1404. var info = parent.parent.meshToolsBinaries[command.name];
  1405. if ((command.hash != null) && (info.hash == command.hash)) return;
  1406. // To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
  1407. var xdomain = (domain.dns == null) ? domain.id : '';
  1408. if (xdomain != '') xdomain += '/';
  1409. // Build the response
  1410. const responseCmd = { action: 'meshToolInfo', name: command.name, tag: command.tag, sessionid: command.sessionid, hash: info.hash, size: info.size, url: info.url };
  1411. 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.
  1412. if (command.cookie === true) { responseCmd.url += ('&auth=' + parent.parent.encodeCookie({ download: info.dlname }, parent.parent.loginCookieEncryptionKey)); }
  1413. if (command.pipe === true) { responseCmd.pipe = true; }
  1414. if (parent.webCertificateHashs[domain.id] != null) { responseCmd.serverhash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex'); }
  1415. try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { }
  1416. break;
  1417. }
  1418. case 'agentupdate': {
  1419. if ((obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
  1420. var func = function agentUpdateFunc(argument, taskid, taskLimiterQueue) { // Medium priority task
  1421. // If agent disconnection, complete and exit now.
  1422. if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; }
  1423. // Agent is requesting an agent update
  1424. obj.agentCoreUpdateTaskId = taskid;
  1425. const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
  1426. var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex, sessionid: agentUpdateFunc.sessionid };
  1427. parent.parent.debug('agentupdate', "Sending user requested agent update url: " + cmd.url);
  1428. // Add the hash
  1429. if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
  1430. // Add server TLS cert hash
  1431. if (isIgnoreHashCheck() == false) {
  1432. const tlsCertHash = parent.webCertificateFullHashs[domain.id];
  1433. if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
  1434. }
  1435. // Send the agent update command
  1436. obj.send(JSON.stringify(cmd));
  1437. }
  1438. func.sessionid = command.sessionid;
  1439. // Agent update. The recovery core was loaded in the agent, send a command to update the agent
  1440. parent.parent.taskLimiter.launch(func, null, 1);
  1441. }
  1442. break;
  1443. }
  1444. case 'agentupdatedownloaded': {
  1445. if (obj.agentCoreUpdateTaskId != null) {
  1446. // Indicate this udpate task is complete
  1447. parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId);
  1448. delete obj.agentCoreUpdateTaskId;
  1449. }
  1450. break;
  1451. }
  1452. case 'errorlog': { // This is the agent error log
  1453. if ((!Array.isArray(command.log)) || (command.log.length == 0) || (parent.parent.agentErrorLog == null)) break;
  1454. var lastLogEntry = command.log[command.log.length - 1];
  1455. if ((lastLogEntry != null) && (typeof lastLogEntry == 'object') && (typeof lastLogEntry.t == 'number')) {
  1456. parent.fs.write(parent.parent.agentErrorLog, obj.dbNodeKey + ', ' + Date.now() + ', ' + str + '\r\n', function (err) { });
  1457. db.Set({ _id: 'al' + obj.dbNodeKey, lastEvent: lastLogEntry.t });
  1458. }
  1459. break;
  1460. }
  1461. case '2faauth': {
  1462. // Validate input
  1463. if ((typeof command.url != 'string') || (typeof command.approved != 'boolean') || (command.url.startsWith('2fa://') == false)) return;
  1464. // parse the URL
  1465. var url = null;
  1466. try { url = require('url').parse(command.url); } catch (ex) { }
  1467. if (url == null) return;
  1468. // Decode the cookie
  1469. var urlSplit = url.query.split('&c=');
  1470. if (urlSplit.length != 2) return;
  1471. const authCookie = parent.parent.decodeCookie(urlSplit[1], null, 1);
  1472. if ((authCookie == null) || (typeof authCookie.c != 'string') || (('code=' + authCookie.c) != urlSplit[0])) return;
  1473. if ((typeof authCookie.n != 'string') || (authCookie.n != obj.dbNodeKey) || (typeof authCookie.u != 'string')) return;
  1474. // Fetch the user
  1475. const user = parent.users[authCookie.u];
  1476. if (user == null) return;
  1477. // Add this device as the authentication push notification device for this user
  1478. if (authCookie.a == 'addAuth') {
  1479. // Do nothing if authentication is not approved.
  1480. // We do not want to indicate that the remote user responded to this.
  1481. if (command.approved !== true) return;
  1482. // Change the user
  1483. user.otpdev = obj.dbNodeKey;
  1484. parent.db.SetUser(user);
  1485. // Notify change
  1486. var targets = ['*', 'server-users', user._id];
  1487. if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
  1488. 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 };
  1489. 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.
  1490. parent.parent.DispatchEvent(targets, obj, event);
  1491. }
  1492. // Complete 2FA checking
  1493. if (authCookie.a == 'checkAuth') {
  1494. if (typeof authCookie.s != 'string') return;
  1495. // Notify 2FA response
  1496. 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 });
  1497. }
  1498. break;
  1499. }
  1500. case 'getUserImage': {
  1501. // Validate input
  1502. if (typeof command.userid != 'string') {
  1503. // Send back the default image if required
  1504. if ((command.default) || (command.sentDefault)) {
  1505. 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) { }
  1506. obj.send(JSON.stringify(command));
  1507. }
  1508. return;
  1509. }
  1510. var useridsplit = command.userid.split('/');
  1511. if ((useridsplit.length != 3) || (useridsplit[1] != domain.id)) return;
  1512. // Add the user's real name if present
  1513. var u = parent.users[command.userid];
  1514. if (u == null) return;
  1515. if (u.name) { command.name = u.name; }
  1516. if (u.realname) { command.realname = u.realname; }
  1517. // An agent can only request images of accounts with rights to the device.
  1518. if (parent.GetNodeRights(command.userid, obj.dbMeshKey, obj.dbNodeKey) != 0) {
  1519. parent.db.Get('im' + command.userid, function (err, images) {
  1520. if ((err == null) && (images != null) && (images.length == 1)) {
  1521. // Send back the account image
  1522. command.image = images[0].image;
  1523. } else {
  1524. // Send back the default image if required
  1525. if ((command.default) || (command.sentDefault)) {
  1526. 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) { }
  1527. }
  1528. }
  1529. obj.send(JSON.stringify(command));
  1530. });
  1531. }
  1532. break;
  1533. }
  1534. case 'getServerImage': {
  1535. if (command.agent === 'assistant') {
  1536. // Return server title and image for MeshCentral Assistant
  1537. if ((domain.assistantcustomization != null) && (typeof domain.assistantcustomization == 'object')) {
  1538. var ok = false;
  1539. if (typeof domain.assistantcustomization.title == 'string') { ok = true; command.title = domain.assistantcustomization.title; }
  1540. 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); } }
  1541. if (ok) { obj.send(JSON.stringify(command)); }
  1542. }
  1543. }
  1544. if (command.agent === 'android') {
  1545. // Return server title and image for MeshCentral Assistant
  1546. if ((domain.androidcustomization != null) && (typeof domain.androidcustomization == 'object')) {
  1547. var ok = false;
  1548. if (typeof domain.androidcustomization.title == 'string') { ok = true; command.title = domain.androidcustomization.title; }
  1549. if (typeof domain.androidcustomization.subtitle == 'string') { ok = true; command.subtitle = domain.androidcustomization.subtitle; }
  1550. 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); } }
  1551. if (ok) { obj.send(JSON.stringify(command)); }
  1552. }
  1553. }
  1554. break;
  1555. }
  1556. case 'guestShare': {
  1557. if ((command.flags == null) || (command.flags == 0)) {
  1558. // Stop any current self-share, this is allowed even if self guest sharing is not allows so to clear any old shares.
  1559. removeGuestSharing(function () {
  1560. delete obj.guestSharing;
  1561. obj.send(JSON.stringify({ action: 'guestShare', flags: command.flags, url: null, viewOnly: false }));
  1562. });
  1563. } else {
  1564. // Add a new self-share, this will replace any share for this device
  1565. if ((domain.agentselfguestsharing == null) || (domain.agentselfguestsharing == false) || (typeof command.flags != 'number')) return; // Check if agent self-sharing is allowed, this is off by default.
  1566. if ((command.flags & 2) == 0) { command.viewOnly = false; } // Only allow "view only" if desktop is shared.
  1567. addGuestSharing(command.flags, command.viewOnly, function (share) {
  1568. obj.guestSharing = true;
  1569. obj.send(JSON.stringify({ action: 'guestShare', url: share.url, flags: share.flags, viewOnly: share.viewOnly }));
  1570. })
  1571. }
  1572. break;
  1573. }
  1574. case 'amtconfig': {
  1575. // Sent by the agent when the agent needs a Intel AMT APF connection to the server
  1576. const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey);
  1577. try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { }
  1578. break;
  1579. }
  1580. case 'script-task': {
  1581. // These command are for running regular batch jobs on the remote device
  1582. if (parent.parent.taskManager != null) { parent.parent.taskManager.agentAction(command, obj); }
  1583. break;
  1584. }
  1585. default: {
  1586. parent.agentStats.unknownAgentActionCount++;
  1587. parent.parent.debug('agent', 'Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
  1588. console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
  1589. break;
  1590. }
  1591. }
  1592. if (parent.parent.pluginHandler != null) {
  1593. parent.parent.pluginHandler.callHook('hook_processAgentData', command, obj, parent);
  1594. }
  1595. }
  1596. }
  1597. function addGuestSharing(flags, viewOnly, func) {
  1598. // Create cookie
  1599. const publicid = 'AS:' + obj.dbNodeKey;
  1600. const extrakey = getRandomAmtPassword();
  1601. const cookie = { a: 6, pid: publicid, k: extrakey }; // New style sharing cookie
  1602. const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
  1603. if (inviteCookie == null) return;
  1604. // Create the server url
  1605. var serverName = parent.getWebServerName(domain, req);
  1606. var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
  1607. var xdomain = (domain.dns == null) ? domain.id : '';
  1608. if (xdomain != '') xdomain += '/';
  1609. var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie;
  1610. if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; }
  1611. // Create a device sharing database entry
  1612. 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 };
  1613. // Add expire time
  1614. if ((typeof domain.agentselfguestsharing == 'object') && (typeof domain.agentselfguestsharing.expire == 'number') && (domain.agentselfguestsharing.expire > 0)) {
  1615. shareEntry.startTime = Date.now();
  1616. shareEntry.expireTime = Date.now() + (60000 * domain.agentselfguestsharing.expire);
  1617. }
  1618. if (viewOnly === true) { shareEntry.viewOnly = true; }
  1619. parent.db.Set(shareEntry);
  1620. // Send out an event that we added a device share
  1621. var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey);
  1622. var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added device share with unlimited time', msgid: 131, msgArgs: ['Agent'], domain: domain.id };
  1623. parent.parent.DispatchEvent(targets, obj, event);
  1624. // Send device share update
  1625. parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
  1626. if (err != null) return;
  1627. // Check device sharing
  1628. var now = Date.now();
  1629. for (var i = 0; i < docs.length; i++) {
  1630. const doc = docs[i];
  1631. if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else {
  1632. // This share is ok, remove extra data we don't need to send.
  1633. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
  1634. }
  1635. }
  1636. // Send device share update
  1637. var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
  1638. parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 });
  1639. // Callback
  1640. if (func) { func({ url: url, flags: flags, viewOnly: viewOnly }); }
  1641. });
  1642. }
  1643. function removeGuestSharing(func) {
  1644. var publicid = 'AS:' + obj.dbNodeKey;
  1645. parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
  1646. if (err != null) return;
  1647. // Remove device sharing
  1648. var now = Date.now(), removedExact = null, removed = false, okDocs = [];
  1649. for (var i = 0; i < docs.length; i++) {
  1650. const doc = docs[i];
  1651. if (doc.publicid == publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; }
  1652. else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else {
  1653. // This share is ok, remove extra data we don't need to send.
  1654. delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
  1655. okDocs.push(doc);
  1656. }
  1657. }
  1658. // Event device share removal
  1659. if (removedExact != null) {
  1660. // Send out an event that we removed a device share
  1661. var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, ['server-shareremove']);
  1662. var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['Agent'], domain: domain.id, publicid: publicid };
  1663. parent.parent.DispatchEvent(targets, obj, event);
  1664. }
  1665. // If we removed any shares, send device share update
  1666. if (removed == true) {
  1667. var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
  1668. parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
  1669. }
  1670. // Call back when done
  1671. if (func) func(removed);
  1672. });
  1673. }
  1674. // Notify update of sessions
  1675. obj.updateSessions = function () {
  1676. // Perform some clean up
  1677. for (var i in obj.sessions) { if (Object.keys(obj.sessions[i]).length == 0) { delete obj.sessions[i]; } }
  1678. if (Object.keys(obj.sessions).length == 0) { delete obj.sessions; }
  1679. // Event the new sessions, this will notify everyone that agent sessions have changed
  1680. var event = { etype: 'node', action: 'devicesessions', nodeid: obj.dbNodeKey, domain: domain.id, sessions: obj.sessions, nolog: 1 };
  1681. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
  1682. }
  1683. // Change the current core information string and event it
  1684. function ChangeAgentCoreInfo(command) {
  1685. if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
  1686. if ((command == null) || (command == null)) return; // Safety, should never happen.
  1687. // If the device is pending a change, hold.
  1688. if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; }
  1689. obj.deviceChanging = true;
  1690. // Check that the mesh exists
  1691. const mesh = parent.meshes[obj.dbMeshKey];
  1692. if (mesh == null) { delete obj.deviceChanging; return; }
  1693. // Get the node and change it if needed
  1694. 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.
  1695. if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
  1696. const device = nodes[0];
  1697. if (device.agent) {
  1698. var changes = [], change = 0, log = 0;
  1699. // Check if anything changes
  1700. if (command.name && (typeof command.name == 'string') && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); }
  1701. 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.
  1702. 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.
  1703. 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.
  1704. if ((typeof command.root == 'boolean') && (command.root !== device.agent.root)) { change = 1; device.agent.root = command.root; }
  1705. if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
  1706. if (command.intelamt) {
  1707. if (!device.intelamt) { device.intelamt = {}; }
  1708. if ((command.intelamt.Versions != null) && (typeof command.intelamt.Versions == 'object')) {
  1709. 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; }
  1710. if ((command.intelamt.Versions.Sku != null) && (typeof command.intelamt.Versions.Sku == 'string')) {
  1711. const sku = parseInt(command.intelamt.Versions.Sku);
  1712. if (device.intelamt.sku !== sku) { device.intelamt.sku = sku; change = 1; log = 1; }
  1713. }
  1714. }
  1715. 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; }
  1716. if ((command.intelamt.Flags != null) && (typeof command.intelamt.Flags == 'number') && (device.intelamt.flags != command.intelamt.Flags)) {
  1717. if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.Flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.Flags + ')'); }
  1718. device.intelamt.flags = command.intelamt.Flags; change = 1; log = 1;
  1719. }
  1720. 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; }
  1721. }
  1722. if (command.av != null) { // Antivirus
  1723. if (!device.av) { device.av = []; }
  1724. if (JSON.stringify(device.av) != JSON.stringify(command.av)) { /*changes.push('AV status');*/ device.av = command.av; change = 1; log = 1; }
  1725. }
  1726. if (command.wsc != null) { // Windows Security Center
  1727. if (!device.wsc) { device.wsc = {}; }
  1728. if (JSON.stringify(device.wsc) != JSON.stringify(command.wsc)) { /*changes.push('Windows Security Center status');*/ device.wsc = command.wsc; change = 1; log = 1; }
  1729. }
  1730. if (command.defender != null) { // Defender For Windows Server
  1731. if (!device.defender) { device.defender = {}; }
  1732. if (JSON.stringify(device.defender) != JSON.stringify(command.defender)) { /*changes.push('Defender status');*/ device.defender = command.defender; change = 1; log = 1; }
  1733. }
  1734. if (command.lastbootuptime != null) { // Last Boot Up Time
  1735. if (!device.lastbootuptime) { device.lastbootuptime = ""; }
  1736. if (device.lastbootuptime != command.lastbootuptime) { /*changes.push('Last Boot Up Time');*/ device.lastbootuptime = command.lastbootuptime; change = 1; log = 1; }
  1737. }
  1738. // Push Messaging Token
  1739. if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) {
  1740. if (typeof device.pmt == 'string') { db.Remove('pmt_' + device.pmt); }
  1741. device.pmt = command.pmt;
  1742. change = 1; // Don't save this change as an event to the db, so no log=1.
  1743. parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
  1744. }
  1745. 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.
  1746. if ((mesh.mtype == 2) && (!args.wanonly)) {
  1747. // In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
  1748. if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
  1749. // 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.
  1750. }
  1751. // Remove old volumes and BitLocker data, this is part of sysinfo.
  1752. delete device.volumes;
  1753. // If there are changes, event the new device
  1754. if (change == 1) {
  1755. // Do some clean up if needed, these values should not be in the database.
  1756. if (device.conn != null) { delete device.conn; }
  1757. if (device.pwr != null) { delete device.pwr; }
  1758. if (device.agct != null) { delete device.agct; }
  1759. if (device.cict != null) { delete device.cict; }
  1760. // Save to the database
  1761. db.Set(device);
  1762. // Event the node change
  1763. var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
  1764. if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
  1765. 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
  1766. 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.
  1767. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
  1768. }
  1769. // Device change is done.
  1770. delete obj.deviceChanging;
  1771. }
  1772. });
  1773. }
  1774. // Change the current core information string and event it
  1775. function ChangeAgentLocationInfo(command) {
  1776. if (obj.agentInfo.capabilities & 0x40) return;
  1777. if ((command == null) || (command == null)) { return; } // Safety, should never happen.
  1778. // Check that the mesh exists
  1779. const mesh = parent.meshes[obj.dbMeshKey];
  1780. if (mesh == null) return;
  1781. // If the device is pending a change, hold.
  1782. if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentLocationInfo(command); }, 100); return; }
  1783. obj.deviceChanging = true;
  1784. // Get the node and change it if needed
  1785. db.Get(obj.dbNodeKey, function (err, nodes) {
  1786. if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
  1787. const device = nodes[0];
  1788. if (device.agent) {
  1789. var changes = [], change = 0;
  1790. // Check if anything changes
  1791. if ((command.publicip) && (device.publicip != command.publicip)) { device.publicip = command.publicip; change = 1; changes.push('public ip'); }
  1792. if ((command.iploc) && (device.iploc != command.iploc)) { device.iploc = command.iploc; change = 1; changes.push('ip location'); }
  1793. // If there are changes, save and event
  1794. if (change == 1) {
  1795. // Do some clean up if needed, these values should not be in the database.
  1796. if (device.conn != null) { delete device.conn; }
  1797. if (device.pwr != null) { delete device.pwr; }
  1798. if (device.agct != null) { delete device.agct; }
  1799. if (device.cict != null) { delete device.cict; }
  1800. // Save the device
  1801. db.Set(device);
  1802. // Event the node change
  1803. 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(', ') };
  1804. if (obj.agentInfo.capabilities & 0x20) { event.nolog = 1; } // If this is a temporary device, don't log changes
  1805. 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.
  1806. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
  1807. }
  1808. }
  1809. // Done changing the device
  1810. delete obj.deviceChanging;
  1811. });
  1812. }
  1813. // Update the mesh agent tab in the database
  1814. function ChangeAgentTag(tag) {
  1815. if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
  1816. if ((tag != null) && (tag.length == 0)) { tag = null; }
  1817. // If the device is pending a change, hold.
  1818. if (obj.deviceChanging === true) {
  1819. var func = function ChangeAgentTagFunc() { ChangeAgentCoreInfo(ChangeAgentTagFunc.tag); }
  1820. func.tag = tag;
  1821. setTimeout(func, 100);
  1822. return;
  1823. }
  1824. obj.deviceChanging = true;
  1825. // Get the node and change it if needed
  1826. db.Get(obj.dbNodeKey, function (err, nodes) {
  1827. if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
  1828. const device = nodes[0];
  1829. if (device.agent) {
  1830. // Parse the agent tag
  1831. var agentTag = null, serverName = null, serverDesc = null, serverTags = null;
  1832. if (tag != null) {
  1833. var taglines = tag.split('\r\n').join('\n').split('\r').join('\n').split('\n');
  1834. for (var i in taglines) {
  1835. var tagline = taglines[i].trim();
  1836. if (tagline.length > 0) {
  1837. if (tagline.startsWith('~')) {
  1838. if (tagline.startsWith('~ServerName:') && (tagline.length > 12) && (serverName == null)) { serverName = tagline.substring(12).trim(); }
  1839. if (tagline.startsWith('~ServerDesc:') && (tagline.length > 12) && (serverDesc == null)) { serverDesc = tagline.substring(12).trim(); }
  1840. if (tagline.startsWith('~ServerTags:') && (tagline.length > 12) && (serverTags == null)) { serverTags = tagline.substring(12).split(','); for (var j in serverTags) { serverTags[j] = serverTags[j].trim(); } }
  1841. } else { if (agentTag == null) { agentTag = tagline; } }
  1842. }
  1843. }
  1844. }
  1845. // Set the agent tag
  1846. var changes = false;
  1847. if (device.agent.tag != agentTag) { device.agent.tag = agentTag; if ((device.agent.tag == null) || (device.agent.tag == '')) { delete device.agent.tag; } changes = true; }
  1848. if (domain.agenttag != null) {
  1849. // Set the device's server name
  1850. if ((serverName != null) && (domain.agenttag.servername === 1) && (device.name != serverName)) { device.name = serverName; changes = true; }
  1851. // Set the device's server description
  1852. if ((serverDesc != null) && (domain.agenttag.serverdesc === 1) && (device.desc != serverDesc)) { device.desc = serverDesc; changes = true; }
  1853. // Set the device's server description if there is no description
  1854. if ((serverDesc != null) && (domain.agenttag.serverdesc === 2) && (device.desc != serverDesc) && ((device.desc == null) || (device.desc == ''))) { device.desc = serverDesc; changes = true; }
  1855. if ((serverTags != null) && (domain.agenttag.servertags != null) && (domain.agenttag.servertags != 0)) {
  1856. // Sort the tags
  1857. serverTags.sort();
  1858. // Stringify the tags
  1859. var st2 = '', st1 = serverTags.join(',');
  1860. if (device.tags != null) { st2 = device.tags.join(','); }
  1861. // Set the device's server tags
  1862. if ((domain.agenttag.servertags === 1) && (st1 != st2)) { device.tags = serverTags; changes = true; }
  1863. // Set the device's server tags if there are not tags
  1864. if ((domain.agenttag.servertags === 2) && (st2 == '')) { device.tags = serverTags; changes = true; }
  1865. // Append to device's server tags
  1866. if ((domain.agenttag.servertags === 3) && (st1 != st2)) {
  1867. if (device.tags == null) { device.tags = []; }
  1868. for (var i in serverTags) { if (device.tags.indexOf(serverTags[i]) == -1) { device.tags.push(serverTags[i]); } }
  1869. device.tags.sort();
  1870. changes = true;
  1871. }
  1872. }
  1873. }
  1874. if (changes == true) {
  1875. // Do some clean up if needed, these values should not be in the database.
  1876. if (device.conn != null) { delete device.conn; }
  1877. if (device.pwr != null) { delete device.pwr; }
  1878. if (device.agct != null) { delete device.agct; }
  1879. if (device.cict != null) { delete device.cict; }
  1880. // Update the device
  1881. db.Set(device);
  1882. // Event the node change
  1883. var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), nolog: 1 };
  1884. 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.
  1885. parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
  1886. }
  1887. }
  1888. // Done changing the device
  1889. delete obj.deviceChanging;
  1890. });
  1891. }
  1892. // Check if we need to update this agent, return true if agent binary update required.
  1893. // Return 0 is no update needed, 1 update using native system, 2 update using meshcore system
  1894. function compareAgentBinaryHash(agentExeInfo, agentHash) {
  1895. // If this is a temporary agent and the server is set to not update temporary agents, don't update the agent.
  1896. if ((obj.agentInfo.capabilities & 0x20) && (args.temporaryagentupdate === false)) return 0;
  1897. // If we are testing the agent update system, always return true
  1898. if ((args.agentupdatetest === true) || (args.agentupdatetest === 1)) return 1;
  1899. if (args.agentupdatetest === 2) return 2;
  1900. // If the hash matches or is null, no update required.
  1901. 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;
  1902. // If this is a macOS x86 or ARM agent type and it matched the universal binary, no update required.
  1903. if ((agentExeInfo.id == 16) || (agentExeInfo.id == 29)) {
  1904. if (domain.meshAgentBinaries && domain.meshAgentBinaries[10005]) {
  1905. if (domain.meshAgentBinaries[10005].hash == agentHash) return 0;
  1906. } else {
  1907. if (parent.parent.meshAgentBinaries[10005].hash == agentHash) return 0;
  1908. }
  1909. }
  1910. // No match, update the agent.
  1911. if (args.agentupdatesystem === 2) return 2; // If set, force a meshcore update.
  1912. 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.
  1913. // NOTE: Windows agents with no commit dates may have bad native update system, so use meshcore system instead.
  1914. // 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.
  1915. if (((obj.AgentCommitDate == null) || (obj.AgentCommitDate < 1612740413000)) && ((agentExeInfo.id == 3) || (agentExeInfo.id == 4))) return 2; // For older Windows agents, use the meshcore update technique.
  1916. return 1; // By default, use the native update technique.
  1917. }
  1918. // Request that the core dump file on this agent be uploaded to the server
  1919. obj.RequestCoreDump = function (agenthashhex, corehashhex) {
  1920. if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); }
  1921. const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey);
  1922. obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}');
  1923. }
  1924. // Return true if we need to ignore the agent hash check
  1925. function isIgnoreHashCheck() {
  1926. if ((args.ignoreagenthashcheck === true) || (domain.ignoreagenthashcheck === true)) return true;
  1927. // Check site wide exceptions
  1928. if (Array.isArray(args.ignoreagenthashcheck)) {
  1929. for (var i = 0; i < args.ignoreagenthashcheck.length; i++) {
  1930. if (require('ipcheck').match(obj.remoteaddr, args.ignoreagenthashcheck[i])) return true;
  1931. }
  1932. }
  1933. // Check domain wide exceptions
  1934. if (Array.isArray(domain.ignoreagenthashcheck)) {
  1935. for (var i = 0; i < domain.ignoreagenthashcheck.length; i++) {
  1936. if (require('ipcheck').match(obj.remoteaddr, domain.ignoreagenthashcheck[i])) return true;
  1937. }
  1938. }
  1939. return false;
  1940. }
  1941. // Generate a random Intel AMT password
  1942. function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
  1943. function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
  1944. return obj;
  1945. };