From dc193fde0223a51103f01e744e16610dead88c1c Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Thu, 22 Aug 2019 15:31:39 -0700 Subject: [PATCH] Added server tracing dialog in web app. --- meshagent.js | 20 +++--- meshcentral.js | 74 ++++++++++++++-------- meshrelay.js | 28 ++++----- meshuser.js | 16 ++++- mpsserver.js | 57 +++++++---------- multiserver.js | 62 +++++++++---------- package.json | 2 +- public/styles/style.css | 12 +++- sample-config.json | 2 + swarmserver.js | 33 ++++------ views/default-min.handlebars | 2 +- views/default.handlebars | 109 +++++++++++++++++++++++++++++++-- views/messenger-min.handlebars | 2 +- webserver.js | 96 ++++++++++++++--------------- 14 files changed, 317 insertions(+), 198 deletions(-) diff --git a/meshagent.js b/meshagent.js index ab1fa1bb..5a66e5a4 100644 --- a/meshagent.js +++ b/meshagent.js @@ -45,8 +45,8 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // Disconnect this agent obj.close = function (arg) { obj.authenticated = -1; - if ((arg == 1) || (arg == null)) { try { ws.close(); if (obj.nodeid != null) { parent.parent.debug(1, 'Soft disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { ws._socket._parent.end(); if (obj.nodeid != null) { parent.parent.debug(1, 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Hard close, close the TCP socket + 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 { ws._socket._parent.end(); if (obj.nodeid != null) { parent.parent.debug('agent', 'Hard disconnect ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } } catch (e) { console.log(e); } } // Hard close, close the TCP socket // If arg == 3, don't communicate with this agent anymore, but don't disconnect (Duplicate agent). // Remove this agent from the webserver list @@ -166,11 +166,11 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // 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(1, 'Clearing core'); + parent.parent.debug('agent', 'Clearing core'); } else { // Update new core //obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + meshcorehash + parent.parent.defaultMeshCores[corename]); // MeshCommand_CoreModule, start core update - //parent.parent.debug(1, 'Updating code ' + corename); + //parent.parent.debug('agent', 'Updating code ' + corename); // Update new core with task limiting so not to flood the server. This is a high priority task. obj.agentCoreUpdatePending = true; @@ -180,7 +180,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { delete obj.agentCoreUpdatePending; obj.sendBinary(common.ShortToStr(10) + common.ShortToStr(0) + argument.hash + argument.core, function () { parent.parent.taskLimiter.completed(taskid); }); // MeshCommand_CoreModule, start core update parent.agentStats.updatingCoreCount++; - parent.parent.debug(1, 'Updating core ' + argument.name); + parent.parent.debug('agent', 'Updating core ' + argument.name); agentCoreIsStable(); } else { // This agent is probably disconnected, nothing to do. @@ -228,7 +228,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // 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(1, 'Agent update required, NodeID=0x' + obj.nodeid.substring(0, 16) + ', ' + obj.agentExeInfo.desc); } + 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) { // Read the agent from disk @@ -448,7 +448,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { //console.log('MeshID', obj.meshid); obj.agentInfo.capabilities = common.ReadInt(msg, 66); const computerNameLen = common.ReadShort(msg, 70); - obj.agentInfo.computerName = msg.substring(72, 72 + computerNameLen); + obj.agentInfo.computerName = Buffer.from(msg.substring(72, 72 + computerNameLen), 'binary').toString('utf8'); obj.dbMeshKey = 'mesh/' + domain.id + '/' + obj.meshid; completeAgentConnection(); } else if (cmd == 4) { @@ -471,7 +471,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { 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(1, '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 (parent.wsagentsDisconnections[obj.nodeid] == null) { @@ -765,7 +765,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // Close the duplicate agent parent.agentStats.duplicateAgentCount++; - if (obj.nodeid != null) { parent.parent.debug(1, 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } + if (obj.nodeid != null) { parent.parent.debug('agent', 'Duplicate agent ' + obj.nodeid + ' (' + obj.remoteaddrport + ')'); } dupAgent.close(3); } else { // Indicate the agent is connected @@ -1034,7 +1034,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { delete obj.receivedCommands; if (obj.unauthsign) delete obj.unauthsign; parent.agentStats.verifiedAgentConnectionCount++; - parent.parent.debug(1, 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').'); + parent.parent.debug('agent', 'Verified agent connection to ' + obj.nodeid + ' (' + obj.remoteaddrport + ').'); obj.authenticated = 1; return true; } diff --git a/meshcentral.js b/meshcentral.js index 4bc4e648..60f92df0 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -44,7 +44,8 @@ function CreateMeshCentralServer(config, args) { obj.certificates = null; obj.connectivityByNode = {}; // This object keeps a list of all connected CIRA and agents, by nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect) obj.peerConnectivityByNode = {}; // This object keeps a list of all connected CIRA and agents of peers, by serverid->nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect) - obj.debugLevel = 0; + obj.debugSources = []; + obj.debugRemoteSources = []; obj.config = config; // Configuration file obj.dbconfig = {}; // Persistance values, loaded from database obj.certificateOperations = null; @@ -108,7 +109,7 @@ function CreateMeshCentralServer(config, args) { try { require('./pass').hash('test', function () { }, 0); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not. // Check for invalid arguments - var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'swarmdebug', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid']; + var validArguments = ['_', 'notls', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'remotedebug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'memorytracking', 'serverid']; for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } } if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; } for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence. @@ -257,8 +258,17 @@ function CreateMeshCentralServer(config, args) { if (typeof obj.args.agentallowedip == 'string') { if (obj.args.agentallowedip == '') { obj.args.agentallowedip = null; } else { obj.args.agentallowedip = obj.args.agentallowedip.split(','); } } if (typeof obj.args.agentblockedip == 'string') { if (obj.args.agentblockedip == '') { obj.args.agentblockedip = null; } else { obj.args.agentblockedip = obj.args.agentblockedip.split(','); } } if (typeof obj.args.swarmallowedip == 'string') { if (obj.args.swarmallowedip == '') { obj.args.swarmallowedip = null; } else { obj.args.swarmallowedip = obj.args.swarmallowedip.split(','); } } - if (typeof obj.args.debug == 'number') obj.debugLevel = obj.args.debug; - if (obj.args.debug == true) obj.debugLevel = 1; + + // Local console tracing + if (typeof obj.args.debug == 'string') { obj.debugSources = obj.args.debug.toLowerCase().split(','); } + else if (typeof obj.args.debug == 'object') { obj.debugSources = obj.args.debug; } + else if (obj.args.debug === true) { obj.debugSources = '*'; } + + // Remote web application tracing + if (typeof obj.args.remotedebug == 'string') { obj.debugRemoteSources = obj.args.remotedebug.toLowerCase().split(','); } + else if (typeof obj.args.remotedebug == 'object') { obj.debugRemoteSources = obj.args.remotedebug; } + else if (obj.args.remotedebug === true) { obj.debugRemoteSources = '*'; } + require('./db.js').CreateDB(obj, function (db) { obj.db = db; @@ -896,7 +906,7 @@ function CreateMeshCentralServer(config, args) { obj.DispatchEvent(['*'], obj, { action: 'servertimelinestats', data: data }); // Event the server stats }, 300000); - //obj.debug(1, 'Server started'); + obj.debug('main', 'Server started'); if (obj.args.nousers == true) { obj.updateServerState('nousers', '1'); } obj.updateServerState('state', 'running'); @@ -940,7 +950,7 @@ function CreateMeshCentralServer(config, args) { // Set all nodes to power state of unknown (0) obj.db.storePowerEvent({ time: new Date(), nodeid: '*', power: 0, s: 2 }, obj.multiServer, function () { // s:2 indicates that the server is shutting down. if (restoreFile) { - obj.debug(1, 'Server stopped, updating settings: ' + restoreFile); + obj.debug('main', 'Server stopped, updating settings: ' + restoreFile); console.log('Updating settings folder...'); var yauzl = require("yauzl"); @@ -966,7 +976,7 @@ function CreateMeshCentralServer(config, args) { zipfile.on("end", function () { setTimeout(function () { obj.fs.unlinkSync(restoreFile); process.exit(123); }); }); }); } else { - obj.debug(1, 'Server stopped'); + obj.debug('main', 'Server stopped'); process.exit(0); } }); @@ -977,26 +987,26 @@ function CreateMeshCentralServer(config, args) { // Event Dispatch obj.AddEventDispatch = function (ids, target) { - obj.debug(3, 'AddEventDispatch', ids); + obj.debug('dispatch', 'AddEventDispatch', ids); for (var i in ids) { var id = ids[i]; if (!obj.eventsDispatch[id]) { obj.eventsDispatch[id] = [target]; } else { obj.eventsDispatch[id].push(target); } } }; obj.RemoveEventDispatch = function (ids, target) { - obj.debug(3, 'RemoveEventDispatch', id); + obj.debug('dispatch', 'RemoveEventDispatch', id); for (var i in ids) { var id = ids[i]; if (obj.eventsDispatch[id]) { var j = obj.eventsDispatch[id].indexOf(target); if (j >= 0) { if (obj.eventsDispatch[id].length == 1) { delete obj.eventsDispatch[id]; } else { obj.eventsDispatch[id].splice(j, 1); } } } } }; obj.RemoveEventDispatchId = function (id) { - obj.debug(3, 'RemoveEventDispatchId', id); + obj.debug('dispatch', 'RemoveEventDispatchId', id); if (obj.eventsDispatch[id] != null) { delete obj.eventsDispatch[id]; } }; obj.RemoveAllEventDispatch = function (target) { - obj.debug(3, 'RemoveAllEventDispatch'); + obj.debug('dispatch', 'RemoveAllEventDispatch'); for (var i in obj.eventsDispatch) { var j = obj.eventsDispatch[i].indexOf(target); if (j >= 0) { if (obj.eventsDispatch[i].length == 1) { delete obj.eventsDispatch[i]; } else { obj.eventsDispatch[i].splice(j, 1); } } } }; obj.DispatchEvent = function (ids, source, event, fromPeerServer) { // If the database is not setup, exit now. if (!obj.db) return; - obj.debug(3, 'DispatchEvent', ids); + obj.debug('dispatch', 'DispatchEvent', ids); if ((typeof event == 'object') && (!event.nolog)) { event.time = new Date(); // The event we store is going to skip some of the fields so we don't store too much stuff in the database. @@ -1340,7 +1350,7 @@ function CreateMeshCentralServer(config, args) { obj.defaultMeshCores[i] = [obj.common.IntToStr(0), ...modulesAdd[i], meshCore].join(''); } obj.defaultMeshCoresHash[i] = obj.crypto.createHash('sha384').update(obj.defaultMeshCores[i]).digest("binary"); - obj.debug(1, 'Core module ' + i + ' is ' + obj.defaultMeshCores[i].length + ' bytes.'); + obj.debug('main', 'Core module ' + i + ' is ' + obj.defaultMeshCores[i].length + ' bytes.'); //console.log('Core module ' + i + ' is ' + obj.defaultMeshCores[i].length + ' bytes.'); // DEBUG, Print the core size //obj.fs.writeFile("C:\\temp\\" + i + ".js", obj.defaultMeshCores[i].substring(4)); // DEBUG, Write the core to file } @@ -1583,19 +1593,19 @@ function CreateMeshCentralServer(config, args) { const decipher = obj.crypto.createDecipheriv('aes-256-gcm', key.slice(0, 32), cookie.slice(0, 12)); decipher.setAuthTag(cookie.slice(12, 16)); const o = JSON.parse(decipher.update(cookie.slice(28), 'binary', 'utf8') + decipher.final('utf8')); - if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug(1, 'ERR: Bad cookie due to invalid time'); return null; } + if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug('cookie', 'ERR: Bad cookie due to invalid time'); return null; } o.time = o.time * 1000; // Decode the cookie creation time o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds) if ((o.expire) == null || (typeof o.expire != 'number')) { // Use a fixed cookie expire time if (timeout == null) { timeout = 2; } - if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) + if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) } else { // An expire time is included in the cookie (in minutes), use this. - if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) + if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) } return o; - } catch (ex) { obj.debug(1, 'ERR: Bad AESGCM cookie due to exception: ' + ex); return null; } + } catch (ex) { obj.debug('cookie', 'ERR: Bad AESGCM cookie due to exception: ' + ex); return null; } }; // Decode a cookie back into an object using a key using AES256 / HMAC-SHA386. Return null if it's not a valid cookie. (key must be 80 bytes or more) @@ -1611,28 +1621,38 @@ function CreateMeshCentralServer(config, args) { hmac.update(rawmsg.slice(48)); if (Buffer.compare(hmac.digest(), Buffer.from(rawmsg.slice(0, 48))) == false) { return null; } const o = JSON.parse(rawmsg.slice(48).toString('utf8')); - if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug(1, 'ERR: Bad cookie due to invalid time'); return null; } + if ((o.time == null) || (o.time == null) || (typeof o.time != 'number')) { obj.debug('cookie', 'ERR: Bad cookie due to invalid time'); return null; } o.time = o.time * 1000; // Decode the cookie creation time o.dtime = Date.now() - o.time; // Decode how long ago the cookie was created (in milliseconds) if ((o.expire) == null || (typeof o.expire != 'number')) { // Use a fixed cookie expire time if (timeout == null) { timeout = 2; } - if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) + if ((o.dtime > (timeout * 60000)) || (o.dtime < -30000)) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) } else { // An expire time is included in the cookie (in minutes), use this. - if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug(1, 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) + if ((o.expire !== 0) && ((o.dtime > (o.expire * 60000)) || (o.dtime < -30000))) { obj.debug('cookie', 'ERR: Bad cookie due to timeout'); return null; } // The cookie is only valid 120 seconds, or 30 seconds back in time (in case other server's clock is not quite right) } return o; - } catch (ex) { obj.debug(1, 'ERR: Bad AESSHA cookie due to exception: ' + ex); return null; } + } catch (ex) { obj.debug('cookie', 'ERR: Bad AESSHA cookie due to exception: ' + ex); return null; } }; // Debug - obj.debug = function (lvl) { - if (lvl > obj.debugLevel) return; - if (arguments.length == 2) { console.log(arguments[1]); } - else if (arguments.length == 3) { console.log(arguments[1], arguments[2]); } - else if (arguments.length == 4) { console.log(arguments[1], arguments[2], arguments[3]); } - else if (arguments.length == 5) { console.log(arguments[1], arguments[2], arguments[3], arguments[4]); } + obj.debug = function (source, ...args) { + // Send event to console + if ((obj.debugSources != null) && ((obj.debugSources == '*') || (obj.debugSources.indexOf(source) >= 0))) { console.log(source.toUpperCase() + ':', ...args); } + + // Send the event to logged in administrators + if ((obj.debugRemoteSources != null) && ((obj.debugRemoteSources == '*') || (obj.debugRemoteSources.indexOf(source) >= 0))) { + for (var sessionid in obj.webserver.wssessions2) { + var ws = obj.webserver.wssessions2[sessionid]; + if ((ws != null) && (ws.userid != null)) { + var user = obj.webserver.users[ws.userid]; + if ((user != null) && (user.siteadmin == 4294967295)) { + try { ws.send(JSON.stringify({ action: 'trace', source: source, args: args, time: Date.now() })); } catch (ex) { } + } + } + } + } }; // Update server state. Writes a server state file. diff --git a/meshrelay.js b/meshrelay.js index 22ca38b2..e6b8c7cf 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -48,8 +48,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Disconnect this agent obj.close = function (arg) { - if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Relay: Soft disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Relay: Hard disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Soft disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket + if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('relay', 'Relay: Hard disconnect (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket // Aggressive cleanup delete obj.id; @@ -143,7 +143,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Check that at least one connection is authenticated if ((obj.authenticated != true) && (relayinfo.peer1.authenticated != true)) { ws.close(); - parent.parent.debug(1, 'Relay without-auth: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); + parent.parent.debug('relay', 'Relay without-auth: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); delete obj.id; delete obj.ws; delete obj.peer; @@ -200,7 +200,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie try { relayinfo.peer1.ws.send('c'); } catch (ex) { } } - parent.parent.debug(1, 'Relay connected: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(obj.peer.ws._socket.remoteAddress) + ')'); + parent.parent.debug('relay', 'Relay connected: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(obj.peer.ws._socket.remoteAddress) + ')'); // Log the connection if (sessionUser != null) { @@ -214,7 +214,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } else { // Connected already, drop (TODO: maybe we should re-connect?) ws.close(); - parent.parent.debug(1, 'Relay duplicate: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); + parent.parent.debug('relay', 'Relay duplicate: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); delete obj.id; delete obj.ws; delete obj.peer; @@ -224,7 +224,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Wait for other relay connection ws._socket.pause(); // Hold traffic until the other connection parent.wsrelays[obj.id] = { peer1: obj, state: 1, timeout: setTimeout(function () { closeBothSides(); }, 30000) }; - parent.parent.debug(1, 'Relay holding: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ') ' + (obj.authenticated ? 'Authenticated' : '')); + parent.parent.debug('relay', 'Relay holding: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ') ' + (obj.authenticated ? 'Authenticated' : '')); // Check if a peer server has this connection if (parent.parent.multiServer != null) { @@ -290,7 +290,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Disconnect the peer try { if (peer.relaySessionCounted) { parent.relaySessionCount--; delete peer.relaySessionCounted; } } catch (ex) { console.log(ex); } - parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(peer.ws._socket.remoteAddress) + ')'); + parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ' --> ' + cleanRemoteAddr(peer.ws._socket.remoteAddress) + ')'); try { peer.ws.close(); } catch (e) { } // Soft disconnect try { peer.ws._socket._parent.end(); } catch (e) { } // Hard disconnect @@ -314,7 +314,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie delete peer.ws; delete peer.peer; } else { - parent.parent.debug(1, 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); + parent.parent.debug('relay', 'Relay disconnect: ' + obj.id + ' (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } try { ws.close(); } catch (ex) { } delete parent.wsrelays[obj.id]; @@ -370,8 +370,8 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie // Send connection request to agent if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one. var command = { nodeid: cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: cookie.tcpport, tcpaddr: cookie.tcpaddr }; - parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } + parent.parent.debug('relay', 'Relay: Sending agent tunnel command: ' + JSON.stringify(command)); + if (obj.sendAgentMessage(command, user._id, cookie.domainid) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } performRelay(); }); return obj; @@ -390,12 +390,12 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie if (req.query.tcpport != null) { var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) }; - parent.parent.debug(1, 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } + parent.parent.debug('relay', 'Relay: Sending agent TCP tunnel command: ' + JSON.stringify(command)); + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } } else if (req.query.udpport != null) { var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, udpport: req.query.udpport, udpaddr: ((req.query.udpaddr == null) ? '127.0.0.1' : req.query.udpaddr) }; - parent.parent.debug(1, 'Relay: Sending agent UDP tunnel command: ' + JSON.stringify(command)); - if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug(1, 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } + parent.parent.debug('relay', 'Relay: Sending agent UDP tunnel command: ' + JSON.stringify(command)); + if (obj.sendAgentMessage(command, user._id, domain.id) == false) { delete obj.id; parent.parent.debug('relay', 'Relay: Unable to contact this agent (' + cleanRemoteAddr(ws._socket.remoteAddress) + ')'); } } performRelay(); }); diff --git a/meshuser.js b/meshuser.js index 5f854223..57b2798c 100644 --- a/meshuser.js +++ b/meshuser.js @@ -53,8 +53,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Disconnect this user obj.close = function (arg) { - if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug(1, 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug(1, 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { ws.close(); parent.parent.debug('user', 'Soft disconnect'); } catch (e) { console.log(e); } } // Soft close, close the websocket + if (arg == 2) { try { ws._socket._parent.end(); parent.parent.debug('user', 'Hard disconnect'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket // Perform cleanup parent.parent.RemoveAllEventDispatch(ws); @@ -314,6 +314,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Send user information to web socket, this is the first thing we send try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: parent.CloneSafeUser(parent.users[user._id]) })); } catch (ex) { } + // Send server tracing information + if (user.siteadmin == 0xFFFFFFFF) { + try { ws.send(JSON.stringify({ action: 'traceinfo', traceSources: parent.parent.debugRemoteSources })); } catch (ex) { } + } + // We are all set, start receiving data ws._socket.resume(); }); @@ -2729,6 +2734,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use ws.send(JSON.stringify({ action: 'createInviteLink', meshid: command.meshid, expire: command.expire, cookie: inviteCookie })); break; } + case 'traceinfo': { + if ((user.siteadmin == 0xFFFFFFFF) && (typeof command.traceSources == 'object')) { + parent.parent.debugRemoteSources = command.traceSources; + parent.parent.DispatchEvent(['*'], obj, { action: 'traceinfo', userid: user._id, username: user.name, traceSources: command.traceSources, nolog: 1, domain: domain.id }); + } + break; + } default: { // Unknown user action console.log('Unknown action from user ' + user.name + ': ' + command.action + '.'); diff --git a/mpsserver.js b/mpsserver.js index a2bfaf61..8be3c95c 100644 --- a/mpsserver.js +++ b/mpsserver.js @@ -167,11 +167,11 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { socket.tag = { first: true, clientCert: socket.getPeerCertificate(true), accumulator: "", activetunnels: 0, boundPorts: [], socket: socket, host: null, nextchannelid: 4, channels: {}, nextsourceport: 0 }; } socket.setEncoding("binary"); - Debug(1, "MPS:New CIRA connection"); + parent.debug('mps', "New CIRA connection"); // Setup the CIRA keep alive timer socket.setTimeout(MAX_IDLE); - socket.on("timeout", () => { ciraTimeoutCount++; Debug(1, "MPS:CIRA timeout, disconnecting."); try { socket.end(); } catch (e) { } }); + socket.on("timeout", () => { ciraTimeoutCount++; parent.debug('mps', "CIRA timeout, disconnecting."); try { socket.end(); } catch (e) { } }); socket.addListener("data", function (data) { if (args.mpsdebug) { var buf = Buffer.from(data, "binary"); console.log("MPS <-- (" + buf.length + "):" + buf.toString('hex')); } // Print out received bytes @@ -299,13 +299,13 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { switch (cmd) { case APFProtocol.KEEPALIVE_REQUEST: { if (len < 5) return 0; - Debug(3, 'MPS:KEEPALIVE_REQUEST'); + parent.debug('mpscmd', 'KEEPALIVE_REQUEST'); SendKeepAliveReply(socket, common.ReadInt(data, 1)); return 5; } case APFProtocol.KEEPALIVE_REPLY: { if (len < 5) return 0; - Debug(3, 'MPS:KEEPALIVE_REPLY'); + parent.debug('mpscmd', 'KEEPALIVE_REPLY'); return 5; } case APFProtocol.PROTOCOLVERSION: { @@ -314,7 +314,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { socket.tag.MajorVersion = common.ReadInt(data, 1); socket.tag.MinorVersion = common.ReadInt(data, 5); socket.tag.SystemId = guidToStr(common.rstr2hex(data.substring(13, 29))).toLowerCase(); - Debug(3, 'MPS:PROTOCOLVERSION', socket.tag.MajorVersion, socket.tag.MinorVersion, socket.tag.SystemId); + parent.debug('mpscmd', 'PROTOCOLVERSION', socket.tag.MajorVersion, socket.tag.MinorVersion, socket.tag.SystemId); return 93; } case APFProtocol.USERAUTH_REQUEST: { @@ -332,16 +332,16 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { password = data.substring(18 + usernameLen + serviceNameLen + methodNameLen, 18 + usernameLen + serviceNameLen + methodNameLen + passwordLen); } //console.log('MPS:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password); - Debug(3, 'MPS:USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password); + parent.debug('mpscmd', 'USERAUTH_REQUEST user=' + username + ', service=' + serviceName + ', method=' + methodName + ', password=' + password); // Check the CIRA password - if ((args.mpspass != null) && (password != args.mpspass)) { incorrectPasswordCount++; Debug(1, 'MPS:Incorrect password', username, password); SendUserAuthFail(socket); return -1; } + if ((args.mpspass != null) && (password != args.mpspass)) { incorrectPasswordCount++; parent.debug('mps', 'Incorrect password', username, password); SendUserAuthFail(socket); return -1; } // Check the CIRA username, which should be the start of the MeshID. - if (usernameLen != 16) { badUserNameLengthCount++; Debug(1, 'MPS:Username length not 16', username, password); SendUserAuthFail(socket); return -1; } + if (usernameLen != 16) { badUserNameLengthCount++; parent.debug('mps', 'Username length not 16', username, password); SendUserAuthFail(socket); return -1; } var meshIdStart = '/' + username, mesh = null; if (obj.parent.webserver.meshes) { for (var i in obj.parent.webserver.meshes) { if (obj.parent.webserver.meshes[i]._id.replace(/\@/g, 'X').replace(/\$/g, 'X').indexOf(meshIdStart) > 0) { mesh = obj.parent.webserver.meshes[i]; break; } } } - if (mesh == null) { meshNotFoundCount++; Debug(1, 'MPS:Mesh not found', username, password); SendUserAuthFail(socket); return -1; } + if (mesh == null) { meshNotFoundCount++; parent.debug('mps', 'Mesh not found', username, password); SendUserAuthFail(socket); return -1; } // If this is a agent-less mesh, use the device guid 3 times as ID. if (mesh.mtype == 1) { @@ -446,7 +446,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var xserviceNameLen = common.ReadInt(data, 1); if (len < 5 + xserviceNameLen) return 0; var xserviceName = data.substring(5, 5 + xserviceNameLen); - Debug(3, 'MPS:SERVICE_REQUEST', xserviceName); + parent.debug('mpscmd', 'SERVICE_REQUEST', xserviceName); if (xserviceName == "pfwd@amt.intel.com") { SendServiceAccept(socket, "pfwd@amt.intel.com"); } if (xserviceName == "auth@amt.intel.com") { SendServiceAccept(socket, "auth@amt.intel.com"); } return 5 + xserviceNameLen; @@ -463,7 +463,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (len < 14 + requestLen + addrLen) return 0; var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen); var port = common.ReadInt(data, 10 + requestLen + addrLen); - Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port); + parent.debug('mpscmd', 'GLOBAL_REQUEST', request, addr + ':' + port); ChangeHostname(socket, addr, socket.tag.SystemId); if (socket.tag.boundPorts.indexOf(port) == -1) { socket.tag.boundPorts.push(port); } SendTcpForwardSuccessReply(socket, port); @@ -475,7 +475,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (len < 14 + requestLen + addrLen) return 0; var addr = data.substring(10 + requestLen, 10 + requestLen + addrLen); var port = common.ReadInt(data, 10 + requestLen + addrLen); - Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port); + parent.debug('mpscmd', 'GLOBAL_REQUEST', request, addr + ':' + port); var portindex = socket.tag.boundPorts.indexOf(port); if (portindex >= 0) { socket.tag.boundPorts.splice(portindex, 1); } SendTcpForwardCancelReply(socket); @@ -493,7 +493,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var oport = common.ReadInt(data, 18 + requestLen + addrLen + oaddrLen); var datalen = common.ReadInt(data, 22 + requestLen + addrLen + oaddrLen); if (len < 26 + requestLen + addrLen + oaddrLen + datalen) return 0; - Debug(2, 'MPS:GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen); + parent.debug('mpscmd', 'GLOBAL_REQUEST', request, addr + ':' + port, oaddr + ':' + oport, datalen); // TODO return 26 + requestLen + addrLen + oaddrLen + datalen; } @@ -523,7 +523,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var SourcePort = common.ReadInt(data, 29 + ChannelTypeLength + TargetLen + SourceLen); channelOpenCount++; - Debug(3, 'MPS:CHANNEL_OPEN', ChannelType, SenderChannel, WindowSize, Target + ':' + TargetPort, Source + ':' + SourcePort); + parent.debug('mpscmd', 'CHANNEL_OPEN', ChannelType, SenderChannel, WindowSize, Target + ':' + TargetPort, Source + ':' + SourcePort); // Check if we understand this channel type //if (ChannelType.toLowerCase() == "direct-tcpip") @@ -554,7 +554,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { cirachannel.amtchannelid = SenderChannel; cirachannel.sendcredits = cirachannel.amtCiraWindow = WindowSize; channelOpenConfirmCount++; - Debug(3, 'MPS:CHANNEL_OPEN_CONFIRMATION', RecipientChannel, SenderChannel, WindowSize); + parent.debug('mpscmd', 'CHANNEL_OPEN_CONFIRMATION', RecipientChannel, SenderChannel, WindowSize); if (cirachannel.closing == 1) { // Close this channel SendChannelClose(cirachannel.socket, cirachannel.amtchannelid); @@ -586,7 +586,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var RecipientChannel = common.ReadInt(data, 1); var ReasonCode = common.ReadInt(data, 5); channelOpenFailCount++; - Debug(3, 'MPS:CHANNEL_OPEN_FAILURE', RecipientChannel, ReasonCode); + parent.debug('mpscmd', 'CHANNEL_OPEN_FAILURE', RecipientChannel, ReasonCode); var cirachannel = socket.tag.channels[RecipientChannel]; if (cirachannel == null) { console.log("MPS Error in CHANNEL_OPEN_FAILURE: Unable to find channelid " + RecipientChannel); return 17; } if (cirachannel.state > 0) { @@ -601,7 +601,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (len < 5) return 0; var RecipientChannel = common.ReadInt(data, 1); channelCloseCount++; - Debug(3, 'MPS:CHANNEL_CLOSE', RecipientChannel); + parent.debug('mpscmd', 'CHANNEL_CLOSE', RecipientChannel); var cirachannel = socket.tag.channels[RecipientChannel]; if (cirachannel == null) { console.log("MPS Error in CHANNEL_CLOSE: Unable to find channelid " + RecipientChannel); return 5; } socket.tag.activetunnels--; @@ -620,7 +620,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var cirachannel = socket.tag.channels[RecipientChannel]; if (cirachannel == null) { console.log("MPS Error in CHANNEL_WINDOW_ADJUST: Unable to find channelid " + RecipientChannel); return 9; } cirachannel.sendcredits += ByteToAdd; - Debug(3, 'MPS:CHANNEL_WINDOW_ADJUST', RecipientChannel, ByteToAdd, cirachannel.sendcredits); + parent.debug('mpscmd', 'CHANNEL_WINDOW_ADJUST', RecipientChannel, ByteToAdd, cirachannel.sendcredits); if (cirachannel.state == 2 && cirachannel.sendBuffer != null) { // Compute how much data we can send if (cirachannel.sendBuffer.length <= cirachannel.sendcredits) { @@ -644,7 +644,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { var RecipientChannel = common.ReadInt(data, 1); var LengthOfData = common.ReadInt(data, 5); if (len < (9 + LengthOfData)) return 0; - Debug(4, 'MPS:CHANNEL_DATA', RecipientChannel, LengthOfData); + parent.debug('mpscmddata', 'CHANNEL_DATA', RecipientChannel, LengthOfData); var cirachannel = socket.tag.channels[RecipientChannel]; if (cirachannel == null) { console.log("MPS Error in CHANNEL_DATA: Unable to find channelid " + RecipientChannel); return 9 + LengthOfData; } cirachannel.amtpendingcredits += LengthOfData; @@ -660,14 +660,14 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { if (len < 7) return 0; var ReasonCode = common.ReadInt(data, 1); disconnectCommandCount++; - Debug(3, 'MPS:DISCONNECT', ReasonCode); + parent.debug('mpscmd', 'DISCONNECT', ReasonCode); try { delete obj.ciraConnections[socket.tag.nodeid]; } catch (e) { } obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 2); return 7; } default: { - Debug(1, 'MPS:Unknown CIRA command: ' + cmd); + parent.debug('mpscmd', 'Unknown CIRA command: ' + cmd); return -1; } } @@ -675,7 +675,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { socket.addListener("close", function () { socketClosedCount++; - Debug(1, 'MPS:CIRA connection closed'); + parent.debug('mps', 'CIRA connection closed'); try { delete obj.ciraConnections[socket.tag.nodeid]; } catch (e) { } obj.parent.ClearConnectivityState(socket.tag.meshid, socket.tag.nodeid, 2); }); @@ -741,7 +741,7 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { } function SendChannelWindowAdjust(socket, channelid, bytestoadd) { - Debug(3, 'MPS:SendChannelWindowAdjust', channelid, bytestoadd); + parent.debug('mpscmd', 'SendChannelWindowAdjust', channelid, bytestoadd); Write(socket, String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + common.IntToStr(channelid) + common.IntToStr(bytestoadd)); } @@ -853,16 +853,5 @@ module.exports.CreateMpsServer = function (parent, db, args, certificates) { function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); } - // Debug - function Debug(lvl) { - if (lvl > obj.parent.debugLevel) return; - if (arguments.length == 2) { console.log(arguments[1]); } - else if (arguments.length == 3) { console.log(arguments[1], arguments[2]); } - else if (arguments.length == 4) { console.log(arguments[1], arguments[2], arguments[3]); } - else if (arguments.length == 5) { console.log(arguments[1], arguments[2], arguments[3], arguments[4]); } - else if (arguments.length == 6) { console.log(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); } - else if (arguments.length == 7) { console.log(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]); } - } - return obj; }; diff --git a/multiserver.js b/multiserver.js index cccffae1..9d398bd9 100644 --- a/multiserver.js +++ b/multiserver.js @@ -61,15 +61,15 @@ module.exports.CreateMultiServer = function (parent, args) { // Get the web socket setup obj.ws = new WebSocket(obj.url + 'meshserver.ashx', { rejectUnauthorized: false, cert: obj.certificates.agent.cert, key: obj.certificates.agent.key }); - obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx'); + obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Connecting to: ' + url + 'meshserver.ashx'); // Register the connection failed event - obj.ws.on('error', function (error) { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); }); - obj.ws.on('close', function () { obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); }); + obj.ws.on('error', function (error) { obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Error: ' + error); disconnect(); }); + obj.ws.on('close', function () { obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Disconnected'); disconnect(); }); // Register the connection event obj.ws.on('open', function () { - obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Connected'); + obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Connected'); obj.connectionState |= 2; obj.nonce = obj.crypto.randomBytes(48).toString('binary'); @@ -95,10 +95,10 @@ module.exports.CreateMultiServer = function (parent, args) { switch (cmd) { case 1: { // Server authentication request - if (msg.length != 98) { obj.parent.parent.debug(1, 'OutPeer: BAD MESSAGE(A1)'); return; } + if (msg.length != 98) { obj.parent.parent.debug('peer', 'OutPeer: BAD MESSAGE(A1)'); return; } // Check that the server hash matches the TLS server certificate public key hash - if (obj.serverCertHash != msg.substring(2, 50)) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; } + if (obj.serverCertHash != msg.substring(2, 50)) { obj.parent.parent.debug('peer', 'OutPeer: Server hash mismatch.'); disconnect(); return; } obj.servernonce = msg.substring(50); // Perform the hash signature using the server agent certificate @@ -114,14 +114,14 @@ module.exports.CreateMultiServer = function (parent, args) { var certlen = obj.common.ReadShort(msg, 2), serverCert = null; var serverCertPem = '-----BEGIN CERTIFICATE-----\r\n' + Buffer.from(msg.substring(4, 4 + certlen), 'binary').toString('base64') + '\r\n-----END CERTIFICATE-----'; try { serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(msg.substring(4, 4 + certlen))); } catch (e) { } - if (serverCert == null) { obj.parent.parent.debug(1, 'OutPeer: Invalid server certificate.'); disconnect(); return; } + if (serverCert == null) { obj.parent.parent.debug('peer', 'OutPeer: Invalid server certificate.'); disconnect(); return; } var serverid = Buffer.from(obj.forge.pki.getPublicKeyFingerprint(serverCert.publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() }), 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); - if (serverid !== obj.agentCertificateHashBase64) { obj.parent.parent.debug(1, 'OutPeer: Server hash mismatch.'); disconnect(); return; } + if (serverid !== obj.agentCertificateHashBase64) { obj.parent.parent.debug('peer', 'OutPeer: Server hash mismatch.'); disconnect(); return; } // Server signature, verify it. This is the fast way, without using forge. (TODO: Use accelerator for this?) const verify = obj.parent.crypto.createVerify('SHA384'); verify.end(Buffer.from(obj.serverCertHash + obj.nonce + obj.servernonce, 'binary')); - if (verify.verify(serverCertPem, Buffer.from(msg.substring(4 + certlen), 'binary')) !== true) { obj.parent.parent.debug(1, 'OutPeer: Server sign check failed.'); disconnect(); return; } + if (verify.verify(serverCertPem, Buffer.from(msg.substring(4 + certlen), 'binary')) !== true) { obj.parent.parent.debug('peer', 'OutPeer: Server sign check failed.'); disconnect(); return; } // Connection is a success, clean up delete obj.nonce; @@ -129,7 +129,7 @@ module.exports.CreateMultiServer = function (parent, args) { obj.serverCertHash = Buffer.from(obj.serverCertHash, 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); // Change this value to base64 obj.connectionState |= 4; obj.retryBackoff = 0; // Set backoff connection timer back to fast. - obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url); + obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Verified peer connection to ' + obj.url); // Send information about our server to the peer if (obj.connectionState == 15) { @@ -152,7 +152,7 @@ module.exports.CreateMultiServer = function (parent, args) { break; } default: { - obj.parent.parent.debug(1, 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd); + obj.parent.parent.debug('peer', 'OutPeer ' + obj.serverid + ': Un-handled command: ' + cmd); break; } } @@ -189,7 +189,7 @@ module.exports.CreateMultiServer = function (parent, args) { function processServerData(msg) { var str = msg.toString('utf8'), command = null; if (str[0] == '{') { - try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug(1, 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it. + try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug('peer', 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it. if (command.action == 'info') { if (obj.authenticated != 3) { // We get the peer's serverid and database identifier. @@ -233,7 +233,7 @@ module.exports.CreateMultiServer = function (parent, args) { obj.serverCertHash = null; obj.pendingData = []; if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } - obj.parent.parent.debug(1, 'InPeer: Connected (' + obj.remoteaddr + ')'); + obj.parent.parent.debug('peer', 'InPeer: Connected (' + obj.remoteaddr + ')'); // Send a message to the peer server obj.send = function (data) { @@ -246,8 +246,8 @@ module.exports.CreateMultiServer = function (parent, args) { // Disconnect this server obj.close = function (arg) { - if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'InPeer: Soft disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket - if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug(1, 'InPeer: Hard disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket + if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug('peer', 'InPeer: Soft disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket + if (arg == 2) { try { obj.ws._socket._parent.end(); obj.parent.parent.debug('peer', 'InPeer: Hard disconnect ' + obj.peerServerId + ' (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Hard close, close the TCP socket if (obj.authenticated == 3) { obj.parent.ClearPeerServer(obj, obj.peerServerId); obj.authenticated = 0; } }; @@ -283,7 +283,7 @@ module.exports.CreateMultiServer = function (parent, args) { } else if (cmd == 2) { // Peer server certificate - if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) { obj.parent.parent.debug(1, 'InPeer: Invalid command 2.'); return; } + if ((msg.length < 4) || ((obj.receivedCommands & 2) != 0)) { obj.parent.parent.debug('peer', 'InPeer: Invalid command 2.'); return; } obj.receivedCommands += 2; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path. // Decode the certificate @@ -296,12 +296,12 @@ module.exports.CreateMultiServer = function (parent, args) { if (obj.peernonce == null) { obj.unauthsign = msg.substring(4 + certlen); } else { - if (processPeerSignature(msg.substring(4 + certlen)) == false) { obj.parent.parent.debug(1, 'InPeer: Invalid signature.'); obj.close(); return; } + if (processPeerSignature(msg.substring(4 + certlen)) == false) { obj.parent.parent.debug('peer', 'InPeer: Invalid signature.'); obj.close(); return; } } completePeerServerConnection(); } else if (cmd == 3) { - if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) { obj.parent.parent.debug(1, 'InPeer: Invalid command 3.'); return; } + if ((msg.length < 56) || ((obj.receivedCommands & 4) != 0)) { obj.parent.parent.debug('peer', 'InPeer: Invalid command 3.'); return; } obj.receivedCommands += 4; // Peer server can't send the same command twice on the same connection ever. Block DOS attack path. completePeerServerConnection(); } @@ -309,11 +309,11 @@ module.exports.CreateMultiServer = function (parent, args) { }); // If error, do nothing - ws.on('error', function (err) { obj.parent.parent.debug(1, 'InPeer: Connection Error: ' + err); }); + ws.on('error', function (err) { obj.parent.parent.debug('peer', 'InPeer: Connection Error: ' + err); }); // If the peer server web socket is closed, clean up. - ws.on('close', function (req) { obj.parent.parent.debug(1, 'InPeer disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); }); - // obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug(1, 'Peer server TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); }); + ws.on('close', function (req) { obj.parent.parent.debug('peer', 'InPeer disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); obj.close(0); }); + // obj.ws._socket._parent.on('close', function (req) { obj.parent.parent.debug('peer', 'Peer server TCP disconnect ' + obj.nodeid + ' (' + obj.remoteaddr + ')'); }); // Start authenticate the peer server by sending a auth nonce & server TLS cert hash. // Send 384 bits SHA382 hash of TLS cert public key + 384 bits nonce @@ -355,7 +355,7 @@ module.exports.CreateMultiServer = function (parent, args) { function processServerData(msg) { var str = msg.toString('utf8'), command = null; if (str[0] == '{') { - try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug(1, 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it. + try { command = JSON.parse(str); } catch (e) { obj.parent.parent.debug('peer', 'Unable to parse server JSON (' + obj.remoteaddr + ').'); return; } // If the command can't be parsed, ignore it. if (command.action == 'info') { if (obj.authenticated != 3) { // We get the peer's serverid and database identifier. @@ -599,21 +599,21 @@ module.exports.CreateMultiServer = function (parent, args) { peerTunnel.connect = function () { // Get the web socket setup - peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url); + peerTunnel.parent.parent.debug('peer', 'FTunnel ' + peerTunnel.serverid + ': Start connect to ' + peerTunnel.url); peerTunnel.ws2 = new WebSocket(peerTunnel.url, { rejectUnauthorized: false }); // Register the connection failed event - peerTunnel.ws2.on('error', function (error) { peerTunnel.parent.parent.debug(1, 'FTunnel ' + obj.serverid + ': Connection error'); peerTunnel.close(); }); + peerTunnel.ws2.on('error', function (error) { peerTunnel.parent.parent.debug('peer', 'FTunnel ' + obj.serverid + ': Connection error'); peerTunnel.close(); }); // If the peer server web socket is closed, clean up. - peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); }); + peerTunnel.ws2.on('close', function (req) { peerTunnel.parent.parent.debug('peer', 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); }); // If a message is received from the peer, Peer ---> Browser (TODO: Pipe this?) peerTunnel.ws2.on('message', function (msg) { try { peerTunnel.ws2._socket.pause(); peerTunnel.ws1.send(msg, function () { peerTunnel.ws2._socket.resume(); }); } catch (e) { } }); // Register the connection event peerTunnel.ws2.on('open', function () { - peerTunnel.parent.parent.debug(1, 'FTunnel ' + peerTunnel.serverid + ': Connected'); + peerTunnel.parent.parent.debug('peer', 'FTunnel ' + peerTunnel.serverid + ': Connected'); // Get the peer server's certificate and compute the server public key hash var serverCert = obj.forge.pki.certificateFromAsn1(obj.forge.asn1.fromDer(peerTunnel.ws2._socket.getPeerCertificate().raw.toString('binary'))); @@ -633,19 +633,19 @@ module.exports.CreateMultiServer = function (parent, args) { peerTunnel.ws1.on('error', function (err) { peerTunnel.close(); }); // If the web socket is closed, close the associated TCP connection. - peerTunnel.ws1.on('close', function (req) { peerTunnel.parent.parent.debug(1, 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); }); + peerTunnel.ws1.on('close', function (req) { peerTunnel.parent.parent.debug('peer', 'FTunnel disconnect ' + peerTunnel.serverid); peerTunnel.close(); }); }; // Disconnect both sides of the tunnel peerTunnel.close = function (arg) { if (arg == 2) { // Hard close, close the TCP socket - if (peerTunnel.ws1 != null) { try { peerTunnel.ws1._socket._parent.end(); peerTunnel.parent.parent.debug(1, 'FTunnel1: Hard disconnect'); } catch (e) { console.log(e); } } - if (peerTunnel.ws2 != null) { try { peerTunnel.ws2._socket._parent.end(); peerTunnel.parent.parent.debug(1, 'FTunnel2: Hard disconnect'); } catch (e) { console.log(e); } } + if (peerTunnel.ws1 != null) { try { peerTunnel.ws1._socket._parent.end(); peerTunnel.parent.parent.debug('peer', 'FTunnel1: Hard disconnect'); } catch (e) { console.log(e); } } + if (peerTunnel.ws2 != null) { try { peerTunnel.ws2._socket._parent.end(); peerTunnel.parent.parent.debug('peer', 'FTunnel2: Hard disconnect'); } catch (e) { console.log(e); } } } else { // Soft close, close the websocket - if (peerTunnel.ws1 != null) { try { peerTunnel.ws1.close(); peerTunnel.parent.parent.debug(1, 'FTunnel1: Soft disconnect '); } catch (e) { console.log(e); } } - if (peerTunnel.ws2 != null) { try { peerTunnel.ws2.close(); peerTunnel.parent.parent.debug(1, 'FTunnel2: Soft disconnect '); } catch (e) { console.log(e); } } + if (peerTunnel.ws1 != null) { try { peerTunnel.ws1.close(); peerTunnel.parent.parent.debug('peer', 'FTunnel1: Soft disconnect '); } catch (e) { console.log(e); } } + if (peerTunnel.ws2 != null) { try { peerTunnel.ws2.close(); peerTunnel.parent.parent.debug('peer', 'FTunnel2: Soft disconnect '); } catch (e) { console.log(e); } } } }; diff --git a/package.json b/package.json index f6fd6446..c1aca61b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.0-a", + "version": "0.4.0-c", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/styles/style.css b/public/styles/style.css index b2512c44..eebeddf5 100644 --- a/public/styles/style.css +++ b/public/styles/style.css @@ -838,7 +838,7 @@ NoMeshesPanel img { color: gray; } -#p3events, #p16events, #p31events { +#p3events, #p16events, #p31events, #p41events { height: calc(100vh - 245px); overflow-y: scroll; } @@ -2539,4 +2539,14 @@ a { border-radius:4px; padding:6px; box-shadow: 0px 0px 15px #666; +} + +.traceEvent { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin: 3px 3px 3px 0; + padding: 3px; + border-radius: 3px; + background-color: #DDD; } \ No newline at end of file diff --git a/sample-config.json b/sample-config.json index 591b3d99..1af8cc8b 100644 --- a/sample-config.json +++ b/sample-config.json @@ -10,6 +10,7 @@ "_Minify": 1, "_SessionTime": 30, "_SessionKey": "MyReallySecretPassword1", + "_SessionSameSite": "strict", "_DbEncryptKey": "MyReallySecretPassword2", "_DbExpire": { "events": 1728000, @@ -20,6 +21,7 @@ "_AllowLoginToken": true, "_AllowFraming": true, "_WebRTC": false, + "_Nice404": false, "_ClickOnce": false, "_SelfUpdate": true, "_AgentPing": 60, diff --git a/swarmserver.js b/swarmserver.js index 91a6c63c..72f8e3e6 100644 --- a/swarmserver.js +++ b/swarmserver.js @@ -202,11 +202,11 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { switch (cmd) { case LegacyMeshProtocol.NODEPUSH: { - Debug(3, 'Swarm:NODEPUSH'); + parent.debug('swarmcmd', 'NODEPUSH'); var nodeblock = obj.decodeNodeBlock(data); if ((nodeblock != null) && (nodeblock.agenttype != null) && (nodeblock.agentversion != null)) { if (socket.pingTimer == null) { socket.pingTimer = setInterval(function () { obj.SendCommand(socket, LegacyMeshProtocol.PING); }, 20000); } - Debug(3, 'Swarm:NODEPUSH:' + JSON.stringify(nodeblock)); + parent.debug('swarmcmd', 'NODEPUSH:' + JSON.stringify(nodeblock)); // Log the agent type if (obj.stats.agenttype[nodeblock.agenttype] == null) { obj.stats.agenttype[nodeblock.agenttype] = 1; } else { obj.stats.agenttype[nodeblock.agenttype]++; } @@ -256,12 +256,12 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { break; } case LegacyMeshProtocol.AMTPROVISIONING: { - Debug(3, 'Swarm:AMTPROVISIONING'); + parent.debug('swarmcmd', 'AMTPROVISIONING'); obj.SendCommand(socket, LegacyMeshProtocol.AMTPROVISIONING, common.ShortToStr(1)); break; } case LegacyMeshProtocol.GETSTATE: { - Debug(3, 'Swarm:GETSTATE'); + parent.debug('swarmcmd', 'GETSTATE'); if (len < 12) break; var statecmd = common.ReadInt(data, 0); //var statesync = common.ReadInt(data, 4); @@ -272,13 +272,13 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { if (socket.tag.update.binary == null) { socket.tag.update.binary = obj.parent.fs.readFileSync(socket.tag.update.path); } var l = Math.min(socket.tag.update.binary.length - socket.tag.updatePtr, 16384); obj.SendCommand(socket, LegacyMeshProtocol.GETSTATE, common.IntToStr(6) + common.IntToStr(socket.tag.updatePtr) + socket.tag.update.binary.toString('binary', socket.tag.updatePtr, socket.tag.updatePtr + l)); // agent.SendQuery(6, AgentFileLen + AgentBlock); - Debug(3, 'Swarm:Sending agent block, ptr = ' + socket.tag.updatePtr + ', len = ' + l); + parent.debug('swarmcmd', 'Sending agent block, ptr = ' + socket.tag.updatePtr + ', len = ' + l); socket.tag.updatePtr += l; if (socket.tag.updatePtr >= socket.tag.update.binary.length) { // Send end-of-transfer obj.SendCommand(socket, LegacyMeshProtocol.GETSTATE, common.IntToStr(7) + common.IntToStr(socket.tag.update.binary.length)); //agent.SendQuery(7, AgentFileLen); - Debug(3, 'Swarm:Sending end of agent, ptr = ' + socket.tag.updatePtr); + parent.debug('swarmcmd', 'Sending end of agent, ptr = ' + socket.tag.updatePtr); obj.parent.taskLimiter.completed(socket.tag.taskid); // Indicate this task complete delete socket.tag.taskid; delete socket.tag.update; @@ -295,11 +295,11 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { break; } case LegacyMeshProtocol.APPSUBSCRIBERS: { - Debug(3, 'Swarm:APPSUBSCRIBERS'); + parent.debug('swarmcmd', 'APPSUBSCRIBERS'); break; } default: { - Debug(1, 'Swarm:Unknown command: ' + cmd + ' of len ' + len + '.'); + parent.debug('swarmcmd', 'Unknown command: ' + cmd + ' of len ' + len + '.'); } } return len; @@ -308,18 +308,18 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { // Called when a legacy agent connects to this server function onConnection(socket) { // Check for blocked IP address - if (checkSwarmIpAddress(socket, obj.args.swarmallowedip) == false) { obj.stats.blockedConnect++; Debug(1, "SWARM:New blocked agent connection"); return; } + if (checkSwarmIpAddress(socket, obj.args.swarmallowedip) == false) { obj.stats.blockedConnect++; parent.debug('swarm', "New blocked agent connection"); return; } obj.stats.connectCount++; socket.tag = { first: true, clientCert: socket.getPeerCertificate(true), accumulator: "" }; - Debug(1, 'SWARM:New legacy agent connection'); + parent.debug('swarm', 'New legacy agent connection'); if ((socket.tag.clientCert == null) || (socket.tag.clientCert.subject == null)) { obj.stats.noCertConnectCount++; } else { obj.stats.clientCertConnectCount++; } socket.addListener("data", onData); socket.addListener("close", function () { obj.stats.onclose++; - Debug(1, 'Swarm:Connection closed'); + parent.debug('swarm', 'Connection closed'); // Perform aggressive cleanup if (this.relaySocket) { try { this.relaySocket.end(); this.relaySocket.removeAllListeners(["data", "end", "error"]); delete this.relaySocket; } catch (ex) { } } @@ -430,16 +430,5 @@ module.exports.CreateSwarmServer = function (parent, db, args, certificates) { return false; } - // Debug - function Debug(lvl) { - if (lvl > obj.parent.debugLevel) return; - if (arguments.length == 2) { console.log(arguments[1]); } - else if (arguments.length == 3) { console.log(arguments[1], arguments[2]); } - else if (arguments.length == 4) { console.log(arguments[1], arguments[2], arguments[3]); } - else if (arguments.length == 5) { console.log(arguments[1], arguments[2], arguments[3], arguments[4]); } - else if (arguments.length == 6) { console.log(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); } - else if (arguments.length == 7) { console.log(arguments[1], arguments[2], arguments[3], arguments[4], arguments[5], arguments[6]); } - } - return obj; }; diff --git a/views/default-min.handlebars b/views/default-min.handlebars index a2951ed7..80820e33 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 58a6bbba..719a0636 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -148,6 +148,7 @@ General Stats Console + Trace   @@ -783,7 +784,7 @@
-