123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178 |
- /**
- * @description MeshCentral MeshAgent communication module
- * @author Ylian Saint-Hilaire & Bryan Roe
- * @copyright Intel Corporation 2018-2022
- * @license Apache-2.0
- * @version v0.0.1
- */
- /*xjslint node: true */
- /*xjslint plusplus: true */
- /*xjslint maxlen: 256 */
- /*jshint node: true */
- /*jshint strict: false */
- /*jshint esversion: 6 */
- "use strict";
- // Construct a MeshAgent object, called upon connection
- module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
- const forge = parent.parent.certificateOperations.forge;
- const common = parent.parent.common;
- parent.agentStats.createMeshAgentCount++;
- parent.parent.debug('agent', 'New agent at ' + req.clientIp + ':' + ws._socket.remotePort);
- var obj = {};
- obj.domain = domain;
- obj.authenticated = 0;
- obj.receivedCommands = 0;
- obj.agentCoreCheck = 0;
- obj.remoteaddr = req.clientIp;
- obj.remoteaddrport = obj.remoteaddr + ':' + ws._socket.remotePort;
- obj.nonce = parent.crypto.randomBytes(48).toString('binary');
- //ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive, 4 minutes
- if (args.agentidletimeout != 0) { ws._socket.setTimeout(args.agentidletimeout, function () { obj.close(1); }); } // Inactivity timeout of 2:30 minutes, by default agent will WebSocket ping every 2 minutes and server will pong back.
- //obj.nodeid = null;
- //obj.meshid = null;
- //obj.dbNodeKey = null;
- //obj.dbMeshKey = null;
- //obj.connectTime = null;
- //obj.agentInfo = null;
- ws._socket.bytesReadEx = 0;
- ws._socket.bytesWrittenEx = 0;
- // Perform data accounting
- function dataAccounting() {
- parent.trafficStats.AgentCtrlIn += (ws._socket.bytesRead - ws._socket.bytesReadEx);
- parent.trafficStats.AgentCtrlOut += (ws._socket.bytesWritten - ws._socket.bytesWrittenEx);
- ws._socket.bytesReadEx = ws._socket.bytesRead;
- ws._socket.bytesWrittenEx = ws._socket.bytesWritten;
- }
- // Send a message to the mesh agent
- obj.send = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data), func); } else { ws.send(data, func); } } catch (e) { } };
- obj.sendBinary = function (data, func) { try { if (typeof data == 'string') { ws.send(Buffer.from(data, 'binary'), func); } else { ws.send(data, func); } } catch (e) { } };
- // Disconnect this agent
- obj.close = function (arg) {
- dataAccounting();
- if ((arg == 1) || (arg == null)) { try { ws.close(); if (obj.nodeid != null) { parent.parent.debug('agent', 'Soft disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Soft close, close the websocket
- if (arg == 2) {
- try {
- if (ws._socket._parent != null)
- ws._socket._parent.end();
- else
- ws._socket.end();
- if (obj.nodeid != null) {
- parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')');
- }
- } catch (e) { console.log(e); }
- }
- // If arg == 2, hard close, close the TCP socket
- // If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent).
- // Stop any current self-share
- if (obj.guestSharing === true) { removeGuestSharing(); }
- // Remove this agent from the webserver list
- if (parent.wsagents[obj.dbNodeKey] == obj) {
- delete parent.wsagents[obj.dbNodeKey];
- parent.parent.ClearConnectivityState(obj.dbMeshKey, obj.dbNodeKey, 1, null, { remoteaddrport: obj.remoteaddrport, name: obj.name });
- }
- // Remove this agent from the list of agents with bad web certificates
- if (obj.badWebCert) { delete parent.wsagentsWithBadWebCerts[obj.badWebCert]; }
- // Get the current mesh
- const mesh = parent.meshes[obj.dbMeshKey];
- // If this is a temporary or recovery agent, or all devices in this group are temporary, remove the agent (0x20 = Temporary, 0x40 = Recovery)
- if (((obj.agentInfo) && (obj.agentInfo.capabilities) && ((obj.agentInfo.capabilities & 0x20) || (obj.agentInfo.capabilities & 0x40))) || ((mesh) && (mesh.flags) && (mesh.flags & 1))) {
- // Delete this node including network interface information and events
- db.Remove(obj.dbNodeKey); // Remove node with that id
- db.Remove('if' + obj.dbNodeKey); // Remove interface information
- db.Remove('nt' + obj.dbNodeKey); // Remove notes
- db.Remove('lc' + obj.dbNodeKey); // Remove last connect time
- db.Remove('si' + obj.dbNodeKey); // Remove system information
- db.Remove('al' + obj.dbNodeKey); // Remove error log last time
- if (db.RemoveSMBIOS) { db.RemoveSMBIOS(obj.dbNodeKey); } // Remove SMBios data
- db.RemoveAllNodeEvents(obj.dbNodeKey); // Remove all events for this node
- db.removeAllPowerEventsForNode(obj.dbNodeKey); // Remove all power events for this node
- // Event node deletion
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
- // Disconnect all connections if needed
- const state = parent.parent.GetConnectivityState(obj.dbNodeKey);
- if ((state != null) && (state.connectivity != null)) {
- if ((state.connectivity & 1) != 0) { parent.wsagents[obj.dbNodeKey].close(); } // Disconnect mesh agent
- if ((state.connectivity & 2) != 0) { parent.parent.mpsserver.closeAllForNode(obj.dbNodeKey); } // Disconnect CIRA connection
- }
- }
- // Set this agent as no longer authenticated
- obj.authenticated = -1;
- // If we where updating the agent using native method, clean that up.
- if (obj.agentUpdate != null) {
- if (obj.agentUpdate.fd) { try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { } }
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- }
- // If we where updating the agent meshcore method, clean that up.
- if (obj.agentCoreUpdateTaskId != null) {
- parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId);
- delete obj.agentCoreUpdateTaskId;
- }
- // Perform timer cleanup
- if (obj.pingtimer) { clearInterval(obj.pingtimer); delete obj.pingtimer; }
- if (obj.pongtimer) { clearInterval(obj.pongtimer); delete obj.pongtimer; }
- // Perform aggressive cleanup
- delete obj.name;
- delete obj.nonce;
- delete obj.nodeid;
- delete obj.unauth;
- delete obj.remoteaddr;
- delete obj.remoteaddrport;
- delete obj.meshid;
- delete obj.connectTime;
- delete obj.agentInfo;
- delete obj.agentExeInfo;
- ws.removeAllListeners(['message', 'close', 'error']);
- };
- // When data is received from the mesh agent web socket
- ws.on('message', function (msg) {
- dataAccounting();
- if (msg.length < 2) return;
- if (typeof msg == 'object') { msg = msg.toString('binary'); } // TODO: Could change this entire method to use Buffer instead of binary string
- if (obj.authenticated == 2) { // We are authenticated
- if ((obj.agentUpdate == null) && (msg.charCodeAt(0) == 123)) { processAgentData(msg); } // Only process JSON messages if meshagent update is not in progress
- if (msg.length < 2) return;
- const cmdid = common.ReadShort(msg, 0);
- if (cmdid == 11) { // MeshCommand_CoreModuleHash
- if (msg.length == 4) { ChangeAgentCoreInfo({ 'caps': 0 }); } // If the agent indicated that no core is running, clear the core information string.
- // Mesh core hash, sent by agent with the hash of the current mesh core.
- // If we are performing an agent update, don't update the core.
- if (obj.agentUpdate != null) { return; }
- // If we are using a custom core, don't try to update it.
- if (obj.agentCoreCheck == 1000) {
- obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
- agentCoreIsStable();
- return;
- }
- // Get the current meshcore hash
- const agentMeshCoreHash = (msg.length == 52) ? msg.substring(4, 52) : null;
- // If the agent indicates this is a custom core, we are done.
- if ((agentMeshCoreHash != null) && (agentMeshCoreHash == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')) {
- obj.agentCoreCheck = 0;
- obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
- agentCoreIsStable();
- return;
- }
- // We need to check if the core is current. Figure out what core we need.
- var corename = null;
- if ((obj.agentInfo != null) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null)) {
- if ((obj.agentCoreCheck == 1001) || (obj.agentCoreUpdate == true)) {
- // If the user asked, use the recovery core.
- corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].rcore;
- } else if (obj.agentCoreCheck == 1011) {
- // If the user asked, use the tiny core.
- corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].tcore;
- } else if (obj.agentInfo.capabilities & 0x40) {
- // If this is a recovery agent, use the agent recovery core.
- corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].arcore;
- } else {
- // This is the normal core for this agent type.
- corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
- }
- }
- // If we have a core, use it.
- if (corename != null) {
- const meshcorehash = parent.parent.defaultMeshCoresHash[corename];
- if (agentMeshCoreHash != meshcorehash) {
- if ((obj.agentCoreCheck < 5) || (obj.agentCoreCheck == 1001) || (obj.agentCoreCheck == 1011) || (obj.agentCoreUpdate == true)) {
- if (meshcorehash == null) {
- // Clear the core
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // MeshCommand_CoreModule, ask mesh agent to clear the core
- parent.agentStats.clearingCoreCount++;
- parent.parent.debug('agent', "Clearing core");
- } else {
- // Setup task limiter options, this system limits how many tasks can run at the same time to spread the server load.
- var taskLimiterOptions = { hash: meshcorehash, core: parent.parent.defaultMeshCores[corename], name: corename };
- // If the agent supports compression, sent the core compressed.
- if ((obj.agentInfo.capabilities & 0x100) && (parent.parent.defaultMeshCoresDeflate[corename])) {
- args.core = parent.parent.defaultMeshCoresDeflate[corename];
- }
- // Update new core with task limiting so not to flood the server. This is a high priority task.
- obj.agentCoreUpdatePending = true;
- parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
- if (obj.authenticated == 2) {
- // Send the updated core.
- delete obj.agentCoreUpdatePending;
- 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
- parent.agentStats.updatingCoreCount++;
- parent.parent.debug('agent', "Updating core " + argument.name);
- } else {
- // This agent is probably disconnected, nothing to do.
- parent.parent.taskLimiter.completed(taskid);
- }
- }, taskLimiterOptions, 0);
- }
- obj.agentCoreCheck++;
- }
- } else {
- obj.agentCoreCheck = 0;
- obj.sendBinary(common.ShortToStr(16) + common.ShortToStr(0)); // MeshCommand_CoreOk. Indicates to the agent that the core is ok. Start it if it's not already started.
- agentCoreIsStable(); // No updates needed, agent is ready to go.
- }
- }
- /*
- // TODO: Check if we have a mesh specific core. If so, use that.
- var agentMeshCoreHash = null;
- if (msg.length == 52) { agentMeshCoreHash = msg.substring(4, 52); }
- if ((agentMeshCoreHash != parent.parent.defaultMeshCoreHash) && (agentMeshCoreHash != parent.parent.defaultMeshCoreNoMeiHash)) {
- if (obj.agentCoreCheck < 5) { // This check is in place to avoid a looping core update.
- if (parent.parent.defaultMeshCoreHash == null) {
- // Update no core
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // Command 10, ask mesh agent to clear the core
- } else {
- // Update new core
- if ((parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].amt == true)) {
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + parent.parent.defaultMeshCoreHash + parent.parent.defaultMeshCore); // Command 10, ask mesh agent to set the core (with MEI support)
- } else {
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + parent.parent.defaultMeshCoreNoMeiHash + parent.parent.defaultMeshCoreNoMei); // Command 10, ask mesh agent to set the core (No MEI)
- }
- }
- obj.agentCoreCheck++;
- }
- } else {
- obj.agentCoreCheck = 0;
- }
- */
- }
- else if (cmdid == 12) { // MeshCommand_AgentHash
- if ((msg.length == 52) && (obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
- const agenthash = msg.substring(4);
- const agentUpdateMethod = compareAgentBinaryHash(obj.agentExeInfo, agenthash);
- if (agentUpdateMethod === 2) { // Use meshcore agent update system
- // Send the recovery core to the agent, if the agent is capable of running one
- if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) {
- parent.agentStats.agentMeshCoreBinaryUpdate++;
- obj.agentCoreUpdate = true;
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0)); // Ask to clear the core
- obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Ask for meshcore hash
- }
- } else if (agentUpdateMethod === 1) { // Use native agent update system
- // Mesh agent update required, do it using task limiter so not to flood the network. Medium priority task.
- parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) {
- if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; } // If agent disconnection, complete and exit now.
- if (obj.nodeid != null) { parent.parent.debug('agent', "Agent update required, NodeID=0x" + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); }
- parent.agentStats.agentBinaryUpdate++;
- if ((obj.agentExeInfo.data == null) && (((obj.agentInfo.capabilities & 0x100) == 0) || (obj.agentExeInfo.zdata == null))) {
- // Read the agent from disk
- parent.fs.open(obj.agentExeInfo.path, 'r', function (err, fd) {
- if (obj.agentExeInfo == null) return; // Agent disconnected during this call.
- if (err) { parent.parent.debug('agentupdate', "ERROR: " + err); return console.error(err); }
- obj.agentUpdate = { ptr: 0, buf: Buffer.alloc(parent.parent.agentUpdateBlockSize + 4), fd: fd, taskid: taskid };
- // MeshCommand_CoreModule, ask mesh agent to clear the core.
- // The new core will only be sent after the agent updates.
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
- // We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result
- //console.log("Agent update file open.");
- obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0)); // Command 13, start mesh agent download
- // Send the first mesh agent update data block
- obj.agentUpdate.buf[0] = 0;
- obj.agentUpdate.buf[1] = 14;
- obj.agentUpdate.buf[2] = 0;
- obj.agentUpdate.buf[3] = 1;
- parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, parent.parent.agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) {
- if (obj.agentUpdate == null) return;
- if ((err != null) || (bytesRead == 0)) {
- // Error reading the agent file, stop here.
- try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- parent.parent.debug('agentupdate', "ERROR: Unable to read first block of agent binary from disk.");
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- } else {
- // Send the first block to the agent
- obj.agentUpdate.ptr += bytesRead;
- parent.parent.debug('agentupdate', "Sent first block of " + bytesRead + " bytes from disk.");
- obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block
- }
- });
- });
- } else {
- // Send the agent from RAM
- obj.agentUpdate = { ptr: 0, buf: Buffer.alloc(parent.parent.agentUpdateBlockSize + 4), taskid: taskid };
- // MeshCommand_CoreModule, ask mesh agent to clear the core.
- // The new core will only be sent after the agent updates.
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
- // We got the agent file open on the server side, tell the agent we are sending an update ending with the SHA384 hash of the result
- obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0)); // Command 13, start mesh agent download
- // Send the first mesh agent update data block
- obj.agentUpdate.buf[0] = 0;
- obj.agentUpdate.buf[1] = 14;
- obj.agentUpdate.buf[2] = 0;
- obj.agentUpdate.buf[3] = 1;
- // If agent supports compression, send the compressed agent if possible.
- if ((obj.agentInfo.capabilities & 0x100) && (obj.agentExeInfo.zdata != null)) {
- // Send compressed data
- obj.agentUpdate.agentUpdateData = obj.agentExeInfo.zdata;
- obj.agentUpdate.agentUpdateHash = obj.agentExeInfo.zhash;
- } else {
- // Send uncompressed data
- obj.agentUpdate.agentUpdateData = obj.agentExeInfo.data;
- obj.agentUpdate.agentUpdateHash = obj.agentExeInfo.hash;
- }
- const len = Math.min(parent.parent.agentUpdateBlockSize, obj.agentUpdate.agentUpdateData.length - obj.agentUpdate.ptr);
- if (len > 0) {
- // Send the first block
- obj.agentUpdate.agentUpdateData.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len);
- obj.agentUpdate.ptr += len;
- obj.sendBinary(obj.agentUpdate.buf); // Command 14, mesh agent first data block
- parent.parent.debug('agentupdate', "Sent first block of " + len + " bytes from RAM.");
- } else {
- // Error
- parent.parent.debug('agentupdate', "ERROR: Len of " + len + " is invalid.");
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- }
- }
- }, null, 1);
- } else {
- // Check the mesh core, if the agent is capable of running one
- if (((obj.agentInfo.capabilities & 16) != 0) && (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core != null)) {
- obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
- }
- }
- }
- }
- else if (cmdid == 14) { // MeshCommand_AgentBinaryBlock
- if ((msg.length == 4) && (obj.agentUpdate != null)) {
- const status = common.ReadShort(msg, 2);
- if (status == 1) {
- if (obj.agentExeInfo.data == null) {
- // Read the agent from disk
- parent.fs.read(obj.agentUpdate.fd, obj.agentUpdate.buf, 4, parent.parent.agentUpdateBlockSize, obj.agentUpdate.ptr, function (err, bytesRead, buffer) {
- if ((obj.agentExeInfo == null) || (obj.agentUpdate == null)) return; // Agent disconnected during this async call.
- if ((err != null) || (bytesRead < 0)) {
- // Error reading the agent file, stop here.
- parent.parent.debug('agentupdate', "ERROR: Unable to read agent #" + obj.agentExeInfo.id + " binary from disk.");
- try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- } else {
- // Send the next block to the agent
- parent.parent.debug('agentupdate', "Sending disk agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + bytesRead + ".");
- obj.agentUpdate.ptr += bytesRead;
- if (bytesRead == parent.parent.agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, bytesRead + 4)); } // Command 14, mesh agent next data block
- if ((bytesRead < parent.parent.agentUpdateBlockSize) || (obj.agentUpdate.ptr == obj.agentExeInfo.size)) {
- parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from disk, ptr=" + obj.agentUpdate.ptr + ".");
- obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentExeInfo.hash); // Command 13, end mesh agent download, send agent SHA384 hash
- try { parent.fs.close(obj.agentUpdate.fd); } catch (ex) { }
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- }
- }
- });
- } else {
- // Send the agent from RAM
- const len = Math.min(parent.parent.agentUpdateBlockSize, obj.agentUpdate.agentUpdateData.length - obj.agentUpdate.ptr);
- if (len > 0) {
- obj.agentUpdate.agentUpdateData.copy(obj.agentUpdate.buf, 4, obj.agentUpdate.ptr, obj.agentUpdate.ptr + len);
- if (len == parent.parent.agentUpdateBlockSize) { obj.sendBinary(obj.agentUpdate.buf); } else { obj.sendBinary(obj.agentUpdate.buf.slice(0, len + 4)); } // Command 14, mesh agent next data block
- parent.parent.debug('agentupdate', "Sending RAM agent #" + obj.agentExeInfo.id + " block, ptr=" + obj.agentUpdate.ptr + ", len=" + len + ".");
- obj.agentUpdate.ptr += len;
- }
- if (obj.agentUpdate.ptr == obj.agentUpdate.agentUpdateData.length) {
- parent.parent.debug('agentupdate', "Completed agent #" + obj.agentExeInfo.id + " update from RAM, ptr=" + obj.agentUpdate.ptr + ".");
- obj.sendBinary(common.ShortToStr(13) + common.ShortToStr(0) + obj.agentUpdate.agentUpdateHash); // Command 13, end mesh agent download, send agent SHA384 hash
- parent.parent.taskLimiter.completed(obj.agentUpdate.taskid); // Indicate this task complete
- delete obj.agentUpdate.buf;
- delete obj.agentUpdate;
- }
- }
- }
- }
- }
- else if (cmdid == 15) { // MeshCommand_AgentTag
- var tag = msg.substring(2);
- while (tag.charCodeAt(tag.length - 1) == 0) { tag = tag.substring(0, tag.length - 1); } // Remove end-of-line zeros.
- ChangeAgentTag(tag);
- }
- } else if (obj.authenticated < 2) { // We are not authenticated
- // Check if this is a un-authenticated JSON
- if (msg.charCodeAt(0) == 123) {
- var str = msg.toString('utf8'), command = null;
- if (str[0] == '{') {
- try { command = JSON.parse(str); } catch (ex) { } // If the command can't be parsed, ignore it.
- if ((command != null) && (command.action === 'agentName') && (typeof command.value == 'string') && (command.value.length > 0) && (command.value.length < 256)) { obj.agentName = command.value; }
- }
- return;
- }
- const cmd = common.ReadShort(msg, 0);
- if (cmd == 1) {
- // Agent authentication request
- if ((msg.length != 98) || ((obj.receivedCommands & 1) != 0)) return;
- obj.receivedCommands += 1; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
- if (isIgnoreHashCheck()) {
- // Send the agent web hash back to the agent
- // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
- obj.sendBinary(common.ShortToStr(1) + msg.substring(2, 50) + obj.nonce); // Command 1, hash + nonce. Use the web hash given by the agent.
- } else {
- // Check that the server hash matches our own web certificate hash (SHA384)
- obj.agentSeenCerthash = msg.substring(2, 50);
- if ((getWebCertHash(domain) != obj.agentSeenCerthash) && (getWebCertFullHash(domain) != obj.agentSeenCerthash) && (parent.defaultWebCertificateHash != obj.agentSeenCerthash) && (parent.defaultWebCertificateFullHash != obj.agentSeenCerthash)) {
- if (parent.parent.supportsProxyCertificatesRequest !== false) {
- obj.badWebCert = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64');
- parent.wsagentsWithBadWebCerts[obj.badWebCert] = obj; // Add this agent to the list of of agents with bad web certificates.
- parent.parent.updateProxyCertificates(false);
- }
- parent.agentStats.agentBadWebCertHashCount++;
- parent.setAgentIssue(obj, "BadWebCertHash: " + Buffer.from(msg.substring(2, 50), 'binary').toString('hex'));
- 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 + ').');
- parent.parent.debug('agent', 'Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
- 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 + ').');
- console.log('Agent reported web cert hash:' + (Buffer.from(msg.substring(2, 50), 'binary').toString('hex')) + '.');
- delete obj.agentSeenCerthash;
- return;
- } else {
- // The hash matched one of the acceptable values, send the agent web hash back to the agent
- // Send 384 bits SHA384 hash of TLS cert + 384 bits nonce
- // Command 1, hash + nonce. Use the web hash given by the agent.
- obj.sendBinary(common.ShortToStr(1) + obj.agentSeenCerthash + obj.nonce);
- }
- }
- // Use our server private key to sign the ServerHash + AgentNonce + ServerNonce
- obj.agentnonce = msg.substring(50, 98);
- // Check if we got the agent auth confirmation
- if ((obj.receivedCommands & 8) == 0) {
- // If we did not get an indication that the agent already validated this server, send the server signature.
- if (obj.useSwarmCert == true) {
- // Perform the hash signature using older swarm server certificate
- parent.parent.certificateOperations.acceleratorPerformSignature(1, msg.substring(2) + obj.nonce, null, function (tag, signature) {
- // Send back our certificate + signature
- obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.swarmCertificateAsn1.length) + parent.swarmCertificateAsn1 + signature); // Command 2, certificate + signature
- });
- } else {
- // Perform the hash signature using the server agent certificate
- parent.parent.certificateOperations.acceleratorPerformSignature(0, msg.substring(2) + obj.nonce, null, function (tag, signature) {
- // Send back our certificate + signature
- obj.sendBinary(common.ShortToStr(2) + common.ShortToStr(parent.agentCertificateAsn1.length) + parent.agentCertificateAsn1 + signature); // Command 2, certificate + signature
- });
- }
- }
- // Check the agent signature if we can
- if (obj.unauthsign != null) {
- if (processAgentSignature(obj.unauthsign) == false) {
- parent.agentStats.agentBadSignature1Count++;
- parent.setAgentIssue(obj, "BadSignature1");
- parent.parent.debug('agent', 'Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').');
- console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
- } else { completeAgentConnection(); }
- }
- }
- else if (cmd == 2) {
- // Agent certificate
- if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) return;
- obj.receivedCommands += 2; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
- // Decode the certificate
- const certlen = common.ReadShort(msg, 2);
- obj.unauth = {};
- try { obj.unauth.nodeid = Buffer.from(forge.pki.getPublicKeyFingerprint(forge.pki.certificateFromAsn1(forge.asn1.fromDer(msg.substring(4, 4 + certlen))).publicKey, { md: forge.md.sha384.create() }).data, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); } catch (ex) { console.log(ex); parent.parent.debug('agent', ex); return; }
- obj.unauth.nodeCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----';
- // Check the agent signature if we can
- if (obj.agentnonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else {
- if (processAgentSignature(msg.substring(4 + certlen)) == false) {
- parent.agentStats.agentBadSignature2Count++;
- parent.setAgentIssue(obj, "BadSignature2");
- parent.parent.debug('agent', 'Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').');
- console.log('Agent connected with bad signature, holding connection (' + obj.remoteaddrport + ').'); return;
- }
- }
- completeAgentConnection();
- }
- else if (cmd == 3) {
- // Agent meshid
- if ((msg.length < 70) || ((obj.receivedCommands & 4) != 0)) return;
- obj.receivedCommands += 4; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
- // Set the meshid
- obj.agentInfo = {};
- obj.agentInfo.infoVersion = common.ReadInt(msg, 2);
- obj.agentInfo.agentId = common.ReadInt(msg, 6);
- obj.agentInfo.agentVersion = common.ReadInt(msg, 10);
- obj.agentInfo.platformType = common.ReadInt(msg, 14);
- if (obj.agentInfo.platformType > 8 || obj.agentInfo.platformType < 1) { obj.agentInfo.platformType = 1; }
- if (msg.substring(50, 66) == '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0') {
- obj.meshid = Buffer.from(msg.substring(18, 50), 'binary').toString('hex'); // Older HEX MeshID
- } else {
- obj.meshid = Buffer.from(msg.substring(18, 66), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); // New Base64 MeshID
- }
- //console.log('MeshID', obj.meshid);
- obj.agentInfo.capabilities = common.ReadInt(msg, 66);
- if (msg.length > 70) {
- const computerNameLen = common.ReadShort(msg, 70);
- obj.agentInfo.computerName = Buffer.from(msg.substring(72, 72 + computerNameLen), 'binary').toString('utf8');
- //console.log('computerName', msg.length, computerNameLen, obj.agentInfo.computerName);
- } else {
- obj.agentInfo.computerName = '';
- //console.log('computerName-none');
- }
- obj.dbMeshKey = 'mesh/' + domain.id + '/' + obj.meshid;
- completeAgentConnection();
- } else if (cmd == 4) {
- if ((msg.length < 2) || ((obj.receivedCommands & 8) != 0)) return;
- obj.receivedCommands += 8; // Agent can't send the same command twice on the same connection ever. Block DOS attack path.
- // Agent already authenticated the server, wants to skip the server signature - which is great for server performance.
- } else if (cmd == 5) {
- // ServerID. Agent is telling us what serverid it expects. Useful if we have many server certificates.
- if ((msg.substring(2, 34) == parent.swarmCertificateHash256) || (msg.substring(2, 50) == parent.swarmCertificateHash384)) { obj.useSwarmCert = true; }
- } else if (cmd == 30) {
- // Agent Commit Date. This is future proofing. Can be used to change server behavior depending on the date range of the agent.
- try { obj.AgentCommitDate = Date.parse(msg.substring(2)) } catch (ex) { }
- //console.log('Connected Agent Commit Date: ' + msg.substring(2) + ", " + Date.parse(msg.substring(2)));
- }
- }
- });
- // If error, do nothing
- ws.on('error', function (err) { parent.parent.debug('agent', 'AGENT WSERR: ' + err); console.log('AGENT WSERR: ' + err); obj.close(0); });
- // If the mesh agent web socket is closed, clean up.
- ws.on('close', function (req) {
- parent.agentStats.agentClose++;
- if (obj.nodeid != null) {
- const agentId = (obj.agentInfo && obj.agentInfo.agentId) ? obj.agentInfo.agentId : 'Unknown';
- //console.log('Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
- parent.parent.debug('agent', 'Agent disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ') id=' + agentId);
- // Log the agent disconnection if we are not testing agent update
- if (args.agentupdatetest == null) {
- if (parent.wsagentsDisconnections[obj.nodeid] == null) {
- parent.wsagentsDisconnections[obj.nodeid] = 1;
- } else {
- parent.wsagentsDisconnections[obj.nodeid] = ++parent.wsagentsDisconnections[obj.nodeid];
- }
- }
- }
- obj.close(0);
- });
- // Return the mesh for this device, in some cases, we may auto-create the mesh.
- function getMeshAutoCreate() {
- var mesh = parent.meshes[obj.dbMeshKey];
- // If the mesh was not found and we are in LAN mode, check of the domain can be corrected
- if ((args.lanonly == true) && (mesh == null)) {
- var smesh = obj.dbMeshKey.split('/');
- for (var i in parent.parent.config.domains) {
- mesh = parent.meshes['mesh/' + i + '/' + smesh[2]];
- if (mesh != null) {
- obj.domain = domain = parent.parent.config.domains[i];
- obj.meshid = smesh[2];
- obj.dbMeshKey = 'mesh/' + i + '/' + smesh[2];
- obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
- break;
- }
- }
- }
- if ((mesh == null) && (typeof domain.orphanagentuser == 'string')) {
- const adminUser = parent.users['user/' + domain.id + '/' + domain.orphanagentuser];
- if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
- // Mesh name is hex instead of base64
- const meshname = obj.meshid.substring(0, 18);
- // Create a new mesh for this device
- const links = {};
- links[adminUser._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
- mesh = { type: 'mesh', _id: obj.dbMeshKey, name: meshname, mtype: 2, desc: '', domain: domain.id, links: links };
- db.Set(mesh);
- parent.meshes[obj.dbMeshKey] = mesh;
- if (adminUser.links == null) adminUser.links = {};
- adminUser.links[obj.dbMeshKey] = { rights: 0xFFFFFFFF };
- db.SetUser(adminUser);
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [adminUser._id, obj.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 });
- }
- } else {
- if ((mesh != null) && (mesh.deleted != null) && (mesh.links)) {
- // Must un-delete this mesh
- var ids = parent.CreateMeshDispatchTargets(mesh._id, [obj.dbNodeKey]);
- // See if users still exists, if so, add links to the mesh
- for (var userid in mesh.links) {
- const user = parent.users[userid];
- if (user) {
- if (user.links == null) { user.links = {}; }
- if (user.links[mesh._id] == null) {
- user.links[mesh._id] = { rights: mesh.links[userid].rights };
- ids.push(user._id);
- db.SetUser(user);
- }
- }
- }
- // Send out an event indicating this mesh was "created"
- parent.parent.DispatchEvent(ids, obj, { etype: 'mesh', meshid: mesh._id, name: mesh.name, mtype: mesh.mtype, desc: mesh.desc, action: 'createmesh', links: mesh.links, msgid: 56, msgArgs: [mesh._id], msg: "Device group undeleted: " + mesh._id, domain: domain.id });
- // Mark the mesh as active
- delete mesh.deleted;
- db.Set(mesh);
- }
- }
- return mesh;
- }
- // Send a PING/PONG message
- function sendPing() { obj.send('{"action":"ping"}'); }
- function sendPong() { obj.send('{"action":"pong"}'); }
- // Once we get all the information about an agent, run this to hook everything up to the server
- function completeAgentConnection() {
- if ((obj.authenticated != 1) || (obj.meshid == null) || obj.pendingCompleteAgentConnection || (obj.agentInfo == null)) { return; }
- obj.pendingCompleteAgentConnection = true;
- // Setup the agent PING/PONG timers
- if ((typeof args.agentping == 'number') && (obj.pingtimer == null)) { obj.pingtimer = setInterval(sendPing, args.agentping * 1000); }
- else if ((typeof args.agentpong == 'number') && (obj.pongtimer == null)) { obj.pongtimer = setInterval(sendPong, args.agentpong * 1000); }
- // If this is a recovery agent
- if (obj.agentInfo.capabilities & 0x40) {
- // Inform mesh agent that it's authenticated.
- delete obj.pendingCompleteAgentConnection;
- obj.authenticated = 2;
- obj.sendBinary(common.ShortToStr(4));
- // Ask for mesh core hash.
- obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0));
- return;
- }
- // Check if we have too many agent sessions
- if (typeof domain.limits.maxagentsessions == 'number') {
- // Count the number of agent sessions for this domain
- var domainAgentSessionCount = 0;
- for (var i in parent.wsagents) { if (parent.wsagents[i].domain.id == domain.id) { domainAgentSessionCount++; } }
- // Check if we have too many user sessions
- if (domainAgentSessionCount >= domain.limits.maxagentsessions) {
- // Too many, hold the connection.
- parent.agentStats.agentMaxSessionHoldCount++;
- return;
- }
- }
- /*
- // Check that the mesh exists
- var mesh = parent.meshes[obj.dbMeshKey];
- if (mesh == null) {
- var holdConnection = true;
- if (typeof domain.orphanagentuser == 'string') {
- var adminUser = parent.users['user/' + domain.id + '/' + args.orphanagentuser];
- if ((adminUser != null) && (adminUser.siteadmin == 0xFFFFFFFF)) {
- // Create a new mesh for this device
- holdConnection = false;
- var links = {};
- links[user._id] = { name: adminUser.name, rights: 0xFFFFFFFF };
- mesh = { type: 'mesh', _id: obj.dbMeshKey, name: obj.meshid, mtype: 2, desc: '', domain: domain.id, links: links };
- db.Set(mesh);
- parent.meshes[obj.meshid] = mesh;
- parent.parent.AddEventDispatch(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), ws);
- if (adminUser.links == null) user.links = {};
- adminUser.links[obj.meshid] = { rights: 0xFFFFFFFF };
- //adminUser.subscriptions = parent.subscribe(adminUser._id, ws);
- db.SetUser(user);
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(meshid, [user._id, obj.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 });
- }
- }
- if (holdConnection == true) {
- // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
- parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- return;
- }
- }
- 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.
- parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- return;
- }
- */
- // Check that the node exists
- db.Get(obj.dbNodeKey, function (err, nodes) {
- if (obj.agentInfo == null) { return; }
- var device, mesh;
- // See if this node exists in the database
- if ((nodes == null) || (nodes.length == 0)) {
- // This device does not exist, use the meshid given by the device
- // Check if we already have too many devices for this domain
- if (domain.limits && (typeof domain.limits.maxdevices == 'number')) {
- db.isMaxType(domain.limits.maxdevices, 'node', domain.id, function (ismax, count) {
- if (ismax == true) {
- // Too many devices in this domain.
- parent.agentStats.maxDomainDevicesReached++;
- } else {
- // We are under the limit, create the new device.
- completeAgentConnection2();
- }
- });
- } else {
- completeAgentConnection2();
- }
- return;
- } else {
- device = nodes[0];
- obj.name = device.name;
- // This device exists, meshid given by the device must be ignored, use the server side one.
- if ((device.meshid != null) && (device.meshid != obj.dbMeshKey)) {
- obj.dbMeshKey = device.meshid;
- obj.meshid = device.meshid.split('/')[2];
- }
- // See if this mesh exists, if it does not we may want to create it.
- mesh = getMeshAutoCreate();
- // Check if the mesh exists
- if (mesh == null) {
- // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
- parent.agentStats.invalidDomainMesh2Count++;
- parent.setAgentIssue(obj, "invalidDomainMesh2");
- parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- return;
- }
- // Check if the mesh is the right type
- if (mesh.mtype != 2) {
- // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
- parent.agentStats.invalidMeshType2Count++;
- parent.setAgentIssue(obj, "invalidMeshType2");
- parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- return;
- }
- // Mark when this device connected
- obj.connectTime = Date.now();
- // Device already exists, look if changes have occured
- var changes = [], change = 0, log = 0;
- if (device.agent == null) { device.agent = { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }; change = 1; }
- if (device.rname != obj.agentInfo.computerName) { device.rname = obj.agentInfo.computerName; change = 1; changes.push('computer name'); }
- if (device.agent.ver != obj.agentInfo.agentVersion) { device.agent.ver = obj.agentInfo.agentVersion; change = 1; changes.push('agent version'); }
- if (device.agent.id != obj.agentInfo.agentId) { device.agent.id = obj.agentInfo.agentId; change = 1; changes.push('agent type'); }
- if ((device.agent.caps & 24) != (obj.agentInfo.capabilities & 24)) { device.agent.caps = obj.agentInfo.capabilities; change = 1; changes.push('agent capabilities'); } // If agent console or javascript support changes, update capabilities
- if (mesh.flags && (mesh.flags & 2) && (device.name != obj.agentInfo.computerName)) { device.name = obj.agentInfo.computerName; change = 1; } // We want the server name to be sync'ed to the hostname
- if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
- if (change == 1) {
- // Do some clean up if needed, these values should not be in the database.
- if (device.conn != null) { delete device.conn; }
- if (device.pwr != null) { delete device.pwr; }
- if (device.agct != null) { delete device.agct; }
- if (device.cict != null) { delete device.cict; }
- // Save the updated device in the database
- db.Set(device);
- // If this is a temporary device, don't log changes
- if (obj.agentInfo.capabilities & 0x20) { log = 0; }
- // Event the node change
- var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
- if (log == 0) { event.nolog = 1; } else { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
- }
- }
- completeAgentConnection3(device, mesh);
- });
- }
- function completeAgentConnection2() {
- // See if this mesh exists, if it does not we may want to create it.
- var mesh = getMeshAutoCreate();
- // Check if the mesh exists
- if (mesh == null) {
- // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
- parent.agentStats.invalidDomainMeshCount++;
- parent.setAgentIssue(obj, "invalidDomainMesh");
- parent.parent.debug('agent', 'Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- console.log('Agent connected with invalid domain/mesh, holding connection (' + obj.remoteaddrport + ', ' + obj.dbMeshKey + ').');
- return;
- }
- // Check if the mesh is the right type
- if (mesh.mtype != 2) {
- // If we disconnect, the agent will just reconnect. We need to log this or tell agent to connect in a few hours.
- parent.agentStats.invalidMeshTypeCount++;
- parent.setAgentIssue(obj, "invalidMeshType");
- parent.parent.debug('agent', 'Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- console.log('Agent connected with invalid mesh type, holding connection (' + obj.remoteaddrport + ').');
- return;
- }
- // Mark when this device connected
- obj.connectTime = Date.now();
- // This node does not exist, create it.
- var agentName = obj.agentName ? obj.agentName : obj.agentInfo.computerName;
- var device = { type: 'node', mtype: mesh.mtype, _id: obj.dbNodeKey, icon: obj.agentInfo.platformType, meshid: obj.dbMeshKey, name: agentName, rname: obj.agentInfo.computerName, domain: domain.id, agent: { ver: obj.agentInfo.agentVersion, id: obj.agentInfo.agentId, caps: obj.agentInfo.capabilities }, host: null };
- db.Set(device);
- // Event the new node
- if (obj.agentInfo.capabilities & 0x20) {
- // This is a temporary agent, don't log.
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, { etype: 'node', action: 'addnode', node: device, domain: domain.id, nolog: 1 });
- } else {
- 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 });
- }
- completeAgentConnection3(device, mesh);
- }
- function completeAgentConnection3(device, mesh) {
- // Check if this agent is already connected
- const dupAgent = parent.wsagents[obj.dbNodeKey];
- parent.wsagents[obj.dbNodeKey] = obj;
- if (dupAgent) {
- // Record duplicate agents
- if (parent.duplicateAgentsLog[obj.dbNodeKey] == null) {
- if (dupAgent.remoteaddr == obj.remoteaddr) {
- parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr], count: 1 };
- } else {
- parent.duplicateAgentsLog[obj.dbNodeKey] = { name: device.name, group: mesh.name, ip: [obj.remoteaddr, dupAgent.remoteaddr], count: 1 };
- }
- } else {
- parent.duplicateAgentsLog[obj.dbNodeKey].name = device.name;
- parent.duplicateAgentsLog[obj.dbNodeKey].group = mesh.name;
- parent.duplicateAgentsLog[obj.dbNodeKey].count++;
- if (parent.duplicateAgentsLog[obj.dbNodeKey].ip.indexOf(obj.remoteaddr) == -1) { parent.duplicateAgentsLog[obj.dbNodeKey].ip.push(obj.remoteaddr); }
- }
- // Close the duplicate agent
- parent.agentStats.duplicateAgentCount++;
- parent.setAgentIssue(obj, 'duplicateAgent');
- if (obj.nodeid != null) { parent.parent.debug('agent', 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); }
- dupAgent.close(3);
- } else {
- // Indicate the agent is connected
- parent.parent.SetConnectivityState(obj.dbMeshKey, obj.dbNodeKey, obj.connectTime, 1, 1, null, { remoteaddrport: obj.remoteaddrport, name: device.name });
- }
- // We are done, ready to communicate with this agent
- delete obj.pendingCompleteAgentConnection;
- obj.authenticated = 2;
- // Check how many times this agent disconnected in the last few minutes.
- const disconnectCount = parent.wsagentsDisconnections[obj.nodeid];
- if (disconnectCount > 6) {
- parent.parent.debug('agent', 'Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
- console.log('Agent in big trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
- parent.agentStats.agentInBigTrouble++;
- // TODO: Log or do something to recover?
- return;
- }
- // Command 4, inform mesh agent that it's authenticated.
- obj.sendBinary(common.ShortToStr(4));
- // Not sure why, but in rare cases, obj.agentInfo is undefined here.
- if ((obj.agentInfo == null) || (typeof obj.agentInfo.capabilities != 'number')) { return; } // This is an odd case.
- obj.agentExeInfo = parent.parent.meshAgentBinaries[obj.agentInfo.agentId];
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[obj.agentInfo.agentId]) { obj.agentExeInfo = domain.meshAgentBinaries[obj.agentInfo.agentId]; }
- // Check if this agent is reconnecting too often.
- if (disconnectCount > 4) {
- // Too many disconnections, this agent has issues. Just clear the core.
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
- parent.parent.debug('agent', 'Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
- parent.agentStats.agentInTrouble++;
- //console.log('Agent in trouble: NodeId=' + obj.nodeid + ', IP=' + obj.remoteaddrport + ', Agent=' + obj.agentInfo.agentId + '.');
- // TODO: Log or do something to recover?
- return;
- }
- // Check if we need to make an native update check
- var corename = null;
- if (parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId] != null) {
- corename = parent.parent.meshAgentsArchitectureNumbers[obj.agentInfo.agentId].core;
- } else {
- // MeshCommand_CoreModule, ask mesh agent to clear the core
- obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0));
- }
- if ((obj.agentExeInfo != null) && (obj.agentExeInfo.update == true)) {
- // Ask the agent for it's executable binary hash
- obj.sendBinary(common.ShortToStr(12) + common.ShortToStr(0));
- } else {
- // Check the mesh core, if the agent is capable of running one
- if (((obj.agentInfo.capabilities & 16) != 0) && (corename != null)) {
- obj.sendBinary(common.ShortToStr(11) + common.ShortToStr(0)); // Command 11, ask for mesh core hash.
- } else {
- agentCoreIsStable(); // No updates needed, agent is ready to go.
- }
- }
- }
- // Indicate to the agent that we want to check Intel AMT configuration
- // This may trigger a CIRA-LMS tunnel from the agent so the server can inspect the device.
- obj.sendUpdatedIntelAmtPolicy = function (policy) {
- if (obj.agentExeInfo && (obj.agentExeInfo.amt == true)) { // Only send Intel AMT policy to agents what could have AMT.
- if (policy == null) { var mesh = parent.meshes[obj.dbMeshKey]; if (mesh == null) return; policy = mesh.amt; }
- if ((policy != null) && (policy.type != 0)) {
- const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey);
- try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { }
- }
- }
- }
- function recoveryAgentCoreIsStable(mesh) {
- parent.agentStats.recoveryCoreIsStableCount++;
- // Recovery agent is doing ok, lets perform main agent checking.
- //console.log('recoveryAgentCoreIsStable()');
- // Fetch the the real agent nodeid
- db.Get('da' + obj.dbNodeKey, function (err, nodes, self) {
- if ((nodes != null) && (nodes.length == 1)) {
- self.realNodeKey = nodes[0].raid;
- // Get agent connection state
- var agentConnected = false;
- var state = parent.parent.GetConnectivityState(self.realNodeKey);
- if (state) { agentConnected = ((state.connectivity & 1) != 0) }
- self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: self.realNodeKey, agent: agentConnected } }));
- } else {
- self.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
- }
- }, obj);
- }
- function agentCoreIsStable() {
- parent.agentStats.coreIsStableCount++;
- // Check that the mesh exists
- const mesh = parent.meshes[obj.dbMeshKey];
- if (mesh == null) {
- parent.agentStats.meshDoesNotExistCount++;
- parent.setAgentIssue(obj, "meshDoesNotExist");
- // TODO: Mark this agent as part of a mesh that does not exists.
- return; // Probably not worth doing anything else. Hold this agent.
- }
- // Check if this is a recovery agent
- if (obj.agentInfo.capabilities & 0x40) {
- recoveryAgentCoreIsStable(mesh);
- return;
- }
- // Fetch the the diagnostic agent nodeid
- db.Get('ra' + obj.dbNodeKey, function (err, nodes) {
- if ((nodes != null) && (nodes.length == 1)) {
- obj.diagnosticNodeKey = nodes[0].daid;
- obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.diagnosticNodeKey } }));
- }
- });
- // Indicate that we want to check the Intel AMT configuration
- // This may trigger a CIRA-LMS tunnel to the server for further processing
- obj.sendUpdatedIntelAmtPolicy();
- // Fetch system information
- db.GetHash('si' + obj.dbNodeKey, function (err, results) {
- if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
- });
- // Agent error log dump
- if (parent.parent.agentErrorLog != null) {
- db.Get('al' + obj.dbNodeKey, function (err, docs) { // Agent Log
- if ((docs != null) && (docs.length == 1) && (typeof docs[0].lastEvent)) {
- obj.send('{"action":"errorlog","startTime":' + docs[0].lastEvent + '}'); // Ask all events after a given time
- } else {
- obj.send('{"action":"errorlog"}'); // Ask all
- }
- });
- }
- // Set agent core dump
- if ((parent.parent.config.settings != null) && ((parent.parent.config.settings.agentcoredump === true) || (parent.parent.config.settings.agentcoredump === false))) {
- obj.send(JSON.stringify({ action: 'coredump', value: parent.parent.config.settings.agentcoredump }));
- if (parent.parent.config.settings.agentcoredump === true) {
- // Check if we requested a core dump file in the last minute, if not, ask if one is present.
- if ((parent.lastCoreDumpRequest == null) || ((Date.now() - parent.lastCoreDumpRequest) >= 60000)) { obj.send(JSON.stringify({ action: 'getcoredump' })); }
- }
- }
- // Do this if IP location is enabled on this domain TODO: Set IP location per device group?
- if (domain.iplocation == true) {
- // Check if we already have IP location information for this node
- db.Get('iploc_' + obj.remoteaddr, function (err, iplocs) {
- if ((iplocs != null) && (iplocs.length == 1)) {
- // We have a location in the database for this remote IP
- const iploc = iplocs[0], x = {};
- if ((iploc != null) && (iploc.ip != null) && (iploc.loc != null)) {
- x.publicip = iploc.ip;
- x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000));
- ChangeAgentLocationInfo(x);
- }
- } else {
- // Check if we need to ask for the IP location
- var doIpLocation = 0;
- if (obj.iploc == null) {
- doIpLocation = 1;
- } else {
- const loc = obj.iploc.split(',');
- if (loc.length < 3) {
- doIpLocation = 2;
- } else {
- var t = new Date((parseFloat(loc[2]) * 1000)), now = Date.now();
- t.setDate(t.getDate() + 20);
- if (t < now) { doIpLocation = 3; }
- }
- }
- // If we need to ask for IP location, see if we have the quota to do it.
- if (doIpLocation > 0) {
- db.getValueOfTheDay('ipLocationRequestLimitor', 10, function (ipLocationLimitor) {
- if ((ipLocationLimitor != null) && (ipLocationLimitor.value > 0)) {
- ipLocationLimitor.value--;
- db.Set(ipLocationLimitor);
- obj.send(JSON.stringify({ action: 'iplocation' }));
- }
- });
- }
- }
- });
- }
- // Indicate server information to the agent.
- var serverInfo = { action: 'serverInfo' };
- if ((typeof domain.terminal == 'object') && (typeof domain.terminal.launchcommand == 'object')) {
- // Send terminal starting command
- serverInfo.termlaunchcommand = {};
- if (typeof domain.terminal.launchcommand.linux == 'string') { serverInfo.termlaunchcommand.linux = domain.terminal.launchcommand.linux; }
- if (typeof domain.terminal.launchcommand.darwin == 'string') { serverInfo.termlaunchcommand.darwin = domain.terminal.launchcommand.darwin; }
- if (typeof domain.terminal.launchcommand.freebsd == 'string') { serverInfo.termlaunchcommand.freebsd = domain.terminal.launchcommand.freebsd; }
- }
- // Enable agent self guest sharing if allowed
- if (domain.agentselfguestsharing) { serverInfo.agentSelfGuestSharing = true; }
- obj.send(JSON.stringify(serverInfo));
- // Plug in handler
- if (parent.parent.pluginHandler != null) {
- parent.parent.pluginHandler.callHook('hook_agentCoreIsStable', obj, parent);
- }
- }
- // Get the web certificate private key hash for the specified domain
- function getWebCertHash(domain) {
- const hash = parent.webCertificateHashs[domain.id];
- if (hash != null) return hash;
- return parent.webCertificateHash;
- }
- // Get the web certificate hash for the specified domain
- function getWebCertFullHash(domain) {
- const hash = parent.webCertificateFullHashs[domain.id];
- if (hash != null) return hash;
- return parent.webCertificateFullHash;
- }
- // Verify the agent signature
- function processAgentSignature(msg) {
- if (isIgnoreHashCheck() == false) {
- var verified = false;
- // This agent did not report a valid TLS certificate hash, fail now.
- if (obj.agentSeenCerthash == null) return false;
- // Raw RSA signatures have an exact length of 256 or 384. PKCS7 is larger.
- if ((msg.length != 384) && (msg.length != 256)) {
- // Verify a PKCS7 signature.
- var msgDer = null;
- try { msgDer = forge.asn1.fromDer(forge.util.createBuffer(msg, 'binary')); } catch (ex) { }
- if (msgDer != null) {
- try {
- const p7 = forge.pkcs7.messageFromAsn1(msgDer);
- const sig = p7.rawCapture.signature;
- // Verify with key hash
- var buf = Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary');
- var verifier = parent.crypto.createVerify('RSA-SHA384');
- verifier.update(buf);
- verified = verifier.verify(obj.unauth.nodeCertPem, sig, 'binary');
- if (verified !== true) {
- // Not a valid signature
- parent.agentStats.invalidPkcsSignatureCount++;
- parent.setAgentIssue(obj, "invalidPkcsSignature");
- return false;
- }
- } catch (ex) { };
- }
- }
- if (verified == false) {
- // Verify the RSA signature. This is the fast way, without using forge.
- const verify = parent.crypto.createVerify('SHA384');
- verify.end(Buffer.from(obj.agentSeenCerthash + obj.nonce + obj.agentnonce, 'binary')); // Test using the private key hash
- if (verify.verify(obj.unauth.nodeCertPem, Buffer.from(msg, 'binary')) !== true) {
- parent.agentStats.invalidRsaSignatureCount++;
- parent.setAgentIssue(obj, "invalidRsaSignature");
- return false;
- }
- }
- }
- // Connection is a success, clean up
- obj.nodeid = obj.unauth.nodeid;
- obj.dbNodeKey = 'node/' + domain.id + '/' + obj.nodeid;
- delete obj.nonce;
- delete obj.agentnonce;
- delete obj.unauth;
- delete obj.receivedCommands;
- delete obj.agentSeenCerthash;
- if (obj.unauthsign) delete obj.unauthsign;
- parent.agentStats.verifiedAgentConnectionCount++;
- parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').');
- obj.authenticated = 1;
- return true;
- }
- // Process incoming agent JSON data
- function processAgentData(msg) {
- if (obj.agentInfo == null) return;
- var i, str = msg.toString('utf8'), command = null;
- if (str[0] == '{') {
- try { command = JSON.parse(str); } catch (ex) {
- // If the command can't be parsed, ignore it.
- parent.agentStats.invalidJsonCount++;
- parent.setAgentIssue(obj, "invalidJson (" + str.length + "): " + str);
- parent.parent.debug('agent', 'Unable to parse agent JSON (' + obj.remoteaddrport + ')');
- console.log('Unable to parse agent JSON (' + obj.remoteaddrport + '): ' + str, ex);
- return;
- }
- if (typeof command != 'object') { return; }
- switch (command.action) {
- case 'msg':
- {
- // If the same console command is processed many times, kick out this agent.
- // This is a safety mesure to guard against the agent DOS'ing the server.
- if (command.type == 'console') {
- if (obj.consoleKickValue == command.value) {
- if (obj.consoleKickCount) { obj.consoleKickCount++; } else { obj.consoleKickCount = 1; }
- if (obj.consoleKickCount > 30) { obj.close(); return; } // 30 identical console messages received, kick out this agent.
- } else {
- obj.consoleKickValue = command.value;
- }
- }
- // Route a message
- parent.routeAgentCommand(command, obj.domain.id, obj.dbNodeKey, obj.dbMeshKey);
- break;
- }
- case 'coreinfo':
- {
- // Sent by the agent to update agent information
- ChangeAgentCoreInfo(command);
- if ((obj.agentCoreUpdate === true) && (obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
- // Agent update. The recovery core was loaded in the agent, send a command to update the agent
- parent.parent.taskLimiter.launch(function (argument, taskid, taskLimiterQueue) { // Medium priority task
- // If agent disconnection, complete and exit now.
- if ((obj.authenticated != 2) || (obj.agentExeInfo == null)) { parent.parent.taskLimiter.completed(taskid); return; }
- // Agent update. The recovery core was loaded in the agent, send a command to update the agent
- obj.agentCoreUpdateTaskId = taskid;
- const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
- var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex };
- parent.parent.debug('agentupdate', "Sending agent update url: " + cmd.url);
- // Add the hash
- if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
- // Add server TLS cert hash
- if (isIgnoreHashCheck() == false) {
- const tlsCertHash = parent.webCertificateFullHashs[domain.id];
- if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
- }
- // Send the agent update command
- obj.send(JSON.stringify(cmd));
- }, null, 1);
- }
- break;
- }
- case 'smbios':
- {
- // SMBIOS information must never be saved when NeDB is in use. NeDB will currupt that database.
- if (db.SetSMBIOS == null) break;
- // See if we need to save SMBIOS information
- if (domain.smbios === true) {
- // Store the RAW SMBios table of this computer
- // Perform sanity checks before storing
- try {
- for (var i in command.value) { var k = parseInt(i); if ((k != i) || (i > 255) || (typeof command.value[i] != 'object') || (command.value[i].length == null) || (command.value[i].length > 1024) || (command.value[i].length < 0)) { delete command.value[i]; } }
- db.SetSMBIOS({ _id: obj.dbNodeKey, domain: domain.id, time: new Date(), value: command.value });
- } catch (ex) { }
- }
- // Event the node interface information change (This is a lot of traffic, probably don't need this).
- //parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'smBiosChange', nodeid: obj.dbNodeKey, domain: domain.id, smbios: command.value, nolog: 1 });
- break;
- }
- case 'netinfo':
- {
- // Check if network information is present
- if ((command.netif2 == null) && (command.netif == null)) return;
- // Escape any field names that have special characters
- if (command.netif2 != null) {
- for (var i in command.netif2) {
- var esc = common.escapeFieldName(i);
- if (esc !== i) { command.netif2[esc] = command.netif2[i]; delete command.netif2[i]; }
- }
- }
- // Sent by the agent to update agent network interface information
- delete command.action;
- command.updateTime = Date.now();
- command._id = 'if' + obj.dbNodeKey;
- command.domain = domain.id;
- command.type = 'ifinfo';
- db.Set(command);
- // Event the node interface information change
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.meshid, [obj.dbNodeKey]), obj, { action: 'ifchange', nodeid: obj.dbNodeKey, domain: domain.id, nolog: 1 });
- break;
- }
- case 'iplocation':
- {
- // Sent by the agent to update location information
- if ((command.type == 'publicip') && (command.value != null) && (typeof command.value == 'object') && (command.value.ip) && (command.value.loc)) {
- var x = {};
- x.publicip = command.value.ip;
- x.iploc = command.value.loc + ',' + (Math.floor(Date.now() / 1000));
- ChangeAgentLocationInfo(x);
- command.value._id = 'iploc_' + command.value.ip;
- command.value.type = 'iploc';
- command.value.date = Date.now();
- db.Set(command.value); // Store the IP to location data in the database
- // Sample Value: { ip: '192.55.64.246', city: 'Hillsboro', region: 'Oregon', country: 'US', loc: '45.4443,-122.9663', org: 'AS4983 Intel Corporation', postal: '97123' }
- }
- break;
- }
- case 'mc1migration':
- {
- if (command.oldnodeid.length != 64) break;
- const oldNodeKey = 'node//' + command.oldnodeid.toLowerCase();
- db.Get(oldNodeKey, function (err, nodes) {
- if ((nodes == null) || (nodes.length != 1)) return;
- const node = nodes[0];
- if (node.meshid == obj.dbMeshKey) {
- // Update the device name & host
- const newNode = { "name": node.name };
- if (node.intelamt != null) { newNode.intelamt = node.intelamt; }
- ChangeAgentCoreInfo(newNode);
- // Delete this node including network interface information and events
- db.Remove(node._id);
- db.Remove('if' + node._id);
- // Event node deletion
- const change = 'Migrated device ' + node.name;
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(node.meshid, [obj.dbNodeKey]), obj, { etype: 'node', action: 'removenode', nodeid: node._id, msg: change, domain: node.domain });
- }
- });
- break;
- }
- case 'openUrl':
- {
- // Sent by the agent to return the status of a open URL action.
- // Nothing is done right now.
- break;
- }
- case 'log':
- {
- // Log a value in the event log
- if ((typeof command.msg == 'string') && (command.msg.length < 4096)) {
- var event = { etype: 'node', action: 'agentlog', nodeid: obj.dbNodeKey, domain: domain.id, msg: command.msg };
- if (typeof command.msgid == 'number') { event.msgid = command.msgid; }
- if (typeof command.guestname == 'string') { event.guestname = command.guestname; }
- if (Array.isArray(command.msgArgs)) { event.msgArgs = command.msgArgs; }
- if (typeof command.remoteaddr == 'string') { event.remoteaddr = command.remoteaddr; }
- var targets = parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]);
- if (typeof command.userid == 'string') {
- var loguser = parent.users[command.userid];
- if (loguser) { event.userid = command.userid; event.username = loguser.name; targets.push(command.userid); }
- }
- if (typeof command.xuserid == 'string') {
- var xloguser = parent.users[command.xuserid];
- if (xloguser) { targets.push(command.xuserid); }
- }
- if ((typeof command.sessionid == 'string') && (command.sessionid.length < 500)) { event.sessionid = command.sessionid; }
- parent.parent.DispatchEvent(targets, obj, event);
- // If this is a help request, see if we need to email notify anyone
- if (event.msgid == 98) {
- // Get the node and change it if needed
- db.Get(obj.dbNodeKey, function (err, nodes) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
- if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
- const device = nodes[0];
- if (typeof device.name == 'string') { parent.parent.NotifyUserOfDeviceHelpRequest(domain, device.meshid, device._id, device.name, command.msgArgs[0], command.msgArgs[1]); }
- });
- }
- }
- break;
- }
- case 'ping': { sendPong(); break; }
- case 'pong': { break; }
- case 'getScript':
- {
- // Used by the agent to get configuration scripts.
- if (command.type == 1) {
- parent.getCiraConfigurationScript(obj.dbMeshKey, function (script) {
- obj.send(JSON.stringify({ action: 'getScript', type: 1, script: script.toString() }));
- });
- } else if (command.type == 2) {
- parent.getCiraCleanupScript(function (script) {
- obj.send(JSON.stringify({ action: 'getScript', type: 2, script: script.toString() }));
- });
- }
- break;
- }
- case 'diagnostic':
- {
- if (typeof command.value == 'object') {
- switch (command.value.command) {
- case 'register': {
- // Only main agent can do this
- if (((obj.agentInfo.capabilities & 0x40) == 0) && (typeof command.value.value == 'string') && (command.value.value.length == 64)) {
- // Store links to diagnostic agent id
- var daNodeKey = 'node/' + domain.id + '/' + db.escapeBase64(command.value.value);
- db.Set({ _id: 'da' + daNodeKey, domain: domain.id, time: obj.connectTime, raid: obj.dbNodeKey }); // DiagnosticAgent --> Agent
- db.Set({ _id: 'ra' + obj.dbNodeKey, domain: domain.id, time: obj.connectTime, daid: daNodeKey }); // Agent --> DiagnosticAgent
- }
- break;
- }
- case 'query': {
- // Only the diagnostic agent can do
- if ((obj.agentInfo.capabilities & 0x40) != 0) {
- // Return nodeid of main agent + connection status
- db.Get('da' + obj.dbNodeKey, function (err, nodes) {
- if ((nodes != null) && (nodes.length == 1)) {
- obj.realNodeKey = nodes[0].raid;
- // Get agent connection state
- var agentConnected = false;
- var state = parent.parent.GetConnectivityState(obj.realNodeKey);
- if (state) { agentConnected = ((state.connectivity & 1) != 0) }
- obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: obj.realNodeKey, agent: agentConnected } }));
- } else {
- obj.send(JSON.stringify({ action: 'diagnostic', value: { command: 'query', value: null } }));
- }
- });
- }
- break;
- }
- case 'log': {
- if (((obj.agentInfo.capabilities & 0x40) != 0) && (typeof command.value.value == 'string') && (command.value.value.length < 256)) {
- // If this is a diagnostic agent, log the event in the log of the main agent
- var event = { etype: 'node', action: 'diagnostic', nodeid: obj.realNodeKey, snodeid: obj.dbNodeKey, domain: domain.id, msg: command.value.value };
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
- }
- break;
- }
- }
- }
- break;
- }
- case 'sysinfo': {
- if ((typeof command.data == 'object') && (typeof command.data.hash == 'string')) {
- // Validate command.data.
- if (common.validateObjectForMongo(command.data, 1024) == false) break;
- // Save to database
- command.data._id = 'si' + obj.dbNodeKey;
- command.data.type = 'sysinfo';
- command.data.domain = domain.id;
- command.data.time = Date.now();
- db.Set(command.data); // Update system information in the database.
- // Event the new sysinfo hash, this will notify everyone that the sysinfo document was changed
- var event = { etype: 'node', action: 'sysinfohash', nodeid: obj.dbNodeKey, domain: domain.id, hash: command.data.hash, nolog: 1 };
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
- }
- break;
- }
- case 'sysinfocheck': {
- // Check system information update
- db.GetHash('si' + obj.dbNodeKey, function (err, results) {
- if ((results != null) && (results.length == 1)) { obj.send(JSON.stringify({ action: 'sysinfo', hash: results[0].hash })); } else { obj.send(JSON.stringify({ action: 'sysinfo' })); }
- });
- break;
- }
- case 'sessions': {
- // This is a list of sessions provided by the agent
- if (obj.sessions == null) { obj.sessions = {}; }
- if (typeof command.value != null) {
- if (command.type == 'kvm') { obj.sessions.kvm = command.value; }
- else if (command.type == 'terminal') { obj.sessions.terminal = command.value; }
- else if (command.type == 'files') { obj.sessions.files = command.value; }
- else if (command.type == 'help') { obj.sessions.help = command.value; }
- else if (command.type == 'tcp') { obj.sessions.tcp = command.value; }
- else if (command.type == 'udp') { obj.sessions.udp = command.value; }
- else if (command.type == 'msg') { obj.sessions.msg = command.value; }
- else if (command.type == 'app') { obj.sessions.app = command.value; }
- }
- // Any "help" session must have an associated app, if not, remove it.
- if (obj.sessions.help != null) {
- for (var i in obj.sessions.help) { if (obj.sessions.help[i] == null) { delete obj.sessions.help[i]; } }
- if (Object.keys(obj.sessions.help).length == 0) { delete obj.sessions.help; }
- }
- // Inform everyone of updated sessions
- obj.updateSessions();
- break;
- }
- case 'battery': {
- // Device battery and power state
- if (obj.sessions == null) { obj.sessions = {}; }
- if (obj.sessions.battery == null) { obj.sessions.battery = {}; }
- if ((command.state == 'ac') || (command.state == 'dc')) { obj.sessions.battery.state = command.state; } else { delete obj.sessions.battery.state; }
- if ((typeof command.level == 'number') && (command.level >= 0) && (command.level <= 100)) { obj.sessions.battery.level = command.level; } else { delete obj.sessions.battery.level; }
- obj.updateSessions();
- break;
- }
- case 'getcoredump': {
- // Check if we requested a core dump file in the last minute, if so, ignore this.
- if ((parent.lastCoreDumpRequest != null) && ((Date.now() - parent.lastCoreDumpRequest) < 60000)) break;
- // Indicates if the agent has a coredump available
- if ((command.exists === true) && (typeof command.agenthashhex == 'string') && (command.agenthashhex.length == 96)) {
- // Check if we already have this exact dump file
- const coreDumpFile = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps', obj.agentInfo.agentId + '-' + command.agenthashhex + '-' + obj.nodeid + '.dmp');
- parent.fs.stat(coreDumpFile, function (err, stats) {
- if (stats != null) return;
- obj.coreDumpPresent = true;
- // Check how many files are in the coredumps folder
- const coreDumpPath = parent.path.join(parent.parent.datapath, '..', 'meshcentral-coredumps');
- parent.fs.readdir(coreDumpPath, function (err, files) {
- if ((files != null) && (files.length >= 20)) return; // Don't get more than 20 core dump files.
- // Get the core dump uploaded to the server.
- parent.lastCoreDumpRequest = Date.now();
- obj.RequestCoreDump(command.agenthashhex, command.corehashhex);
- });
- });
- }
- break;
- }
- case 'tunnelCloseStats': {
- // TODO: This this extra stats from the tunnel, you can merge this into the tunnel event in the database.
- //console.log(command);
- // Validate input
- if ((command.sent == null) || (typeof command.sent != 'string')) return;
- if ((command.sentActual == null) || (typeof command.sentActual != 'string')) return;
- if ((command.sentActual == null) || (typeof command.sentActual != 'number')) return;
- // Event the session closed compression data.
- 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 + '.' };
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
- break;
- }
- case 'lmsinfo': {
- // Agents send the LMS port bindings
- // Example: {"action":"lmsinfo","value":{"ports":["623","16992"]}}
- break;
- }
- case 'plugin': {
- if ((parent.parent.pluginHandler == null) || (typeof command.plugin != 'string')) break;
- try {
- parent.parent.pluginHandler.plugins[command.plugin].serveraction(command, obj, parent);
- } catch (e) {
- parent.parent.debug('agent', 'Error loading plugin handler (' + e + ')');
- console.log('Error loading plugin handler (' + e + ')');
- }
- break;
- }
- case 'meshToolInfo': {
- // Return information about a MeshCentral tool. Current tools are 'MeshCentralRouter' and 'MeshCentralAssistant'
- // Information includes file hash and download location URL
- if (typeof command.name != 'string') break;
- var info = parent.parent.meshToolsBinaries[command.name];
- if ((command.hash != null) && (info.hash == command.hash)) return;
- // To build the connection URL, if we are using a sub-domain or one with a DNS, we need to craft the URL correctly.
- var xdomain = (domain.dns == null) ? domain.id : '';
- if (xdomain != '') xdomain += '/';
- // Build the response
- const responseCmd = { action: 'meshToolInfo', name: command.name, tag: command.tag, sessionid: command.sessionid, hash: info.hash, size: info.size, url: info.url };
- 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.
- if (command.cookie === true) { responseCmd.url += ('&auth=' + parent.parent.encodeCookie({ download: info.dlname }, parent.parent.loginCookieEncryptionKey)); }
- if (command.pipe === true) { responseCmd.pipe = true; }
- if (parent.webCertificateHashs[domain.id] != null) { responseCmd.serverhash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex'); }
- try { ws.send(JSON.stringify(responseCmd)); } catch (ex) { }
- break;
- }
- case 'agentupdate': {
- if ((obj.agentExeInfo != null) && (typeof obj.agentExeInfo.url == 'string')) {
- var func = function agentUpdateFunc(argument, taskid, taskLimiterQueue) { // Medium priority task
- // If agent disconnection, complete and exit now.
- if (obj.authenticated != 2) { parent.parent.taskLimiter.completed(taskid); return; }
- // Agent is requesting an agent update
- obj.agentCoreUpdateTaskId = taskid;
- const url = '*' + require('url').parse(obj.agentExeInfo.url).path;
- var cmd = { action: 'agentupdate', url: url, hash: obj.agentExeInfo.hashhex, sessionid: agentUpdateFunc.sessionid };
- parent.parent.debug('agentupdate', "Sending user requested agent update url: " + cmd.url);
- // Add the hash
- if (obj.agentExeInfo.fileHash != null) { cmd.hash = obj.agentExeInfo.fileHashHex; } else { cmd.hash = obj.agentExeInfo.hashhex; }
- // Add server TLS cert hash
- if (isIgnoreHashCheck() == false) {
- const tlsCertHash = parent.webCertificateFullHashs[domain.id];
- if (tlsCertHash != null) { cmd.servertlshash = Buffer.from(tlsCertHash, 'binary').toString('hex'); }
- }
- // Send the agent update command
- obj.send(JSON.stringify(cmd));
- }
- func.sessionid = command.sessionid;
- // Agent update. The recovery core was loaded in the agent, send a command to update the agent
- parent.parent.taskLimiter.launch(func, null, 1);
- }
- break;
- }
- case 'agentupdatedownloaded': {
- if (obj.agentCoreUpdateTaskId != null) {
- // Indicate this udpate task is complete
- parent.parent.taskLimiter.completed(obj.agentCoreUpdateTaskId);
- delete obj.agentCoreUpdateTaskId;
- }
- break;
- }
- case 'errorlog': { // This is the agent error log
- if ((!Array.isArray(command.log)) || (command.log.length == 0) || (parent.parent.agentErrorLog == null)) break;
- var lastLogEntry = command.log[command.log.length - 1];
- if ((lastLogEntry != null) && (typeof lastLogEntry == 'object') && (typeof lastLogEntry.t == 'number')) {
- parent.fs.write(parent.parent.agentErrorLog, obj.dbNodeKey + ', ' + Date.now() + ', ' + str + '\r\n', function (err) { });
- db.Set({ _id: 'al' + obj.dbNodeKey, lastEvent: lastLogEntry.t });
- }
- break;
- }
- case '2faauth': {
- // Validate input
- if ((typeof command.url != 'string') || (typeof command.approved != 'boolean') || (command.url.startsWith('2fa://') == false)) return;
- // parse the URL
- var url = null;
- try { url = require('url').parse(command.url); } catch (ex) { }
- if (url == null) return;
- // Decode the cookie
- var urlSplit = url.query.split('&c=');
- if (urlSplit.length != 2) return;
- const authCookie = parent.parent.decodeCookie(urlSplit[1], null, 1);
- if ((authCookie == null) || (typeof authCookie.c != 'string') || (('code=' + authCookie.c) != urlSplit[0])) return;
- if ((typeof authCookie.n != 'string') || (authCookie.n != obj.dbNodeKey) || (typeof authCookie.u != 'string')) return;
- // Fetch the user
- const user = parent.users[authCookie.u];
- if (user == null) return;
- // Add this device as the authentication push notification device for this user
- if (authCookie.a == 'addAuth') {
- // Do nothing if authentication is not approved.
- // We do not want to indicate that the remote user responded to this.
- if (command.approved !== true) return;
- // Change the user
- user.otpdev = obj.dbNodeKey;
- parent.db.SetUser(user);
- // Notify change
- var targets = ['*', 'server-users', user._id];
- if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
- 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 };
- 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.
- parent.parent.DispatchEvent(targets, obj, event);
- }
- // Complete 2FA checking
- if (authCookie.a == 'checkAuth') {
- if (typeof authCookie.s != 'string') return;
- // Notify 2FA response
- 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 });
- }
- break;
- }
- case 'getUserImage': {
- // Validate input
- if (typeof command.userid != 'string') {
- // Send back the default image if required
- if ((command.default) || (command.sentDefault)) {
- 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) { }
- obj.send(JSON.stringify(command));
- }
- return;
- }
- var useridsplit = command.userid.split('/');
- if ((useridsplit.length != 3) || (useridsplit[1] != domain.id)) return;
- // Add the user's real name if present
- var u = parent.users[command.userid];
- if (u == null) return;
- if (u.name) { command.name = u.name; }
- if (u.realname) { command.realname = u.realname; }
- // An agent can only request images of accounts with rights to the device.
- if (parent.GetNodeRights(command.userid, obj.dbMeshKey, obj.dbNodeKey) != 0) {
- parent.db.Get('im' + command.userid, function (err, images) {
- if ((err == null) && (images != null) && (images.length == 1)) {
- // Send back the account image
- command.image = images[0].image;
- } else {
- // Send back the default image if required
- if ((command.default) || (command.sentDefault)) {
- 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) { }
- }
- }
- obj.send(JSON.stringify(command));
- });
- }
- break;
- }
- case 'getServerImage': {
- if (command.agent === 'assistant') {
- // Return server title and image for MeshCentral Assistant
- if ((domain.assistantcustomization != null) && (typeof domain.assistantcustomization == 'object')) {
- var ok = false;
- if (typeof domain.assistantcustomization.title == 'string') { ok = true; command.title = domain.assistantcustomization.title; }
- 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); } }
- if (ok) { obj.send(JSON.stringify(command)); }
- }
- }
- if (command.agent === 'android') {
- // Return server title and image for MeshCentral Assistant
- if ((domain.androidcustomization != null) && (typeof domain.androidcustomization == 'object')) {
- var ok = false;
- if (typeof domain.androidcustomization.title == 'string') { ok = true; command.title = domain.androidcustomization.title; }
- if (typeof domain.androidcustomization.subtitle == 'string') { ok = true; command.subtitle = domain.androidcustomization.subtitle; }
- 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); } }
- if (ok) { obj.send(JSON.stringify(command)); }
- }
- }
- break;
- }
- case 'guestShare': {
- if ((command.flags == null) || (command.flags == 0)) {
- // Stop any current self-share, this is allowed even if self guest sharing is not allows so to clear any old shares.
- removeGuestSharing(function () {
- delete obj.guestSharing;
- obj.send(JSON.stringify({ action: 'guestShare', flags: command.flags, url: null, viewOnly: false }));
- });
- } else {
- // Add a new self-share, this will replace any share for this device
- if ((domain.agentselfguestsharing == null) || (domain.agentselfguestsharing == false) || (typeof command.flags != 'number')) return; // Check if agent self-sharing is allowed, this is off by default.
- if ((command.flags & 2) == 0) { command.viewOnly = false; } // Only allow "view only" if desktop is shared.
- addGuestSharing(command.flags, command.viewOnly, function (share) {
- obj.guestSharing = true;
- obj.send(JSON.stringify({ action: 'guestShare', url: share.url, flags: share.flags, viewOnly: share.viewOnly }));
- })
- }
- break;
- }
- case 'amtconfig': {
- // Sent by the agent when the agent needs a Intel AMT APF connection to the server
- const cookie = parent.parent.encodeCookie({ a: 'apf', n: obj.dbNodeKey, m: obj.dbMeshKey }, parent.parent.loginCookieEncryptionKey);
- try { obj.send(JSON.stringify({ action: 'amtconfig', user: '**MeshAgentApfTunnel**', pass: cookie })); } catch (ex) { }
- break;
- }
- case 'script-task': {
- // These command are for running regular batch jobs on the remote device
- if (parent.parent.taskManager != null) { parent.parent.taskManager.agentAction(command, obj); }
- break;
- }
- default: {
- parent.agentStats.unknownAgentActionCount++;
- parent.parent.debug('agent', 'Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
- console.log('Unknown agent action (' + obj.remoteaddrport + '): ' + JSON.stringify(command) + '.');
- break;
- }
- }
- if (parent.parent.pluginHandler != null) {
- parent.parent.pluginHandler.callHook('hook_processAgentData', command, obj, parent);
- }
- }
- }
- function addGuestSharing(flags, viewOnly, func) {
- // Create cookie
- const publicid = 'AS:' + obj.dbNodeKey;
- const extrakey = getRandomAmtPassword();
- const cookie = { a: 6, pid: publicid, k: extrakey }; // New style sharing cookie
- const inviteCookie = parent.parent.encodeCookie(cookie, parent.parent.invitationLinkEncryptionKey);
- if (inviteCookie == null) return;
- // Create the server url
- var serverName = parent.getWebServerName(domain, req);
- var httpsPort = ((args.aliasport == null) ? args.port : args.aliasport); // Use HTTPS alias port is specified
- var xdomain = (domain.dns == null) ? domain.id : '';
- if (xdomain != '') xdomain += '/';
- var url = 'https://' + serverName + ':' + httpsPort + '/' + xdomain + 'sharing?c=' + inviteCookie;
- if (serverName.split('.') == 1) { url = '/' + xdomain + page + '?c=' + inviteCookie; }
- // Create a device sharing database entry
- 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 };
- // Add expire time
- if ((typeof domain.agentselfguestsharing == 'object') && (typeof domain.agentselfguestsharing.expire == 'number') && (domain.agentselfguestsharing.expire > 0)) {
- shareEntry.startTime = Date.now();
- shareEntry.expireTime = Date.now() + (60000 * domain.agentselfguestsharing.expire);
- }
- if (viewOnly === true) { shareEntry.viewOnly = true; }
- parent.db.Set(shareEntry);
- // Send out an event that we added a device share
- var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey);
- var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'addedDeviceShare', msg: 'Added device share with unlimited time', msgid: 131, msgArgs: ['Agent'], domain: domain.id };
- parent.parent.DispatchEvent(targets, obj, event);
- // Send device share update
- parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
- if (err != null) return;
- // Check device sharing
- var now = Date.now();
- for (var i = 0; i < docs.length; i++) {
- const doc = docs[i];
- if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); delete docs[i]; } else {
- // This share is ok, remove extra data we don't need to send.
- delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
- }
- }
- // Send device share update
- var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
- parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: docs, nolog: 1 });
- // Callback
- if (func) { func({ url: url, flags: flags, viewOnly: viewOnly }); }
- });
- }
- function removeGuestSharing(func) {
- var publicid = 'AS:' + obj.dbNodeKey;
- parent.db.GetAllTypeNodeFiltered([obj.dbNodeKey], domain.id, 'deviceshare', null, function (err, docs) {
- if (err != null) return;
- // Remove device sharing
- var now = Date.now(), removedExact = null, removed = false, okDocs = [];
- for (var i = 0; i < docs.length; i++) {
- const doc = docs[i];
- if (doc.publicid == publicid) { parent.db.Remove(doc._id, function () { }); removedExact = doc; removed = true; }
- else if (doc.expireTime < now) { parent.db.Remove(doc._id, function () { }); removed = true; } else {
- // This share is ok, remove extra data we don't need to send.
- delete doc._id; delete doc.domain; delete doc.nodeid; delete doc.type;
- okDocs.push(doc);
- }
- }
- // Event device share removal
- if (removedExact != null) {
- // Send out an event that we removed a device share
- var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, ['server-shareremove']);
- var event = { etype: 'node', nodeid: obj.dbNodeKey, action: 'removedDeviceShare', msg: 'Removed Device Share', msgid: 102, msgArgs: ['Agent'], domain: domain.id, publicid: publicid };
- parent.parent.DispatchEvent(targets, obj, event);
- }
- // If we removed any shares, send device share update
- if (removed == true) {
- var targets = parent.CreateNodeDispatchTargets(obj.dbMeshKey, obj.dbNodeKey, []);
- parent.parent.DispatchEvent(targets, obj, { etype: 'node', nodeid: obj.dbNodeKey, action: 'deviceShareUpdate', domain: domain.id, deviceShares: okDocs, nolog: 1 });
- }
- // Call back when done
- if (func) func(removed);
- });
- }
- // Notify update of sessions
- obj.updateSessions = function () {
- // Perform some clean up
- for (var i in obj.sessions) { if (Object.keys(obj.sessions[i]).length == 0) { delete obj.sessions[i]; } }
- if (Object.keys(obj.sessions).length == 0) { delete obj.sessions; }
- // Event the new sessions, this will notify everyone that agent sessions have changed
- var event = { etype: 'node', action: 'devicesessions', nodeid: obj.dbNodeKey, domain: domain.id, sessions: obj.sessions, nolog: 1 };
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(obj.dbMeshKey, [obj.dbNodeKey]), obj, event);
- }
- // Change the current core information string and event it
- function ChangeAgentCoreInfo(command) {
- if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
- if ((command == null) || (command == null)) return; // Safety, should never happen.
- // If the device is pending a change, hold.
- if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentCoreInfo(command); }, 100); return; }
- obj.deviceChanging = true;
- // Check that the mesh exists
- const mesh = parent.meshes[obj.dbMeshKey];
- if (mesh == null) { delete obj.deviceChanging; return; }
- // Get the node and change it if needed
- db.Get(obj.dbNodeKey, function (err, nodes) { // TODO: THIS IS A BIG RACE CONDITION HERE, WE NEED TO FIX THAT. If this call is made twice at the same time on the same device, data will be missed.
- if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
- const device = nodes[0];
- if (device.agent) {
- var changes = [], change = 0, log = 0;
- // Check if anything changes
- if (command.name && (typeof command.name == 'string') && (command.name != device.name)) { change = 1; log = 1; device.name = command.name; changes.push('name'); }
- if ((command.caps != null) && (device.agent.core != command.value)) { if ((command.value == null) && (device.agent.core != null)) { delete device.agent.core; } else { device.agent.core = command.value; } change = 1; } // Don't save this as an event to the db.
- if ((command.caps != null) && ((device.agent.caps & 0xFFFFFFE7) != (command.caps & 0xFFFFFFE7))) { device.agent.caps = ((device.agent.caps & 24) + (command.caps & 0xFFFFFFE7)); change = 1; } // Allow Javascript on the agent to change all capabilities except console and javascript support, Don't save this as an event to the db.
- if ((command.osdesc != null) && (typeof command.osdesc == 'string') && (device.osdesc != command.osdesc)) { device.osdesc = command.osdesc; change = 1; changes.push('os desc'); } // Don't save this as an event to the db.
- if ((typeof command.root == 'boolean') && (command.root !== device.agent.root)) { change = 1; device.agent.root = command.root; }
- if (device.ip != obj.remoteaddr) { device.ip = obj.remoteaddr; change = 1; }
- if (command.intelamt) {
- if (!device.intelamt) { device.intelamt = {}; }
- if ((command.intelamt.Versions != null) && (typeof command.intelamt.Versions == 'object')) {
- 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; }
- if ((command.intelamt.Versions.Sku != null) && (typeof command.intelamt.Versions.Sku == 'string')) {
- const sku = parseInt(command.intelamt.Versions.Sku);
- if (device.intelamt.sku !== sku) { device.intelamt.sku = sku; change = 1; log = 1; }
- }
- }
- 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; }
- if ((command.intelamt.Flags != null) && (typeof command.intelamt.Flags == 'number') && (device.intelamt.flags != command.intelamt.Flags)) {
- if (device.intelamt.flags) { changes.push('AMT flags (' + device.intelamt.flags + ' --> ' + command.intelamt.Flags + ')'); } else { changes.push('AMT flags (' + command.intelamt.Flags + ')'); }
- device.intelamt.flags = command.intelamt.Flags; change = 1; log = 1;
- }
- if ((command.intelamt.UUID != null) && (typeof command.intelamt.UUID == 'string') && (device.intelamt.uuid != command.intelamt.UUID)) { changes.push('AMT uuid'); device.intelamt.uuid = command.intelamt.UUID; change = 1; log = 1; }
- }
- if (command.av != null) { // Antivirus
- if (!device.av) { device.av = []; }
- if (JSON.stringify(device.av) != JSON.stringify(command.av)) { /*changes.push('AV status');*/ device.av = command.av; change = 1; log = 1; }
- }
- if (command.wsc != null) { // Windows Security Center
- if (!device.wsc) { device.wsc = {}; }
- if (JSON.stringify(device.wsc) != JSON.stringify(command.wsc)) { /*changes.push('Windows Security Center status');*/ device.wsc = command.wsc; change = 1; log = 1; }
- }
- if (command.defender != null) { // Defender For Windows Server
- if (!device.defender) { device.defender = {}; }
- if (JSON.stringify(device.defender) != JSON.stringify(command.defender)) { /*changes.push('Defender status');*/ device.defender = command.defender; change = 1; log = 1; }
- }
- if (command.lastbootuptime != null) { // Last Boot Up Time
- if (!device.lastbootuptime) { device.lastbootuptime = ""; }
- if (device.lastbootuptime != command.lastbootuptime) { /*changes.push('Last Boot Up Time');*/ device.lastbootuptime = command.lastbootuptime; change = 1; log = 1; }
- }
- // Push Messaging Token
- if ((command.pmt != null) && (typeof command.pmt == 'string') && (device.pmt != command.pmt)) {
- if (typeof device.pmt == 'string') { db.Remove('pmt_' + device.pmt); }
- device.pmt = command.pmt;
- change = 1; // Don't save this change as an event to the db, so no log=1.
- parent.removePmtFromAllOtherNodes(device); // We need to make sure to remove this push messaging token from any other device on this server, all domains included.
- }
- 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.
- if ((mesh.mtype == 2) && (!args.wanonly)) {
- // In WAN mode, the hostname of a computer is not important. Don't log hostname changes.
- if (device.host != obj.remoteaddr) { device.host = obj.remoteaddr; change = 1; changes.push('host'); }
- // TODO: Check that the agent has an interface that is the same as the one we got this websocket connection on. Only set if we have a match.
- }
- // Remove old volumes and BitLocker data, this is part of sysinfo.
- delete device.volumes;
- // If there are changes, event the new device
- if (change == 1) {
- // Do some clean up if needed, these values should not be in the database.
- if (device.conn != null) { delete device.conn; }
- if (device.pwr != null) { delete device.pwr; }
- if (device.agct != null) { delete device.agct; }
- if (device.cict != null) { delete device.cict; }
- // Save to the database
- db.Set(device);
- // Event the node change
- var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device) };
- if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
- if ((log == 0) || ((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || (changes.length == 0)) { event.nolog = 1; } // If this is a temporary device, don't log changes
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
- }
- // Device change is done.
- delete obj.deviceChanging;
- }
- });
- }
- // Change the current core information string and event it
- function ChangeAgentLocationInfo(command) {
- if (obj.agentInfo.capabilities & 0x40) return;
- if ((command == null) || (command == null)) { return; } // Safety, should never happen.
- // Check that the mesh exists
- const mesh = parent.meshes[obj.dbMeshKey];
- if (mesh == null) return;
- // If the device is pending a change, hold.
- if (obj.deviceChanging === true) { setTimeout(function () { ChangeAgentLocationInfo(command); }, 100); return; }
- obj.deviceChanging = true;
- // Get the node and change it if needed
- db.Get(obj.dbNodeKey, function (err, nodes) {
- if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
- const device = nodes[0];
- if (device.agent) {
- var changes = [], change = 0;
- // Check if anything changes
- if ((command.publicip) && (device.publicip != command.publicip)) { device.publicip = command.publicip; change = 1; changes.push('public ip'); }
- if ((command.iploc) && (device.iploc != command.iploc)) { device.iploc = command.iploc; change = 1; changes.push('ip location'); }
- // If there are changes, save and event
- if (change == 1) {
- // Do some clean up if needed, these values should not be in the database.
- if (device.conn != null) { delete device.conn; }
- if (device.pwr != null) { delete device.pwr; }
- if (device.agct != null) { delete device.agct; }
- if (device.cict != null) { delete device.cict; }
- // Save the device
- db.Set(device);
- // Event the node change
- var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), msgid: 59, msgArgs: [device.name, mesh.name, changes.join(', ')], msg: 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', ') };
- if (obj.agentInfo.capabilities & 0x20) { event.nolog = 1; } // If this is a temporary device, don't log changes
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
- }
- }
- // Done changing the device
- delete obj.deviceChanging;
- });
- }
- // Update the mesh agent tab in the database
- function ChangeAgentTag(tag) {
- if ((obj.agentInfo == null) || (obj.agentInfo.capabilities & 0x40)) return;
- if ((tag != null) && (tag.length == 0)) { tag = null; }
- // If the device is pending a change, hold.
- if (obj.deviceChanging === true) {
- var func = function ChangeAgentTagFunc() { ChangeAgentCoreInfo(ChangeAgentTagFunc.tag); }
- func.tag = tag;
- setTimeout(func, 100);
- return;
- }
- obj.deviceChanging = true;
- // Get the node and change it if needed
- db.Get(obj.dbNodeKey, function (err, nodes) {
- if ((nodes == null) || (nodes.length != 1)) { delete obj.deviceChanging; return; }
- const device = nodes[0];
- if (device.agent) {
- // Parse the agent tag
- var agentTag = null, serverName = null, serverDesc = null, serverTags = null;
- if (tag != null) {
- var taglines = tag.split('\r\n').join('\n').split('\r').join('\n').split('\n');
- for (var i in taglines) {
- var tagline = taglines[i].trim();
- if (tagline.length > 0) {
- if (tagline.startsWith('~')) {
- if (tagline.startsWith('~ServerName:') && (tagline.length > 12) && (serverName == null)) { serverName = tagline.substring(12).trim(); }
- if (tagline.startsWith('~ServerDesc:') && (tagline.length > 12) && (serverDesc == null)) { serverDesc = tagline.substring(12).trim(); }
- if (tagline.startsWith('~ServerTags:') && (tagline.length > 12) && (serverTags == null)) { serverTags = tagline.substring(12).split(','); for (var j in serverTags) { serverTags[j] = serverTags[j].trim(); } }
- } else { if (agentTag == null) { agentTag = tagline; } }
- }
- }
- }
- // Set the agent tag
- var changes = false;
- if (device.agent.tag != agentTag) { device.agent.tag = agentTag; if ((device.agent.tag == null) || (device.agent.tag == '')) { delete device.agent.tag; } changes = true; }
- if (domain.agenttag != null) {
- // Set the device's server name
- if ((serverName != null) && (domain.agenttag.servername === 1) && (device.name != serverName)) { device.name = serverName; changes = true; }
- // Set the device's server description
- if ((serverDesc != null) && (domain.agenttag.serverdesc === 1) && (device.desc != serverDesc)) { device.desc = serverDesc; changes = true; }
- // Set the device's server description if there is no description
- if ((serverDesc != null) && (domain.agenttag.serverdesc === 2) && (device.desc != serverDesc) && ((device.desc == null) || (device.desc == ''))) { device.desc = serverDesc; changes = true; }
- if ((serverTags != null) && (domain.agenttag.servertags != null) && (domain.agenttag.servertags != 0)) {
- // Sort the tags
- serverTags.sort();
- // Stringify the tags
- var st2 = '', st1 = serverTags.join(',');
- if (device.tags != null) { st2 = device.tags.join(','); }
- // Set the device's server tags
- if ((domain.agenttag.servertags === 1) && (st1 != st2)) { device.tags = serverTags; changes = true; }
- // Set the device's server tags if there are not tags
- if ((domain.agenttag.servertags === 2) && (st2 == '')) { device.tags = serverTags; changes = true; }
- // Append to device's server tags
- if ((domain.agenttag.servertags === 3) && (st1 != st2)) {
- if (device.tags == null) { device.tags = []; }
- for (var i in serverTags) { if (device.tags.indexOf(serverTags[i]) == -1) { device.tags.push(serverTags[i]); } }
- device.tags.sort();
- changes = true;
- }
- }
- }
- if (changes == true) {
- // Do some clean up if needed, these values should not be in the database.
- if (device.conn != null) { delete device.conn; }
- if (device.pwr != null) { delete device.pwr; }
- if (device.agct != null) { delete device.agct; }
- if (device.cict != null) { delete device.cict; }
- // Update the device
- db.Set(device);
- // Event the node change
- var event = { etype: 'node', action: 'changenode', nodeid: obj.dbNodeKey, domain: domain.id, node: parent.CloneSafeNode(device), nolog: 1 };
- if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come.
- parent.parent.DispatchEvent(parent.CreateMeshDispatchTargets(device.meshid, [obj.dbNodeKey]), obj, event);
- }
- }
- // Done changing the device
- delete obj.deviceChanging;
- });
- }
- // Check if we need to update this agent, return true if agent binary update required.
- // Return 0 is no update needed, 1 update using native system, 2 update using meshcore system
- function compareAgentBinaryHash(agentExeInfo, agentHash) {
- // If this is a temporary agent and the server is set to not update temporary agents, don't update the agent.
- if ((obj.agentInfo.capabilities & 0x20) && (args.temporaryagentupdate === false)) return 0;
- // If we are testing the agent update system, always return true
- if ((args.agentupdatetest === true) || (args.agentupdatetest === 1)) return 1;
- if (args.agentupdatetest === 2) return 2;
- // If the hash matches or is null, no update required.
- 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;
- // If this is a macOS x86 or ARM agent type and it matched the universal binary, no update required.
- if ((agentExeInfo.id == 16) || (agentExeInfo.id == 29)) {
- if (domain.meshAgentBinaries && domain.meshAgentBinaries[10005]) {
- if (domain.meshAgentBinaries[10005].hash == agentHash) return 0;
- } else {
- if (parent.parent.meshAgentBinaries[10005].hash == agentHash) return 0;
- }
- }
- // No match, update the agent.
- if (args.agentupdatesystem === 2) return 2; // If set, force a meshcore update.
- 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.
- // NOTE: Windows agents with no commit dates may have bad native update system, so use meshcore system instead.
- // 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.
- if (((obj.AgentCommitDate == null) || (obj.AgentCommitDate < 1612740413000)) && ((agentExeInfo.id == 3) || (agentExeInfo.id == 4))) return 2; // For older Windows agents, use the meshcore update technique.
- return 1; // By default, use the native update technique.
- }
- // Request that the core dump file on this agent be uploaded to the server
- obj.RequestCoreDump = function (agenthashhex, corehashhex) {
- if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); }
- const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey);
- obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}');
- }
- // Return true if we need to ignore the agent hash check
- function isIgnoreHashCheck() {
- if ((args.ignoreagenthashcheck === true) || (domain.ignoreagenthashcheck === true)) return true;
- // Check site wide exceptions
- if (Array.isArray(args.ignoreagenthashcheck)) {
- for (var i = 0; i < args.ignoreagenthashcheck.length; i++) {
- if (require('ipcheck').match(obj.remoteaddr, args.ignoreagenthashcheck[i])) return true;
- }
- }
- // Check domain wide exceptions
- if (Array.isArray(domain.ignoreagenthashcheck)) {
- for (var i = 0; i < domain.ignoreagenthashcheck.length; i++) {
- if (require('ipcheck').match(obj.remoteaddr, domain.ignoreagenthashcheck[i])) return true;
- }
- }
- return false;
- }
- // Generate a random Intel AMT password
- function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); }
- function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
- return obj;
- };
|